fuzzytool 0.4.0__tar.gz → 0.5.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 (110) hide show
  1. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/CHANGELOG.md +14 -0
  2. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/CITATION.cff +1 -1
  3. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/PKG-INFO +8 -1
  4. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/README.md +7 -0
  5. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/ROADMAP.md +14 -4
  6. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/api/python.md +4 -0
  7. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/guide/type2.md +46 -0
  8. fuzzytool-0.5.0/docs/images/gt2_zslices.png +0 -0
  9. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/__init__.py +9 -1
  10. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/type2/__init__.py +10 -0
  11. fuzzytool-0.5.0/fuzzytool/type2/general.py +211 -0
  12. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/pyproject.toml +1 -1
  13. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/tests/test_type2.py +56 -0
  14. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/.github/workflows/ci.yml +0 -0
  15. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/.github/workflows/docs.yml +0 -0
  16. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/.github/workflows/release-pypi.yml +0 -0
  17. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/.gitignore +0 -0
  18. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/.zenodo.json +0 -0
  19. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/CLAUDE.md +0 -0
  20. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/CONTRIBUTING.md +0 -0
  21. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/LICENSE +0 -0
  22. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/citing.md +0 -0
  23. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/extending.md +0 -0
  24. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/getting-started.md +0 -0
  25. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/guide/anfis.md +0 -0
  26. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/guide/batch-and-io.md +0 -0
  27. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/guide/clustering.md +0 -0
  28. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/guide/defuzzification.md +0 -0
  29. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/guide/fraud-alert.md +0 -0
  30. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/guide/ftransform.md +0 -0
  31. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/guide/mamdani.md +0 -0
  32. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/guide/mcdm.md +0 -0
  33. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/guide/membership.md +0 -0
  34. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/guide/rule-learning.md +0 -0
  35. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/guide/rules.md +0 -0
  36. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/guide/tsk.md +0 -0
  37. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/guide/visualization.md +0 -0
  38. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/images/clusters.png +0 -0
  39. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/images/credit_control_surface.png +0 -0
  40. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/images/fraud_anomaly_terms.png +0 -0
  41. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/images/fraud_control_surface.png +0 -0
  42. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/images/gs_score_terms.png +0 -0
  43. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/images/it2_score.png +0 -0
  44. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/images/premium_terms.png +0 -0
  45. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/images/tut_advisor_surface.png +0 -0
  46. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/images/tut_advisor_volatility.png +0 -0
  47. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/images/tut_anfis.png +0 -0
  48. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/images/tut_clusters.png +0 -0
  49. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/index.md +0 -0
  50. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/installation.md +0 -0
  51. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/integrations.md +0 -0
  52. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/javascripts/mathjax.js +0 -0
  53. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/tutorials/anfis-learning.md +0 -0
  54. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/tutorials/clustering-segmentation.md +0 -0
  55. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/tutorials/risk-advisor.md +0 -0
  56. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/docs/tutorials/topsis-decision.md +0 -0
  57. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/examples/anfis.py +0 -0
  58. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/examples/clustering.py +0 -0
  59. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/examples/credit_risk.py +0 -0
  60. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/examples/credit_risk_it2.py +0 -0
  61. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/examples/fraud_alert.py +0 -0
  62. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/examples/ftransform.py +0 -0
  63. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/examples/mcdm.py +0 -0
  64. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/anfis.py +0 -0
  65. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/cluster.py +0 -0
  66. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/datasets.py +0 -0
  67. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/defuzz.py +0 -0
  68. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/ftransform.py +0 -0
  69. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/fuzzynum.py +0 -0
  70. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/inference/__init__.py +0 -0
  71. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/inference/mamdani.py +0 -0
  72. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/inference/tsk.py +0 -0
  73. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/inference/tsukamoto.py +0 -0
  74. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/integrations/__init__.py +0 -0
  75. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/integrations/_util.py +0 -0
  76. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/integrations/agents.py +0 -0
  77. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/integrations/optuna.py +0 -0
  78. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/integrations/pandas.py +0 -0
  79. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/integrations/parallel.py +0 -0
  80. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/integrations/scipy.py +0 -0
  81. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/integrations/sklearn.py +0 -0
  82. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/integrations/torch.py +0 -0
  83. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/integrations/turboswarm.py +0 -0
  84. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/learn.py +0 -0
  85. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/mcdm.py +0 -0
  86. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/membership.py +0 -0
  87. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/norms.py +0 -0
  88. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/rules.py +0 -0
  89. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/serialize.py +0 -0
  90. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/sets.py +0 -0
  91. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/type2/inference.py +0 -0
  92. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/type2/reduction.py +0 -0
  93. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/type2/sets.py +0 -0
  94. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/fuzzytool/viz.py +0 -0
  95. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/mkdocs.yml +0 -0
  96. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/notebooks/01_quickstart.ipynb +0 -0
  97. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/notebooks/02_type2.ipynb +0 -0
  98. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/notebooks/03_clustering.ipynb +0 -0
  99. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/notebooks/04_learning.ipynb +0 -0
  100. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/notebooks/README.md +0 -0
  101. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/paper.bib +0 -0
  102. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/paper.md +0 -0
  103. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/tests/test_cluster.py +0 -0
  104. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/tests/test_engineering.py +0 -0
  105. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/tests/test_inference.py +0 -0
  106. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/tests/test_integrations.py +0 -0
  107. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/tests/test_learn.py +0 -0
  108. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/tests/test_learning.py +0 -0
  109. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/tests/test_mcdm.py +0 -0
  110. {fuzzytool-0.4.0 → fuzzytool-0.5.0}/tests/test_membership.py +0 -0
