fuzzytool 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/CHANGELOG.md +34 -20
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/CITATION.cff +2 -1
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/PKG-INFO +12 -2
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/README.md +11 -1
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/ROADMAP.md +16 -1
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/api/python.md +20 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/citing.md +4 -3
- fuzzytool-0.2.0/docs/guide/batch-and-io.md +51 -0
- fuzzytool-0.2.0/docs/guide/mcdm.md +60 -0
- fuzzytool-0.2.0/docs/guide/rule-learning.md +48 -0
- fuzzytool-0.2.0/examples/mcdm.py +39 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/__init__.py +13 -5
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/anfis.py +13 -0
- fuzzytool-0.2.0/fuzzytool/fuzzynum.py +161 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/inference/__init__.py +2 -1
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/inference/mamdani.py +32 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/inference/tsk.py +21 -0
- fuzzytool-0.2.0/fuzzytool/inference/tsukamoto.py +65 -0
- fuzzytool-0.2.0/fuzzytool/learn.py +73 -0
- fuzzytool-0.2.0/fuzzytool/mcdm.py +124 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/membership.py +92 -2
- fuzzytool-0.2.0/fuzzytool/serialize.py +97 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/sets.py +59 -1
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/mkdocs.yml +3 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/pyproject.toml +1 -1
- fuzzytool-0.2.0/tests/test_engineering.py +78 -0
- fuzzytool-0.2.0/tests/test_learn.py +62 -0
- fuzzytool-0.2.0/tests/test_mcdm.py +77 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/.github/workflows/ci.yml +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/.github/workflows/docs.yml +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/.github/workflows/release-pypi.yml +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/.gitignore +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/.zenodo.json +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/CLAUDE.md +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/CONTRIBUTING.md +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/LICENSE +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/comparison.md +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/extending.md +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/getting-started.md +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/guide/anfis.md +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/guide/clustering.md +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/guide/defuzzification.md +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/guide/ftransform.md +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/guide/mamdani.md +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/guide/membership.md +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/guide/rules.md +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/guide/tsk.md +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/guide/type2.md +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/guide/visualization.md +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/index.md +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/installation.md +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/examples/anfis.py +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/examples/clustering.py +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/examples/credit_risk.py +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/examples/credit_risk_it2.py +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/examples/ftransform.py +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/cluster.py +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/datasets.py +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/defuzz.py +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/ftransform.py +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/norms.py +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/rules.py +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/type2/__init__.py +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/type2/inference.py +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/type2/reduction.py +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/type2/sets.py +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/viz.py +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/notebooks/01_quickstart.ipynb +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/notebooks/02_type2.ipynb +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/notebooks/03_clustering.ipynb +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/notebooks/04_learning.ipynb +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/notebooks/README.md +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/paper.bib +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/paper.md +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/tests/test_cluster.py +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/tests/test_inference.py +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/tests/test_learning.py +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/tests/test_membership.py +0 -0
- {fuzzytool-0.1.0 → fuzzytool-0.2.0}/tests/test_type2.py +0 -0
|
@@ -6,12 +6,43 @@ All notable changes to this project are documented here. The format is based on
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.2.0] - 2026-06-24
|
|
10
|
+
|
|
9
11
|
### Added
|
|
10
12
|
|
|
13
|
+
- **Fuzzy numbers & MCDM**: `fuzzytool.fuzzynum` (triangular/trapezoidal numbers
|
|
14
|
+
with arithmetic, alpha-cuts, centroid, distance, ranking) and `fuzzytool.mcdm`
|
|
15
|
+
(`fuzzy_topsis`, `fuzzy_ahp`).
|
|
16
|
+
- **Rule learning**: `wang_mendel` generates a Mamdani rule base from data.
|
|
17
|
+
- **Tsukamoto** inference (`fuzzytool.inference.Tsukamoto`) with monotonic
|
|
18
|
+
consequents; added invertible `ramp_up` / `ramp_down` membership functions and
|
|
19
|
+
an `inverse` on `sigmoid`.
|
|
20
|
+
- **Batch inference**: `Mamdani.predict` / `TSK.predict` evaluate array-valued
|
|
21
|
+
inputs in a vectorized pass.
|
|
22
|
+
- **Serialization**: `fz.save` / `fz.load` (JSON) for Mamdani/TSK systems, plus
|
|
23
|
+
`to_dict`/`from_dict` on membership functions and variables.
|
|
24
|
+
- **scikit-learn compatibility**: `ANFIS.get_params` / `set_params`.
|
|
25
|
+
|
|
26
|
+
## [0.1.0] - 2026-06-24
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- Core membership functions: triangular, trapezoidal, gaussian, generalized
|
|
31
|
+
bell, sigmoid (`fuzzytool.membership`).
|
|
32
|
+
- T-norms and s-norms (min/prod/Łukasiewicz, max/probor/Łukasiewicz), resolved
|
|
33
|
+
by name (`fuzzytool.norms`).
|
|
34
|
+
- `Variable` (linguistic variable) with auto-generated or explicit terms, and an
|
|
35
|
+
operator-based rule-antecedent expression tree (`&`, `|`, `~`).
|
|
36
|
+
- **Mamdani** inference with configurable implication/aggregation and
|
|
37
|
+
defuzzification (centroid, bisector, MOM/SOM/LOM).
|
|
38
|
+
- **Takagi-Sugeno (TSK)** inference (zero- and first-order, plus callable
|
|
39
|
+
consequents).
|
|
40
|
+
- `fuzzytool.viz`: membership-function plots and 2-input control surfaces.
|
|
41
|
+
- `fuzzytool.datasets.credit_risk`: the flagship example system.
|
|
11
42
|
- Example notebooks (`notebooks/`): quickstart, interval type-2, clustering, and
|
|
12
43
|
ANFIS/F-transform — committed executed.
|
|
13
|
-
- Documentation
|
|
14
|
-
`.zenodo.json` for DOI archival
|
|
44
|
+
- Documentation (MkDocs Material), CI, a comparison page vs scikit-fuzzy, a
|
|
45
|
+
citing/releasing page, and `.zenodo.json` for DOI archival.
|
|
15
46
|
- **ANFIS** (`fuzzytool.anfis.ANFIS`): a trainable first-order Sugeno system over
|
|
16
47
|
a grid partition, fit with Jang's hybrid scheme (least-squares consequents +
|
|
17
48
|
gradient-descent premises). `fit` / `predict` / `history_`.
|
|
@@ -32,21 +63,4 @@ All notable changes to this project are documented here. The format is based on
|
|
|
32
63
|
- `IT2Mamdani` (center-of-sets type reduction) and `IT2TSK` engines.
|
|
33
64
|
- Karnik-Mendel type reduction: `km_endpoint`, `karnik_mendel`, `centroid_it2`.
|
|
34
65
|
- `viz.plot_it2_variable` (shaded FOU); `datasets.credit_risk_it2`.
|
|
35
|
-
|
|
36
|
-
## [0.1.0] - 2026-06-24
|
|
37
|
-
|
|
38
|
-
### Added
|
|
39
|
-
|
|
40
|
-
- Core membership functions: triangular, trapezoidal, gaussian, generalized
|
|
41
|
-
bell, sigmoid (`fuzzytool.membership`).
|
|
42
|
-
- T-norms and s-norms (min/prod/Łukasiewicz, max/probor/Łukasiewicz), resolved
|
|
43
|
-
by name (`fuzzytool.norms`).
|
|
44
|
-
- `Variable` (linguistic variable) with auto-generated or explicit terms, and an
|
|
45
|
-
operator-based rule-antecedent expression tree (`&`, `|`, `~`).
|
|
46
|
-
- **Mamdani** inference with configurable implication/aggregation and
|
|
47
|
-
defuzzification (centroid, bisector, MOM/SOM/LOM).
|
|
48
|
-
- **Takagi-Sugeno (TSK)** inference (zero- and first-order, plus callable
|
|
49
|
-
consequents).
|
|
50
|
-
- `fuzzytool.viz`: membership-function plots and 2-input control surfaces.
|
|
51
|
-
- `fuzzytool.datasets.tipper`: the classic example system.
|
|
52
|
-
- Test suite, MkDocs Material documentation, and CI.
|
|
66
|
+
- Test suite covering every module.
|
|
@@ -5,8 +5,9 @@ type: software
|
|
|
5
5
|
authors:
|
|
6
6
|
- family-names: Salmeron
|
|
7
7
|
given-names: Jose L.
|
|
8
|
-
version: 0.
|
|
8
|
+
version: 0.2.0
|
|
9
9
|
date-released: 2026-06-24
|
|
10
|
+
doi: 10.5281/zenodo.20836712
|
|
10
11
|
license: MIT
|
|
11
12
|
repository-code: "https://github.com/fuzzytool/fuzzytool.github.io"
|
|
12
13
|
url: "https://fuzzytool.github.io"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fuzzytool
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: A clean, extensible fuzzy-logic toolkit in pure Python + NumPy
|
|
5
5
|
Project-URL: Homepage, https://fuzzytool.github.io
|
|
6
6
|
Project-URL: Documentation, https://fuzzytool.github.io
|
|
@@ -41,6 +41,7 @@ Description-Content-Type: text/markdown
|
|
|
41
41
|
<a href="https://fuzzytool.github.io/"><img src="https://img.shields.io/badge/docs-fuzzytool.github.io-3f51b5" alt="Docs"></a>
|
|
42
42
|
<img src="https://img.shields.io/pypi/pyversions/fuzzytool?logo=python&logoColor=white" alt="Python versions">
|
|
43
43
|
<img src="https://img.shields.io/badge/license-MIT-blue" alt="License: MIT">
|
|
44
|
+
<a href="https://doi.org/10.5281/zenodo.20836712"><img src="https://zenodo.org/badge/DOI/10.5281/zenodo.20836712.svg" alt="DOI"></a>
|
|
44
45
|
</p>
|
|
45
46
|
|
|
46
47
|
# fuzzytool
|
|
@@ -90,7 +91,16 @@ s-norm, `~` the complement.
|
|
|
90
91
|
| 3 | **Type-2 / interval type-2** sets (footprint of uncertainty) + Karnik-Mendel type reduction | ✅ |
|
|
91
92
|
| 4 | **Fuzzy clustering**: fuzzy c-means, Gustafson-Kessel, possibilistic | ✅ |
|
|
92
93
|
| 5 | **ANFIS** (trainable TSK) + **F-transform** (direct/inverse) | ✅ |
|
|
93
|
-
| 6 | Notebooks, JOSS `paper.md`, Zenodo DOI, PyPI release |
|
|
94
|
+
| 6 | Notebooks, JOSS `paper.md`, Zenodo DOI, PyPI release | ✅ |
|
|
95
|
+
|
|
96
|
+
### v0.2.0
|
|
97
|
+
|
|
98
|
+
- **Fuzzy numbers & MCDM:** triangular/trapezoidal fuzzy-number arithmetic,
|
|
99
|
+
**Fuzzy TOPSIS** and **Fuzzy AHP**.
|
|
100
|
+
- **Rule learning:** **Wang-Mendel** rule-base generation from data; **Tsukamoto**
|
|
101
|
+
inference (monotonic consequents).
|
|
102
|
+
- **Engineering:** vectorized **batch inference** (`predict`), JSON
|
|
103
|
+
**save/load**, and a **scikit-learn** estimator interface for ANFIS.
|
|
94
104
|
|
|
95
105
|
See [`ROADMAP.md`](ROADMAP.md).
|
|
96
106
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<a href="https://fuzzytool.github.io/"><img src="https://img.shields.io/badge/docs-fuzzytool.github.io-3f51b5" alt="Docs"></a>
|
|
5
5
|
<img src="https://img.shields.io/pypi/pyversions/fuzzytool?logo=python&logoColor=white" alt="Python versions">
|
|
6
6
|
<img src="https://img.shields.io/badge/license-MIT-blue" alt="License: MIT">
|
|
7
|
+
<a href="https://doi.org/10.5281/zenodo.20836712"><img src="https://zenodo.org/badge/DOI/10.5281/zenodo.20836712.svg" alt="DOI"></a>
|
|
7
8
|
</p>
|
|
8
9
|
|
|
9
10
|
# fuzzytool
|
|
@@ -53,7 +54,16 @@ s-norm, `~` the complement.
|
|
|
53
54
|
| 3 | **Type-2 / interval type-2** sets (footprint of uncertainty) + Karnik-Mendel type reduction | ✅ |
|
|
54
55
|
| 4 | **Fuzzy clustering**: fuzzy c-means, Gustafson-Kessel, possibilistic | ✅ |
|
|
55
56
|
| 5 | **ANFIS** (trainable TSK) + **F-transform** (direct/inverse) | ✅ |
|
|
56
|
-
| 6 | Notebooks, JOSS `paper.md`, Zenodo DOI, PyPI release |
|
|
57
|
+
| 6 | Notebooks, JOSS `paper.md`, Zenodo DOI, PyPI release | ✅ |
|
|
58
|
+
|
|
59
|
+
### v0.2.0
|
|
60
|
+
|
|
61
|
+
- **Fuzzy numbers & MCDM:** triangular/trapezoidal fuzzy-number arithmetic,
|
|
62
|
+
**Fuzzy TOPSIS** and **Fuzzy AHP**.
|
|
63
|
+
- **Rule learning:** **Wang-Mendel** rule-base generation from data; **Tsukamoto**
|
|
64
|
+
inference (monotonic consequents).
|
|
65
|
+
- **Engineering:** vectorized **batch inference** (`predict`), JSON
|
|
66
|
+
**save/load**, and a **scikit-learn** estimator interface for ANFIS.
|
|
57
67
|
|
|
58
68
|
See [`ROADMAP.md`](ROADMAP.md).
|
|
59
69
|
|
|
@@ -42,7 +42,22 @@ self-contained, testable increment.
|
|
|
42
42
|
- [x] F-transform: direct and inverse over a triangular fuzzy partition
|
|
43
43
|
(partition of unity), with smoothing/denoising.
|
|
44
44
|
|
|
45
|
-
##
|
|
45
|
+
## ✅ v0.2.0 — Decision-making, rule learning, engineering
|
|
46
|
+
|
|
47
|
+
- [x] Fuzzy numbers (triangular/trapezoidal) + arithmetic; fuzzy MCDM
|
|
48
|
+
(`fuzzy_topsis`, `fuzzy_ahp`).
|
|
49
|
+
- [x] Rule learning from data (`wang_mendel`); Tsukamoto inference with
|
|
50
|
+
invertible ramp/sigmoid MFs.
|
|
51
|
+
- [x] Vectorized batch inference (`Mamdani.predict` / `TSK.predict`);
|
|
52
|
+
JSON serialization (`save`/`load`); scikit-learn estimator interface on ANFIS.
|
|
53
|
+
|
|
54
|
+
## Ideas for later
|
|
55
|
+
|
|
56
|
+
- Fuzzy cognitive maps (+ grey FCM).
|
|
57
|
+
- General (non-interval) type-2; more defuzzifiers.
|
|
58
|
+
- FIS tuning via metaheuristics (e.g. PSO).
|
|
59
|
+
|
|
60
|
+
## ⏳ Phase 6 — Release (v0.1.0)
|
|
46
61
|
|
|
47
62
|
- [x] Example notebooks (`notebooks/`) and a comparison page vs scikit-fuzzy.
|
|
48
63
|
- [x] JOSS `paper.md`; Zenodo metadata (`.zenodo.json`) and a citing/releasing page.
|
|
@@ -30,6 +30,26 @@ Generated from docstrings with `mkdocstrings`.
|
|
|
30
30
|
|
|
31
31
|
::: fuzzytool.inference.tsk
|
|
32
32
|
|
|
33
|
+
## Inference — Tsukamoto
|
|
34
|
+
|
|
35
|
+
::: fuzzytool.inference.tsukamoto
|
|
36
|
+
|
|
37
|
+
## Rule learning (Wang-Mendel)
|
|
38
|
+
|
|
39
|
+
::: fuzzytool.learn
|
|
40
|
+
|
|
41
|
+
## Fuzzy numbers
|
|
42
|
+
|
|
43
|
+
::: fuzzytool.fuzzynum
|
|
44
|
+
|
|
45
|
+
## Fuzzy MCDM
|
|
46
|
+
|
|
47
|
+
::: fuzzytool.mcdm
|
|
48
|
+
|
|
49
|
+
## Serialization
|
|
50
|
+
|
|
51
|
+
::: fuzzytool.serialize
|
|
52
|
+
|
|
33
53
|
## Interval type-2 — sets
|
|
34
54
|
|
|
35
55
|
::: fuzzytool.type2.sets
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## Citing
|
|
4
4
|
|
|
5
|
-
If you use fuzzytool in academic work, please cite it.
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
If you use fuzzytool in academic work, please cite it. The archival DOI (all
|
|
6
|
+
versions) is [10.5281/zenodo.20836712](https://doi.org/10.5281/zenodo.20836712);
|
|
7
|
+
each release also mints its own versioned DOI. Repository citation metadata lives
|
|
8
|
+
in [`CITATION.cff`](https://github.com/fuzzytool/fuzzytool.github.io/blob/main/CITATION.cff).
|
|
8
9
|
|
|
9
10
|
## Releasing (maintainers)
|
|
10
11
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Batch inference, serialization & scikit-learn
|
|
2
|
+
|
|
3
|
+
## Batch (vectorized) inference
|
|
4
|
+
|
|
5
|
+
Calling a system evaluates one sample. `predict(**arrays)` evaluates many at once
|
|
6
|
+
— pass an array per variable and get an array back. Available on `Mamdani` and
|
|
7
|
+
`TSK`.
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
import numpy as np
|
|
11
|
+
from fuzzytool import datasets
|
|
12
|
+
|
|
13
|
+
sys, *_ = datasets.credit_risk()
|
|
14
|
+
scores = np.array([520.0, 660.0, 800.0])
|
|
15
|
+
dtis = np.array([42.0, 30.0, 10.0])
|
|
16
|
+
|
|
17
|
+
sys.predict(score=scores, dti=dtis) # array of premiums, one per sample
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The result matches calling the system once per sample, but the firing, implication
|
|
21
|
+
and aggregation steps run vectorized.
|
|
22
|
+
|
|
23
|
+
## Saving and loading systems
|
|
24
|
+
|
|
25
|
+
Serialize a Mamdani or TSK system to JSON and restore it later. Connectives and
|
|
26
|
+
the defuzzifier must be given **by name**, variables must use **built-in**
|
|
27
|
+
membership functions, and TSK consequents must be numbers or coefficient mappings.
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
import fuzzytool as fz
|
|
31
|
+
|
|
32
|
+
fz.save(sys, "credit_risk.json")
|
|
33
|
+
restored = fz.load("credit_risk.json")
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`fuzzytool.membership.to_dict`/`from_dict` and `Variable.to_dict`/`from_dict`
|
|
37
|
+
expose the building blocks if you need finer control.
|
|
38
|
+
|
|
39
|
+
## scikit-learn compatibility
|
|
40
|
+
|
|
41
|
+
`ANFIS` follows the estimator protocol (`fit` returns `self`, plus `predict`,
|
|
42
|
+
`get_params`, `set_params`), so it drops into a `Pipeline` or `GridSearchCV`
|
|
43
|
+
without importing scikit-learn at all:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import fuzzytool as fz
|
|
47
|
+
|
|
48
|
+
model = fz.ANFIS(n_inputs=2, n_mf=3)
|
|
49
|
+
model.get_params() # {'n_inputs': 2, 'n_mf': 3, 'learning_rate': 0.05}
|
|
50
|
+
clone = fz.ANFIS(**model.get_params())
|
|
51
|
+
```
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Fuzzy numbers & MCDM
|
|
2
|
+
|
|
3
|
+
## Fuzzy numbers
|
|
4
|
+
|
|
5
|
+
[`fuzzytool.fuzzynum`][fuzzytool.fuzzynum] provides triangular (TFN) and
|
|
6
|
+
trapezoidal (TrFN) fuzzy numbers with arithmetic, alpha-cuts, a crisp centroid,
|
|
7
|
+
and a vertex distance.
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from fuzzytool.fuzzynum import tfn, trfn, rank
|
|
11
|
+
|
|
12
|
+
a, b = tfn(1, 2, 3), tfn(2, 3, 4)
|
|
13
|
+
a + b # TFN(3, 5, 7)
|
|
14
|
+
a * 2 # TFN(2, 4, 6)
|
|
15
|
+
a.centroid() # 2.0
|
|
16
|
+
a.alpha_cut(0.5) # (1.5, 2.5)
|
|
17
|
+
rank([tfn(0,1,2), tfn(5,6,7)]) # [1, 0] (largest first)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`+`/`-` are exact; `*`/`/` use the standard positive-support approximation.
|
|
21
|
+
|
|
22
|
+
## Fuzzy multi-criteria decision making
|
|
23
|
+
|
|
24
|
+
[`fuzzytool.mcdm`][fuzzytool.mcdm] offers two classic methods.
|
|
25
|
+
|
|
26
|
+
### Fuzzy TOPSIS (Chen)
|
|
27
|
+
|
|
28
|
+
Rank alternatives by closeness to the fuzzy positive-ideal solution. The decision
|
|
29
|
+
matrix and weights are triangular fuzzy numbers; `benefit[j]` says whether
|
|
30
|
+
criterion `j` is maximized.
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from fuzzytool.fuzzynum import tfn
|
|
34
|
+
from fuzzytool.mcdm import fuzzy_topsis
|
|
35
|
+
|
|
36
|
+
matrix = [
|
|
37
|
+
[tfn(7, 8, 9), tfn(5, 6, 7)], # alternative A: ratings per criterion
|
|
38
|
+
[tfn(3, 4, 5), tfn(8, 9, 9)], # alternative B
|
|
39
|
+
]
|
|
40
|
+
weights = [tfn(0.4, 0.5, 0.6), tfn(0.3, 0.4, 0.5)]
|
|
41
|
+
res = fuzzy_topsis(matrix, weights, benefit=[True, True])
|
|
42
|
+
res.closeness # closeness coefficient per alternative
|
|
43
|
+
res.ranking # alternative indices, best first
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Fuzzy AHP (Chang's extent analysis)
|
|
47
|
+
|
|
48
|
+
Derive crisp criterion weights from a fuzzy pairwise-comparison matrix.
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from fuzzytool.fuzzynum import tfn
|
|
52
|
+
from fuzzytool.mcdm import fuzzy_ahp
|
|
53
|
+
|
|
54
|
+
one = tfn(1, 1, 1)
|
|
55
|
+
matrix = [
|
|
56
|
+
[one, tfn(2, 3, 4)],
|
|
57
|
+
[tfn(1/4, 1/3, 1/2), one],
|
|
58
|
+
]
|
|
59
|
+
fuzzy_ahp(matrix) # normalized weights, e.g. [0.74, 0.26]
|
|
60
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Rule learning & Tsukamoto
|
|
2
|
+
|
|
3
|
+
## Learning a rule base from data (Wang-Mendel)
|
|
4
|
+
|
|
5
|
+
[`wang_mendel`][fuzzytool.learn.wang_mendel] generates a Mamdani rule base from
|
|
6
|
+
data: each sample becomes one rule (picking the highest-membership term per
|
|
7
|
+
variable), and antecedent conflicts are resolved by rule degree.
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
import numpy as np
|
|
11
|
+
import fuzzytool as fz
|
|
12
|
+
|
|
13
|
+
X = np.random.default_rng(0).uniform(0, 10, size=(300, 2))
|
|
14
|
+
y = (X[:, 0] + X[:, 1]) / 2
|
|
15
|
+
|
|
16
|
+
a = fz.Variable("a", (0, 10), terms=["lo", "mid", "hi"])
|
|
17
|
+
b = fz.Variable("b", (0, 10), terms=["lo", "mid", "hi"])
|
|
18
|
+
out = fz.Variable("out", (0, 10), terms=["lo", "mid", "hi"])
|
|
19
|
+
|
|
20
|
+
sys = fz.wang_mendel(X, y, [a, b], out) # a ready-to-use Mamdani system
|
|
21
|
+
sys(a=8, b=7)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The input variables supply the partition; the learned system is a plain
|
|
25
|
+
`Mamdani`, so it also supports batch [`predict`](batch-and-io.md) and
|
|
26
|
+
serialization.
|
|
27
|
+
|
|
28
|
+
## Tsukamoto inference
|
|
29
|
+
|
|
30
|
+
[`Tsukamoto`][fuzzytool.inference.tsukamoto.Tsukamoto] consequents are
|
|
31
|
+
**monotonic** membership functions; a rule firing with strength `w` outputs the
|
|
32
|
+
value where its consequent reaches `w` (its inverse), and the system returns the
|
|
33
|
+
firing-weighted average — no defuzzification.
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
import fuzzytool as fz
|
|
37
|
+
|
|
38
|
+
x = fz.Variable("x", (0, 10), terms=["lo", "hi"])
|
|
39
|
+
sys = fz.Tsukamoto()
|
|
40
|
+
sys.rule(x["lo"], fz.ramp_down(0, 30)) # monotonic consequents only
|
|
41
|
+
sys.rule(x["hi"], fz.ramp_up(0, 30))
|
|
42
|
+
sys(x=7)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Use [`ramp_up`][fuzzytool.membership.ramp_up],
|
|
46
|
+
[`ramp_down`][fuzzytool.membership.ramp_down], or
|
|
47
|
+
[`sigmoid`][fuzzytool.membership.sigmoid] — each exposes the `inverse` Tsukamoto
|
|
48
|
+
needs.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Fuzzy MCDM: rank loan applicants with Fuzzy TOPSIS, weight criteria with AHP.
|
|
2
|
+
|
|
3
|
+
python examples/mcdm.py
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from fuzzytool.fuzzynum import tfn
|
|
7
|
+
from fuzzytool.mcdm import fuzzy_ahp, fuzzy_topsis
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main() -> None:
|
|
11
|
+
# Criteria: repayment ability (benefit), collateral (benefit), risk (cost).
|
|
12
|
+
# Weights from a fuzzy pairwise comparison (AHP).
|
|
13
|
+
one = tfn(1, 1, 1)
|
|
14
|
+
pairwise = [
|
|
15
|
+
[one, tfn(2, 3, 4), tfn(1, 2, 3)],
|
|
16
|
+
[tfn(1/4, 1/3, 1/2), one, tfn(1/3, 1/2, 1)],
|
|
17
|
+
[tfn(1/3, 1/2, 1), tfn(1, 2, 3), one],
|
|
18
|
+
]
|
|
19
|
+
crisp_w = fuzzy_ahp(pairwise)
|
|
20
|
+
print("AHP criterion weights:", crisp_w.round(3))
|
|
21
|
+
|
|
22
|
+
# Fuzzy weights for TOPSIS (triangular around the AHP weights).
|
|
23
|
+
weights = [tfn(max(0, w - 0.1), w, min(1, w + 0.1)) for w in crisp_w]
|
|
24
|
+
|
|
25
|
+
applicants = ["Ana", "Beto", "Carla"]
|
|
26
|
+
matrix = [
|
|
27
|
+
[tfn(7, 8, 9), tfn(6, 7, 8), tfn(2, 3, 4)], # Ana
|
|
28
|
+
[tfn(4, 5, 6), tfn(8, 9, 9), tfn(5, 6, 7)], # Beto
|
|
29
|
+
[tfn(6, 7, 8), tfn(3, 4, 5), tfn(1, 2, 3)], # Carla
|
|
30
|
+
]
|
|
31
|
+
res = fuzzy_topsis(matrix, weights, benefit=[True, True, False])
|
|
32
|
+
|
|
33
|
+
print("\nFuzzy TOPSIS ranking (best first):")
|
|
34
|
+
for place, i in enumerate(res.ranking, 1):
|
|
35
|
+
print(f" {place}. {applicants[i]:6s} CC = {res.closeness[i]:.3f}")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
if __name__ == "__main__":
|
|
39
|
+
main()
|
|
@@ -18,12 +18,14 @@ t-/s-norms, defuzzifiers): a new variant is a new callable, never a change to
|
|
|
18
18
|
the inference loop.
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
|
-
from . import cluster, datasets, defuzz, membership, norms, type2
|
|
21
|
+
from . import cluster, datasets, defuzz, fuzzynum, mcdm, membership, norms, type2
|
|
22
22
|
from .anfis import ANFIS
|
|
23
23
|
from .cluster import fuzzy_cmeans, gustafson_kessel, possibilistic_cmeans
|
|
24
24
|
from .ftransform import FTransform
|
|
25
|
-
from .inference import TSK, Mamdani
|
|
26
|
-
from .
|
|
25
|
+
from .inference import TSK, Mamdani, Tsukamoto
|
|
26
|
+
from .learn import wang_mendel
|
|
27
|
+
from .membership import gauss, gbell, ramp_down, ramp_up, sigmoid, trap, tri
|
|
28
|
+
from .serialize import load, save
|
|
27
29
|
from .sets import Variable
|
|
28
30
|
from .type2 import (
|
|
29
31
|
IT2TSK,
|
|
@@ -34,15 +36,18 @@ from .type2 import (
|
|
|
34
36
|
it2_scale,
|
|
35
37
|
)
|
|
36
38
|
|
|
37
|
-
__version__ = "0.
|
|
39
|
+
__version__ = "0.2.0"
|
|
38
40
|
|
|
39
41
|
__all__ = [
|
|
40
42
|
"__version__",
|
|
41
43
|
"Variable",
|
|
42
44
|
"Mamdani",
|
|
43
45
|
"TSK",
|
|
46
|
+
"Tsukamoto",
|
|
47
|
+
# rule learning
|
|
48
|
+
"wang_mendel",
|
|
44
49
|
# membership shortcuts
|
|
45
|
-
"tri", "trap", "gauss", "gbell", "sigmoid",
|
|
50
|
+
"tri", "trap", "gauss", "gbell", "sigmoid", "ramp_up", "ramp_down",
|
|
46
51
|
# interval type-2
|
|
47
52
|
"IT2Mamdani", "IT2TSK",
|
|
48
53
|
"it2", "it2_scale", "it2_gauss_uncertain_mean", "it2_gauss_uncertain_std",
|
|
@@ -50,6 +55,9 @@ __all__ = [
|
|
|
50
55
|
"fuzzy_cmeans", "gustafson_kessel", "possibilistic_cmeans",
|
|
51
56
|
# learning & approximation
|
|
52
57
|
"ANFIS", "FTransform",
|
|
58
|
+
# serialization
|
|
59
|
+
"save", "load",
|
|
53
60
|
# submodules
|
|
54
61
|
"membership", "norms", "defuzz", "datasets", "type2", "cluster",
|
|
62
|
+
"fuzzynum", "mcdm",
|
|
55
63
|
]
|
|
@@ -134,6 +134,19 @@ class ANFIS:
|
|
|
134
134
|
*_, wn, _ = self._firing(X)
|
|
135
135
|
return self._outputs(X, wn)[1]
|
|
136
136
|
|
|
137
|
+
# --- scikit-learn estimator interface ---------------------------------
|
|
138
|
+
|
|
139
|
+
def get_params(self, deep: bool = True) -> dict:
|
|
140
|
+
"""Hyperparameters, for scikit-learn compatibility (Pipeline/GridSearch)."""
|
|
141
|
+
return {"n_inputs": self.p, "n_mf": self.n_mf, "learning_rate": self.lr}
|
|
142
|
+
|
|
143
|
+
def set_params(self, **params) -> ANFIS:
|
|
144
|
+
"""Set hyperparameters (resets trained state), returning ``self``."""
|
|
145
|
+
merged = self.get_params()
|
|
146
|
+
merged.update(params)
|
|
147
|
+
self.__init__(**merged)
|
|
148
|
+
return self
|
|
149
|
+
|
|
137
150
|
def __repr__(self) -> str:
|
|
138
151
|
return f"ANFIS(n_inputs={self.p}, n_mf={self.n_mf}, rules={self.R})"
|
|
139
152
|
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""Fuzzy numbers and their arithmetic.
|
|
2
|
+
|
|
3
|
+
A fuzzy number is a convex, normal fuzzy set on the reals. This module provides
|
|
4
|
+
the two most used shapes — triangular (TFN) and trapezoidal (TrFN) — with
|
|
5
|
+
standard fuzzy arithmetic (``+ - * /`` and scalar scaling), alpha-cuts, a crisp
|
|
6
|
+
(centroid) value, and a vertex distance used by fuzzy MCDM (see
|
|
7
|
+
:mod:`fuzzytool.mcdm`).
|
|
8
|
+
|
|
9
|
+
Multiplication and division use the common positive-support approximation
|
|
10
|
+
(operate on the ordered defining points), which is exact for ``+``/``-`` and a
|
|
11
|
+
good approximation for ``*``/``/`` when supports are positive.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from numbers import Real
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class FuzzyNumber:
|
|
22
|
+
"""Base class for fuzzy numbers defined by ordered points ``p``."""
|
|
23
|
+
|
|
24
|
+
points: tuple[float, ...]
|
|
25
|
+
|
|
26
|
+
def __init__(self, *points: float) -> None:
|
|
27
|
+
pts = tuple(float(x) for x in points)
|
|
28
|
+
if list(pts) != sorted(pts):
|
|
29
|
+
raise ValueError(f"fuzzy-number points must be non-decreasing, got {pts}")
|
|
30
|
+
self.points = pts
|
|
31
|
+
|
|
32
|
+
# subclasses implement the membership and the crisp value
|
|
33
|
+
def __call__(self, x): # pragma: no cover - overridden
|
|
34
|
+
raise NotImplementedError
|
|
35
|
+
|
|
36
|
+
def centroid(self) -> float: # pragma: no cover - overridden
|
|
37
|
+
raise NotImplementedError
|
|
38
|
+
|
|
39
|
+
def alpha_cut(self, alpha: float) -> tuple[float, float]: # pragma: no cover
|
|
40
|
+
raise NotImplementedError
|
|
41
|
+
|
|
42
|
+
def _binary(self, other, op, neg=False):
|
|
43
|
+
cls = type(self)
|
|
44
|
+
if isinstance(other, Real):
|
|
45
|
+
other = cls(*([float(other)] * len(self.points)))
|
|
46
|
+
if not isinstance(other, cls):
|
|
47
|
+
return NotImplemented
|
|
48
|
+
b = other.points[::-1] if neg else other.points
|
|
49
|
+
return cls(*[op(a, c) for a, c in zip(self.points, b)])
|
|
50
|
+
|
|
51
|
+
def __add__(self, other):
|
|
52
|
+
return self._binary(other, lambda a, b: a + b)
|
|
53
|
+
|
|
54
|
+
def __sub__(self, other):
|
|
55
|
+
return self._binary(other, lambda a, b: a - b, neg=True)
|
|
56
|
+
|
|
57
|
+
def __mul__(self, other):
|
|
58
|
+
if isinstance(other, Real):
|
|
59
|
+
k = float(other)
|
|
60
|
+
pts = [k * p for p in self.points]
|
|
61
|
+
return type(self)(*(pts if k >= 0 else pts[::-1]))
|
|
62
|
+
return self._binary(other, lambda a, b: a * b)
|
|
63
|
+
|
|
64
|
+
__rmul__ = __mul__
|
|
65
|
+
__radd__ = __add__
|
|
66
|
+
|
|
67
|
+
def __truediv__(self, other):
|
|
68
|
+
return self._binary(other, lambda a, b: a / b, neg=True)
|
|
69
|
+
|
|
70
|
+
def distance(self, other: FuzzyNumber) -> float:
|
|
71
|
+
"""Vertex distance to another fuzzy number of the same shape."""
|
|
72
|
+
if type(self) is not type(other):
|
|
73
|
+
raise TypeError("distance requires fuzzy numbers of the same shape")
|
|
74
|
+
a = np.asarray(self.points)
|
|
75
|
+
b = np.asarray(other.points)
|
|
76
|
+
return float(np.sqrt(np.mean((a - b) ** 2)))
|
|
77
|
+
|
|
78
|
+
def __eq__(self, other) -> bool:
|
|
79
|
+
return type(self) is type(other) and self.points == other.points
|
|
80
|
+
|
|
81
|
+
def __hash__(self) -> int:
|
|
82
|
+
return hash((type(self).__name__, self.points))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class TriangularFuzzyNumber(FuzzyNumber):
|
|
86
|
+
"""Triangular fuzzy number ``(a, b, c)`` with peak at ``b``."""
|
|
87
|
+
|
|
88
|
+
def __init__(self, a: float, b: float, c: float) -> None:
|
|
89
|
+
super().__init__(a, b, c)
|
|
90
|
+
self.a, self.b, self.c = self.points
|
|
91
|
+
|
|
92
|
+
def __call__(self, x):
|
|
93
|
+
x = np.asarray(x, dtype=float)
|
|
94
|
+
left = np.divide(x - self.a, self.b - self.a, out=np.zeros_like(x),
|
|
95
|
+
where=self.b != self.a)
|
|
96
|
+
right = np.divide(self.c - x, self.c - self.b, out=np.zeros_like(x),
|
|
97
|
+
where=self.c != self.b)
|
|
98
|
+
left = np.where(self.b == self.a, (x >= self.a).astype(float), left)
|
|
99
|
+
right = np.where(self.c == self.b, (x <= self.c).astype(float), right)
|
|
100
|
+
return np.clip(np.minimum(left, right), 0.0, 1.0)
|
|
101
|
+
|
|
102
|
+
def centroid(self) -> float:
|
|
103
|
+
return (self.a + self.b + self.c) / 3.0
|
|
104
|
+
|
|
105
|
+
def alpha_cut(self, alpha: float) -> tuple[float, float]:
|
|
106
|
+
return (self.a + alpha * (self.b - self.a),
|
|
107
|
+
self.c - alpha * (self.c - self.b))
|
|
108
|
+
|
|
109
|
+
def __repr__(self) -> str:
|
|
110
|
+
return f"TFN({self.a}, {self.b}, {self.c})"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class TrapezoidalFuzzyNumber(FuzzyNumber):
|
|
114
|
+
"""Trapezoidal fuzzy number ``(a, b, c, d)`` with flat top ``[b, c]``."""
|
|
115
|
+
|
|
116
|
+
def __init__(self, a: float, b: float, c: float, d: float) -> None:
|
|
117
|
+
super().__init__(a, b, c, d)
|
|
118
|
+
self.a, self.b, self.c, self.d = self.points
|
|
119
|
+
|
|
120
|
+
def __call__(self, x):
|
|
121
|
+
x = np.asarray(x, dtype=float)
|
|
122
|
+
left = np.divide(x - self.a, self.b - self.a, out=np.ones_like(x),
|
|
123
|
+
where=self.b != self.a)
|
|
124
|
+
right = np.divide(self.d - x, self.d - self.c, out=np.ones_like(x),
|
|
125
|
+
where=self.d != self.c)
|
|
126
|
+
return np.clip(np.minimum.reduce([left, np.ones_like(x), right]), 0.0, 1.0)
|
|
127
|
+
|
|
128
|
+
def centroid(self) -> float:
|
|
129
|
+
a, b, c, d = self.points
|
|
130
|
+
# Centroid of a trapezoid (handles the triangular degenerate case).
|
|
131
|
+
num = (d ** 2 + c ** 2 + c * d) - (a ** 2 + b ** 2 + a * b)
|
|
132
|
+
den = 3.0 * ((c + d) - (a + b))
|
|
133
|
+
return num / den if den != 0 else (a + b + c + d) / 4.0
|
|
134
|
+
|
|
135
|
+
def alpha_cut(self, alpha: float) -> tuple[float, float]:
|
|
136
|
+
return (self.a + alpha * (self.b - self.a),
|
|
137
|
+
self.d - alpha * (self.d - self.c))
|
|
138
|
+
|
|
139
|
+
def __repr__(self) -> str:
|
|
140
|
+
return f"TrFN({self.a}, {self.b}, {self.c}, {self.d})"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def tfn(a: float, b: float, c: float) -> TriangularFuzzyNumber:
|
|
144
|
+
"""Shortcut for :class:`TriangularFuzzyNumber`."""
|
|
145
|
+
return TriangularFuzzyNumber(a, b, c)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def trfn(a: float, b: float, c: float, d: float) -> TrapezoidalFuzzyNumber:
|
|
149
|
+
"""Shortcut for :class:`TrapezoidalFuzzyNumber`."""
|
|
150
|
+
return TrapezoidalFuzzyNumber(a, b, c, d)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def rank(numbers: list[FuzzyNumber]) -> list[int]:
|
|
154
|
+
"""Indices that sort ``numbers`` from largest to smallest by centroid."""
|
|
155
|
+
return sorted(range(len(numbers)), key=lambda i: numbers[i].centroid(), reverse=True)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
__all__ = [
|
|
159
|
+
"FuzzyNumber", "TriangularFuzzyNumber", "TrapezoidalFuzzyNumber",
|
|
160
|
+
"tfn", "trfn", "rank",
|
|
161
|
+
]
|