fuzzytool 0.3.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 (111) hide show
  1. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/CHANGELOG.md +30 -0
  2. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/CITATION.cff +2 -2
  3. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/PKG-INFO +17 -2
  4. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/README.md +14 -1
  5. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/ROADMAP.md +16 -5
  6. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/api/python.md +8 -0
  7. {fuzzytool-0.3.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.3.0 → fuzzytool-0.5.0}/docs/integrations.md +33 -0
  10. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/__init__.py +9 -1
  11. fuzzytool-0.5.0/fuzzytool/integrations/_util.py +96 -0
  12. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/integrations/scipy.py +9 -50
  13. fuzzytool-0.5.0/fuzzytool/integrations/turboswarm.py +120 -0
  14. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/type2/__init__.py +10 -0
  15. fuzzytool-0.5.0/fuzzytool/type2/general.py +211 -0
  16. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/pyproject.toml +2 -1
  17. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/tests/test_integrations.py +20 -0
  18. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/tests/test_type2.py +56 -0
  19. fuzzytool-0.3.0/fuzzytool/integrations/_util.py +0 -39
  20. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/.github/workflows/ci.yml +0 -0
  21. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/.github/workflows/docs.yml +0 -0
  22. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/.github/workflows/release-pypi.yml +0 -0
  23. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/.gitignore +0 -0
  24. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/.zenodo.json +0 -0
  25. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/CLAUDE.md +0 -0
  26. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/CONTRIBUTING.md +0 -0
  27. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/LICENSE +0 -0
  28. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/citing.md +0 -0
  29. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/extending.md +0 -0
  30. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/getting-started.md +0 -0
  31. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/guide/anfis.md +0 -0
  32. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/guide/batch-and-io.md +0 -0
  33. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/guide/clustering.md +0 -0
  34. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/guide/defuzzification.md +0 -0
  35. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/guide/fraud-alert.md +0 -0
  36. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/guide/ftransform.md +0 -0
  37. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/guide/mamdani.md +0 -0
  38. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/guide/mcdm.md +0 -0
  39. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/guide/membership.md +0 -0
  40. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/guide/rule-learning.md +0 -0
  41. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/guide/rules.md +0 -0
  42. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/guide/tsk.md +0 -0
  43. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/guide/visualization.md +0 -0
  44. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/images/clusters.png +0 -0
  45. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/images/credit_control_surface.png +0 -0
  46. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/images/fraud_anomaly_terms.png +0 -0
  47. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/images/fraud_control_surface.png +0 -0
  48. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/images/gs_score_terms.png +0 -0
  49. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/images/it2_score.png +0 -0
  50. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/images/premium_terms.png +0 -0
  51. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/images/tut_advisor_surface.png +0 -0
  52. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/images/tut_advisor_volatility.png +0 -0
  53. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/images/tut_anfis.png +0 -0
  54. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/images/tut_clusters.png +0 -0
  55. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/index.md +0 -0
  56. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/installation.md +0 -0
  57. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/javascripts/mathjax.js +0 -0
  58. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/tutorials/anfis-learning.md +0 -0
  59. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/tutorials/clustering-segmentation.md +0 -0
  60. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/tutorials/risk-advisor.md +0 -0
  61. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/docs/tutorials/topsis-decision.md +0 -0
  62. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/examples/anfis.py +0 -0
  63. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/examples/clustering.py +0 -0
  64. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/examples/credit_risk.py +0 -0
  65. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/examples/credit_risk_it2.py +0 -0
  66. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/examples/fraud_alert.py +0 -0
  67. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/examples/ftransform.py +0 -0
  68. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/examples/mcdm.py +0 -0
  69. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/anfis.py +0 -0
  70. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/cluster.py +0 -0
  71. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/datasets.py +0 -0
  72. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/defuzz.py +0 -0
  73. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/ftransform.py +0 -0
  74. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/fuzzynum.py +0 -0
  75. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/inference/__init__.py +0 -0
  76. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/inference/mamdani.py +0 -0
  77. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/inference/tsk.py +0 -0
  78. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/inference/tsukamoto.py +0 -0
  79. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/integrations/__init__.py +0 -0
  80. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/integrations/agents.py +0 -0
  81. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/integrations/optuna.py +0 -0
  82. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/integrations/pandas.py +0 -0
  83. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/integrations/parallel.py +0 -0
  84. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/integrations/sklearn.py +0 -0
  85. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/integrations/torch.py +0 -0
  86. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/learn.py +0 -0
  87. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/mcdm.py +0 -0
  88. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/membership.py +0 -0
  89. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/norms.py +0 -0
  90. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/rules.py +0 -0
  91. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/serialize.py +0 -0
  92. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/sets.py +0 -0
  93. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/type2/inference.py +0 -0
  94. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/type2/reduction.py +0 -0
  95. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/type2/sets.py +0 -0
  96. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/fuzzytool/viz.py +0 -0
  97. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/mkdocs.yml +0 -0
  98. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/notebooks/01_quickstart.ipynb +0 -0
  99. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/notebooks/02_type2.ipynb +0 -0
  100. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/notebooks/03_clustering.ipynb +0 -0
  101. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/notebooks/04_learning.ipynb +0 -0
  102. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/notebooks/README.md +0 -0
  103. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/paper.bib +0 -0
  104. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/paper.md +0 -0
  105. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/tests/test_cluster.py +0 -0
  106. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/tests/test_engineering.py +0 -0
  107. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/tests/test_inference.py +0 -0
  108. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/tests/test_learn.py +0 -0
  109. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/tests/test_learning.py +0 -0
  110. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/tests/test_mcdm.py +0 -0
  111. {fuzzytool-0.3.0 → fuzzytool-0.5.0}/tests/test_membership.py +0 -0