@@ -6,6 +6,20 @@ All notable changes to this project are documented here. The format is based on
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.5.0] - 2026-06-28
10
+
11
+ ### Added
12
+
13
+ - **General type-2 (GT2) fuzzy sets** via the zSlices / alpha-plane
14
+ representation (`fuzzytool.type2.general`):
15
+ - `GeneralType2MF` — a GT2 set as a stack of IT2 z-slices; also exposes the
16
+ overall FOU (`lower`/`upper`), so it can stand in for its IT2 footprint.
17
+ - Constructors `gt2_from_it2`, `gt2_gauss_uncertain_mean`, `gt2_scale`
18
+ (triangular secondary membership peaking at the principal MF).
19
+ - `GeneralType2Mamdani` — inference as a z-weighted stack of IT2 Mamdani
20
+ runs, reusing the Karnik-Mendel machinery.
21
+ - `centroid_gt2` — zSlices type reduction of a single GT2 set.
22
+
9
23
  ## [0.4.0] - 2026-06-28
10
24
 
11
25
  ### Added
@@ -5,7 +5,7 @@ type: software
5
5
  authors:
6
6
  - family-names: Salmeron
7
7
  given-names: Jose L.
8
- version: 0.4.0
8
+ version: 0.5.0
9
9
  date-released: 2026-06-28
10
10
  doi: 10.5281/zenodo.20836712
11
11
  license: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fuzzytool
3
- Version: 0.4.0
3
+ Version: 0.5.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
@@ -118,6 +118,13 @@ s-norm, `~` the complement.
118
118
  | 6 | Notebooks, JOSS `paper.md`, Zenodo DOI, PyPI release | ✅ |
119
119
  | 7 | **Ecosystem integrations**: pandas, scikit-learn, PyTorch, SciPy, turboswarm, Optuna, Joblib/Dask, LLM agents | ✅ |
120
120
 
121
+ ### v0.5.0
122
+
123
+ - **General type-2 (GT2) fuzzy sets** via the zSlices / alpha-plane
124
+ representation: `GeneralType2MF`, the `gt2_*` constructors,
125
+ `GeneralType2Mamdani` inference and `centroid_gt2` type reduction — reusing
126
+ the existing interval type-2 / Karnik-Mendel machinery.
127
+
121
128
  ### v0.4.0
