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.
Files changed (79) hide show
  1. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/CHANGELOG.md +34 -20
  2. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/CITATION.cff +2 -1
  3. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/PKG-INFO +12 -2
  4. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/README.md +11 -1
  5. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/ROADMAP.md +16 -1
  6. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/api/python.md +20 -0
  7. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/citing.md +4 -3
  8. fuzzytool-0.2.0/docs/guide/batch-and-io.md +51 -0
  9. fuzzytool-0.2.0/docs/guide/mcdm.md +60 -0
  10. fuzzytool-0.2.0/docs/guide/rule-learning.md +48 -0
  11. fuzzytool-0.2.0/examples/mcdm.py +39 -0
  12. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/__init__.py +13 -5
  13. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/anfis.py +13 -0
  14. fuzzytool-0.2.0/fuzzytool/fuzzynum.py +161 -0
  15. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/inference/__init__.py +2 -1
  16. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/inference/mamdani.py +32 -0
  17. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/inference/tsk.py +21 -0
  18. fuzzytool-0.2.0/fuzzytool/inference/tsukamoto.py +65 -0
  19. fuzzytool-0.2.0/fuzzytool/learn.py +73 -0
  20. fuzzytool-0.2.0/fuzzytool/mcdm.py +124 -0
  21. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/membership.py +92 -2
  22. fuzzytool-0.2.0/fuzzytool/serialize.py +97 -0
  23. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/sets.py +59 -1
  24. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/mkdocs.yml +3 -0
  25. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/pyproject.toml +1 -1
  26. fuzzytool-0.2.0/tests/test_engineering.py +78 -0
  27. fuzzytool-0.2.0/tests/test_learn.py +62 -0
  28. fuzzytool-0.2.0/tests/test_mcdm.py +77 -0
  29. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/.github/workflows/ci.yml +0 -0
  30. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/.github/workflows/docs.yml +0 -0
  31. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/.github/workflows/release-pypi.yml +0 -0
  32. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/.gitignore +0 -0
  33. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/.zenodo.json +0 -0
  34. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/CLAUDE.md +0 -0
  35. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/CONTRIBUTING.md +0 -0
  36. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/LICENSE +0 -0
  37. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/comparison.md +0 -0
  38. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/extending.md +0 -0
  39. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/getting-started.md +0 -0
  40. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/guide/anfis.md +0 -0
  41. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/guide/clustering.md +0 -0
  42. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/guide/defuzzification.md +0 -0
  43. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/guide/ftransform.md +0 -0
  44. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/guide/mamdani.md +0 -0
  45. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/guide/membership.md +0 -0
  46. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/guide/rules.md +0 -0
  47. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/guide/tsk.md +0 -0
  48. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/guide/type2.md +0 -0
  49. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/guide/visualization.md +0 -0
  50. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/index.md +0 -0
  51. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/docs/installation.md +0 -0
  52. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/examples/anfis.py +0 -0
  53. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/examples/clustering.py +0 -0
  54. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/examples/credit_risk.py +0 -0
  55. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/examples/credit_risk_it2.py +0 -0
  56. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/examples/ftransform.py +0 -0
  57. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/cluster.py +0 -0
  58. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/datasets.py +0 -0
  59. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/defuzz.py +0 -0
  60. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/ftransform.py +0 -0
  61. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/norms.py +0 -0
  62. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/rules.py +0 -0
  63. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/type2/__init__.py +0 -0
  64. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/type2/inference.py +0 -0
  65. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/type2/reduction.py +0 -0
  66. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/type2/sets.py +0 -0
  67. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/fuzzytool/viz.py +0 -0
  68. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/notebooks/01_quickstart.ipynb +0 -0
  69. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/notebooks/02_type2.ipynb +0 -0
  70. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/notebooks/03_clustering.ipynb +0 -0
  71. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/notebooks/04_learning.ipynb +0 -0
  72. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/notebooks/README.md +0 -0
  73. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/paper.bib +0 -0
  74. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/paper.md +0 -0
  75. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/tests/test_cluster.py +0 -0
  76. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/tests/test_inference.py +0 -0
  77. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/tests/test_learning.py +0 -0
  78. {fuzzytool-0.1.0 → fuzzytool-0.2.0}/tests/test_membership.py +0 -0
  79. {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: a comparison page vs scikit-fuzzy and a citing/releasing page;
14
- `.zenodo.json` for DOI archival; `notebooks` optional dependency group.
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.1.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.1.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
- ## Phase 6 Release
45
+ ## v0.2.0Decision-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. Repository metadata lives
6
- in [`CITATION.cff`](https://github.com/fuzzytool/fuzzytool.github.io/blob/main/CITATION.cff);
7
- once a release is archived on Zenodo, cite the versioned DOI it mints.
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 .membership import gauss, gbell, sigmoid, trap, tri
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.1.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
+ ]
@@ -2,5 +2,6 @@
2
2
 
3
3
  from .mamdani import Mamdani
4
4
  from .tsk import TSK
5
+ from .tsukamoto import Tsukamoto
5
6
 
6
- __all__ = ["Mamdani", "TSK"]
7
+ __all__ = ["Mamdani", "TSK", "Tsukamoto"]