@@ -6,6 +6,36 @@ 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
+
23
+ ## [0.4.0] - 2026-06-28
24
+
25
+ ### Added
26
+
27
+ - **turboswarm integration** (`fuzzytool.integrations.turboswarm`, extra
28
+ `[turboswarm]`): `tune` fits a system's built-in membership-function
29
+ parameters to data with the sibling library's gradient-free, global Particle
30
+ Swarm Optimization — the metaheuristic counterpart of the SciPy least-squares
31
+ tuner. Returns turboswarm's `PsoResult`.
32
+
33
+ ### Changed
34
+
35
+ - Shared membership-function tuning helpers (`sanitize_mf_params`,
36
+ `tunable_terms`, `MF_PENALTY`) moved to `fuzzytool.integrations._util` and
37
+ reused by both the SciPy and turboswarm tuners.
38
+
9
39
  ## [0.3.0] - 2026-06-27
10
40
 
11
41
  ### Added
@@ -5,8 +5,8 @@ type: software
5
5
  authors:
6
6
  - family-names: Salmeron
7
7
  given-names: Jose L.
8
- version: 0.3.0
9
- date-released: 2026-06-27
8
+ version: 0.5.0
9
+ date-released: 2026-06-28
10
10
  doi: 10.5281/zenodo.20836712
11
11
  license: MIT
12
12
  repository-code: "https://github.com/fuzzytool/fuzzytool.github.io"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fuzzytool
3
- Version: 0.3.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
@@ -53,6 +53,8 @@ Provides-Extra: sklearn
53
53
  Requires-Dist: scikit-learn>=1.0; extra == 'sklearn'
54
54
  Provides-Extra: torch
55
55
  Requires-Dist: torch>=1.12; extra == 'torch'
56
+ Provides-Extra: turboswarm
57
+ Requires-Dist: turboswarm>=0.7; extra == 'turboswarm'
56
58
  Provides-Extra: viz
57
59
  Requires-Dist: matplotlib>=3.4; extra == 'viz'
58
60
  Description-Content-Type: text/markdown
@@ -114,7 +116,20 @@ s-norm, `~` the complement.
114
116
  | 4 | **Fuzzy clustering**: fuzzy c-means, Gustafson-Kessel, possibilistic | ✅ |
115
117
  | 5 | **ANFIS** (trainable TSK) + **F-transform** (direct/inverse) | ✅ |
116
118
  | 6 | Notebooks, JOSS `paper.md`, Zenodo DOI, PyPI release | ✅ |