122
129
 
123
130
  - **turboswarm integration:** gradient-free, global tuning of a fuzzy system's
@@ -57,6 +57,13 @@ s-norm, `~` the complement.
57
57
  | 6 | Notebooks, JOSS `paper.md`, Zenodo DOI, PyPI release | ✅ |
58
58
  | 7 | **Ecosystem integrations**: pandas, scikit-learn, PyTorch, SciPy, turboswarm, Optuna, Joblib/Dask, LLM agents | ✅ |
59
59
 
60
+ ### v0.5.0
61
+
62
+ - **General type-2 (GT2) fuzzy sets** via the zSlices / alpha-plane
63
+ representation: `GeneralType2MF`, the `gt2_*` constructors,
64
+ `GeneralType2Mamdani` inference and `centroid_gt2` type reduction — reusing
65
+ the existing interval type-2 / Karnik-Mendel machinery.
66
+
60
67
  ### v0.4.0
61
68
 
62
69
  - **turboswarm integration:** gradient-free, global tuning of a fuzzy system's
@@ -72,16 +72,26 @@ Each pulls its dependency in only on import, behind its own extra.
72
72
  - [x] **Agents (LangChain/LangGraph)** (`[agents]`): `explain` plus an
73
73
  `inference_tool` LangChain tool that reports which rules fired.
74
74
 
75
+ ## ✅ General type-2 (zSlices)
76
+
77
+ - [x] `GeneralType2MF` via the zSlices / alpha-plane representation (a stack of
78
+ IT2 slices; also exposes the overall FOU, so it degrades to IT2).
79
+ - [x] Constructors `gt2_from_it2`, `gt2_gauss_uncertain_mean`, `gt2_scale`
80
+ (triangular secondary MF).
81
+ - [x] `GeneralType2Mamdani` (z-weighted stack of IT2 Mamdani inferences) and
82
+ `centroid_gt2` zSlices type reduction.
83
+
75
84
  ## Ideas for later
76
85
 
77
86
  - Fuzzy cognitive maps (+ grey FCM).
78
- - General (non-interval) type-2; more defuzzifiers.
87
+ - More defuzzifiers; non-triangular GT2 secondary MFs.
79
88
 
80
89
  ## ⏳ Phase 6 — Release (v0.1.0)
81
90
 
82
91
  - [x] Example notebooks (`notebooks/`) and a comparison page vs scikit-fuzzy.
83
92
  - [x] JOSS `paper.md`; Zenodo metadata (`.zenodo.json`) and a citing/releasing page.
84
93
  - [x] GitHub Pages live (`https://fuzzytool.github.io`).
85
- - [ ] Register the PyPI trusted publisher, then tag `v0.1.0` to publish (the
86
- `release-pypi.yml` workflow is ready).
87
- - [ ] Enable the Zenodo GitHub integration and cut a release to mint a DOI.
94
+ - [x] PyPI trusted publisher registered; tagging a version runs `release-pypi.yml`
95
+ (published 0.1.0 0.4.0 via OIDC, no stored token).
96
+ - [x] Zenodo GitHub integration; each GitHub release is archived with a DOI
97
+ (`10.5281/zenodo.20836712`).
@@ -62,6 +62,10 @@ Generated from docstrings with `mkdocstrings`.
62
62
 
63
63
  ::: fuzzytool.type2.reduction
64
64
 
65
+ ## General type-2 (zSlices)
66
+
67
+ ::: fuzzytool.type2.general
68
+
65
69
  ## Fuzzy clustering
66
70
 
67
71
  ::: fuzzytool.cluster
