bulanik 0.3.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.
- bulanik-0.3.0/.gitignore +14 -0
- bulanik-0.3.0/CHANGELOG.md +127 -0
- bulanik-0.3.0/CITATION.cff +29 -0
- bulanik-0.3.0/CONTRIBUTING.md +51 -0
- bulanik-0.3.0/LICENSE +21 -0
- bulanik-0.3.0/PKG-INFO +113 -0
- bulanik-0.3.0/README.md +81 -0
- bulanik-0.3.0/conftest.py +32 -0
- bulanik-0.3.0/docs/scripts/_figutil.py +177 -0
- bulanik-0.3.0/docs/scripts/api_index.py +124 -0
- bulanik-0.3.0/docs/scripts/build_figures.py +46 -0
- bulanik-0.3.0/docs/scripts/fig_anfis_fit.py +50 -0
- bulanik-0.3.0/docs/scripts/fig_example_plotting.py +22 -0
- bulanik-0.3.0/docs/scripts/fig_it2_fou.py +31 -0
- bulanik-0.3.0/docs/scripts/fig_mamdani_aggregate.py +58 -0
- bulanik-0.3.0/docs/scripts/fig_mcdm_dematel.py +32 -0
- bulanik-0.3.0/docs/scripts/fig_mcdm_ranking.py +61 -0
- bulanik-0.3.0/docs/scripts/fig_mcdm_weights.py +35 -0
- bulanik-0.3.0/docs/scripts/fig_membership_shapes.py +50 -0
- bulanik-0.3.0/docs/scripts/fig_numbers_interval.py +55 -0
- bulanik-0.3.0/docs/scripts/fig_numbers_it2.py +44 -0
- bulanik-0.3.0/docs/scripts/fig_numbers_orthopair.py +138 -0
- bulanik-0.3.0/docs/scripts/fig_numbers_threepart.py +68 -0
- bulanik-0.3.0/docs/scripts/fig_numbers_triangular.py +133 -0
- bulanik-0.3.0/docs/site/about/changelog.md +1 -0
- bulanik-0.3.0/docs/site/about/citing.md +21 -0
- bulanik-0.3.0/docs/site/about/contributing.md +1 -0
- bulanik-0.3.0/docs/site/about/license.md +7 -0
- bulanik-0.3.0/docs/site/about/references.md +220 -0
- bulanik-0.3.0/docs/site/api/aggregate.md +1 -0
- bulanik-0.3.0/docs/site/api/anfis.md +1 -0
- bulanik-0.3.0/docs/site/api/index.md +32 -0
- bulanik-0.3.0/docs/site/api/inference.md +28 -0
- bulanik-0.3.0/docs/site/api/lumix.md +1 -0
- bulanik-0.3.0/docs/site/api/mcdm.md +52 -0
- bulanik-0.3.0/docs/site/api/numbers.md +1 -0
- bulanik-0.3.0/docs/site/api/plot.md +7 -0
- bulanik-0.3.0/docs/site/api/scales.md +1 -0
- bulanik-0.3.0/docs/site/api/sets.md +1 -0
- bulanik-0.3.0/docs/site/api/sezgi.md +1 -0
- bulanik-0.3.0/docs/site/api/type2.md +1 -0
- bulanik-0.3.0/docs/site/assets/extra.css +25 -0
- bulanik-0.3.0/docs/site/assets/figures/alpha-cut-arithmetic-dark.svg +4762 -0
- bulanik-0.3.0/docs/site/assets/figures/alpha-cut-arithmetic-light.svg +4762 -0
- bulanik-0.3.0/docs/site/assets/figures/anfis-fit-dark.svg +9176 -0
- bulanik-0.3.0/docs/site/assets/figures/anfis-fit-light.svg +9176 -0
- bulanik-0.3.0/docs/site/assets/figures/defuzzification-dark.svg +1442 -0
- bulanik-0.3.0/docs/site/assets/figures/defuzzification-light.svg +1442 -0
- bulanik-0.3.0/docs/site/assets/figures/dematel-cause-effect-dark.svg +2289 -0
- bulanik-0.3.0/docs/site/assets/figures/dematel-cause-effect-light.svg +2289 -0
- bulanik-0.3.0/docs/site/assets/figures/example-plotting-membership-dark.svg +856 -0
- bulanik-0.3.0/docs/site/assets/figures/example-plotting-membership-light.svg +856 -0
- bulanik-0.3.0/docs/site/assets/figures/it2-fou-dark.svg +5721 -0
- bulanik-0.3.0/docs/site/assets/figures/it2-fou-light.svg +5721 -0
- bulanik-0.3.0/docs/site/assets/figures/it2-trapezoid-dark.svg +4224 -0
- bulanik-0.3.0/docs/site/assets/figures/it2-trapezoid-light.svg +4224 -0
- bulanik-0.3.0/docs/site/assets/figures/iv-orthopair-operations-dark.svg +2645 -0
- bulanik-0.3.0/docs/site/assets/figures/iv-orthopair-operations-light.svg +2641 -0
- bulanik-0.3.0/docs/site/assets/figures/iv-orthopair-regions-dark.svg +3706 -0
- bulanik-0.3.0/docs/site/assets/figures/iv-orthopair-regions-light.svg +3703 -0
- bulanik-0.3.0/docs/site/assets/figures/linguistic-scale-ifn-dark.svg +1985 -0
- bulanik-0.3.0/docs/site/assets/figures/linguistic-scale-ifn-light.svg +1985 -0
- bulanik-0.3.0/docs/site/assets/figures/linguistic-scale-tfn-dark.svg +2779 -0
- bulanik-0.3.0/docs/site/assets/figures/linguistic-scale-tfn-light.svg +2779 -0
- bulanik-0.3.0/docs/site/assets/figures/mamdani-aggregate-dark.svg +8137 -0
- bulanik-0.3.0/docs/site/assets/figures/mamdani-aggregate-light.svg +8137 -0
- bulanik-0.3.0/docs/site/assets/figures/mamdani-surface-dark.svg +6404 -0
- bulanik-0.3.0/docs/site/assets/figures/mamdani-surface-light.svg +6404 -0
- bulanik-0.3.0/docs/site/assets/figures/mcdm-pipeline-dark.svg +2104 -0
- bulanik-0.3.0/docs/site/assets/figures/mcdm-pipeline-light.svg +2104 -0
- bulanik-0.3.0/docs/site/assets/figures/mcdm-ranking-methods-dark.svg +2395 -0
- bulanik-0.3.0/docs/site/assets/figures/mcdm-ranking-methods-light.svg +2395 -0
- bulanik-0.3.0/docs/site/assets/figures/mcdm-weights-compare-dark.svg +1858 -0
- bulanik-0.3.0/docs/site/assets/figures/mcdm-weights-compare-light.svg +1858 -0
- bulanik-0.3.0/docs/site/assets/figures/membership-shapes-dark.svg +11093 -0
- bulanik-0.3.0/docs/site/assets/figures/membership-shapes-light.svg +11093 -0
- bulanik-0.3.0/docs/site/assets/figures/neutrosophic-radar-dark.svg +1789 -0
- bulanik-0.3.0/docs/site/assets/figures/neutrosophic-radar-light.svg +1789 -0
- bulanik-0.3.0/docs/site/assets/figures/orthopair-aggregation-dark.svg +4829 -0
- bulanik-0.3.0/docs/site/assets/figures/orthopair-aggregation-light.svg +4829 -0
- bulanik-0.3.0/docs/site/assets/figures/orthopair-operators-dark.svg +7682 -0
- bulanik-0.3.0/docs/site/assets/figures/orthopair-operators-light.svg +7682 -0
- bulanik-0.3.0/docs/site/assets/figures/orthopair-regions-dark.svg +6484 -0
- bulanik-0.3.0/docs/site/assets/figures/orthopair-regions-light.svg +6484 -0
- bulanik-0.3.0/docs/site/assets/figures/picture-simplex-dark.svg +5122 -0
- bulanik-0.3.0/docs/site/assets/figures/picture-simplex-light.svg +5122 -0
- bulanik-0.3.0/docs/site/assets/figures/spherical-octant-dark.svg +3267 -0
- bulanik-0.3.0/docs/site/assets/figures/spherical-octant-light.svg +3267 -0
- bulanik-0.3.0/docs/site/assets/figures/tfn-anatomy-dark.svg +4812 -0
- bulanik-0.3.0/docs/site/assets/figures/tfn-anatomy-light.svg +4812 -0
- bulanik-0.3.0/docs/site/assets/mathjax-config.js +12 -0
- bulanik-0.3.0/docs/site/concepts/choosing.md +117 -0
- bulanik-0.3.0/docs/site/concepts/conventions.md +249 -0
- bulanik-0.3.0/docs/site/examples/01-fuzzy-numbers.md +37 -0
- bulanik-0.3.0/docs/site/examples/02-scale-and-experts.md +36 -0
- bulanik-0.3.0/docs/site/examples/03-fuzzy-ahp.md +34 -0
- bulanik-0.3.0/docs/site/examples/04-swara-topsis-supplier.md +35 -0
- bulanik-0.3.0/docs/site/examples/05-intuitionistic-pipeline.md +36 -0
- bulanik-0.3.0/docs/site/examples/06-zimmermann-lumix.md +37 -0
- bulanik-0.3.0/docs/site/examples/07-bwm-lumix.md +38 -0
- bulanik-0.3.0/docs/site/examples/08-fuzzy-bwm-sezgi.md +38 -0
- bulanik-0.3.0/docs/site/examples/09-weights-to-lumix.md +39 -0
- bulanik-0.3.0/docs/site/examples/10-mamdani-tipper.md +34 -0
- bulanik-0.3.0/docs/site/examples/11-sugeno-controller.md +34 -0
- bulanik-0.3.0/docs/site/examples/12-tune-sugeno-with-sezgi.md +38 -0
- bulanik-0.3.0/docs/site/examples/13-tsukamoto.md +33 -0
- bulanik-0.3.0/docs/site/examples/14-it2-vs-t1-noise.md +34 -0
- bulanik-0.3.0/docs/site/examples/15-anfis-sinc.md +35 -0
- bulanik-0.3.0/docs/site/examples/16-anfis-with-sezgi.md +38 -0
- bulanik-0.3.0/docs/site/examples/17-plotting.md +45 -0
- bulanik-0.3.0/docs/site/examples/18-interval-valued-ahp-codas.md +37 -0
- bulanik-0.3.0/docs/site/examples/19-it2-ahp-marcos.md +36 -0
- bulanik-0.3.0/docs/site/examples/20-ranking-methods-compared.md +35 -0
- bulanik-0.3.0/docs/site/examples/21-critic-piprecia.md +36 -0
- bulanik-0.3.0/docs/site/examples/22-fuzzy-dematel.md +35 -0
- bulanik-0.3.0/docs/site/examples/23-interval-valued-ahp.md +35 -0
- bulanik-0.3.0/docs/site/examples/24-fuzzy-ranking.md +35 -0
- bulanik-0.3.0/docs/site/examples/index.md +39 -0
- bulanik-0.3.0/docs/site/guide/aggregation.md +177 -0
- bulanik-0.3.0/docs/site/guide/anfis.md +254 -0
- bulanik-0.3.0/docs/site/guide/benchmarks.md +315 -0
- bulanik-0.3.0/docs/site/guide/bridge-lumix.md +245 -0
- bulanik-0.3.0/docs/site/guide/bridge-sezgi.md +232 -0
- bulanik-0.3.0/docs/site/guide/fuzzy-topsis.md +144 -0
- bulanik-0.3.0/docs/site/guide/gallery.md +193 -0
- bulanik-0.3.0/docs/site/guide/mamdani.md +277 -0
- bulanik-0.3.0/docs/site/guide/numbers-interval.md +241 -0
- bulanik-0.3.0/docs/site/guide/numbers-it2.md +200 -0
- bulanik-0.3.0/docs/site/guide/numbers-orthopair.md +187 -0
- bulanik-0.3.0/docs/site/guide/numbers-threepart.md +184 -0
- bulanik-0.3.0/docs/site/guide/numbers-triangular.md +240 -0
- bulanik-0.3.0/docs/site/guide/ranking-more.md +596 -0
- bulanik-0.3.0/docs/site/guide/ranking.md +362 -0
- bulanik-0.3.0/docs/site/guide/sets-and-scales.md +220 -0
- bulanik-0.3.0/docs/site/guide/sugeno-tsukamoto.md +207 -0
- bulanik-0.3.0/docs/site/guide/type2.md +248 -0
- bulanik-0.3.0/docs/site/guide/weighting-more.md +448 -0
- bulanik-0.3.0/docs/site/guide/weighting.md +263 -0
- bulanik-0.3.0/docs/site/index.md +87 -0
- bulanik-0.3.0/docs/site/install.md +72 -0
- bulanik-0.3.0/docs/site/quickstart.md +162 -0
- bulanik-0.3.0/examples/01_fuzzy_numbers.py +32 -0
- bulanik-0.3.0/examples/02_scale_and_experts.py +28 -0
- bulanik-0.3.0/examples/03_fuzzy_ahp.py +23 -0
- bulanik-0.3.0/examples/04_swara_topsis_supplier.py +36 -0
- bulanik-0.3.0/examples/05_intuitionistic_pipeline.py +41 -0
- bulanik-0.3.0/examples/06_zimmermann_lumix.py +33 -0
- bulanik-0.3.0/examples/07_bwm_lumix.py +24 -0
- bulanik-0.3.0/examples/08_fuzzy_bwm_sezgi.py +30 -0
- bulanik-0.3.0/examples/09_weights_to_lumix.py +60 -0
- bulanik-0.3.0/examples/10_mamdani_tipper.py +45 -0
- bulanik-0.3.0/examples/11_sugeno_controller.py +38 -0
- bulanik-0.3.0/examples/12_tune_sugeno_with_sezgi.py +42 -0
- bulanik-0.3.0/examples/13_tsukamoto.py +18 -0
- bulanik-0.3.0/examples/14_it2_vs_t1_noise.py +59 -0
- bulanik-0.3.0/examples/15_anfis_sinc.py +30 -0
- bulanik-0.3.0/examples/16_anfis_with_sezgi.py +25 -0
- bulanik-0.3.0/examples/17_plotting.py +63 -0
- bulanik-0.3.0/examples/18_interval_valued_ahp_codas.py +64 -0
- bulanik-0.3.0/examples/19_it2_ahp_marcos.py +75 -0
- bulanik-0.3.0/examples/20_ranking_methods_compared.py +56 -0
- bulanik-0.3.0/examples/21_critic_piprecia.py +46 -0
- bulanik-0.3.0/examples/22_fuzzy_dematel.py +72 -0
- bulanik-0.3.0/examples/23_interval_valued_ahp.py +87 -0
- bulanik-0.3.0/examples/24_fuzzy_ranking.py +71 -0
- bulanik-0.3.0/mkdocs.yml +160 -0
- bulanik-0.3.0/pyproject.toml +91 -0
- bulanik-0.3.0/src/bulanik/__init__.py +66 -0
- bulanik-0.3.0/src/bulanik/_linalg.py +135 -0
- bulanik-0.3.0/src/bulanik/_validate.py +93 -0
- bulanik-0.3.0/src/bulanik/aggregate.py +226 -0
- bulanik-0.3.0/src/bulanik/anfis.py +873 -0
- bulanik-0.3.0/src/bulanik/inference/__init__.py +49 -0
- bulanik-0.3.0/src/bulanik/inference/_common.py +255 -0
- bulanik-0.3.0/src/bulanik/inference/mamdani.py +338 -0
- bulanik-0.3.0/src/bulanik/inference/rules.py +708 -0
- bulanik-0.3.0/src/bulanik/inference/sugeno.py +339 -0
- bulanik-0.3.0/src/bulanik/inference/tsukamoto.py +207 -0
- bulanik-0.3.0/src/bulanik/lumix.py +510 -0
- bulanik-0.3.0/src/bulanik/mcdm/__init__.py +94 -0
- bulanik-0.3.0/src/bulanik/mcdm/_common.py +230 -0
- bulanik-0.3.0/src/bulanik/mcdm/bwm.py +195 -0
- bulanik-0.3.0/src/bulanik/mcdm/dematel.py +274 -0
- bulanik-0.3.0/src/bulanik/mcdm/fuzzy.py +463 -0
- bulanik-0.3.0/src/bulanik/mcdm/fuzzy_ranking.py +541 -0
- bulanik-0.3.0/src/bulanik/mcdm/outranking.py +236 -0
- bulanik-0.3.0/src/bulanik/mcdm/ranking.py +512 -0
- bulanik-0.3.0/src/bulanik/mcdm/results.py +192 -0
- bulanik-0.3.0/src/bulanik/mcdm/scoring.py +806 -0
- bulanik-0.3.0/src/bulanik/mcdm/weighting.py +1015 -0
- bulanik-0.3.0/src/bulanik/numbers/__init__.py +66 -0
- bulanik-0.3.0/src/bulanik/numbers/_base.py +230 -0
- bulanik-0.3.0/src/bulanik/numbers/it2.py +469 -0
- bulanik-0.3.0/src/bulanik/numbers/iv_neutrosophic.py +369 -0
- bulanik-0.3.0/src/bulanik/numbers/iv_orthopair.py +605 -0
- bulanik-0.3.0/src/bulanik/numbers/orthopair.py +594 -0
- bulanik-0.3.0/src/bulanik/numbers/threepart.py +795 -0
- bulanik-0.3.0/src/bulanik/numbers/triangular.py +552 -0
- bulanik-0.3.0/src/bulanik/plot/__init__.py +83 -0
- bulanik-0.3.0/src/bulanik/plot/_style.py +605 -0
- bulanik-0.3.0/src/bulanik/plot/inference.py +262 -0
- bulanik-0.3.0/src/bulanik/plot/mcdm.py +364 -0
- bulanik-0.3.0/src/bulanik/plot/numbers.py +1067 -0
- bulanik-0.3.0/src/bulanik/plot/sets.py +319 -0
- bulanik-0.3.0/src/bulanik/py.typed +0 -0
- bulanik-0.3.0/src/bulanik/scales.py +245 -0
- bulanik-0.3.0/src/bulanik/sets.py +668 -0
- bulanik-0.3.0/src/bulanik/sezgi.py +702 -0
- bulanik-0.3.0/src/bulanik/type2.py +1093 -0
- bulanik-0.3.0/tests/benchmarks/THIRD_PARTY_NOTICES.md +89 -0
- bulanik-0.3.0/tests/benchmarks/_bench_support.py +85 -0
- bulanik-0.3.0/tests/benchmarks/_hdi_analysis.py +45 -0
- bulanik-0.3.0/tests/benchmarks/_inference_cases.py +111 -0
- bulanik-0.3.0/tests/benchmarks/_mcdm_cases.py +208 -0
- bulanik-0.3.0/tests/benchmarks/data/anfis_mackey_glass_results.json +394 -0
- bulanik-0.3.0/tests/benchmarks/data/anfis_regression_results.json +780 -0
- bulanik-0.3.0/tests/benchmarks/data/hdi_2023_extract.csv +194 -0
- bulanik-0.3.0/tests/benchmarks/data/hdi_results.json +85 -0
- bulanik-0.3.0/tests/benchmarks/data/inference_results.json +1095 -0
- bulanik-0.3.0/tests/benchmarks/data/mcdm_results.json +6211 -0
- bulanik-0.3.0/tests/benchmarks/test_bench_anfis_mackey_glass.py +57 -0
- bulanik-0.3.0/tests/benchmarks/test_bench_fixtures.py +53 -0
- bulanik-0.3.0/tests/benchmarks/test_bench_hdi.py +42 -0
- bulanik-0.3.0/tests/benchmarks/test_bench_inference.py +55 -0
- bulanik-0.3.0/tests/benchmarks/test_bench_mcdm_frozen.py +69 -0
- bulanik-0.3.0/tests/conftest.py +75 -0
- bulanik-0.3.0/tests/strategies.py +132 -0
- bulanik-0.3.0/tests/test_aggregate.py +201 -0
- bulanik-0.3.0/tests/test_ahp_interval.py +222 -0
- bulanik-0.3.0/tests/test_ahp_numbers.py +120 -0
- bulanik-0.3.0/tests/test_anfis.py +511 -0
- bulanik-0.3.0/tests/test_api_index.py +43 -0
- bulanik-0.3.0/tests/test_bwm_core.py +74 -0
- bulanik-0.3.0/tests/test_critic_piprecia.py +422 -0
- bulanik-0.3.0/tests/test_dematel.py +221 -0
- bulanik-0.3.0/tests/test_docs.py +53 -0
- bulanik-0.3.0/tests/test_electre.py +243 -0
- bulanik-0.3.0/tests/test_examples.py +89 -0
- bulanik-0.3.0/tests/test_fuzzy_mcdm.py +340 -0
- bulanik-0.3.0/tests/test_fuzzy_ranking.py +366 -0
- bulanik-0.3.0/tests/test_it2_number.py +235 -0
- bulanik-0.3.0/tests/test_iv_neutrosophic.py +193 -0
- bulanik-0.3.0/tests/test_iv_orthopair.py +304 -0
- bulanik-0.3.0/tests/test_linalg.py +85 -0
- bulanik-0.3.0/tests/test_lumix_bridge.py +186 -0
- bulanik-0.3.0/tests/test_mamdani.py +310 -0
- bulanik-0.3.0/tests/test_orthopair.py +484 -0
- bulanik-0.3.0/tests/test_plot.py +1059 -0
- bulanik-0.3.0/tests/test_ranking.py +186 -0
- bulanik-0.3.0/tests/test_rules.py +253 -0
- bulanik-0.3.0/tests/test_scales.py +70 -0
- bulanik-0.3.0/tests/test_scoring.py +697 -0
- bulanik-0.3.0/tests/test_sets.py +319 -0
- bulanik-0.3.0/tests/test_sezgi_bridge.py +226 -0
- bulanik-0.3.0/tests/test_sugeno.py +138 -0
- bulanik-0.3.0/tests/test_threepart.py +511 -0
- bulanik-0.3.0/tests/test_triangular.py +200 -0
- bulanik-0.3.0/tests/test_tsukamoto.py +105 -0
- bulanik-0.3.0/tests/test_type2.py +545 -0
- bulanik-0.3.0/tests/test_validate.py +69 -0
- bulanik-0.3.0/tests/test_weighting.py +315 -0
- bulanik-0.3.0/uv.lock +3124 -0
bulanik-0.3.0/.gitignore
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to bulanik are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project follows
|
|
5
|
+
[Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [0.3.0] - 2026-11-05
|
|
8
|
+
|
|
9
|
+
Interval-valued AHP and fuzzy versions of three more ranking methods. The public v0.2 API is unchanged apart from
|
|
10
|
+
the two changes listed below.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `ahp_interval_valued`: AHP over interval-valued orthopair comparison matrices, with the
|
|
15
|
+
`multiplicative` and `possibility` methods.
|
|
16
|
+
- `fuzzy_codas`, `fuzzy_waspas` (with `combine="fuzzy"` or `combine="scores"`) and
|
|
17
|
+
`fuzzy_cocoso`, also reachable through `rank_fuzzy(method=...)`.
|
|
18
|
+
- An `IVQROFN` score named `"defuzzified"` (q = 1).
|
|
19
|
+
- Examples 23 and 24 (24 executed examples in all).
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Crisp `codas` accepts zeros in benefit columns.
|
|
24
|
+
- `ANFIS.fit` results differ from v0.2: `lr` is now the largest step tried, and hybrid training
|
|
25
|
+
solves the consequents once more per `fit`, so `history` values change.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Crisp `cocoso` treats rounding-size gaps at a column extreme as zero.
|
|
30
|
+
- `ANFIS.fit` kept gradient steps that raised the training error and could return a model far worse
|
|
31
|
+
than least squares alone, with no warning. A step that raises the error is now halved or dropped,
|
|
32
|
+
and hybrid training ends on solved consequents.
|
|
33
|
+
|
|
34
|
+
[0.3.0]: https://github.com/tdelphi1981/bulanik/releases/tag/v0.3.0
|
|
35
|
+
|
|
36
|
+
## [0.2.0] - 2026-11-04
|
|
37
|
+
|
|
38
|
+
Interval-valued and interval type-2 numbers, six new ranking methods, new weighting
|
|
39
|
+
methods, DEMATEL and three new figures. The public v0.1 API is unchanged: this release
|
|
40
|
+
only adds names.
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
|
|
44
|
+
- Interval-valued numbers: `IVQROFN` (interval-valued q-rung orthopair) with the aliases
|
|
45
|
+
`IVIFN` (q = 1), `IVPFN` (q = 2) and `IVFFN` (q = 3), and `IVNeutrosophicFN` (interval
|
|
46
|
+
neutrosophic). Frozen and validated, with the algebraic and Einstein operator families,
|
|
47
|
+
scores, accuracies and distances. The aliases are separate classes: they neither compare
|
|
48
|
+
equal to nor mix with an `IVQROFN` of the same rung.
|
|
49
|
+
- `IT2TrFN`: interval type-2 trapezoidal fuzzy numbers (upper and lower trapezoid with
|
|
50
|
+
heights, lower nested inside upper), with the arithmetic of Chen and Lee (2010a) and the
|
|
51
|
+
DTraT score of Kahraman et al. (2014).
|
|
52
|
+
- Ranking methods: CODAS, COPRAS, MARCOS, CoCoSo, WISP and ELECTRE I (with net concordance
|
|
53
|
+
and discordance indices for a complete ranking), all registered in
|
|
54
|
+
`rank_fuzzy(method=...)`. Zero, negative and constant columns, and signed default
|
|
55
|
+
scores, raise a `ValueError` naming the method where the method cannot handle them
|
|
56
|
+
(constant columns are neutral in CoCoSo, CRITIC and COPRAS; ELECTRE and CoCoSo accept zeros
|
|
57
|
+
and negatives); a magnitude spread so large that a scaled entry underflows to 0 raises the
|
|
58
|
+
same way in CODAS, MARCOS and WISP.
|
|
59
|
+
- Weighting methods: CRITIC (objective), PIPRECIA (crisp and `TFN` judgements, with an
|
|
60
|
+
optional inverse pass) and `ahp_fuzzy_numbers` (AHP over a comparison matrix of any one
|
|
61
|
+
fuzzy number kind). PIPRECIA requires every judgement `s_j` to lie in (0, 2) and names the
|
|
62
|
+
offending index otherwise.
|
|
63
|
+
- DEMATEL: `dematel` for crisp or fuzzy direct-relation matrices, `cfcs` (Opricovic and
|
|
64
|
+
Tzeng 2003) to convert triangular matrices from several experts, and `DEMATELResult` with
|
|
65
|
+
`as_weights()` so the factor weights feed every ranking method. A singular or
|
|
66
|
+
ill-conditioned $I - N$ (spectral radius numerically 1) raises instead of returning a huge
|
|
67
|
+
total relation.
|
|
68
|
+
- `bulanik.plot`: `dematel` (cause-effect diagram), `iv_orthopair` (interval rectangles over
|
|
69
|
+
the feasible region) and `it2_number` (upper and lower trapezoids with the footprint of
|
|
70
|
+
uncertainty).
|
|
71
|
+
- Documentation: guide pages for interval-valued numbers, interval type-2 numbers, the new
|
|
72
|
+
ranking and weighting methods and DEMATEL; new gallery figures; API reference for all new
|
|
73
|
+
names; examples 18 to 22 (22 executed examples in all); the reference list extended to
|
|
74
|
+
every source cited in the v0.2 docstrings, each DOI checked against Crossref.
|
|
75
|
+
|
|
76
|
+
### Changed
|
|
77
|
+
|
|
78
|
+
- The dark categorical plot palette, slot 6 (green), is `#2ea32e` instead of `#008300`,
|
|
79
|
+
which was too dark on the slate surface. Figures that use the sixth colour change in dark
|
|
80
|
+
mode only.
|
|
81
|
+
|
|
82
|
+
### Notes on sourcing
|
|
83
|
+
|
|
84
|
+
- The 7-term interval type-2 linguistic scale in example 19 and in the gallery is
|
|
85
|
+
illustrative: it follows the style of the type-2 literature, and its values are not a
|
|
86
|
+
transcription of any published table.
|
|
87
|
+
- The CRITIC tests are checked against values derived by hand from the published formulas,
|
|
88
|
+
not against a worked example printed in the paper.
|
|
89
|
+
- The DEMATEL tests include a cross-check against the vignette of the R package `dematel`.
|
|
90
|
+
|
|
91
|
+
[0.2.0]: https://github.com/tdelphi1981/bulanik/releases/tag/v0.2.0
|
|
92
|
+
|
|
93
|
+
## [0.1.0] - 2026-11-03
|
|
94
|
+
|
|
95
|
+
First release.
|
|
96
|
+
|
|
97
|
+
### Added
|
|
98
|
+
|
|
99
|
+
- Fuzzy numbers: triangular (`TFN`) and trapezoidal (`TrFN`); intuitionistic (`IFN`),
|
|
100
|
+
Pythagorean (`PFN`), Fermatean (`FFN`) and q-rung orthopair (`QROFN`); spherical
|
|
101
|
+
(`SphericalFN`), picture (`PictureFN`) and single-valued neutrosophic
|
|
102
|
+
(`NeutrosophicFN`). Immutable, validated on construction, with named operator
|
|
103
|
+
families (`algebraic`, `einstein`), scores, accuracies and distances.
|
|
104
|
+
- Aggregation: weighted average and weighted geometric mean, and expert aggregation of
|
|
105
|
+
`[expert][alternative][criterion]` ratings.
|
|
106
|
+
- Fuzzy sets: membership functions (triangular, trapezoidal, gaussian, bell, sigmoid,
|
|
107
|
+
linear ramps), linguistic variables, t-norms and t-conorms by name, and linguistic
|
|
108
|
+
scales (`SAATY_TFN`, `FUZZY_BWM_TFN` and illustrative IF/TFN scales).
|
|
109
|
+
- Fuzzy inference: Mamdani, Sugeno (zero and first order) and Tsukamoto systems from
|
|
110
|
+
plain-text rules, with `explain` and a no-rule-fired policy.
|
|
111
|
+
- Interval type-2 inference: interval type-2 membership functions and Mamdani and
|
|
112
|
+
Sugeno systems, with KM, EKM, Nie-Tan and BMM type reduction.
|
|
113
|
+
- ANFIS: grid-partition first-order Sugeno ANFIS with hybrid (Jang) or gradient
|
|
114
|
+
training, exported to a Sugeno system.
|
|
115
|
+
- MCDM: AHP, fuzzy AHP (Buckley), SWARA and entropy weights; best-worst consistency
|
|
116
|
+
indices (crisp and fuzzy); TOPSIS, VIKOR, WASPAS and EDAS on crisp matrices;
|
|
117
|
+
`DecisionMatrix` and `rank_fuzzy` for fuzzy-number matrices; Chen's fuzzy TOPSIS.
|
|
118
|
+
- `bulanik.lumix` (extra `lumix`): linear best-worst method, Zimmermann fuzzy
|
|
119
|
+
programming and MCDM scores as LumiX data.
|
|
120
|
+
- `bulanik.sezgi` (extra `sezgi`): nonlinear and fuzzy best-worst method and ANFIS
|
|
121
|
+
training with sezgi metaheuristics.
|
|
122
|
+
- `bulanik.plot` (extra `plot`): figures for numbers, scales, inference, type-2 sets and
|
|
123
|
+
MCDM results in matching light and dark themes.
|
|
124
|
+
- Documentation site (MkDocs Material) with a user guide, API reference, 17 executed
|
|
125
|
+
examples and a verified reference list; 17 runnable scripts in `examples/`.
|
|
126
|
+
|
|
127
|
+
[0.1.0]: https://github.com/tdelphi1981/bulanik/releases/tag/v0.1.0
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: >-
|
|
3
|
+
If you use bulanik in your research, please cite it using the metadata
|
|
4
|
+
below.
|
|
5
|
+
type: software
|
|
6
|
+
title: bulanik
|
|
7
|
+
version: 0.3.0
|
|
8
|
+
date-released: "2026-11-05"
|
|
9
|
+
abstract: >-
|
|
10
|
+
Easy-to-use fuzzy numbers, fuzzy inference and fuzzy multi-criteria
|
|
11
|
+
decision making in pure Python, with optional bridges to LumiX and sezgi
|
|
12
|
+
and publication-quality figures.
|
|
13
|
+
authors:
|
|
14
|
+
- family-names: Berber
|
|
15
|
+
given-names: Tolga
|
|
16
|
+
email: tolga.berber@fen.ktu.edu.tr
|
|
17
|
+
orcid: "https://orcid.org/0000-0002-6487-5581"
|
|
18
|
+
affiliation: "Department of Computer Science, Karadeniz Technical University"
|
|
19
|
+
license: MIT
|
|
20
|
+
repository-code: "https://github.com/tdelphi1981/bulanik"
|
|
21
|
+
url: "https://tdelphi1981.github.io/bulanik/"
|
|
22
|
+
keywords:
|
|
23
|
+
- fuzzy-logic
|
|
24
|
+
- fuzzy-inference
|
|
25
|
+
- fuzzy-numbers
|
|
26
|
+
- mcdm
|
|
27
|
+
- anfis
|
|
28
|
+
- optimization
|
|
29
|
+
- python
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Contributing to bulanik
|
|
2
|
+
|
|
3
|
+
## Development setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
git clone https://github.com/tdelphi1981/bulanik
|
|
7
|
+
cd bulanik
|
|
8
|
+
uv sync # dev group: pytest, hypothesis, coverage, matplotlib, lumix-opt, ortools, sezgi
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Tests
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
uv run python -m pytest -q
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The suite runs every script in `examples/`. Coverage is measured only when you ask for
|
|
18
|
+
it, and then it must stay at or above 95% (`fail_under` in `pyproject.toml`):
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
uv run python -m pytest -q --cov=bulanik
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Do not run pytest with `-W error`: OR-Tools' SWIG bindings emit warnings that
|
|
25
|
+
turn into crashes. Property tests use Hypothesis.
|
|
26
|
+
|
|
27
|
+
## Documentation
|
|
28
|
+
|
|
29
|
+
The site is MkDocs Material, sources in `docs/site/`, configuration in `mkdocs.yml`.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
uv sync --group docs
|
|
33
|
+
uv run mkdocs serve # live preview
|
|
34
|
+
uv run mkdocs build --strict # must finish with zero warnings
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`tests/test_docs.py` runs the strict build only when the docs group is installed
|
|
38
|
+
(`uv sync --group docs`); without it the test is skipped. Deselect it with
|
|
39
|
+
`-m "not docs"`. Building the site needs **Python 3.11 or newer** (the docs group pins a
|
|
40
|
+
matplotlib release without Python 3.10 wheels) and **network access** (the privacy plugin
|
|
41
|
+
downloads external assets such as the MathJax script). Python snippets in the docs are executed at
|
|
42
|
+
build time with markdown-exec, so the output shown is real.
|
|
43
|
+
|
|
44
|
+
## Style
|
|
45
|
+
|
|
46
|
+
- Python 3.10+, type hints on public API, no runtime dependencies in the core.
|
|
47
|
+
- Numbers and sets are immutable; invalid input raises a specific error, never returns silently.
|
|
48
|
+
- Docstrings use Google style (`Args:`, `Returns:`, `Raises:`, `Examples:`).
|
|
49
|
+
- Cited works need a verified reference: add them to `docs/site/about/references.md` with a
|
|
50
|
+
DOI that resolves (check it against Crossref or the publisher), never from memory.
|
|
51
|
+
- Keep commits focused; do not change library behaviour in documentation commits.
|
bulanik-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tolga Berber
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
bulanik-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: bulanik
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Easy-to-use fuzzy numbers, fuzzy inference and fuzzy MCDM, with LumiX and sezgi bridges
|
|
5
|
+
Project-URL: Homepage, https://github.com/tdelphi1981/bulanik
|
|
6
|
+
Project-URL: Documentation, https://tdelphi1981.github.io/bulanik/
|
|
7
|
+
Project-URL: Repository, https://github.com/tdelphi1981/bulanik
|
|
8
|
+
Project-URL: Issues, https://github.com/tdelphi1981/bulanik/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/tdelphi1981/bulanik/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: Tolga Berber <tolga.berber@fen.ktu.edu.tr>
|
|
11
|
+
License: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: fuzzy,fuzzy-inference,fuzzy-logic,mcdm,optimization
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Provides-Extra: lumix
|
|
25
|
+
Requires-Dist: lumix-opt>=0.3.2; extra == 'lumix'
|
|
26
|
+
Requires-Dist: ortools>=9.8; extra == 'lumix'
|
|
27
|
+
Provides-Extra: plot
|
|
28
|
+
Requires-Dist: matplotlib>=3.8; extra == 'plot'
|
|
29
|
+
Provides-Extra: sezgi
|
|
30
|
+
Requires-Dist: sezgi>=0.1.6; extra == 'sezgi'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# bulanik
|
|
34
|
+
|
|
35
|
+
Easy-to-use fuzzy numbers, fuzzy inference and fuzzy multi-criteria decision making
|
|
36
|
+
(MCDM) in pure Python, with no runtime dependencies. *Bulanık* is Turkish for *fuzzy*.
|
|
37
|
+
Optional bridges connect it to [LumiX](https://pypi.org/project/lumix-opt/) (optimization
|
|
38
|
+
modelling) and [sezgi](https://github.com/tdelphi1981/sezgi) (metaheuristics), and an
|
|
39
|
+
optional plotting module draws the results in a light and a dark theme.
|
|
40
|
+
|
|
41
|
+
**Documentation:** <https://tdelphi1981.github.io/bulanik/>
|
|
42
|
+
|
|
43
|
+
## Install
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install bulanik # core, no runtime dependencies
|
|
47
|
+
pip install "bulanik[lumix]" # + the LumiX bridge (bulanik.lumix)
|
|
48
|
+
pip install "bulanik[sezgi]" # + the sezgi bridge (bulanik.sezgi)
|
|
49
|
+
pip install "bulanik[plot]" # + figures (bulanik.plot, needs matplotlib)
|
|
50
|
+
pip install "bulanik[lumix,sezgi,plot]" # everything
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Python 3.10 to 3.13. For development, clone the repository and run `uv sync` (see
|
|
54
|
+
[Contributing](https://github.com/tdelphi1981/bulanik/blob/main/CONTRIBUTING.md)).
|
|
55
|
+
|
|
56
|
+
## Quickstart
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from bulanik import IFN, TFN
|
|
60
|
+
from bulanik.aggregate import wa
|
|
61
|
+
|
|
62
|
+
a, b = IFN(0.6, 0.3), IFN(0.5, 0.4)
|
|
63
|
+
print(a + b) # IFN(mu=0.8, nu=0.12)
|
|
64
|
+
print(a.add(b, ops="einstein")) # the Einstein operator family
|
|
65
|
+
print(wa([a, b], [0.7, 0.3])) # weighted average
|
|
66
|
+
print((TFN(1, 2, 3) + TFN(3, 4, 5)).score()) # crisp score for ranking
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Every operator family, score and method is chosen by name (`ops="einstein"`,
|
|
70
|
+
`score("normalized")`); an unknown name raises a `ValueError` that lists the valid ones.
|
|
71
|
+
|
|
72
|
+
## What is in it
|
|
73
|
+
|
|
74
|
+
- **Fuzzy numbers:** triangular and trapezoidal; intuitionistic, Pythagorean, Fermatean
|
|
75
|
+
and q-rung orthopair; spherical, picture and neutrosophic; their interval-valued
|
|
76
|
+
versions (`IVIFN`, `IVPFN`, `IVFFN`, `IVQROFN`, `IVNeutrosophicFN`) and interval type-2
|
|
77
|
+
trapezoidal numbers (`IT2TrFN`). Immutable and validated.
|
|
78
|
+
- **Aggregation, sets and scales:** weighted average and geometric mean, expert
|
|
79
|
+
aggregation, membership functions, linguistic variables and linguistic scales.
|
|
80
|
+
- **Fuzzy inference:** Mamdani, Sugeno and Tsukamoto systems from plain-text rules;
|
|
81
|
+
interval type-2 systems with KM, EKM, Nie-Tan and BMM type reduction; ANFIS.
|
|
82
|
+
- **Decision making:** AHP, fuzzy AHP (also over any number kind), SWARA, entropy, CRITIC
|
|
83
|
+
and PIPRECIA weights; DEMATEL with CFCS; best-worst consistency; TOPSIS, VIKOR, WASPAS,
|
|
84
|
+
EDAS, CODAS, COPRAS, MARCOS, CoCoSo, WISP and ELECTRE, on crisp matrices or fuzzy
|
|
85
|
+
ratings (fuzzy CODAS, WASPAS and CoCoSo included), and Chen's fuzzy TOPSIS. Interval-valued
|
|
86
|
+
AHP (`ahp_interval_valued`) with the multiplicative and possibility methods.
|
|
87
|
+
- **Bridges:** with LumiX, the linear best-worst method, Zimmermann fuzzy programming and
|
|
88
|
+
MCDM scores as optimization data; with sezgi, nonlinear and fuzzy BWM and ANFIS training.
|
|
89
|
+
- **Figures:** `bulanik.plot` for numbers, scales, inference, rankings and DEMATEL.
|
|
90
|
+
|
|
91
|
+
The [documentation](https://tdelphi1981.github.io/bulanik/) has a user guide, an API
|
|
92
|
+
reference, and 24 runnable examples (also in [`examples/`](https://github.com/tdelphi1981/bulanik/tree/main/examples)).
|
|
93
|
+
|
|
94
|
+
## Development
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
uv sync
|
|
98
|
+
uv run python -m pytest -q
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The dev group installs LumiX, OR-Tools, sezgi and matplotlib, so the bridge tests and
|
|
102
|
+
every example run; without an extra, its tests and examples are skipped. Coverage is
|
|
103
|
+
measured on the pure-Python core.
|
|
104
|
+
|
|
105
|
+
## Citing
|
|
106
|
+
|
|
107
|
+
Please cite bulanik using [`CITATION.cff`](https://github.com/tdelphi1981/bulanik/blob/main/CITATION.cff) (GitHub's "Cite this repository"),
|
|
108
|
+
and the original papers behind the methods you use, listed with DOIs in the
|
|
109
|
+
[references](https://tdelphi1981.github.io/bulanik/about/references.html).
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
MIT. See [LICENSE](https://github.com/tdelphi1981/bulanik/blob/main/LICENSE).
|
bulanik-0.3.0/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# bulanik
|
|
2
|
+
|
|
3
|
+
Easy-to-use fuzzy numbers, fuzzy inference and fuzzy multi-criteria decision making
|
|
4
|
+
(MCDM) in pure Python, with no runtime dependencies. *Bulanık* is Turkish for *fuzzy*.
|
|
5
|
+
Optional bridges connect it to [LumiX](https://pypi.org/project/lumix-opt/) (optimization
|
|
6
|
+
modelling) and [sezgi](https://github.com/tdelphi1981/sezgi) (metaheuristics), and an
|
|
7
|
+
optional plotting module draws the results in a light and a dark theme.
|
|
8
|
+
|
|
9
|
+
**Documentation:** <https://tdelphi1981.github.io/bulanik/>
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install bulanik # core, no runtime dependencies
|
|
15
|
+
pip install "bulanik[lumix]" # + the LumiX bridge (bulanik.lumix)
|
|
16
|
+
pip install "bulanik[sezgi]" # + the sezgi bridge (bulanik.sezgi)
|
|
17
|
+
pip install "bulanik[plot]" # + figures (bulanik.plot, needs matplotlib)
|
|
18
|
+
pip install "bulanik[lumix,sezgi,plot]" # everything
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Python 3.10 to 3.13. For development, clone the repository and run `uv sync` (see
|
|
22
|
+
[Contributing](https://github.com/tdelphi1981/bulanik/blob/main/CONTRIBUTING.md)).
|
|
23
|
+
|
|
24
|
+
## Quickstart
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from bulanik import IFN, TFN
|
|
28
|
+
from bulanik.aggregate import wa
|
|
29
|
+
|
|
30
|
+
a, b = IFN(0.6, 0.3), IFN(0.5, 0.4)
|
|
31
|
+
print(a + b) # IFN(mu=0.8, nu=0.12)
|
|
32
|
+
print(a.add(b, ops="einstein")) # the Einstein operator family
|
|
33
|
+
print(wa([a, b], [0.7, 0.3])) # weighted average
|
|
34
|
+
print((TFN(1, 2, 3) + TFN(3, 4, 5)).score()) # crisp score for ranking
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Every operator family, score and method is chosen by name (`ops="einstein"`,
|
|
38
|
+
`score("normalized")`); an unknown name raises a `ValueError` that lists the valid ones.
|
|
39
|
+
|
|
40
|
+
## What is in it
|
|
41
|
+
|
|
42
|
+
- **Fuzzy numbers:** triangular and trapezoidal; intuitionistic, Pythagorean, Fermatean
|
|
43
|
+
and q-rung orthopair; spherical, picture and neutrosophic; their interval-valued
|
|
44
|
+
versions (`IVIFN`, `IVPFN`, `IVFFN`, `IVQROFN`, `IVNeutrosophicFN`) and interval type-2
|
|
45
|
+
trapezoidal numbers (`IT2TrFN`). Immutable and validated.
|
|
46
|
+
- **Aggregation, sets and scales:** weighted average and geometric mean, expert
|
|
47
|
+
aggregation, membership functions, linguistic variables and linguistic scales.
|
|
48
|
+
- **Fuzzy inference:** Mamdani, Sugeno and Tsukamoto systems from plain-text rules;
|
|
49
|
+
interval type-2 systems with KM, EKM, Nie-Tan and BMM type reduction; ANFIS.
|
|
50
|
+
- **Decision making:** AHP, fuzzy AHP (also over any number kind), SWARA, entropy, CRITIC
|
|
51
|
+
and PIPRECIA weights; DEMATEL with CFCS; best-worst consistency; TOPSIS, VIKOR, WASPAS,
|
|
52
|
+
EDAS, CODAS, COPRAS, MARCOS, CoCoSo, WISP and ELECTRE, on crisp matrices or fuzzy
|
|
53
|
+
ratings (fuzzy CODAS, WASPAS and CoCoSo included), and Chen's fuzzy TOPSIS. Interval-valued
|
|
54
|
+
AHP (`ahp_interval_valued`) with the multiplicative and possibility methods.
|
|
55
|
+
- **Bridges:** with LumiX, the linear best-worst method, Zimmermann fuzzy programming and
|
|
56
|
+
MCDM scores as optimization data; with sezgi, nonlinear and fuzzy BWM and ANFIS training.
|
|
57
|
+
- **Figures:** `bulanik.plot` for numbers, scales, inference, rankings and DEMATEL.
|
|
58
|
+
|
|
59
|
+
The [documentation](https://tdelphi1981.github.io/bulanik/) has a user guide, an API
|
|
60
|
+
reference, and 24 runnable examples (also in [`examples/`](https://github.com/tdelphi1981/bulanik/tree/main/examples)).
|
|
61
|
+
|
|
62
|
+
## Development
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
uv sync
|
|
66
|
+
uv run python -m pytest -q
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The dev group installs LumiX, OR-Tools, sezgi and matplotlib, so the bridge tests and
|
|
70
|
+
every example run; without an extra, its tests and examples are skipped. Coverage is
|
|
71
|
+
measured on the pure-Python core.
|
|
72
|
+
|
|
73
|
+
## Citing
|
|
74
|
+
|
|
75
|
+
Please cite bulanik using [`CITATION.cff`](https://github.com/tdelphi1981/bulanik/blob/main/CITATION.cff) (GitHub's "Cite this repository"),
|
|
76
|
+
and the original papers behind the methods you use, listed with DOIs in the
|
|
77
|
+
[references](https://tdelphi1981.github.io/bulanik/about/references.html).
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
MIT. See [LICENSE](https://github.com/tdelphi1981/bulanik/blob/main/LICENSE).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Root pytest configuration: doctest collection over ``src/bulanik``.
|
|
2
|
+
|
|
3
|
+
``bulanik.lumix`` and ``bulanik.sezgi`` import optional extras at module level,
|
|
4
|
+
so collecting their doctests would fail without the extra. Skip a bridge module
|
|
5
|
+
when its dependencies are not importable; likewise ``bulanik.plot`` without
|
|
6
|
+
matplotlib. With matplotlib, doctests and tests draw on the non-interactive
|
|
7
|
+
Agg backend and every figure is closed after each test.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import importlib.util
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
collect_ignore = []
|
|
15
|
+
|
|
16
|
+
if any(importlib.util.find_spec(m) is None for m in ("lumix", "ortools")):
|
|
17
|
+
collect_ignore.append("src/bulanik/lumix.py")
|
|
18
|
+
if importlib.util.find_spec("sezgi") is None:
|
|
19
|
+
collect_ignore.append("src/bulanik/sezgi.py")
|
|
20
|
+
if importlib.util.find_spec("matplotlib") is None:
|
|
21
|
+
collect_ignore.append("src/bulanik/plot")
|
|
22
|
+
else:
|
|
23
|
+
import matplotlib
|
|
24
|
+
|
|
25
|
+
matplotlib.use("Agg")
|
|
26
|
+
|
|
27
|
+
@pytest.fixture(autouse=True)
|
|
28
|
+
def _close_matplotlib_figures():
|
|
29
|
+
yield
|
|
30
|
+
import matplotlib.pyplot as plt
|
|
31
|
+
|
|
32
|
+
plt.close("all")
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Shared plumbing for the docs figure scripts.
|
|
2
|
+
|
|
3
|
+
All plotting goes through `bulanik.plot`; this module only
|
|
4
|
+
|
|
5
|
+
- renders a figure in both house themes and saves `<name>-light.svg` and
|
|
6
|
+
`<name>-dark.svg` into `docs/site/assets/figures/` with deterministic output
|
|
7
|
+
(fixed SVG id salt, no creation date), and
|
|
8
|
+
- offers page-level layout helpers: multi-panel figures and annotations
|
|
9
|
+
(notes, brackets, arrows, markers) drawn with the active theme's tokens from
|
|
10
|
+
`bulanik.plot.current_theme()`.
|
|
11
|
+
|
|
12
|
+
A figure is a function `draw() -> Figure | Axes` that runs inside the theme.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import io
|
|
18
|
+
import os
|
|
19
|
+
import re
|
|
20
|
+
import sys
|
|
21
|
+
from collections.abc import Callable, Mapping
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
os.environ.setdefault("MPLBACKEND", "Agg")
|
|
26
|
+
|
|
27
|
+
import matplotlib as mpl # noqa: E402
|
|
28
|
+
import matplotlib.pyplot as plt # noqa: E402
|
|
29
|
+
from matplotlib import patheffects # noqa: E402
|
|
30
|
+
from matplotlib.axes import Axes # noqa: E402
|
|
31
|
+
from matplotlib.figure import Figure # noqa: E402
|
|
32
|
+
|
|
33
|
+
from bulanik import plot # noqa: E402
|
|
34
|
+
|
|
35
|
+
FIGURES = Path(__file__).resolve().parents[1] / "site" / "assets" / "figures"
|
|
36
|
+
THEMES = ("light", "dark")
|
|
37
|
+
Draw = Callable[[], "Figure | Axes"]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def tokens() -> Mapping[str, Any]:
|
|
41
|
+
"""The active theme's tokens (`bulanik.plot.current_theme()`)."""
|
|
42
|
+
return plot.current_theme()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def render(name: str, draw: Draw, out: Path = FIGURES) -> list[Path]:
|
|
46
|
+
"""Draw the figure once per theme and save `<name>-<theme>.svg`."""
|
|
47
|
+
out.mkdir(parents=True, exist_ok=True)
|
|
48
|
+
paths = []
|
|
49
|
+
for mode in THEMES:
|
|
50
|
+
with plot.theme(mode), mpl.rc_context({"svg.hashsalt": f"bulanik-{name}"}):
|
|
51
|
+
res = draw()
|
|
52
|
+
fig = res.figure if isinstance(res, Axes) else res
|
|
53
|
+
path = out / f"{name}-{mode}.svg"
|
|
54
|
+
buffer = io.StringIO()
|
|
55
|
+
fig.savefig(buffer, format="svg", metadata={"Date": None, "Creator": "bulanik docs"})
|
|
56
|
+
plt.close(fig)
|
|
57
|
+
path.write_text(compact(buffer.getvalue()), encoding="utf-8")
|
|
58
|
+
paths.append(path)
|
|
59
|
+
return paths
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
_DECIMALS = re.compile(r"(\d\.\d{2})\d+")
|
|
63
|
+
_PATH_DATA = re.compile(r' d="[^"]*"') # only path geometry; transforms keep full precision
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def compact(svg: str) -> str:
|
|
67
|
+
"""Round path coordinates to 0.01 pt: invisible at any zoom, and the files shrink by ~40%.
|
|
68
|
+
|
|
69
|
+
Haloed labels are written as outline paths (not glyph references), so the
|
|
70
|
+
coordinate text dominates the file size.
|
|
71
|
+
"""
|
|
72
|
+
return _PATH_DATA.sub(lambda m: _DECIMALS.sub(r"\1", m.group(0)), svg)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def the_text(ax: Axes, *, startswith: str | None = None, equals: str | None = None):
|
|
76
|
+
"""The one text of `ax` that matches; fail loudly on zero or several matches."""
|
|
77
|
+
found = [
|
|
78
|
+
t
|
|
79
|
+
for t in ax.texts
|
|
80
|
+
if (t.get_text().startswith(startswith) if startswith is not None else t.get_text() == equals)
|
|
81
|
+
]
|
|
82
|
+
if len(found) != 1:
|
|
83
|
+
raise AssertionError(f"expected exactly one text matching {startswith or equals!r}, found {len(found)}")
|
|
84
|
+
return found[0]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def run(figures: Mapping[str, Draw], argv: list[str] | None = None) -> None:
|
|
88
|
+
"""Command-line entry: render all figures, or only the names given."""
|
|
89
|
+
names = (argv if argv is not None else sys.argv[1:]) or list(figures)
|
|
90
|
+
for name in names:
|
|
91
|
+
for path in render(name, figures[name]):
|
|
92
|
+
print(path.relative_to(Path.cwd()) if path.is_relative_to(Path.cwd()) else path)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# -- layout ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def panels(nrows: int, ncols: int, *, figsize: tuple[float, float], **kw: Any) -> tuple[Figure, Any]:
|
|
99
|
+
"""A constrained-layout figure with a grid of axes, in the active theme."""
|
|
100
|
+
fig, axes = plt.subplots(nrows, ncols, figsize=figsize, layout="constrained", **kw)
|
|
101
|
+
return fig, axes
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def halo(width: float = 3.0) -> list[Any]:
|
|
105
|
+
"""Surface-coloured outline so lines never cut through a label."""
|
|
106
|
+
return [patheffects.withStroke(linewidth=width, foreground=tokens()["surface"], alpha=0.9)]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def note(ax: Axes, x: float, y: float, text: str, *, ink: str = "text_secondary", size: float = 8.5, **kw: Any) -> Any:
|
|
110
|
+
"""Annotation text in a text token (never a series colour), with a halo."""
|
|
111
|
+
kw.setdefault("ha", "center")
|
|
112
|
+
kw.setdefault("va", "bottom")
|
|
113
|
+
kw.setdefault("zorder", 9)
|
|
114
|
+
return ax.text(x, y, text, color=tokens()[ink], fontsize=size, path_effects=halo(), **kw)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def bracket(ax: Axes, x0: float, x1: float, y: float, text: str, *, tick: float = 0.035, below: bool = False) -> None:
|
|
118
|
+
"""A hairline bracket over [x0, x1] at height y with a centred label."""
|
|
119
|
+
t = tokens()
|
|
120
|
+
s = -1 if below else 1
|
|
121
|
+
ax.plot([x0, x0, x1, x1], [y - s * tick, y, y, y - s * tick], color=t["muted"], lw=0.8,
|
|
122
|
+
solid_joinstyle="miter", zorder=7, clip_on=False)
|
|
123
|
+
note(ax, (x0 + x1) / 2, y + s * 0.02, text, va="top" if below else "bottom", ink="text_secondary")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def arrow(ax: Axes, start: tuple[float, float], end: tuple[float, float], color: str, *,
|
|
127
|
+
lw: float = 1.2, shrink: float = 6.0, alpha: float = 1.0, rad: float = 0.0,
|
|
128
|
+
shrink_end: float | None = None) -> None:
|
|
129
|
+
"""A thin arrow in a series colour (a mark, so it may carry the hue).
|
|
130
|
+
|
|
131
|
+
`shrink_end` (points) stops the head short of the target marker; it defaults to `shrink`.
|
|
132
|
+
"""
|
|
133
|
+
ax.annotate(
|
|
134
|
+
"", xy=end, xytext=start, zorder=6,
|
|
135
|
+
arrowprops=dict(arrowstyle="-|>,head_length=0.45,head_width=0.22", color=color, lw=lw,
|
|
136
|
+
shrinkA=shrink, shrinkB=shrink if shrink_end is None else shrink_end, alpha=alpha,
|
|
137
|
+
connectionstyle=f"arc3,rad={rad}"),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def dot(ax: Axes, x: float, y: float, color: str, *, size: float = 8.0, marker: str = "o", zorder: float = 8) -> None:
|
|
142
|
+
"""A marker with the house 2 px surface ring."""
|
|
143
|
+
ax.plot([x], [y], marker=marker, ms=size, mfc=color, mec=tokens()["surface"], mew=1.6,
|
|
144
|
+
linestyle="none", zorder=zorder, clip_on=False)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def key(ax: Axes, items: list[tuple[str, str, str]], *, loc: str = "upper right", **kw: Any) -> Any:
|
|
148
|
+
"""A compact legend: (marker, colour, text) per entry, text in ink tokens."""
|
|
149
|
+
from matplotlib.lines import Line2D
|
|
150
|
+
|
|
151
|
+
t = tokens()
|
|
152
|
+
handles = [
|
|
153
|
+
Line2D([], [], marker=m, linestyle="none", ms=7, mfc=c, mec=t["surface"], mew=1.4, label=s)
|
|
154
|
+
if m else Line2D([], [], color=c, lw=1.8, label=s)
|
|
155
|
+
for m, c, s in items
|
|
156
|
+
]
|
|
157
|
+
kw.setdefault("fontsize", 8.5)
|
|
158
|
+
leg = ax.legend(handles=handles, loc=loc, frameon=False, labelcolor=t["text_secondary"],
|
|
159
|
+
handletextpad=0.4, borderaxespad=0.3, **kw)
|
|
160
|
+
leg.set_zorder(10)
|
|
161
|
+
return leg
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def label(ax: Axes, x: float, y: float, text: str, *, dx: float = 7, dy: float = 0, ha: str = "left",
|
|
165
|
+
va: str = "center", size: float = 8.5, ink: str = "text_secondary") -> Any:
|
|
166
|
+
"""Point label offset by (dx, dy) points, in a text token with a halo."""
|
|
167
|
+
return ax.annotate(text, (x, y), xytext=(dx, dy), textcoords="offset points", ha=ha, va=va,
|
|
168
|
+
fontsize=size, color=tokens()[ink], path_effects=halo(), zorder=9)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def callout(ax: Axes, xy: tuple[float, float], at: tuple[float, float], text: str, *, ha: str = "left",
|
|
172
|
+
va: str = "center", size: float = 8.5, ink: str = "text_secondary") -> Any:
|
|
173
|
+
"""A label placed at data point `at`, tied to `xy` by a hairline leader."""
|
|
174
|
+
t = tokens()
|
|
175
|
+
return ax.annotate(text, xy, xytext=at, textcoords="data", ha=ha, va=va, fontsize=size, color=t[ink],
|
|
176
|
+
path_effects=halo(), zorder=9,
|
|
177
|
+
arrowprops=dict(arrowstyle="-", color=t["muted"], lw=0.6, shrinkA=3, shrinkB=6))
|