117
- | 7 | **Ecosystem integrations**: pandas, scikit-learn, PyTorch, SciPy, Optuna, Joblib/Dask, LLM agents | ✅ |
119
+ | 7 | **Ecosystem integrations**: pandas, scikit-learn, PyTorch, SciPy, turboswarm, Optuna, Joblib/Dask, LLM agents | ✅ |
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
+
128
+ ### v0.4.0
129
+
130
+ - **turboswarm integration:** gradient-free, global tuning of a fuzzy system's
131
+ membership functions with Particle Swarm Optimization
132
+ (`integrations.turboswarm.tune`) — the metaheuristic sibling of the SciPy tuner.
118
133
 
119
134
  ### v0.3.0
120
135
 
@@ -55,7 +55,20 @@ s-norm, `~` the complement.
55
55
  | 4 | **Fuzzy clustering**: fuzzy c-means, Gustafson-Kessel, possibilistic | ✅ |
56
56
  | 5 | **ANFIS** (trainable TSK) + **F-transform** (direct/inverse) | ✅ |
57
57
  | 6 | Notebooks, JOSS `paper.md`, Zenodo DOI, PyPI release | ✅ |
58
- | 7 | **Ecosystem integrations**: pandas, scikit-learn, PyTorch, SciPy, Optuna, Joblib/Dask, LLM agents | ✅ |
58
+ | 7 | **Ecosystem integrations**: pandas, scikit-learn, PyTorch, SciPy, turboswarm, Optuna, Joblib/Dask, LLM agents | ✅ |
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
+
67
+ ### v0.4.0
68
+
69
+ - **turboswarm integration:** gradient-free, global tuning of a fuzzy system's
70
+ membership functions with Particle Swarm Optimization
71
+ (`integrations.turboswarm.tune`) — the metaheuristic sibling of the SciPy tuner.
59
72
 
60
73
  ### v0.3.0
61
74
 
@@ -63,6 +63,8 @@ Each pulls its dependency in only on import, behind its own extra.
63
63
  `nn.Module` trainable by autograd and composable into a network.
64
64
  - [x] **SciPy** (`[scipy]`): `tune` MF parameters to data via
65
65
  `scipy.optimize.least_squares` (returns the `OptimizeResult`).
66
+ - [x] **turboswarm** (`[turboswarm]`): `tune` MF parameters with gradient-free
67
+ Particle Swarm Optimization (the sibling PSO library; returns `PsoResult`).
66
68
  - [x] **Optuna** (`[optuna]`): `suggest_inference_spec`, `suggest_anfis` and a
67
69
  ready-made `tune_anfis` study.
68
70
  - [x] **Joblib / Dask** (`[parallel]`, `[dask]`): `parallel_predict`,
@@ -70,17 +72,26 @@ Each pulls its dependency in only on import, behind its own extra.
70
72
  - [x] **Agents (LangChain/LangGraph)** (`[agents]`): `explain` plus an
71
73
  `inference_tool` LangChain tool that reports which rules fired.
72
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
+
73
84
  ## Ideas for later
74
85
 
75
86
  - Fuzzy cognitive maps (+ grey FCM).
76
- - General (non-interval) type-2; more defuzzifiers.
77
- - FIS tuning via metaheuristics (e.g. PSO).
87
+ - More defuzzifiers; non-triangular GT2 secondary MFs.
78
88
 
79
89
  ## ⏳ Phase 6 — Release (v0.1.0)
80
90
 
81
91
  - [x] Example notebooks (`notebooks/`) and a comparison page vs scikit-fuzzy.
82
92
  - [x] JOSS `paper.md`; Zenodo metadata (`.zenodo.json`) and a citing/releasing page.
83
93
  - [x] GitHub Pages live (`https://fuzzytool.github.io`).
84
- - [ ] Register the PyPI trusted publisher, then tag `v0.1.0` to publish (the
85
- `release-pypi.yml` workflow is ready).
86
- - [ ] 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
@@ -90,6 +94,10 @@ Generated from docstrings with `mkdocstrings`.
90
94
 
91
95
  ::: fuzzytool.integrations.scipy
92
96
 
97
+ ## Integrations — turboswarm (PSO)
98
+
99
+ ::: fuzzytool.integrations.turboswarm
100
+
93
101
  ## Integrations — Optuna
94
102
 
95
103
  ::: fuzzytool.integrations.optuna
@@ -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
+ ```
@@ -10,6 +10,7 @@ third-party dependency **only when imported**, so the base install stays light.
10
10
  | scikit-learn | `fuzzytool.integrations.sklearn` | `pip install fuzzytool[sklearn]` |
11
11
  | PyTorch | `fuzzytool.integrations.torch` | `pip install fuzzytool[torch]` |
12
12
  | SciPy | `fuzzytool.integrations.scipy` | `pip install fuzzytool[scipy]` |
13
+ | turboswarm | `fuzzytool.integrations.turboswarm` | `pip install fuzzytool[turboswarm]` |
13
14
  | Optuna | `fuzzytool.integrations.optuna` | `pip install fuzzytool[optuna]` |
14
15
  | Joblib / Dask | `fuzzytool.integrations.parallel` | `pip install fuzzytool[parallel]` / `[dask]` |
15
16
  | LLM agents | `fuzzytool.integrations.agents` | `pip install fuzzytool[agents]` |
@@ -199,6 +200,38 @@ Only built-in shapes (`tri`, `trap`, `gauss`, `gbell`, `sigmoid`, `ramp_up`,
199
200
  `tune_outputs=False` to keep a Mamdani's output sets fixed, and forward any
200
201
  `least_squares` keyword (e.g. `max_nfev=200`).
201
202
 
203
+ ## turboswarm
204
+
205
+ [turboswarm](https://pypi.org/project/turboswarm/) is fuzzytool's sibling
206
+ Particle Swarm Optimization library. Its `tune` is the **gradient-free, global**
207
+ counterpart of the SciPy one: slower, but it escapes poor local optima and needs
208
+ no derivatives — a good fit for the rugged error surfaces fuzzy rule bases
209
+ produce. Same call shape, returns turboswarm's `PsoResult`:
210
+
211
+ ```python
212
+ import numpy as np
213
+ import fuzzytool as fz
214
+ from fuzzytool.integrations.turboswarm import tune
215
+
216
+ rng = np.random.default_rng(0)
217
+ X = rng.uniform(0, 10, size=(150, 1))
218
+ y = np.sin(X[:, 0]) + 0.1 * X[:, 0]
219
+
220
+ x = fz.Variable("x", (0, 10), terms=["lo", "mid", "hi"])
221
+ sys = fz.TSK()
222
+ sys.rule(x["lo"], 0.0)
223
+ sys.rule(x["mid"], 0.5)
224
+ sys.rule(x["hi"], 1.0)
225
+
226
+ result = tune(sys, X, y, columns=["x"], n_particles=20, max_iter=40, seed=0)
227
+ result.best_value # PsoResult; the system now fits the data better
228
+ ```
229
+
230
+ Each parameter is searched within `±margin × universe` around its current value
231
+ (`margin=0.5` by default); shape validity is enforced every evaluation. Any
232
+ `turboswarm.minimize` keyword (`topology`, `patience`, `max_time`, …) is
233
+ forwarded.
234
+
202
235
  ## Optuna
203
236
 
204
237
  Fuzzy systems have many discrete/continuous knobs. These helpers turn an Optuna
@@ -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.3.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
@@ -0,0 +1,96 @@
1
+ """Shared helpers for the integration submodules."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .. import membership as mf
6
+
7
+ # Returned in place of a non-finite residual so tuners avoid degenerate regions.
8
+ MF_PENALTY = 1e6
9
+
10
+
11
+ def input_variable_names(system) -> list[str]:
12
+ """Ordered, de-duplicated names of the input variables a system reads.
13
+
14
+ Walks every rule's antecedent tree of a :class:`~fuzzytool.inference.Mamdani`
15
+ or :class:`~fuzzytool.inference.TSK` system and collects the variables its
16
+ propositions reference, in first-seen order. This is the natural column
17
+ order for feeding a 2-D array or a DataFrame to the system.
18
+ """
19
+ names: list[str] = []
20
+ seen: set[str] = set()
21
+
22
+ def walk(node) -> None:
23
+ if hasattr(node, "variable"): # Proposition (atom)
24
+ name = node.variable.name
25
+ if name not in seen:
26
+ seen.add(name)
27
+ names.append(name)
28
+ if hasattr(node, "left"): # And / Or
29
+ walk(node.left)
30
+ walk(node.right)
31
+ if hasattr(node, "operand"): # Not
32
+ walk(node.operand)
33
+
34
+ for rule in getattr(system, "rules", []):
35
+ walk(rule.antecedent)
36
+ return names
37
+
38
+
39
+ def output_name(system, default: str = "output") -> str:
40
+ """Best-effort name for a single-output system (Mamdani tracks it; TSK does not)."""
41
+ outputs = getattr(system, "_outputs", None)
42
+ if outputs:
43
+ return next(iter(outputs))
44
+ return default
45
+
46
+
47
+ def sanitize_mf_params(kind: str, params) -> list[float]:
48
+ """Repair a membership-function parameter slice so it is always a valid shape.
49
+
50
+ Keeps breakpoints ordered and widths positive, so a tuner (SciPy least-squares
51
+ or turboswarm PSO) can explore freely without producing degenerate sets.
52
+ """
53
+ p = [float(v) for v in params]
54
+ if kind in ("tri", "trap", "ramp_up", "ramp_down"):
55
+ return sorted(p) # keep breakpoints ordered
56
+ if kind == "gauss":
57
+ return [p[0], abs(p[1]) or 1e-6] # sigma > 0
58
+ if kind == "gbell":
59
+ return [abs(p[0]) or 1e-6, abs(p[1]) or 1e-6, p[2]] # width, slope > 0
60
+ return p
61
+
62
+
63
+ def tunable_terms(system, tune_outputs: bool = True):
64
+ """Collect ``(variable, term_name, kind, params)`` for every tunable MF.
65
+
66
+ Walks the rules' propositions (and, if ``tune_outputs``, the Mamdani output
67
+ variables), keeping only terms backed by a built-in membership shape; custom
68
+ callables are skipped.
69
+ """
70
+ seen: dict[int, object] = {}
71
+
72
+ def walk(node):
73
+ if hasattr(node, "variable"):
74
+ seen.setdefault(id(node.variable), node.variable)
75
+ if hasattr(node, "left"):
76
+ walk(node.left)
77
+ walk(node.right)
78
+ if hasattr(node, "operand"):
79
+ walk(node.operand)
80
+
81
+ for rule in getattr(system, "rules", []):
82
+ walk(rule.antecedent)
83
+ variables = dict(seen)
84
+ if tune_outputs:
85
+ for out in getattr(system, "_outputs", {}).values():
86
+ variables.setdefault(id(out), out)
87
+
88
+ terms = []
89
+ for var in variables.values():
90
+ for term_name, membership in var.terms.items():
91
+ try:
92
+ spec = mf.to_dict(membership)
93
+ except TypeError:
94
+ continue # custom callable: skip
95
+ terms.append((var, term_name, spec["type"], list(spec["params"])))
96
+ return terms
@@ -20,27 +20,18 @@ from typing import TYPE_CHECKING
20
20
  import numpy as np
21
21
 
22
22
  from .. import membership as mf
23
- from ._util import input_variable_names
23
+ from ._util import (
24
+ MF_PENALTY,
25
+ input_variable_names,
26
+ sanitize_mf_params,
27
+ tunable_terms,
28
+ )
24
29
 
25
30
  if TYPE_CHECKING:
26
31
  from scipy.optimize import OptimizeResult
27
32
 
28
33
  from ..inference import TSK, Mamdani
29
34
 
30
- # Per-shape repair applied before rebuilding, so every candidate is a valid MF.
31
- _PENALTY = 1e6
32
-
33
-
34
- def _sanitize(kind: str, params: list[float]) -> list[float]:
35
- p = [float(v) for v in params]
36
- if kind in ("tri", "trap", "ramp_up", "ramp_down"):
37
- return sorted(p) # keep breakpoints ordered
38
- if kind == "gauss":
39
- return [p[0], abs(p[1]) or 1e-6] # sigma > 0
40
- if kind == "gbell":
41
- return [abs(p[0]) or 1e-6, abs(p[1]) or 1e-6, p[2]] # width, slope > 0
42
- return p
43
-
44
35
 
45
36
  def _require_scipy():
46
37
  try:
@@ -53,38 +44,6 @@ def _require_scipy():
53
44
  return least_squares
54
45
 
55
46
 
56
- def _tunable_terms(system, tune_outputs: bool):
57
- """Variables and term names whose membership functions can be tuned."""
58
- # Collect the Variable instances behind the rules' propositions.
59
- seen: dict[int, object] = {}
60
-
61
- def walk(node):
62
- if hasattr(node, "variable"):
63
- seen.setdefault(id(node.variable), node.variable)
64
- if hasattr(node, "left"):
65
- walk(node.left)
66
- walk(node.right)
67
- if hasattr(node, "operand"):
68
- walk(node.operand)
69
-
70
- for rule in system.rules:
71
- walk(rule.antecedent)
72
- variables = dict(seen)
73
- if tune_outputs:
74
- for out in getattr(system, "_outputs", {}).values():
75
- variables.setdefault(id(out), out)
76
-
77
- terms = []
78
- for var in variables.values():
79
- for term_name, membership in var.terms.items():
80
- try:
81
- spec = mf.to_dict(membership)
82
- except TypeError:
83
- continue # custom callable: skip
84
- terms.append((var, term_name, spec["type"], list(spec["params"])))
85
- return terms
86
-
87
-
88
47
  def tune(
89
48
  system: Mamdani | TSK,
90
49
  X: np.ndarray,
@@ -115,7 +74,7 @@ def tune(
115
74
  names = list(columns) if columns is not None else input_variable_names(system)
116
75
  inputs = {name: X[:, j] for j, name in enumerate(names)}
117
76
 
118
- terms = _tunable_terms(system, tune_outputs)
77
+ terms = tunable_terms(system, tune_outputs)
119
78
  if not terms:
120
79
  raise ValueError("the system has no tunable built-in membership functions")
121
80
 
@@ -130,13 +89,13 @@ def tune(
130
89
  def apply(p):
131
90
  for (var, term_name, kind, _params), (start, size) in zip(terms, slices):
132
91
  var[term_name] = mf.from_dict(
133
- {"type": kind, "params": _sanitize(kind, p[start:start + size])})
92
+ {"type": kind, "params": sanitize_mf_params(kind, p[start:start + size])})
134
93
 
135
94
  def residual(p):
136
95
  apply(p)
137
96
  pred = np.asarray(system.predict(**inputs), dtype=float)
138
97
  r = pred - y
139
- return np.where(np.isfinite(r), r, _PENALTY)
98
+ return np.where(np.isfinite(r), r, MF_PENALTY)
140
99
 
141
100
  result = least_squares(residual, p0, **least_squares_kwargs)
142
101
  apply(result.x) # leave the system at the best fit
@@ -0,0 +1,120 @@
1
+ """turboswarm integration: tune a fuzzy system with Particle Swarm Optimization.
2
+
3
+ Install with ``pip install fuzzytool[turboswarm]``. :func:`tune` fits the
4
+ parameters of a system's built-in membership functions to data with
5
+ `turboswarm <https://pypi.org/project/turboswarm/>`_'s PSO — a **gradient-free,
6
+ global** optimizer. It is the metaheuristic sibling of
7
+ :func:`fuzzytool.integrations.scipy.tune` (local least-squares): slower, but it
8
+ escapes poor local optima and needs no derivatives, which suits the rugged,
9
+ non-smooth error surfaces fuzzy rule bases often produce.
10
+
11
+ Each parameter is searched within a box of ``±margin × universe`` around its
12
+ current value; shape validity (ordered breakpoints, positive widths) is enforced
13
+ every evaluation, so the swarm explores freely. The system is mutated in place
14
+ with the best parameters found and the turboswarm ``PsoResult`` is returned.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from typing import TYPE_CHECKING
20
+
21
+ import numpy as np
22
+
23
+ from .. import membership as mf
24
+ from ._util import (
25
+ MF_PENALTY,
26
+ input_variable_names,
27
+ sanitize_mf_params,
28
+ tunable_terms,
29
+ )
30
+
31
+ if TYPE_CHECKING:
32
+ from turboswarm import PsoResult
33
+
34
+ from ..inference import TSK, Mamdani
35
+
36
+
37
+ def _require_turboswarm():
38
+ try:
39
+ from turboswarm import minimize
40
+ except ImportError as exc: # pragma: no cover
41
+ raise ImportError(
42
+ "the turboswarm integration needs turboswarm; install with "
43
+ "`pip install fuzzytool[turboswarm]`"
44
+ ) from exc
45
+ return minimize
46
+
47
+
48
+ def tune(
49
+ system: Mamdani | TSK,
50
+ X: np.ndarray,
51
+ y: np.ndarray,
52
+ columns: list[str] | None = None,
53
+ tune_outputs: bool = True,
54
+ margin: float = 0.5,
55
+ n_particles: int = 30,
56
+ max_iter: int = 100,
57
+ seed: int | None = None,
58
+ **minimize_kwargs,
59
+ ) -> PsoResult:
60
+ """Fit a system's membership-function parameters to data with PSO.
61
+
62
+ Extra keyword arguments are forwarded to :func:`turboswarm.minimize`
63
+ (e.g. ``topology``, ``patience``, ``max_time``).
64
+
65
+ Args:
66
+ system: a Mamdani or TSK system (mutated in place with the best params).
67
+ X: inputs, shape ``(n_samples, n_inputs)``.
68
+ y: targets, shape ``(n_samples,)``.
69
+ columns: input variable names matching ``X``'s columns, in order.
70
+ Defaults to the variables the rules reference.
71
+ tune_outputs: also tune the output variables' MFs (Mamdani only).
72
+ margin: search half-width per parameter as a fraction of its variable's
73
+ universe span (``0.5`` searches ±50% of the span around each value).
74
+ n_particles: swarm size.
75
+ max_iter: PSO iterations.
76
+ seed: PSO seed for reproducibility.
77
+
78
+ Returns:
79
+ The turboswarm ``PsoResult`` (``.best_position``, ``.best_value``, …).
80
+ """
81
+ minimize = _require_turboswarm()
82
+ X = np.asarray(X, dtype=float)
83
+ y = np.asarray(y, dtype=float)
84
+ names = list(columns) if columns is not None else input_variable_names(system)
85
+ inputs = {name: X[:, j] for j, name in enumerate(names)}
86
+
87
+ terms = tunable_terms(system, tune_outputs)
88
+ if not terms:
89
+ raise ValueError("the system has no tunable built-in membership functions")
90
+
91
+ # Flatten parameters; each gets a search box of ±(margin * span) around p0.
92
+ slices, p0, bounds = [], [], []
93
+ for var, _term, _kind, params in terms:
94
+ delta = margin * (var.high - var.low)
95
+ start = len(p0)
96
+ for value in params:
97
+ p0.append(value)
98
+ bounds.append((value - delta, value + delta))
99
+ slices.append((start, len(params)))
100
+
101
+ def apply(position):
102
+ for (var, term_name, kind, _params), (start, size) in zip(terms, slices):
103
+ var[term_name] = mf.from_dict(
104
+ {"type": kind,
105
+ "params": sanitize_mf_params(kind, position[start:start + size])})
106
+
107
+ def objective(position):
108
+ apply(position)
109
+ pred = np.asarray(system.predict(**inputs), dtype=float)
110
+ r = pred - y
111
+ r = np.where(np.isfinite(r), r, MF_PENALTY)
112
+ return float(np.mean(r * r)) # mean squared error
113
+
114
+ result = minimize(objective, bounds=bounds, n_particles=n_particles,
115
+ max_iter=max_iter, seed=seed, **minimize_kwargs)
116
+ apply(result.best_position) # leave the system at the best fit
117
+ return result
118
+
119
+
120
+ __all__ = ["tune"]
@@ -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
  ]