mumo-engine 1.0.0__py3-none-any.whl

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.
@@ -0,0 +1,248 @@
1
+ Metadata-Version: 2.4
2
+ Name: mumo-engine
3
+ Version: 1.0.0
4
+ Summary: Boundary-based statistics and risk-corridor calculations
5
+ Author: Mumo Framework Authors
6
+ License-Expression: MIT
7
+ Keywords: statistics,risk,boundaries,mumo
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Science/Research
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
14
+ Requires-Python: >=3.9
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Dynamic: license-file
18
+
19
+ ================================================================================
20
+ MUMO MATHEMATICAL FRAMEWORK ENGINE (mumo-engine)
21
+ ================================================================================
22
+ Version: 1.0.0
23
+ Author: Mumo Framework Authors
24
+ License: MIT (LICENSE)
25
+
26
+ --------------------------------------------------------------------------------
27
+ 1. EXECUTIVE SUMMARY & LIBRARY PURPOSE
28
+ --------------------------------------------------------------------------------
29
+ The Mumo Framework (`mumo-engine`) is an advanced mathematical computing
30
+ library designed to replace legacy statistical metrics (such as standard
31
+ deviation, raw arithmetic means, and isolated medians) with dynamic risk-corridor
32
+ tracking, extreme boundary isolation, consensus core squeezing, autotuned parameter
33
+ self-calibration, and multi-dimensional tensor risk modeling.
34
+
35
+ Traditional statistical tools suffer from critical real-world limitations:
36
+ - Standard deviation produces abstract squared variance units rather than
37
+ unit-accurate risk limits.
38
+ - Single outliers or sensor typos skew basic mean calculations.
39
+ - Single-point medians flip wildly in large streaming data.
40
+ - Manual tuning parameters introduce human operator bias.
41
+
42
+ The Mumo Engine solves these problems by establishing boundary envelopes, dynamic
43
+ consensus squeezing, calculus-based derivative velocity tracking, and automated
44
+ parameter self-calibration across massive datasets and GPU-accelerated matrices.
45
+
46
+
47
+ --------------------------------------------------------------------------------
48
+ 2. PIPELINE ARCHITECTURE DIAGRAM
49
+ --------------------------------------------------------------------------------
50
+
51
+ [ THE TOTAL SC-PGMF PIPELINE ]
52
+ |
53
+ +----------------------------+----------------------------+
54
+ | | |
55
+ v v v
56
+ [ Boundary Modules ] [ Core Stabilisers ] [ Calculus Tensors ]
57
+ * P-MPU (Ceiling Limit) * P-GMCSI (True Baseline) * Derivatives (Velocity)
58
+ * P-MPD (Safety Floor) * P-MBTS (Tension Weight) * Integrals (Total Strain)
59
+ * MBV (Risk Corridor) * MAEI (Master Engines) * Matrices (Multi-Scale)
60
+
61
+
62
+ --------------------------------------------------------------------------------
63
+ 3. COMPLETE FORMULA BREAKDOWN: FUNCTIONS, SYMBOLS & GAPS FILLED
64
+ --------------------------------------------------------------------------------
65
+
66
+ [SECTION A: FOUNDATIONAL MUMO MODEL (Small Sample Sizes: n, k = 1)]
67
+
68
+ 1. Mumo Potential Upside (MPU)
69
+ * Formula: MPU = ( x_n + (sum(x_i for i=1 to n-1) / (n-1)) ) / 2
70
+ * Symbol Definitions:
71
+ - x_n : The single maximum value in the sorted dataset X.
72
+ - x_i : Individual baseline entries in the sorted dataset.
73
+ - n : Total number of data points.
74
+ * Function: Evaluates maximum peak performance capability relative to baseline.
75
+ * Gap Filled: Replaces raw maximum values (which are highly vulnerable to
76
+ single data entry errors or noise spikes) with a baseline-anchored peak limit.
77
+
78
+ 2. Mumo Potential Downside (MPD)
79
+ * Formula: MPD = ( x_1 + (sum(x_i for i=2 to n) / (n-1)) ) / 2
80
+ * Symbol Definitions:
81
+ - x_1 : The single minimum value in the sorted dataset X.
82
+ - x_i : Individual baseline entries in the upper dataset.
83
+ - n : Total number of data points.
84
+ * Function: Evaluates system risk floor safety relative to the remaining baseline.
85
+ * Gap Filled: Eliminates raw minimum metrics by evaluating worst-case failure
86
+ risk in proportion to baseline health.
87
+
88
+ 3. Mumo Average (MA)
89
+ * Formula: MA = ( MPU + MPD ) / 2
90
+ * Function: Establishes the central equilibrium point between MPU and MPD.
91
+ * Gap Filled: Provides an unwarped balance point for small sample counts.
92
+
93
+
94
+ [SECTION B: GENERALIZED & PARAMETRIC BOUNDARY MODULES (Big Data & Slice k)]
95
+
96
+ 4. Parametric Mumo Potential Upside (P-MPU)
97
+ * Formula: P-MPU = alpha * (sum(top k) / k) + (1 - alpha) * (sum(remaining) / (n - k))
98
+ * Symbol Definitions:
99
+ - k : Boundary slice size (number of top/bottom boundary elements).
100
+ - alpha : Weighting slider controlling boundary sensitivity (0 <= alpha <= 1).
101
+ - n : Total size of the data array.
102
+ * Function: Calculates continuous upper performance capacity across massive data streams.
103
+ * Gap Filled: Prevents single data typos or corrupted streaming telemetry from
104
+ warping performance capacity estimates.
105
+
106
+ 5. Parametric Mumo Potential Downside (P-MPD)
107
+ * Formula: P-MPD = alpha * (sum(bottom k) / k) + (1 - alpha) * (sum(remaining) / (n - k))
108
+ * Function: Establishes a flexible operational safety floor across large database streams.
109
+ * Gap Filled: Stops transient system glitches from triggering false automated
110
+ emergency shutdown routines.
111
+
112
+ 6. Parametric Generalized Mumo Average (P-GMA)
113
+ * Formula: P-GMA = beta * (P-MPU) + (1 - beta) * (P-MPD)
114
+ * Symbol Definitions:
115
+ - beta : Risk-reward balance parameter slider (0 <= beta <= 1).
116
+ * Function: Blends peak upside boundaries and risk floors according to operational goals.
117
+ * Gap Filled: Enables real-time tuning of automated trading bots or server network
118
+ throttling based on target risk tolerance.
119
+
120
+ 7. Mumo Bounds of Variance (MBV)
121
+ * Formula: MBV = P-MPU - P-MPD
122
+ * Function: Measures the physical width of the operational risk corridor.
123
+ * Gap Filled: Replaces statistical standard deviation (which yields abstract
124
+ squared units) with a unit-accurate physical range measurement matching the input data.
125
+
126
+
127
+ [SECTION C: CORE STABILISERS]
128
+
129
+ 8. Parametric Generalized Mumo Core-Squeeze Index (P-GMCSI)
130
+ * Formula: P-GMCSI = alpha * (sum(Core Cluster C_k) / k) + (1 - alpha) * (sum(Remaining Mass) / (n - k))
131
+ * Symbol Definitions:
132
+ - C_k : The contiguous central k-value slice of the sorted dataset.
133
+ - R_{n-k} : Every value outside C_k; this set contains n-k values.
134
+ - Slice position: start at index floor((n-k)/2) in the sorted dataset.
135
+ * Function: Squeezes middle elements to produce an uncorrupted central consensus value.
136
+ * Gap Filled: Eliminates median flipping in large datasets while preserving full
137
+ consensus awareness of outer wing mass.
138
+
139
+ For dynamic fractional sizing, set k = max(1, floor(p*n)) (capped at n-1)
140
+ using autotune_parameters(data, fraction=p).k. This is the framework's
141
+ fractional-size mode (equivalent to delta=1); omitting it leaves k under
142
+ caller control (equivalent to delta=0). The calculation functions
143
+ themselves remain deterministic and accept k as an ordinary parameter.
144
+ When using k for P-MBTS, MAEI, or MCHI as well, choose p < 0.5 so
145
+ 2*k < n and P-MBTS has at least one value remaining between its two
146
+ boundary slices.
147
+
148
+ 9. Mumo Adaptive Equilibrium Index (MAEI)
149
+ * Formula: MAEI = gamma * (P-GMA) + (1 - gamma) * (P-GMCSI)
150
+ * Symbol Definitions:
151
+ - gamma : Balance switch between outer threat boundary tracking and inner core stability.
152
+ * Function: Master Engine unifying boundary limits and core consensus into one metric.
153
+ * Gap Filled: Provides hardware engines with dynamic mathematical survival instincts.
154
+
155
+
156
+ [SECTION D: AUTOTUNE ZERO-BIAS ENGINE (SC-PGMF)]
157
+
158
+ 10. Autotune Self-Calibration (SC-MPU, SC-MPD, SC-GMA, SC-GMCSI, SC-MAEI)
159
+ * Formulas:
160
+ k_auto = floor(p * n)
161
+ alpha_auto = (x_n - x_1) / mean(x)
162
+ beta_auto = (x_n - mean(x)) / MBV_baseline
163
+ * Symbol Definitions:
164
+ - p : Target fractional boundary depth (e.g., 0.05 for 5%).
165
+ - delta, omega : Mode switches (0 = manual, 1 = fully automated autotune).
166
+ * Function: Auto-derives k, alpha, and beta parameters straight from data volume and variance.
167
+ * Gap Filled: Erases human parameter bias and eliminates manual recalibration during sudden market spikes.
168
+
169
+
170
+ [SECTION E: CALCULUS TENSORS & MULTI-DIMENSIONAL EXTENSIONS]
171
+
172
+ 11. Risk Derivatives (Velocity & Acceleration)
173
+ * Formulas:
174
+ Velocity = d(MBV)/dt = d(P-MPU)/dt - d(P-MPD)/dt
175
+ Acceleration = d^2(MBV)/dt^2
176
+ * Function: Tracks the rate of expansion and acceleration of system risk corridors over time vectors.
177
+ * Gap Filled: Predicts market crashes or hardware failures before safety floors break by detecting positive acceleration.
178
+
179
+ 12. Cumulative Risk Mass Integration (Total Exposure Volume)
180
+ * Formula: Total Exposure Volume = integral_a^b ( P-MPD(t) - f(t) ) dt
181
+ * Symbol Definitions:
182
+ - f(t) : Continuous metric spot curve over time t from a to b.
183
+ * Function: Integrates continuous structural strain volume over time windows.
184
+ * Gap Filled: Quantifies cumulative physical fatigue or financial drawdown duration rather than static point snapshots.
185
+
186
+ 13. Multi-Dimensional Risk Matrix (Tensor Surface Transformation)
187
+ * Formula: Y = Omega * X
188
+ * Symbol Definitions:
189
+ - X : High-dimensional data matrix of multi-asset streams.
190
+ - Omega : Automated parameter state matrix.
191
+ * Function: Executes multi-asset risk surface calculations simultaneously.
192
+ * Gap Filled: Enables GPU hardware to compute thousands of asset risk metrics in a single clock cycle.
193
+
194
+
195
+ --------------------------------------------------------------------------------
196
+ 4. INSTALLATION & QUICKSTART
197
+ --------------------------------------------------------------------------------
198
+ Install via PyPI:
199
+ pip install mumo-engine
200
+
201
+ Python Example:
202
+ from mumo_framework import MumoEngine
203
+
204
+ data = [12.0, 15.2, 11.8, 45.0, 14.1, 13.9, 10.5]
205
+
206
+ # Calculate Ceiling, Safety Floor, and Risk Corridor Width
207
+ p_mpu = MumoEngine.calculate_p_mpu(data, k=2, alpha=0.7)
208
+ p_mpd = MumoEngine.calculate_p_mpd(data, k=2, alpha=0.7)
209
+ mbv = MumoEngine.calculate_mbv(data, k=2, alpha=0.7)
210
+
211
+ print(f"Performance Ceiling (P-MPU): {p_mpu:.2f}")
212
+ print(f"Safety Floor (P-MPD): {p_mpd:.2f}")
213
+ print(f"Risk Corridor Width (MBV): {mbv:.2f}")
214
+
215
+ Fractional core/boundary sizing:
216
+ params = MumoEngine.autotune_parameters(data, fraction=0.10)
217
+ p_gmcsi = MumoEngine.calculate_p_gmcsi(data, k=params.k, alpha=0.7)
218
+
219
+ For combined P-MBTS, MAEI, or MCHI calculations, use a fraction below 0.5
220
+ and a dataset of at least three values so the returned k satisfies 2*k < n.
221
+
222
+ Interactive calculator:
223
+ python -m mumo_framework
224
+
225
+ The calculator accepts numbers separated by commas or spaces over as many
226
+ input lines as needed. Submit a blank line to finish the dataset, then enter
227
+ k, alpha, beta, and gamma. Its dataset size is limited by available memory.
228
+ P-MBTS uses the mean of the values remaining after removing the lowest k and
229
+ highest k entries as mu_rest_core; therefore, it requires 2*k < n.
230
+
231
+ Interactive desktop calculator:
232
+ mumo-framework-gui
233
+
234
+ Or, when running from the source checkout:
235
+ python -m mumo_gui
236
+
237
+ The desktop calculator lets you edit or paste a dataset and interactively adjust
238
+ k, alpha, beta, and gamma. Results update as the data or parameters change.
239
+ It uses Python's built-in Tkinter interface and adds no third-party dependencies.
240
+
241
+
242
+ --------------------------------------------------------------------------------
243
+ 5. LICENSE
244
+ --------------------------------------------------------------------------------
245
+ This project is licensed under the MIT License. See the accompanying LICENSE
246
+ file for full terms.
247
+
248
+ Based on the original Mumo Mathematical Framework created by Mumo Framework Authors (Root Project).
@@ -0,0 +1,8 @@
1
+ mumo_framework.py,sha256=5Ic9wNniA8umITis_s0irgOOY2crl0gXMx-SWtTJlp4,19589
2
+ mumo_gui.py,sha256=Lc19gUEQ6Y0Od7fE6XLVJ0yCGkAZSDaMqcmHAfg8Nhw,12938
3
+ mumo_engine-1.0.0.dist-info/licenses/LICENSE,sha256=U98_Lr-sm-XQxYvF_87awv4rBBnbTQLROaUFmOmQ-OA,1079
4
+ mumo_engine-1.0.0.dist-info/METADATA,sha256=MScomEZKyWZNhSnUPQjkcXFuDyGTMeE-Iwc7J6XmFC0,12111
5
+ mumo_engine-1.0.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
6
+ mumo_engine-1.0.0.dist-info/entry_points.txt,sha256=HxnKiwgwGLTM7HOf_LmoEZQu-n5vUkQzDBkreke5xvU,90
7
+ mumo_engine-1.0.0.dist-info/top_level.txt,sha256=kYMn7AUPAdA9TtL6p7zCLtuSmmOspilP-lG1TcTjehw,24
8
+ mumo_engine-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ mumo-framework = mumo_framework:main
3
+ mumo-framework-gui = mumo_gui:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mumo Framework Authors
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.
@@ -0,0 +1,2 @@
1
+ mumo_framework
2
+ mumo_gui
mumo_framework.py ADDED
@@ -0,0 +1,545 @@
1
+ """Statistical boundary and risk-corridor calculations for the Mumo framework.
2
+
3
+ Based on the original Mumo Mathematical Framework created by Mumo Framework Authors (Root Project).
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from dataclasses import dataclass
9
+ import math
10
+ from typing import Iterable, List, Optional, Sequence, Tuple
11
+
12
+ __version__ = "1.0.0"
13
+
14
+
15
+ def _finite_float(value: object, name: str) -> float:
16
+ if isinstance(value, (bool, str, bytes)):
17
+ raise TypeError("{} must be a real number".format(name))
18
+ try:
19
+ result = float(value)
20
+ except (TypeError, ValueError, OverflowError) as exc:
21
+ raise TypeError("{} must be a real number".format(name)) from exc
22
+ if not math.isfinite(result):
23
+ raise ValueError("{} must be finite".format(name))
24
+ return result
25
+
26
+
27
+ def _ordered_data(data: Iterable[float]) -> List[float]:
28
+ if isinstance(data, (str, bytes)):
29
+ raise TypeError("data must be an iterable of numbers")
30
+ try:
31
+ iterator = iter(data)
32
+ except TypeError as exc:
33
+ raise TypeError("data must be an iterable of numbers") from exc
34
+ values = [_finite_float(value, "data value") for value in iterator]
35
+ if len(values) < 2:
36
+ raise ValueError("data must contain at least two values")
37
+ return sorted(values)
38
+
39
+
40
+ def _positive_integer(value: int, name: str) -> int:
41
+ if isinstance(value, bool) or not isinstance(value, int):
42
+ raise TypeError("{} must be an integer".format(name))
43
+ if value <= 0:
44
+ raise ValueError("{} must be greater than zero".format(name))
45
+ return value
46
+
47
+
48
+ def _slice_size(k: int, n: int) -> int:
49
+ value = _positive_integer(k, "k")
50
+ if value >= n:
51
+ raise ValueError("k must be smaller than the number of data values")
52
+ return value
53
+
54
+
55
+ def _weight(value: float, name: str) -> float:
56
+ result = _finite_float(value, name)
57
+ if not 0.0 <= result <= 1.0:
58
+ raise ValueError("{} must be between 0 and 1".format(name))
59
+ return result
60
+
61
+
62
+ def _mean(values: Sequence[float]) -> float:
63
+ return math.fsum(values) / len(values)
64
+
65
+
66
+ def calculate_mpu(data: Iterable[float]) -> float:
67
+ """Return MPU: the average of the maximum and the remaining-data mean."""
68
+ values = _ordered_data(data)
69
+ return (values[-1] + _mean(values[:-1])) / 2.0
70
+
71
+
72
+ def calculate_mpd(data: Iterable[float]) -> float:
73
+ """Return MPD: the average of the minimum and the remaining-data mean."""
74
+ values = _ordered_data(data)
75
+ return (values[0] + _mean(values[1:])) / 2.0
76
+
77
+
78
+ def calculate_ma(data: Iterable[float]) -> float:
79
+ """Return the foundational Mumo average, (MPU + MPD) / 2."""
80
+ return (calculate_mpu(data) + calculate_mpd(data)) / 2.0
81
+
82
+
83
+ def calculate_mbts(data: Iterable[float]) -> float:
84
+ """Return MBTS using the edge range and the mean of interior values."""
85
+ values = _ordered_data(data)
86
+ if len(values) < 3:
87
+ raise ValueError("MBTS requires at least three data values")
88
+ rest_mean = _mean(values[1:-1])
89
+ return ((values[-1] - values[0]) / 2.0 + rest_mean) / 2.0
90
+
91
+
92
+ def calculate_mpu_k(data: Iterable[float], k: int) -> float:
93
+ """Return the unweighted generalized upside metric for the top k values."""
94
+ values = _ordered_data(data)
95
+ size = _slice_size(k, len(values))
96
+ return (_mean(values[-size:]) + _mean(values[:-size])) / 2.0
97
+
98
+
99
+ def calculate_mpd_k(data: Iterable[float], k: int) -> float:
100
+ """Return the unweighted generalized downside metric for the bottom k."""
101
+ values = _ordered_data(data)
102
+ size = _slice_size(k, len(values))
103
+ return (_mean(values[:size]) + _mean(values[size:])) / 2.0
104
+
105
+
106
+ def calculate_gma_k(data: Iterable[float], k: int) -> float:
107
+ """Return GMA for a boundary slice of size k."""
108
+ return (calculate_mpu_k(data, k) + calculate_mpd_k(data, k)) / 2.0
109
+
110
+
111
+ def calculate_p_mpu(data: Iterable[float], k: int, alpha: float = 0.5) -> float:
112
+ """Return parametric upside, weighting the top-k slice by alpha."""
113
+ values = _ordered_data(data)
114
+ size = _slice_size(k, len(values))
115
+ weight = _weight(alpha, "alpha")
116
+ return weight * _mean(values[-size:]) + (1.0 - weight) * _mean(values[:-size])
117
+
118
+
119
+ def calculate_p_mpd(data: Iterable[float], k: int, alpha: float = 0.5) -> float:
120
+ """Return parametric downside, weighting the bottom-k slice by alpha."""
121
+ values = _ordered_data(data)
122
+ size = _slice_size(k, len(values))
123
+ weight = _weight(alpha, "alpha")
124
+ return weight * _mean(values[:size]) + (1.0 - weight) * _mean(values[size:])
125
+
126
+
127
+ def calculate_p_gma(
128
+ data: Iterable[float],
129
+ k: int,
130
+ alpha: float = 0.5,
131
+ beta: float = 0.5,
132
+ ) -> float:
133
+ """Blend parametric upside and downside using beta."""
134
+ balance = _weight(beta, "beta")
135
+ upside = calculate_p_mpu(data, k, alpha)
136
+ downside = calculate_p_mpd(data, k, alpha)
137
+ return balance * upside + (1.0 - balance) * downside
138
+
139
+
140
+ def calculate_mbv(data: Iterable[float], k: int, alpha: float = 0.5) -> float:
141
+ """Return MBV, the unit-preserving width between parametric boundaries."""
142
+ return calculate_p_mpu(data, k, alpha) - calculate_p_mpd(data, k, alpha)
143
+
144
+
145
+ def calculate_p_mbts(
146
+ data: Iterable[float], k: int, alpha: float = 0.5
147
+ ) -> float:
148
+ """Return parametric MBTS using edge slices and the remaining-data mean.
149
+
150
+ The supplied formula's ``mu_rest_core`` is interpreted as the mean of
151
+ values left after removing the bottom-k and top-k boundary slices.
152
+ """
153
+ values = _ordered_data(data)
154
+ size = _slice_size(k, len(values))
155
+ if 2 * size >= len(values):
156
+ raise ValueError("P-MBTS requires at least one value outside both k-slices")
157
+ weight = _weight(alpha, "alpha")
158
+ upper_mean = _mean(values[-size:])
159
+ lower_mean = _mean(values[:size])
160
+ rest_core_mean = _mean(values[size:-size])
161
+ return ((upper_mean - lower_mean) / 2.0 + weight * rest_core_mean) / 2.0
162
+
163
+
164
+ def calculate_p_gmcsi(
165
+ data: Iterable[float], k: int, alpha: float = 0.5
166
+ ) -> float:
167
+ """Return P-GMCSI using the centered k-value core cluster.
168
+
169
+ Values are sorted first. The core is the contiguous slice starting at
170
+ ``floor((n - k) / 2)``; the remainder contains every value outside it.
171
+ For fractional sizing, obtain ``k`` from
172
+ ``autotune_parameters(data, fraction=p).k``.
173
+ """
174
+ values = _ordered_data(data)
175
+ size = _slice_size(k, len(values))
176
+ weight = _weight(alpha, "alpha")
177
+ start = (len(values) - size) // 2
178
+ core = values[start : start + size]
179
+ remainder = values[:start] + values[start + size :]
180
+ return weight * _mean(core) + (1.0 - weight) * _mean(remainder)
181
+
182
+
183
+ def calculate_maei(
184
+ data: Iterable[float],
185
+ k: int,
186
+ alpha: float = 0.5,
187
+ beta: float = 0.5,
188
+ gamma: float = 0.5,
189
+ ) -> float:
190
+ """Blend the generalized average and core-squeeze index using gamma."""
191
+ balance = _weight(gamma, "gamma")
192
+ generalized = calculate_p_gma(data, k, alpha, beta)
193
+ core = calculate_p_gmcsi(data, k, alpha)
194
+ return balance * generalized + (1.0 - balance) * core
195
+
196
+
197
+ def calculate_mchi(
198
+ data: Iterable[float],
199
+ k: int,
200
+ alpha: float = 0.5,
201
+ gamma: float = 0.5,
202
+ ) -> float:
203
+ """Blend parametric MBTS and the core-squeeze index using gamma."""
204
+ balance = _weight(gamma, "gamma")
205
+ tension = calculate_p_mbts(data, k, alpha)
206
+ core = calculate_p_gmcsi(data, k, alpha)
207
+ return balance * tension + (1.0 - balance) * core
208
+
209
+
210
+ @dataclass(frozen=True)
211
+ class MumoParameters:
212
+ """Autotuned parameters for a dataset."""
213
+
214
+ k: int
215
+ alpha: float
216
+ beta: float
217
+ gamma: float = 0.5
218
+
219
+
220
+ def autotune_parameters(
221
+ data: Iterable[float],
222
+ fraction: float = 0.05,
223
+ mbv_baseline: Optional[float] = None,
224
+ gamma: float = 0.5,
225
+ ) -> MumoParameters:
226
+ """Derive bounded k, alpha, and beta values from a dataset.
227
+
228
+ The boundary/core size is ``floor(fraction * n)``, clamped to
229
+ ``[1, n - 1]`` so both the selected slice and its remainder are nonempty.
230
+ The specification's alpha and beta ratios are clamped to [0, 1]. If no
231
+ MBV baseline is supplied, the observed data range is used. Constant data
232
+ receives neutral alpha/beta settings where the corresponding ratio is
233
+ undefined.
234
+
235
+ When the returned ``k`` will also be used for P-MBTS, MAEI, or MCHI, it
236
+ must additionally satisfy ``2 * k < n``; choose a fraction below 0.5
237
+ for the usual fractional-sizing workflow.
238
+ """
239
+ values = _ordered_data(data)
240
+ proportion = _finite_float(fraction, "fraction")
241
+ if not 0.0 < proportion <= 1.0:
242
+ raise ValueError("fraction must be greater than 0 and at most 1")
243
+ balance = _weight(gamma, "gamma")
244
+
245
+ size = min(len(values) - 1, max(1, math.floor(proportion * len(values))))
246
+ span = values[-1] - values[0]
247
+ average = _mean(values)
248
+ if span == 0.0:
249
+ alpha = 0.0
250
+ elif average == 0.0:
251
+ alpha = 1.0
252
+ else:
253
+ alpha = min(1.0, max(0.0, span / abs(average)))
254
+
255
+ baseline = span if mbv_baseline is None else _finite_float(
256
+ mbv_baseline, "mbv_baseline"
257
+ )
258
+ if baseline < 0.0:
259
+ raise ValueError("mbv_baseline must not be negative")
260
+ if baseline == 0.0:
261
+ if span != 0.0:
262
+ raise ValueError("mbv_baseline must be non-zero for non-constant data")
263
+ beta = 0.5
264
+ else:
265
+ beta = min(1.0, max(0.0, (values[-1] - average) / baseline))
266
+ return MumoParameters(k=size, alpha=alpha, beta=beta, gamma=balance)
267
+
268
+
269
+ def _time_values(times: Optional[Sequence[float]], count: int) -> List[float]:
270
+ if times is None:
271
+ return [float(index) for index in range(count)]
272
+ if len(times) != count:
273
+ raise ValueError("times and values must have the same length")
274
+ result = [_finite_float(value, "time") for value in times]
275
+ if any(right <= left for left, right in zip(result, result[1:])):
276
+ raise ValueError("times must be strictly increasing")
277
+ return result
278
+
279
+
280
+ def risk_velocity(
281
+ mbv_values: Iterable[float], times: Optional[Sequence[float]] = None
282
+ ) -> List[float]:
283
+ """Return interval risk velocities (first differences of MBV over time)."""
284
+ values = [_finite_float(value, "MBV value") for value in mbv_values]
285
+ if len(values) < 2:
286
+ raise ValueError("at least two MBV values are required")
287
+ time_values = _time_values(times, len(values))
288
+ return [
289
+ (right_value - left_value) / (right_time - left_time)
290
+ for left_value, right_value, left_time, right_time in zip(
291
+ values, values[1:], time_values, time_values[1:]
292
+ )
293
+ ]
294
+
295
+
296
+ def risk_acceleration(
297
+ mbv_values: Iterable[float], times: Optional[Sequence[float]] = None
298
+ ) -> List[float]:
299
+ """Return acceleration estimates between adjacent risk-velocity intervals."""
300
+ values = [_finite_float(value, "MBV value") for value in mbv_values]
301
+ if len(values) < 3:
302
+ raise ValueError("at least three MBV values are required")
303
+ time_values = _time_values(times, len(values))
304
+ velocity = risk_velocity(values, time_values)
305
+ midpoints = [
306
+ (left + right) / 2.0 for left, right in zip(time_values, time_values[1:])
307
+ ]
308
+ return [
309
+ (right_velocity - left_velocity) / (right_time - left_time)
310
+ for left_velocity, right_velocity, left_time, right_time in zip(
311
+ velocity, velocity[1:], midpoints, midpoints[1:]
312
+ )
313
+ ]
314
+
315
+
316
+ def cumulative_exposure(
317
+ floor_values: Iterable[float],
318
+ observations: Iterable[float],
319
+ times: Optional[Sequence[float]] = None,
320
+ ) -> float:
321
+ """Integrate floor minus observed values with the trapezoidal rule."""
322
+ floors = [_finite_float(value, "floor value") for value in floor_values]
323
+ spots = [_finite_float(value, "observation") for value in observations]
324
+ if len(floors) != len(spots):
325
+ raise ValueError("floor_values and observations must have the same length")
326
+ if len(floors) < 2:
327
+ raise ValueError("at least two points are required for integration")
328
+ time_values = _time_values(times, len(floors))
329
+ gaps = [floor - spot for floor, spot in zip(floors, spots)]
330
+ return math.fsum(
331
+ (left_gap + right_gap) * (right_time - left_time) / 2.0
332
+ for left_gap, right_gap, left_time, right_time in zip(
333
+ gaps, gaps[1:], time_values, time_values[1:]
334
+ )
335
+ )
336
+
337
+
338
+ def _matrix(values: Iterable[Iterable[float]], name: str) -> List[List[float]]:
339
+ if isinstance(values, (str, bytes)):
340
+ raise TypeError("{} must be a matrix".format(name))
341
+ try:
342
+ rows = [
343
+ [_finite_float(value, "{} value".format(name)) for value in row]
344
+ for row in values
345
+ ]
346
+ except TypeError as exc:
347
+ raise TypeError("{} must be a matrix of numbers".format(name)) from exc
348
+ if not rows or not rows[0]:
349
+ raise ValueError("{} must not be empty".format(name))
350
+ width = len(rows[0])
351
+ if any(len(row) != width for row in rows):
352
+ raise ValueError("{} must be rectangular".format(name))
353
+ return rows
354
+
355
+
356
+ def boundary_matrix(
357
+ data: Iterable[Iterable[float]], k: int, alpha: float = 0.5
358
+ ) -> List[Tuple[float, float, float]]:
359
+ """Calculate (P-MPU, P-MPD, MBV) for each row in a dataset matrix."""
360
+ rows = _matrix(data, "data")
361
+ return [
362
+ (
363
+ calculate_p_mpu(row, k, alpha),
364
+ calculate_p_mpd(row, k, alpha),
365
+ calculate_mbv(row, k, alpha),
366
+ )
367
+ for row in rows
368
+ ]
369
+
370
+
371
+ def matrix_multiply(
372
+ omega: Iterable[Iterable[float]], data: Iterable[Iterable[float]]
373
+ ) -> List[List[float]]:
374
+ """Multiply two compatible rectangular matrices (Y = Omega * X)."""
375
+ left = _matrix(omega, "omega")
376
+ right = _matrix(data, "data")
377
+ if len(left[0]) != len(right):
378
+ raise ValueError("omega columns must match data rows")
379
+ columns = list(zip(*right))
380
+ return [
381
+ [math.fsum(a * b for a, b in zip(row, column)) for column in columns]
382
+ for row in left
383
+ ]
384
+
385
+
386
+ def _read_interactive_dataset() -> List[float]:
387
+ print("Enter numbers separated by spaces or commas; use multiple lines.")
388
+ print("Press Enter on a blank line when you have finished the dataset.")
389
+ values: List[float] = []
390
+ while True:
391
+ line = input("data> ").strip()
392
+ if not line:
393
+ break
394
+ try:
395
+ line_values = [
396
+ _finite_float(token, "data value")
397
+ for token in line.replace(",", " ").split()
398
+ ]
399
+ except (TypeError, ValueError) as exc:
400
+ print("Invalid data: {}. Please re-enter that line.".format(exc))
401
+ continue
402
+ values.extend(line_values)
403
+ return values
404
+
405
+
406
+ def _read_interactive_float(prompt: str) -> float:
407
+ while True:
408
+ try:
409
+ return _finite_float(input(prompt).strip(), prompt.strip())
410
+ except (TypeError, ValueError) as exc:
411
+ print("Invalid number: {}. Please try again.".format(exc))
412
+
413
+
414
+ def _read_interactive_int(prompt: str) -> int:
415
+ while True:
416
+ raw = input(prompt).strip()
417
+ try:
418
+ return int(raw)
419
+ except ValueError:
420
+ print("Please enter a whole number.")
421
+
422
+
423
+ def _read_interactive_k(count: int) -> int:
424
+ maximum = (count - 1) // 2
425
+ while True:
426
+ value = _read_interactive_int(
427
+ "Boundary/core size k (1 to {}): ".format(maximum)
428
+ )
429
+ if 1 <= value <= maximum:
430
+ return value
431
+ print("For these formulas, k must be at least 1 and 2*k must be < n.")
432
+
433
+
434
+ def _read_interactive_weight(name: str) -> float:
435
+ while True:
436
+ value = _read_interactive_float("{} (0 to 1): ".format(name))
437
+ try:
438
+ return _weight(value, name)
439
+ except ValueError as exc:
440
+ print("{} Please try again.".format(exc))
441
+
442
+
443
+ def main() -> int:
444
+ """Run an interactive prompt for calculating Mumo formulas."""
445
+ print("Mumo Framework interactive calculator")
446
+ print("Enter as many data values as needed; available memory is the limit.")
447
+ try:
448
+ while True:
449
+ try:
450
+ data = _read_interactive_dataset()
451
+ values = _ordered_data(data)
452
+ k = _read_interactive_k(len(values))
453
+ alpha = _read_interactive_weight("alpha")
454
+ beta = _read_interactive_weight("beta")
455
+ gamma = _read_interactive_weight("gamma")
456
+
457
+ results = [
458
+ ("MPU", calculate_mpu(values)),
459
+ ("MPD", calculate_mpd(values)),
460
+ ("MA", calculate_ma(values)),
461
+ ("MBTS", calculate_mbts(values)),
462
+ ("MPU-k", calculate_mpu_k(values, k)),
463
+ ("MPD-k", calculate_mpd_k(values, k)),
464
+ ("GMA-k", calculate_gma_k(values, k)),
465
+ ("P-MPU", calculate_p_mpu(values, k, alpha)),
466
+ ("P-MPD", calculate_p_mpd(values, k, alpha)),
467
+ ("P-GMA", calculate_p_gma(values, k, alpha, beta)),
468
+ ("MBV", calculate_mbv(values, k, alpha)),
469
+ ("P-MBTS", calculate_p_mbts(values, k, alpha)),
470
+ ("P-GMCSI", calculate_p_gmcsi(values, k, alpha)),
471
+ ("MAEI", calculate_maei(values, k, alpha, beta, gamma)),
472
+ ("MCHI", calculate_mchi(values, k, alpha, gamma)),
473
+ ]
474
+ print("\nResults ({} values):".format(len(values)))
475
+ for name, result in results:
476
+ print("{:10s} {:.10g}".format(name + ":", result))
477
+ print("Autotuned:", autotune_parameters(values))
478
+ except (TypeError, ValueError) as exc:
479
+ print("Could not calculate results: {}".format(exc))
480
+
481
+ again = input("\nCalculate another dataset? [y/N]: ").strip().lower()
482
+ if again not in ("y", "yes"):
483
+ return 0
484
+ except (EOFError, KeyboardInterrupt):
485
+ print("\nExiting Mumo calculator.")
486
+ return 0
487
+
488
+
489
+ class MumoEngine:
490
+ """Convenient namespace exposing the framework's calculations."""
491
+
492
+ calculate_mpu = staticmethod(calculate_mpu)
493
+ calculate_mpd = staticmethod(calculate_mpd)
494
+ calculate_ma = staticmethod(calculate_ma)
495
+ calculate_mbts = staticmethod(calculate_mbts)
496
+ calculate_mpu_k = staticmethod(calculate_mpu_k)
497
+ calculate_mpd_k = staticmethod(calculate_mpd_k)
498
+ calculate_gma_k = staticmethod(calculate_gma_k)
499
+ calculate_p_mpu = staticmethod(calculate_p_mpu)
500
+ calculate_p_mpd = staticmethod(calculate_p_mpd)
501
+ calculate_p_gma = staticmethod(calculate_p_gma)
502
+ calculate_mbv = staticmethod(calculate_mbv)
503
+ calculate_p_mbts = staticmethod(calculate_p_mbts)
504
+ calculate_p_gmcsi = staticmethod(calculate_p_gmcsi)
505
+ calculate_maei = staticmethod(calculate_maei)
506
+ calculate_mchi = staticmethod(calculate_mchi)
507
+ autotune_parameters = staticmethod(autotune_parameters)
508
+ risk_velocity = staticmethod(risk_velocity)
509
+ risk_acceleration = staticmethod(risk_acceleration)
510
+ cumulative_exposure = staticmethod(cumulative_exposure)
511
+ boundary_matrix = staticmethod(boundary_matrix)
512
+ matrix_multiply = staticmethod(matrix_multiply)
513
+
514
+
515
+ __all__ = [
516
+ "MumoEngine",
517
+ "MumoParameters",
518
+ "__version__",
519
+ "autotune_parameters",
520
+ "boundary_matrix",
521
+ "calculate_gma_k",
522
+ "calculate_ma",
523
+ "calculate_maei",
524
+ "calculate_mbv",
525
+ "calculate_mbts",
526
+ "calculate_mchi",
527
+ "calculate_mpd",
528
+ "calculate_mpd_k",
529
+ "calculate_mpu",
530
+ "calculate_mpu_k",
531
+ "calculate_p_gma",
532
+ "calculate_p_gmcsi",
533
+ "calculate_p_mbts",
534
+ "calculate_p_mpd",
535
+ "calculate_p_mpu",
536
+ "cumulative_exposure",
537
+ "matrix_multiply",
538
+ "risk_acceleration",
539
+ "risk_velocity",
540
+ "main",
541
+ ]
542
+
543
+
544
+ if __name__ == "__main__":
545
+ raise SystemExit(main())
mumo_gui.py ADDED
@@ -0,0 +1,365 @@
1
+ """Interactive desktop calculator for the Mumo framework."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ import tkinter as tk
7
+ from tkinter import ttk
8
+ from typing import Dict, List
9
+
10
+ from mumo_framework import (
11
+ calculate_ma,
12
+ calculate_maei,
13
+ calculate_mbts,
14
+ calculate_mbv,
15
+ calculate_mchi,
16
+ calculate_mpd,
17
+ calculate_mpd_k,
18
+ calculate_mpu,
19
+ calculate_mpu_k,
20
+ calculate_p_gma,
21
+ calculate_p_gmcsi,
22
+ calculate_p_mbts,
23
+ calculate_p_mpd,
24
+ calculate_p_mpu,
25
+ )
26
+
27
+
28
+ _SAMPLE_DATA = "10, 12.5, 14, 15.5, 18, 21, 25, 28.5, 32, 36"
29
+
30
+
31
+ def _parse_dataset(text: str) -> List[float]:
32
+ """Parse comma- or whitespace-separated finite numbers."""
33
+ tokens = text.replace(",", " ").split()
34
+ if not tokens:
35
+ raise ValueError("Enter at least three data values.")
36
+
37
+ values = []
38
+ for token in tokens:
39
+ try:
40
+ value = float(token)
41
+ except ValueError as exc:
42
+ raise ValueError("Could not read {!r} as a number.".format(token)) from exc
43
+ if not math.isfinite(value):
44
+ raise ValueError("All data values must be finite numbers.")
45
+ values.append(value)
46
+ if len(values) < 3:
47
+ raise ValueError("Enter at least three data values.")
48
+ return values
49
+
50
+
51
+ def _calculate_results(
52
+ data: List[float], k: int, alpha: float, beta: float, gamma: float
53
+ ) -> Dict[str, float]:
54
+ """Calculate the displayed metrics for the current inputs."""
55
+ return {
56
+ "MPU": calculate_mpu(data),
57
+ "MPD": calculate_mpd(data),
58
+ "MA": calculate_ma(data),
59
+ "MBTS": calculate_mbts(data),
60
+ "MPU-k": calculate_mpu_k(data, k),
61
+ "MPD-k": calculate_mpd_k(data, k),
62
+ "P-MPU": calculate_p_mpu(data, k, alpha),
63
+ "P-MPD": calculate_p_mpd(data, k, alpha),
64
+ "P-GMA": calculate_p_gma(data, k, alpha, beta),
65
+ "MBV": calculate_mbv(data, k, alpha),
66
+ "P-MBTS": calculate_p_mbts(data, k, alpha),
67
+ "P-GMCSI": calculate_p_gmcsi(data, k, alpha),
68
+ "MAEI": calculate_maei(data, k, alpha, beta, gamma),
69
+ "MCHI": calculate_mchi(data, k, alpha, gamma),
70
+ }
71
+
72
+
73
+ class MumoCalculator:
74
+ """Tkinter interface with live dataset and parameter updates."""
75
+
76
+ def __init__(self, root: tk.Tk) -> None:
77
+ self.root = root
78
+ self.root.title("Mumo Framework | Interactive Calculator")
79
+ self.root.minsize(760, 650)
80
+ self.root.geometry("940x800")
81
+ self.root.configure(background="#f3f6fb")
82
+ self._pending_update: str | None = None
83
+
84
+ self.k = tk.IntVar(value=2)
85
+ self.alpha = tk.DoubleVar(value=0.6)
86
+ self.beta = tk.DoubleVar(value=0.5)
87
+ self.gamma = tk.DoubleVar(value=0.5)
88
+ self.status = tk.StringVar(value="Edit the data or adjust a parameter to recalculate.")
89
+ self.count = tk.StringVar(value="0 values")
90
+ self._metric_values: Dict[str, tk.StringVar] = {}
91
+
92
+ self._style()
93
+ self._build()
94
+ self._set_sample()
95
+
96
+ def _style(self) -> None:
97
+ style = ttk.Style(self.root)
98
+ style.configure("TFrame", background="#f3f6fb")
99
+ style.configure("Panel.TFrame", background="#ffffff")
100
+ style.configure(
101
+ "Title.TLabel",
102
+ background="#f3f6fb",
103
+ foreground="#14213d",
104
+ font=("Segoe UI", 22, "bold"),
105
+ )
106
+ style.configure(
107
+ "Subtitle.TLabel",
108
+ background="#f3f6fb",
109
+ foreground="#65748b",
110
+ font=("Segoe UI", 10),
111
+ )
112
+ style.configure(
113
+ "PanelTitle.TLabel",
114
+ background="#ffffff",
115
+ foreground="#14213d",
116
+ font=("Segoe UI", 11, "bold"),
117
+ )
118
+ style.configure(
119
+ "MetricName.TLabel",
120
+ background="#ffffff",
121
+ foreground="#65748b",
122
+ font=("Segoe UI", 9),
123
+ )
124
+ style.configure(
125
+ "MetricValue.TLabel",
126
+ background="#ffffff",
127
+ foreground="#14213d",
128
+ font=("Segoe UI", 16, "bold"),
129
+ )
130
+ style.configure(
131
+ "HighlightValue.TLabel",
132
+ background="#ffffff",
133
+ foreground="#3157d5",
134
+ font=("Segoe UI", 18, "bold"),
135
+ )
136
+ style.configure("TButton", font=("Segoe UI", 9), padding=(10, 6))
137
+ style.configure("TScale", background="#ffffff")
138
+
139
+ def _build(self) -> None:
140
+ outer = ttk.Frame(self.root, padding=(24, 20))
141
+ outer.pack(fill="both", expand=True)
142
+ ttk.Label(outer, text="Mumo Interactive Calculator", style="Title.TLabel").pack(
143
+ anchor="w"
144
+ )
145
+ ttk.Label(
146
+ outer,
147
+ text="Explore how your dataset and model parameters shape each index.",
148
+ style="Subtitle.TLabel",
149
+ ).pack(anchor="w", pady=(4, 18))
150
+
151
+ columns = ttk.Frame(outer)
152
+ columns.pack(fill="x")
153
+ columns.columnconfigure(0, weight=3)
154
+ columns.columnconfigure(1, weight=2)
155
+
156
+ data_panel = ttk.Frame(columns, style="Panel.TFrame", padding=16)
157
+ data_panel.grid(row=0, column=0, sticky="nsew", padx=(0, 10))
158
+ ttk.Label(data_panel, text="Dataset", style="PanelTitle.TLabel").pack(
159
+ anchor="w"
160
+ )
161
+ ttk.Label(
162
+ data_panel,
163
+ text="Paste or type numbers, separated by commas, spaces, or new lines.",
164
+ style="MetricName.TLabel",
165
+ ).pack(anchor="w", pady=(3, 10))
166
+ text_frame = ttk.Frame(data_panel, style="Panel.TFrame")
167
+ text_frame.pack(fill="both", expand=True)
168
+ self.dataset = tk.Text(
169
+ text_frame,
170
+ height=5,
171
+ wrap="word",
172
+ undo=True,
173
+ font=("Consolas", 10),
174
+ relief="solid",
175
+ borderwidth=1,
176
+ padx=9,
177
+ pady=8,
178
+ )
179
+ scrollbar = ttk.Scrollbar(
180
+ text_frame, orient="vertical", command=self.dataset.yview
181
+ )
182
+ self.dataset.configure(yscrollcommand=scrollbar.set)
183
+ self.dataset.pack(side="left", fill="both", expand=True)
184
+ scrollbar.pack(side="right", fill="y")
185
+ self.dataset.bind("<<Modified>>", self._on_dataset_change)
186
+
187
+ data_actions = ttk.Frame(data_panel, style="Panel.TFrame")
188
+ data_actions.pack(fill="x", pady=(9, 0))
189
+ ttk.Button(data_actions, text="Load sample", command=self._set_sample).pack(
190
+ side="left"
191
+ )
192
+ ttk.Button(data_actions, text="Clear", command=self._clear_dataset).pack(
193
+ side="left", padx=(7, 0)
194
+ )
195
+ ttk.Label(data_actions, textvariable=self.count, style="MetricName.TLabel").pack(
196
+ side="right", pady=6
197
+ )
198
+
199
+ parameters = ttk.Frame(columns, style="Panel.TFrame", padding=16)
200
+ parameters.grid(row=0, column=1, sticky="nsew")
201
+ ttk.Label(parameters, text="Parameters", style="PanelTitle.TLabel").pack(
202
+ anchor="w", pady=(0, 5)
203
+ )
204
+ self._add_parameter(
205
+ parameters, "Core / boundary size (k)", self.k, 1, 4, "{:d}",
206
+ integer=True,
207
+ )
208
+ self._add_parameter(parameters, "Alpha", self.alpha, 0, 1, "{:.2f}")
209
+ self._add_parameter(parameters, "Beta", self.beta, 0, 1, "{:.2f}")
210
+ self._add_parameter(parameters, "Gamma", self.gamma, 0, 1, "{:.2f}")
211
+
212
+ results_panel = ttk.Frame(outer, style="Panel.TFrame", padding=16)
213
+ results_panel.pack(fill="both", expand=True, pady=(16, 0))
214
+ ttk.Label(results_panel, text="Calculated indices", style="PanelTitle.TLabel").pack(
215
+ anchor="w", pady=(0, 12)
216
+ )
217
+ metrics = ttk.Frame(results_panel, style="Panel.TFrame")
218
+ metrics.pack(fill="both", expand=True)
219
+ metric_names = [
220
+ "MPU", "MPD", "MA", "MBTS", "MPU-k", "MPD-k", "P-MPU",
221
+ "P-MPD", "P-GMA", "MBV", "P-MBTS", "P-GMCSI", "MAEI", "MCHI",
222
+ ]
223
+ for column in range(3):
224
+ metrics.columnconfigure(column, weight=1)
225
+ for index, name in enumerate(metric_names):
226
+ row, column = divmod(index, 3)
227
+ card = ttk.Frame(metrics, style="Panel.TFrame", padding=(8, 7))
228
+ card.grid(row=row, column=column, sticky="nsew", padx=4, pady=3)
229
+ ttk.Label(card, text=name, style="MetricName.TLabel").pack(anchor="w")
230
+ value = tk.StringVar(value="—")
231
+ self._metric_values[name] = value
232
+ value_style = (
233
+ "HighlightValue.TLabel" if name in ("MAEI", "MCHI") else "MetricValue.TLabel"
234
+ )
235
+ ttk.Label(card, textvariable=value, style=value_style).pack(anchor="w")
236
+
237
+ self.status_label = ttk.Label(
238
+ outer,
239
+ textvariable=self.status,
240
+ style="Subtitle.TLabel",
241
+ wraplength=880,
242
+ )
243
+ self.status_label.pack(anchor="w", pady=(11, 0))
244
+
245
+ def _add_parameter(
246
+ self,
247
+ parent: ttk.Frame,
248
+ label: str,
249
+ variable: tk.Variable,
250
+ minimum: int,
251
+ maximum: int,
252
+ number_format: str,
253
+ integer: bool = False,
254
+ ) -> None:
255
+ row = ttk.Frame(parent, style="Panel.TFrame")
256
+ row.pack(fill="x", pady=5)
257
+ heading = ttk.Frame(row, style="Panel.TFrame")
258
+ heading.pack(fill="x")
259
+ ttk.Label(heading, text=label, style="MetricName.TLabel").pack(side="left")
260
+ value_label = ttk.Label(heading, style="PanelTitle.TLabel")
261
+ value_label.pack(side="right")
262
+
263
+ def update_label(*_args: object) -> None:
264
+ value_label.configure(text=number_format.format(variable.get()))
265
+
266
+ variable.trace_add("write", update_label)
267
+ update_label()
268
+ if integer:
269
+ scale = tk.Scale(
270
+ row,
271
+ from_=minimum,
272
+ to=maximum,
273
+ resolution=1,
274
+ showvalue=False,
275
+ variable=variable,
276
+ command=self._on_parameter_change,
277
+ orient="horizontal",
278
+ highlightthickness=0,
279
+ borderwidth=0,
280
+ background="#ffffff",
281
+ troughcolor="#e8edf5",
282
+ activebackground="#3157d5",
283
+ )
284
+ else:
285
+ scale = ttk.Scale(
286
+ row,
287
+ from_=minimum,
288
+ to=maximum,
289
+ variable=variable,
290
+ command=self._on_parameter_change,
291
+ )
292
+ scale.pack(fill="x", pady=(2, 0))
293
+ if integer:
294
+ self.k_scale = scale
295
+
296
+ def _on_dataset_change(self, _event: tk.Event) -> None:
297
+ if not self.dataset.edit_modified():
298
+ return
299
+ self.dataset.edit_modified(False)
300
+ if self._pending_update is not None:
301
+ self.root.after_cancel(self._pending_update)
302
+ self._pending_update = self.root.after(250, self._recalculate)
303
+
304
+ def _on_parameter_change(self, _value: str) -> None:
305
+ self._recalculate()
306
+
307
+ def _set_sample(self) -> None:
308
+ self.dataset.delete("1.0", "end")
309
+ self.dataset.insert("1.0", _SAMPLE_DATA)
310
+ self.dataset.edit_modified(False)
311
+ self._recalculate()
312
+
313
+ def _clear_dataset(self) -> None:
314
+ self.dataset.delete("1.0", "end")
315
+ self.dataset.edit_modified(False)
316
+ self._recalculate()
317
+
318
+ def _recalculate(self) -> None:
319
+ self._pending_update = None
320
+ try:
321
+ data = _parse_dataset(self.dataset.get("1.0", "end-1c"))
322
+ except ValueError as exc:
323
+ self.count.set("— values")
324
+ self.status.set(str(exc))
325
+ self.status_label.configure(foreground="#b42318")
326
+ for value in self._metric_values.values():
327
+ value.set("—")
328
+ return
329
+
330
+ maximum_k = (len(data) - 1) // 2
331
+ self.k_scale.configure(from_=1, to=maximum_k)
332
+ if self.k.get() < 1 or self.k.get() > maximum_k:
333
+ self.k.set(min(maximum_k, max(1, self.k.get())))
334
+ self.count.set("{} values".format(len(data)))
335
+
336
+ try:
337
+ results = _calculate_results(
338
+ data, self.k.get(), self.alpha.get(), self.beta.get(), self.gamma.get()
339
+ )
340
+ except (TypeError, ValueError) as exc:
341
+ self.status.set("Could not calculate indices: {}".format(exc))
342
+ self.status_label.configure(foreground="#b42318")
343
+ for value in self._metric_values.values():
344
+ value.set("—")
345
+ return
346
+
347
+ for name, result in results.items():
348
+ self._metric_values[name].set("{:.6g}".format(result))
349
+ self.status.set(
350
+ "Sorted values are used for boundary calculations. "
351
+ "P-MBTS uses the mean after removing the lowest k and highest k values."
352
+ )
353
+ self.status_label.configure(foreground="#65748b")
354
+
355
+
356
+ def main() -> int:
357
+ """Launch the interactive desktop calculator."""
358
+ root = tk.Tk()
359
+ MumoCalculator(root)
360
+ root.mainloop()
361
+ return 0
362
+
363
+
364
+ if __name__ == "__main__":
365
+ raise SystemExit(main())