@@ -82,3 +82,49 @@ plt.show()
82
82
  ```
83
83
 
84
84
  ![IT2 score variable: each term's LMF and UMF with a shaded footprint of uncertainty](../images/it2_score.png)
85
+
86
+ ## General type-2 (zSlices)
87
+
88
+ An IT2 set treats every point inside its FOU as equally possible. A **general
89
+ type-2** set adds a *secondary* membership that weights those possibilities —
90
+ the third dimension IT2 discards. fuzzytool represents a GT2 set with the
91
+ **zSlices / alpha-plane** decomposition: slicing the secondary domain at levels
92
+ `z` turns the GT2 set into a stack of ordinary IT2 sets, so inference and type
93
+ reduction reuse the IT2 machinery and combine the slices weighted by `z`.
94
+
95
+ Each constructor builds a GT2 set from an IT2 footprint plus a **triangular
96
+ secondary** peaking at the principal (mid) MF — narrow at `z = 1`, the full FOU
97
+ at `z → 0`:
98
+
99
+ ```python
100
+ import fuzzytool as fz
101
+
102
+ score = fz.Variable("score", (300, 850))
103
+ score["good"] = fz.gt2_gauss_uncertain_mean(740, 780, 50, n_slices=5)
104
+ score["poor"] = fz.gt2_gauss_uncertain_mean(420, 480, 70, n_slices=5)
105
+ premium = fz.Variable("premium", (0, 12))
106
+ premium["low"] = fz.gt2_gauss_uncertain_mean(1.5, 2.5, 1.5)
107
+ premium["high"] = fz.gt2_gauss_uncertain_mean(9.5, 10.5, 1.5)
108
+
109
+ sys = fz.GeneralType2Mamdani()
110
+ sys.rule(score["good"], premium["low"])
111
+ sys.rule(score["poor"], premium["high"])
112
+ sys(score=780) # -> 2.31 (z-weighted average of per-slice IT2 results)
113
+ ```
114
+
115
+ ![A general type-2 set as nested z-slices (alpha-planes) over its footprint of uncertainty](../images/gt2_zslices.png)
116
+
117
+ A GT2 term also exposes `lower`/`upper` (its overall FOU), so it can stand in for
118
+ its IT2 footprint inside an `IT2Mamdani` if you want the cheaper approximation.
119
+ Build a GT2 set from *any* IT2 footprint with
120
+ [`gt2_from_it2`][fuzzytool.type2.general.gt2_from_it2], and type-reduce a single
121
+ GT2 set with `centroid_gt2`:
122
+
123
+ ```python
124
+ import numpy as np
125
+ from fuzzytool.type2 import centroid_gt2
126
+
127
+ good = fz.gt2_gauss_uncertain_mean(740, 780, 50, n_slices=6)
128
+ universe = np.linspace(300, 850, 400)
129
+ centroid_gt2(good, universe) # -> ~754.7 (z-weighted centroid)
130
+ ```
@@ -29,14 +29,19 @@ from .serialize import load, save
29
29
  from .sets import Variable
30
30
  from .type2 import (
31
31
  IT2TSK,
32
+ GeneralType2Mamdani,
33
+ GeneralType2MF,
32
34
  IT2Mamdani,
35
+ gt2_from_it2,
36
+ gt2_gauss_uncertain_mean,
37
+ gt2_scale,
33
38
  it2,
34
39
  it2_gauss_uncertain_mean,
35
40
  it2_gauss_uncertain_std,
36
41
  it2_scale,
37
42
  )
38
43
 
39
- __version__ = "0.4.0"
44
+ __version__ = "0.5.0"
40
45
 
41
46
  __all__ = [
42
47
  "__version__",
@@ -51,6 +56,9 @@ __all__ = [
51
56
  # interval type-2
52
57
  "IT2Mamdani", "IT2TSK",
53
58
  "it2", "it2_scale", "it2_gauss_uncertain_mean", "it2_gauss_uncertain_std",
59
+ # general type-2
60
+ "GeneralType2Mamdani", "GeneralType2MF",
61
+ "gt2_from_it2", "gt2_gauss_uncertain_mean", "gt2_scale",
54
62
  # fuzzy clustering
55
63
  "fuzzy_cmeans", "gustafson_kessel", "possibilistic_cmeans",
56
64
  # learning & approximation
@@ -9,6 +9,14 @@ IT2 rules reuse the very same operator syntax as type-1 rules (``|``, ``&``,
9
9
  tree and collapse the result with Karnik-Mendel type reduction.
10
10
  """
11
11
 
12
+ from .general import (
13
+ GeneralType2Mamdani,
14
+ GeneralType2MF,
15
+ centroid_gt2,
16
+ gt2_from_it2,
17
+ gt2_gauss_uncertain_mean,
18
+ gt2_scale,
19
+ )
12
20
  from .inference import IT2TSK, IT2Mamdani
13
21
  from .reduction import centroid_it2, karnik_mendel, km_endpoint
14
22
  from .sets import (
@@ -24,4 +32,6 @@ __all__ = [
24
32
  "it2", "it2_scale", "it2_gauss_uncertain_mean", "it2_gauss_uncertain_std",
25
33
  "IT2Mamdani", "IT2TSK",
26
34
  "karnik_mendel", "km_endpoint", "centroid_it2",
35
+ "GeneralType2MF", "GeneralType2Mamdani", "centroid_gt2",
36
+ "gt2_from_it2", "gt2_gauss_uncertain_mean", "gt2_scale",
27
37
  ]
@@ -0,0 +1,211 @@
1
+ """General type-2 (GT2) fuzzy sets via the zSlices / alpha-plane representation.
2
+
3
+ An interval type-2 set treats every point inside its footprint of uncertainty
4
+ (FOU) as equally possible. A **general** type-2 set adds a *secondary* membership
5
+ that weights those possibilities — the third dimension IT2 throws away.
6
+
7
+ A practical, exact way to handle that third dimension is the **zSlices** (alpha
8
+ -plane) decomposition (Wagner & Hagras): slice the secondary domain at levels
9
+ ``z`` and, at each level, the GT2 set reduces to an ordinary IT2 set whose FOU is
10
+ the ``z``-cut. Inference and type reduction then run the existing IT2 machinery
11
+ on each slice and combine the results, weighted by ``z``.
12
+
13
+ Here a GT2 set is built from an IT2 footprint plus a **triangular secondary**
14
+ peaking at the FOU's principal (mid) MF: at ``z -> 0`` a slice is the full FOU,
15
+ at ``z = 1`` it collapses to the principal MF.
16
+
17
+ * :class:`GeneralType2MF` — a stack of IT2 z-slices (also exposes the overall
18
+ FOU via ``lower``/``upper``, so it degrades gracefully to IT2).
19
+ * :func:`gt2_from_it2`, :func:`gt2_gauss_uncertain_mean`, :func:`gt2_scale` —
20
+ constructors.
21
+ * :class:`GeneralType2Mamdani` — z-weighted stack of IT2 Mamdani inferences.
22
+ * :func:`centroid_gt2` — zSlices centroid of a single GT2 set.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import numpy as np
28
+
29
+ from ..sets import Variable
30
+ from .inference import IT2Mamdani
31
+ from .reduction import centroid_it2
32
+ from .sets import IntervalType2MF, it2
33
+
34
+
35
+ class GeneralType2MF:
36
+ """A general type-2 set represented as a stack of IT2 ``z``-slices.
37
+
38
+ Args:
39
+ slices: IT2 sets, one per ``z`` level, narrowing toward the principal MF
40
+ as ``z`` increases.
41
+ zlevels: the secondary levels (in ``(0, 1]``), ascending, aligned with
42
+ ``slices``.
43
+ footprint: the overall FOU as an IT2 set (used for ``lower``/``upper`` so
44
+ the GT2 set can stand in for its IT2 footprint).
45
+ """
46
+
47
+ def __init__(self, slices: list[IntervalType2MF], zlevels: np.ndarray,
48
+ footprint: IntervalType2MF) -> None:
49
+ self.slices = list(slices)
50
+ self.zlevels = np.asarray(zlevels, dtype=float)
51
+ self._footprint = footprint
52
+
53
+ @property
54
+ def n_slices(self) -> int:
55
+ return len(self.slices)
56
+
57
+ def slice(self, i: int) -> IntervalType2MF:
58
+ """The IT2 set at ``z``-level index ``i``."""
59
+ return self.slices[i]
60
+
61
+ def lower(self, x):
62
+ return self._footprint.lower(x)
63
+
64
+ def upper(self, x):
65
+ return self._footprint.upper(x)
66
+
67
+ def __call__(self, x):
68
+ return self._footprint(x)
69
+
70
+ def __repr__(self) -> str:
71
+ return f"GeneralType2MF(n_slices={self.n_slices})"
72
+
73
+
74
+ def gt2_from_it2(footprint: IntervalType2MF, n_slices: int = 5) -> GeneralType2MF:
75
+ """Build a GT2 set from an IT2 FOU with a triangular secondary MF.
76
+
77
+ The secondary membership peaks at the principal MF (the midline of the FOU)
78
+ and falls linearly to 0 at the LMF/UMF. The ``z``-slice at level ``z`` is the
79
+ IT2 set whose FOU is narrowed from the full footprint toward that midline by
80
+ a factor ``(1 - z)``.
81
+
82
+ Args:
83
+ footprint: the IT2 set describing the full footprint of uncertainty.
84
+ n_slices: number of ``z`` levels (``z = 1/n .. 1``). More slices = a finer
85
+ secondary, at proportional cost.
86
+ """
87
+ if n_slices < 1:
88
+ raise ValueError("n_slices must be >= 1")
89
+ zlevels = np.linspace(1.0 / n_slices, 1.0, n_slices)
90
+
91
+ def mid(x):
92
+ return (footprint.lower(x) + footprint.upper(x)) / 2.0
93
+
94
+ slices = []
95
+ for z in zlevels:
96
+ # Default-arg binding captures the current z in each closure.
97
+ def lower(x, z=z):
98
+ m = mid(x)
99
+ return m - (1.0 - z) * (m - footprint.lower(x))
100
+
101
+ def upper(x, z=z):
102
+ m = mid(x)
103
+ return m + (1.0 - z) * (footprint.upper(x) - m)
104
+
105
+ slices.append(it2(lower, upper))
106
+ return GeneralType2MF(slices, zlevels, footprint)
107
+
108
+
109
+ def gt2_gauss_uncertain_mean(c1: float, c2: float, sigma: float,
110
+ n_slices: int = 5) -> GeneralType2MF:
111
+ """GT2 Gaussian with an uncertain mean in ``[c1, c2]`` (triangular secondary)."""
112
+ from .sets import it2_gauss_uncertain_mean
113
+ return gt2_from_it2(it2_gauss_uncertain_mean(c1, c2, sigma), n_slices)
114
+
115
+
116
+ def gt2_scale(mf, scale: float, n_slices: int = 5) -> GeneralType2MF:
117
+ """GT2 set from a height-uncertainty FOU (UMF ``mf``, LMF ``scale * mf``)."""
118
+ from .sets import it2_scale
119
+ return gt2_from_it2(it2_scale(mf, scale), n_slices)
120
+
121
+
122
+ def centroid_gt2(gt2mf: GeneralType2MF, universe) -> float:
123
+ """zSlices centroid of a GT2 set: ``z``-weighted mean of per-slice IT2 centroids."""
124
+ z = gt2mf.zlevels
125
+ mids = np.array([sum(centroid_it2(gt2mf.slice(i), universe)) / 2.0
126
+ for i in range(gt2mf.n_slices)])
127
+ return float(np.sum(z * mids) / np.sum(z))
128
+
129
+
130
+ def _antecedent_variables(node, out: dict) -> None:
131
+ if hasattr(node, "variable"):
132
+ out.setdefault(id(node.variable), node.variable)
133
+ if hasattr(node, "left"):
134
+ _antecedent_variables(node.left, out)
135
+ _antecedent_variables(node.right, out)
136
+ if hasattr(node, "operand"):
137
+ _antecedent_variables(node.operand, out)
138
+
139
+
140
+ class GeneralType2Mamdani:
141
+ """General type-2 Mamdani inference via the zSlices decomposition.
142
+
143
+ At each ``z`` level the GT2 terms collapse to their IT2 ``z``-slice and the
144
+ existing :class:`~fuzzytool.type2.inference.IT2Mamdani` engine
145
+ (center-of-sets type reduction) produces a crisp output ``y(z)``. The final
146
+ result is the ``z``-weighted average ``Σ z·y(z) / Σ z`` — the centroid of the
147
+ type-reduced general type-2 output.
148
+
149
+ Variables may mix GT2 and IT2/type-1 terms; only the GT2 terms are sliced.
150
+ All GT2 terms must share the same ``z`` levels (same ``n_slices``).
151
+ """
152
+
153
+ def __init__(self, tnorm="min", snorm="max") -> None:
154
+ self._it2 = IT2Mamdani(tnorm, snorm)
155
+
156
+ def rule(self, antecedent, consequent, weight: float = 1.0) -> GeneralType2Mamdani:
157
+ """Add ``IF antecedent THEN output is term`` and return ``self``."""
158
+ self._it2.rule(antecedent, consequent, weight)
159
+ return self
160
+
161
+ @property
162
+ def rules(self):
163
+ return self._it2.rules
164
+
165
+ def _gt2_variables(self) -> list[Variable]:
166
+ found: dict[int, Variable] = {}
167
+ for r in self._it2.rules:
168
+ _antecedent_variables(r.antecedent, found)
169
+ found.setdefault(id(r.consequent.variable), r.consequent.variable)
170
+ return [v for v in found.values()
171
+ if any(isinstance(t, GeneralType2MF) for t in v.terms.values())]
172
+
173
+ def __call__(self, **inputs: float):
174
+ """Run inference. Returns a float for one output, else a dict by name."""
175
+ if not self._it2.rules:
176
+ raise RuntimeError("no rules defined")
177
+ variables = self._gt2_variables()
178
+ if not variables:
179
+ raise RuntimeError("no general type-2 terms found; use IT2Mamdani instead")
180
+ zlevels = next(t.zlevels for v in variables for t in v.terms.values()
181
+ if isinstance(t, GeneralType2MF))
182
+
183
+ z_acc, y_acc = [], []
184
+ for i, z in enumerate(zlevels):
185
+ saved = {id(v): v.terms for v in variables}
186
+ try:
187
+ for v in variables:
188
+ v.terms = {name: (t.slice(i) if isinstance(t, GeneralType2MF) else t)
189
+ for name, t in v.terms.items()}
190
+ self._it2._centroids.clear()
191
+ y = self._it2(**inputs)
192
+ finally:
193
+ for v in variables:
194
+ v.terms = saved[id(v)]
195
+ z_acc.append(float(z))
196
+ y_acc.append(y)
197
+
198
+ z = np.asarray(z_acc)
199
+ if isinstance(y_acc[0], dict):
200
+ return {k: float(np.sum(z * np.array([y[k] for y in y_acc])) / np.sum(z))
201
+ for k in y_acc[0]}
202
+ return float(np.sum(z * np.asarray(y_acc)) / np.sum(z))
203
+
204
+ def __repr__(self) -> str:
205
+ return f"GeneralType2Mamdani(rules={len(self._it2.rules)})"
206
+
207
+
208
+ __all__ = [
209
+ "GeneralType2MF", "gt2_from_it2", "gt2_gauss_uncertain_mean", "gt2_scale",
210
+ "centroid_gt2", "GeneralType2Mamdani",
211
+ ]
@@ -8,7 +8,7 @@ build-backend = "hatchling.build"
8
8
 
9
9
  [project]
10
10
  name = "fuzzytool"
11
- version = "0.4.0"
11
+ version = "0.5.0"
12
12
  description = "A clean, extensible fuzzy-logic toolkit in pure Python + NumPy"
13
13
  readme = "README.md"
14
14
  requires-python = ">=3.9"
@@ -75,3 +75,59 @@ def test_type1_and_it2_terms_mix_in_one_antecedent():
75
75
  lo, hi = (a["hi"] & b["hi"]).eval_interval({"a": 0.8, "b": 1.0},
76
76
  fz.norms.t_min, fz.norms.s_max)
77
77
  assert lo <= hi
78
+
79
+
80
+ # --- general type-2 (zSlices) -----------------------------------------------
81
+
82
+ def test_gt2_exposes_footprint_and_slices():
83
+ g = fz.gt2_gauss_uncertain_mean(740, 780, 50, n_slices=5)
84
+ x = np.linspace(300, 850, 200)
85
+ # degrades to its IT2 footprint via lower/upper
86
+ assert np.all(g.lower(x) <= g.upper(x) + 1e-12)
87
+ assert g.n_slices == 5 and len(g.zlevels) == 5
88
+ # the z=1 slice (last) collapses toward the principal MF: near-zero FOU
89
+ top = g.slice(g.n_slices - 1)
90
+ assert np.max(top.upper(x) - top.lower(x)) < 1e-9
91
+ # the lowest slice spans (almost) the full footprint
92
+ bottom = g.slice(0)
93
+ assert np.max(bottom.upper(x) - bottom.lower(x)) > 0.0
94
+
95
+
96
+ def test_gt2_mamdani_runs_and_is_bounded():
97
+ score = fz.Variable("score", (300, 850))
98
+ score["good"] = fz.gt2_gauss_uncertain_mean(740, 780, 50)
99
+ score["poor"] = fz.gt2_gauss_uncertain_mean(420, 480, 70)
100
+ premium = fz.Variable("premium", (0, 12))
101
+ premium["low"] = fz.gt2_gauss_uncertain_mean(1.5, 2.5, 1.5)
102
+ premium["high"] = fz.gt2_gauss_uncertain_mean(9.5, 10.5, 1.5)
103
+
104
+ sys = fz.GeneralType2Mamdani()
105
+ sys.rule(score["good"], premium["low"])
106
+ sys.rule(score["poor"], premium["high"])
107
+ # a good score gives a low premium, a poor score a high one
108
+ assert sys(score=780) < sys(score=450)
109
+ assert 0.0 <= sys(score=780) <= 12.0
110
+
111
+
112
+ def test_gt2_does_not_mutate_terms_after_call():
113
+ score = fz.Variable("score", (300, 850))
114
+ score["good"] = fz.gt2_gauss_uncertain_mean(740, 780, 50)
115
+ premium = fz.Variable("premium", (0, 12))
116
+ premium["low"] = fz.gt2_gauss_uncertain_mean(1.5, 2.5, 1.5)
117
+ sys = fz.GeneralType2Mamdani()
118
+ sys.rule(score["good"], premium["low"])
119
+ sys(score=760)
120
+ # terms are restored to the original GT2 objects (not left as a slice)
121
+ from fuzzytool.type2 import GeneralType2MF
122
+ assert isinstance(score.terms["good"], GeneralType2MF)
123
+ assert isinstance(premium.terms["low"], GeneralType2MF)
124
+
125
+
126
+ def test_centroid_gt2_matches_it2_order():
127
+ from fuzzytool.type2 import centroid_gt2
128
+ g = fz.gt2_gauss_uncertain_mean(740, 780, 50, n_slices=6)
129
+ uni = np.linspace(300, 850, 400)
130
+ c_gt2 = centroid_gt2(g, uni)
131
+ c_it2 = sum(centroid_it2(g, uni)) / 2.0
132
+ assert 700 < c_gt2 < 820
133
+ assert abs(c_gt2 - c_it2) < 20 # close, but GT2 weights the principal MF
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes