openfll-python 0.2.0__cp314-cp314-win_amd64.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.
PyFLL/PyFLL.pyi ADDED
@@ -0,0 +1,306 @@
1
+ """Type stubs for PyFLL.
2
+
3
+ These stubs provide IDE autocompletion and mypy type-checking for the
4
+ PyFLL Python module, even though the actual implementation is a compiled
5
+ .pyd (C++ extension via pybind11).
6
+
7
+ Source of truth:
8
+ - C++ binding: src/PyFLL/PyFLL.cpp
9
+ - C++ API: src/OpenFLL/linear/sugeno_engine.h
10
+
11
+ When you change the C++ binding, please update this file to match.
12
+ """
13
+ from typing import Any, List, Literal, Sequence, Tuple, Union
14
+
15
+ # ============================================================================
16
+ # Literal type aliases — for IDE autocomplete on string-enum parameters
17
+ # ============================================================================
18
+
19
+ # Membership function type names
20
+ MfType = Literal[
21
+ "constant",
22
+ "triangular",
23
+ "trapezoidal",
24
+ "gaussian",
25
+ "polynomial",
26
+ ]
27
+
28
+ # t-norm names (used for AND or set_default_t_norm)
29
+ TNorms = Literal[
30
+ "min",
31
+ "prod_and",
32
+ "bounded_diff",
33
+ "drastic_prod",
34
+ "einstein_prod",
35
+ "hamacher_prod",
36
+ ]
37
+
38
+ # s-norm names (used for OR or set_default_s_norm)
39
+ SNorms = Literal[
40
+ "max",
41
+ "algebraic_sum",
42
+ "bounded_sum",
43
+ "drastic_sum",
44
+ "einstein_sum",
45
+ "hamacher_sum",
46
+ ]
47
+
48
+
49
+ # ============================================================================
50
+ # SugenoEngine — the main public class
51
+ # ============================================================================
52
+
53
+ class SugenoEngine:
54
+ """Linear facade for Sugeno fuzzy logic model.
55
+
56
+ Provides a flat, name-based API on top of OpenFLL's fluent
57
+ SugenoBuilder / RulesBuilder. Build the model by:
58
+
59
+ 1. Registering input/output variables (add_input_var / add_output_var).
60
+ 2. Adding membership functions to those variables
61
+ (add_membership_func: name + type + parameter list).
62
+ 3. Adding rules as plain strings
63
+ (add_rule: 'IF x1 IS "low" AND x2 IS "high" THEN y IS "fast"').
64
+ 4. Calling build() to compile the model.
65
+ 5. Setting input values (set_input), running calculate(),
66
+ and reading outputs (get_output).
67
+
68
+ Threading: instances are NOT thread-safe. Use one engine per thread.
69
+ """
70
+
71
+ def __init__(self) -> None:
72
+ """Create an empty Sugeno engine."""
73
+ ...
74
+
75
+ # ------------------------------------------------------------------------
76
+ # Configuration
77
+ # ------------------------------------------------------------------------
78
+
79
+ def add_input_var(self, name: str) -> None:
80
+ """Register an input variable.
81
+
82
+ Args:
83
+ name: variable name. Must be non-empty and not already used.
84
+
85
+ Raises:
86
+ ValueError: name is empty or already registered.
87
+ RuntimeError: called after build().
88
+ """
89
+ ...
90
+
91
+ def add_output_var(self, name: str) -> None:
92
+ """Register an output variable.
93
+
94
+ Args:
95
+ name: variable name. Must be non-empty and not already used.
96
+
97
+ Raises:
98
+ ValueError: name is empty or already registered.
99
+ RuntimeError: called after build().
100
+ """
101
+ ...
102
+
103
+ def add_membership_func(
104
+ self,
105
+ var_name: str,
106
+ mf_name: str,
107
+ type: MfType,
108
+ params: Union[List[float], Tuple[float, ...]],
109
+ ) -> None:
110
+ """Add a membership function to a registered variable.
111
+
112
+ Args:
113
+ var_name: name of a previously registered input or output variable.
114
+ mf_name: unique name of the membership function (e.g. "low", "high").
115
+ type: one of:
116
+ "constant" — params = [value]
117
+ "triangular" — params = [a, b, c]
118
+ "trapezoidal" — params = [a, b, c, d]
119
+ "gaussian" — params = [center, sigma]
120
+ "polynomial" — currently raises ValueError (not yet supported)
121
+ params: list/tuple of doubles; size depends on type.
122
+
123
+ Raises:
124
+ ValueError: bad type or wrong parameter count, duplicate mf_name.
125
+ KeyError: var_name is not registered.
126
+ RuntimeError: called after build().
127
+ """
128
+ ...
129
+
130
+ def add_rule(self, rule: str) -> None:
131
+ """Add a fuzzy rule in textual form.
132
+
133
+ Format:
134
+ IF <var1> IS "<mf1>" ((AND|OR) [<norm_name>] <var2> IS "<mf2>")*
135
+ THEN <out_var> IS "<out_mf>"
136
+
137
+ Operators (IF/AND/OR/THEN/IS) are case-insensitive. Variable and MF
138
+ names are case-sensitive. Quoted MF names may contain spaces.
139
+
140
+ Optional [<norm_name>] after AND/OR sets an explicit t/s-norm
141
+ (e.g. "AND[prod_and]", "OR[algebraic_sum]"). If omitted, the engine's
142
+ default norm (set_default_t_norm / set_default_s_norm) is used.
143
+
144
+ Args:
145
+ rule: the rule as a string.
146
+
147
+ Raises:
148
+ ValueError: syntax error or unknown norm name.
149
+ KeyError: a variable or MF name is unknown.
150
+ RuntimeError: called after build().
151
+ """
152
+ ...
153
+
154
+ def add_rule(
155
+ self,
156
+ antecedent: List[Tuple[str, str]],
157
+ consequent: List[Tuple[str, str]],
158
+ t_norm: Union[TNorms, str, None] = None,
159
+ s_norm: Union[SNorms, str, None] = None,
160
+ ) -> None:
161
+ """Add a fuzzy rule in structural (tuple) form.
162
+
163
+ Equivalent to the textual form, but expressed as Python data structures
164
+ (lists of (var_name, mf_name) tuples). This is the preferred form for
165
+ programmatic rule generation: easier to build dynamically and faster
166
+ to parse than the textual form.
167
+
168
+ Args:
169
+ antecedent: list of (var_name, mf_name) pairs. Non-empty.
170
+ All operators between terms are AND (use the textual
171
+ add_rule(str) to express OR).
172
+ consequent: list with exactly one (var_name, mf_name) pair.
173
+ t_norm: optional t-norm name applied to all AND-operators in
174
+ the antecedent. If None or empty string, the engine's
175
+ default t-norm (set_default_t_norm) is used.
176
+ One of: "min", "prod_and", "bounded_diff", "drastic_prod",
177
+ "einstein_prod", "hamacher_prod".
178
+ s_norm: optional s-norm name. (Used only if you later extend
179
+ the engine to support OR in tuple form; currently unused
180
+ for AND-only rules.) If None or empty string, the engine's
181
+ default s-norm (set_default_s_norm) is used.
182
+
183
+ Example:
184
+ >>> engine.add_rule(
185
+ ... antecedent=[("x1", "low"), ("x2", "high")],
186
+ ... consequent=[("y", "open")],
187
+ ... t_norm="prod_and",
188
+ ... )
189
+
190
+ Raises:
191
+ ValueError: empty antecedent, wrong consequent size, unknown
192
+ variable/MF, or unknown norm.
193
+ KeyError: a variable or MF is unknown.
194
+ RuntimeError: called after build().
195
+ """
196
+ ...
197
+
198
+ def set_default_t_norm(self, name: TNorms) -> None:
199
+ """Set default t-norm (for AND) used when a rule has no [<norm>].
200
+
201
+ Args:
202
+ name: one of "min", "prod_and", "bounded_diff", "drastic_prod",
203
+ "einstein_prod", "hamacher_prod".
204
+
205
+ Raises:
206
+ ValueError: unknown name or not a t-norm.
207
+ RuntimeError: called after build().
208
+ """
209
+ ...
210
+
211
+ def set_default_s_norm(self, name: SNorms) -> None:
212
+ """Set default s-norm (for OR) used when a rule has no [<norm>].
213
+
214
+ Args:
215
+ name: one of "max", "algebraic_sum", "bounded_sum", "drastic_sum",
216
+ "einstein_sum", "hamacher_sum".
217
+
218
+ Raises:
219
+ ValueError: unknown name or not an s-norm.
220
+ RuntimeError: called after build().
221
+ """
222
+ ...
223
+
224
+ def build(self) -> None:
225
+ """Compile the model.
226
+
227
+ After build() you cannot add more variables, MFs, or rules. Must be
228
+ called before calculate() / set_input() / get_output().
229
+
230
+ Raises:
231
+ RuntimeError: configuration conflict (no inputs, no outputs,
232
+ no rules, duplicate names, etc.).
233
+ """
234
+ ...
235
+
236
+ # ------------------------------------------------------------------------
237
+ # Execution
238
+ # ------------------------------------------------------------------------
239
+
240
+ def set_input(self, name: str, value: float) -> None:
241
+ """Set value of an input variable.
242
+
243
+ Args:
244
+ name: name of a registered input variable.
245
+ value: new crisp value.
246
+
247
+ Raises:
248
+ KeyError: name is not a registered input variable.
249
+ RuntimeError: called before build().
250
+ """
251
+ ...
252
+
253
+ def get_output(self, name: str) -> float:
254
+ """Read value of an output variable.
255
+
256
+ Must be called after calculate() to reflect the latest result.
257
+
258
+ Returns:
259
+ Crisp output value (defuzzified for Mamdani, weighted sum for Sugeno).
260
+
261
+ Raises:
262
+ KeyError: name is not a registered output variable.
263
+ RuntimeError: called before build().
264
+ """
265
+ ...
266
+
267
+ def calculate(self) -> None:
268
+ """Run fuzzy inference.
269
+
270
+ Reads current input values (set via set_input) and writes defuzzified
271
+ results to registered output variables (read via get_output).
272
+
273
+ Raises:
274
+ RuntimeError: called before build().
275
+ """
276
+ ...
277
+
278
+ def predict_batch(self, X: object, *, input_names: Sequence[str]) -> Any:
279
+ """Run native batch inference.
280
+
281
+ Args:
282
+ X: 2D NumPy-compatible numeric array with shape
283
+ (n_samples, n_features). Non-contiguous arrays are accepted.
284
+ input_names: required sequence of unique strings. Column i maps to
285
+ input_names[i] and every registered input must appear once.
286
+
287
+ Returns:
288
+ NumPy ndarray with dtype float64 and shape (n_samples,).
289
+
290
+ Raises:
291
+ TypeError: X cannot be converted to a numeric array or input_names
292
+ is not a sequence of strings.
293
+ ValueError: wrong shape, name/feature count mismatch, duplicate
294
+ names, or incomplete input_names.
295
+ KeyError: an input name is unknown.
296
+ RuntimeError: called before build() or unsupported output config.
297
+ """
298
+ ...
299
+
300
+ # ------------------------------------------------------------------------
301
+ # Introspection
302
+ # ------------------------------------------------------------------------
303
+
304
+ def is_built(self) -> bool:
305
+ """Return True if build() has been called successfully."""
306
+ ...
PyFLL/__init__.py ADDED
@@ -0,0 +1,97 @@
1
+ """PyFLL — Python биндинги для OpenFLL Linear Facade.
2
+
3
+ Стабы классов (для griffe / mypy / IDE) + runtime загрузка .pyd через
4
+ стандартный `from ._core import ...` — без importlib-хаков.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import importlib.machinery
9
+ import importlib.util
10
+ import os
11
+ import sys
12
+ from typing import Any, List, Literal, Sequence, Tuple, Union
13
+
14
+ __all__ = ["SugenoEngine", "MfType", "TNorms", "SNorms"]
15
+
16
+
17
+ # ============================================================================
18
+ # Literal типы — экспортируются как обычные переменные модуля
19
+ # ============================================================================
20
+ MfType = Literal[
21
+ "constant", "triangular", "trapezoidal", "gaussian", "polynomial"
22
+ ]
23
+ TNorms = Literal[
24
+ "min", "prod_and", "bounded_diff", "drastic_prod", "einstein_prod", "hamacher_prod"
25
+ ]
26
+ SNorms = Literal[
27
+ "max", "algebraic_sum", "bounded_sum", "drastic_sum", "einstein_sum", "hamacher_sum"
28
+ ]
29
+
30
+
31
+ # ============================================================================
32
+ # Стаб SugenoEngine — type hints и docstring для IDE/mkdocstrings
33
+ # ============================================================================
34
+ # В runtime этот класс заменяется на реальный из _core.pyd через
35
+ # `from ._core import SugenoEngine` ниже. Здесь он нужен только для
36
+ # статического анализа (griffe, mypy, IDE).
37
+ class SugenoEngine:
38
+ """Linear facade for Sugeno fuzzy logic model.
39
+
40
+ Two forms of rules are supported:
41
+ - Textual: add_rule('IF x1 IS "low" AND x2 IS "high" THEN y IS "open"')
42
+ Supports AND/OR with optional [norm_name] for explicit t/s-norms.
43
+ - Structural (tuple): add_rule(
44
+ antecedent=[("x1", "low"), ("x2", "high")],
45
+ consequent=[("y", "open")],
46
+ t_norm="prod_and",
47
+ )
48
+ Preferred for programmatic rule generation; AND-only.
49
+
50
+ See full API in PyFLL.pyi (mirrored here for mkdocstrings static parsing).
51
+ """
52
+
53
+ def __init__(self) -> None:
54
+ """Create an empty Sugeno engine."""
55
+ ...
56
+
57
+ def add_input_var(self, name: str) -> None: ...
58
+ def add_output_var(self, name: str) -> None: ...
59
+
60
+ def add_membership_func(
61
+ self,
62
+ var_name: str,
63
+ mf_name: str,
64
+ type: MfType,
65
+ params: Union[List[float], Tuple[float, ...]],
66
+ ) -> None: ...
67
+
68
+ def add_rule(self, rule: str) -> None: ...
69
+ def add_rule(
70
+ self,
71
+ antecedent: List[Tuple[str, str]],
72
+ consequent: List[Tuple[str, str]],
73
+ t_norm: Union[TNorms, str, None] = None,
74
+ s_norm: Union[SNorms, str, None] = None,
75
+ ) -> None: ...
76
+ def set_default_t_norm(self, name: TNorms) -> None: ...
77
+ def set_default_s_norm(self, name: SNorms) -> None: ...
78
+ def build(self) -> None: ...
79
+ def set_input(self, name: str, value: float) -> None: ...
80
+ def get_output(self, name: str) -> float: ...
81
+ def calculate(self) -> None: ...
82
+ def predict_batch(self, X: object, *, input_names: Sequence[str]) -> Any: ...
83
+ def is_built(self) -> bool: ...
84
+
85
+
86
+ # ============================================================================
87
+ # Runtime: загрузка SugenoEngine из _core.pyd через стандартный import.
88
+ # Если .pyd не собран (например, в mkdocs build без wheel) — оставляем стаб.
89
+ # ============================================================================
90
+ try:
91
+ from ._core import SugenoEngine as _RealSugenoEngine
92
+ SugenoEngine = _RealSugenoEngine
93
+ del _RealSugenoEngine
94
+ except ImportError:
95
+ # _core.pyd не найден (исходники без wheel). Стаб остаётся — будет ошибка
96
+ # только при инстанцировании SugenoEngine().
97
+ pass
Binary file
Binary file
PyFLL/libstdc++-6.dll ADDED
Binary file
Binary file
openfll/PyFLL.pyi ADDED
@@ -0,0 +1,284 @@
1
+ """Type stubs for PyFLL.
2
+
3
+ These stubs provide IDE autocompletion and mypy type-checking for the
4
+ PyFLL Python module, even though the actual implementation is a compiled
5
+ .pyd (C++ extension via pybind11).
6
+
7
+ Source of truth:
8
+ - C++ binding: src/PyFLL/PyFLL.cpp
9
+ - C++ API: src/OpenFLL/linear/sugeno_engine.h
10
+
11
+ When you change the C++ binding, please update this file to match.
12
+ """
13
+ from typing import List, Literal, Tuple, Union
14
+
15
+ # ============================================================================
16
+ # Literal type aliases — for IDE autocomplete on string-enum parameters
17
+ # ============================================================================
18
+
19
+ # Membership function type names
20
+ MfType = Literal[
21
+ "constant",
22
+ "triangular",
23
+ "trapezoidal",
24
+ "gaussian",
25
+ "polynomial",
26
+ ]
27
+
28
+ # t-norm names (used for AND or set_default_t_norm)
29
+ TNorms = Literal[
30
+ "min",
31
+ "prod_and",
32
+ "bounded_diff",
33
+ "drastic_prod",
34
+ "einstein_prod",
35
+ "hamacher_prod",
36
+ ]
37
+
38
+ # s-norm names (used for OR or set_default_s_norm)
39
+ SNorms = Literal[
40
+ "max",
41
+ "algebraic_sum",
42
+ "bounded_sum",
43
+ "drastic_sum",
44
+ "einstein_sum",
45
+ "hamacher_sum",
46
+ ]
47
+
48
+
49
+ # ============================================================================
50
+ # SugenoEngine — the main public class
51
+ # ============================================================================
52
+
53
+ class SugenoEngine:
54
+ """Linear facade for Sugeno fuzzy logic model.
55
+
56
+ Provides a flat, name-based API on top of OpenFLL's fluent
57
+ SugenoBuilder / RulesBuilder. Build the model by:
58
+
59
+ 1. Registering input/output variables (add_input_var / add_output_var).
60
+ 2. Adding membership functions to those variables
61
+ (add_membership_func: name + type + parameter list).
62
+ 3. Adding rules as plain strings
63
+ (add_rule: 'IF x1 IS "low" AND x2 IS "high" THEN y IS "fast"').
64
+ 4. Calling build() to compile the model.
65
+ 5. Setting input values (set_input), running calculate(),
66
+ and reading outputs (get_output).
67
+
68
+ Threading: instances are NOT thread-safe. Use one engine per thread.
69
+ """
70
+
71
+ def __init__(self) -> None:
72
+ """Create an empty Sugeno engine."""
73
+ ...
74
+
75
+ # ------------------------------------------------------------------------
76
+ # Configuration
77
+ # ------------------------------------------------------------------------
78
+
79
+ def add_input_var(self, name: str) -> None:
80
+ """Register an input variable.
81
+
82
+ Args:
83
+ name: variable name. Must be non-empty and not already used.
84
+
85
+ Raises:
86
+ ValueError: name is empty or already registered.
87
+ RuntimeError: called after build().
88
+ """
89
+ ...
90
+
91
+ def add_output_var(self, name: str) -> None:
92
+ """Register an output variable.
93
+
94
+ Args:
95
+ name: variable name. Must be non-empty and not already used.
96
+
97
+ Raises:
98
+ ValueError: name is empty or already registered.
99
+ RuntimeError: called after build().
100
+ """
101
+ ...
102
+
103
+ def add_membership_func(
104
+ self,
105
+ var_name: str,
106
+ mf_name: str,
107
+ type: MfType,
108
+ params: Union[List[float], Tuple[float, ...]],
109
+ ) -> None:
110
+ """Add a membership function to a registered variable.
111
+
112
+ Args:
113
+ var_name: name of a previously registered input or output variable.
114
+ mf_name: unique name of the membership function (e.g. "low", "high").
115
+ type: one of:
116
+ "constant" — params = [value]
117
+ "triangular" — params = [a, b, c]
118
+ "trapezoidal" — params = [a, b, c, d]
119
+ "gaussian" — params = [center, sigma]
120
+ "polynomial" — currently raises ValueError (not yet supported)
121
+ params: list/tuple of doubles; size depends on type.
122
+
123
+ Raises:
124
+ ValueError: bad type or wrong parameter count, duplicate mf_name.
125
+ KeyError: var_name is not registered.
126
+ RuntimeError: called after build().
127
+ """
128
+ ...
129
+
130
+ def add_rule(self, rule: str) -> None:
131
+ """Add a fuzzy rule in textual form.
132
+
133
+ Format:
134
+ IF <var1> IS "<mf1>" ((AND|OR) [<norm_name>] <var2> IS "<mf2>")*
135
+ THEN <out_var> IS "<out_mf>"
136
+
137
+ Operators (IF/AND/OR/THEN/IS) are case-insensitive. Variable and MF
138
+ names are case-sensitive. Quoted MF names may contain spaces.
139
+
140
+ Optional [<norm_name>] after AND/OR sets an explicit t/s-norm
141
+ (e.g. "AND[prod_and]", "OR[algebraic_sum]"). If omitted, the engine's
142
+ default norm (set_default_t_norm / set_default_s_norm) is used.
143
+
144
+ Args:
145
+ rule: the rule as a string.
146
+
147
+ Raises:
148
+ ValueError: syntax error or unknown norm name.
149
+ KeyError: a variable or MF name is unknown.
150
+ RuntimeError: called after build().
151
+ """
152
+ ...
153
+
154
+ def add_rule(
155
+ self,
156
+ antecedent: List[Tuple[str, str]],
157
+ consequent: List[Tuple[str, str]],
158
+ t_norm: Union[TNorms, str, None] = None,
159
+ s_norm: Union[SNorms, str, None] = None,
160
+ ) -> None:
161
+ """Add a fuzzy rule in structural (tuple) form.
162
+
163
+ Equivalent to the textual form, but expressed as Python data structures
164
+ (lists of (var_name, mf_name) tuples). This is the preferred form for
165
+ programmatic rule generation: easier to build dynamically and faster
166
+ to parse than the textual form.
167
+
168
+ Args:
169
+ antecedent: list of (var_name, mf_name) pairs. Non-empty.
170
+ All operators between terms are AND (use the textual
171
+ add_rule(str) to express OR).
172
+ consequent: list with exactly one (var_name, mf_name) pair.
173
+ t_norm: optional t-norm name applied to all AND-operators in
174
+ the antecedent. If None or empty string, the engine's
175
+ default t-norm (set_default_t_norm) is used.
176
+ One of: "min", "prod_and", "bounded_diff", "drastic_prod",
177
+ "einstein_prod", "hamacher_prod".
178
+ s_norm: optional s-norm name. (Used only if you later extend
179
+ the engine to support OR in tuple form; currently unused
180
+ for AND-only rules.) If None or empty string, the engine's
181
+ default s-norm (set_default_s_norm) is used.
182
+
183
+ Example:
184
+ >>> engine.add_rule(
185
+ ... antecedent=[("x1", "low"), ("x2", "high")],
186
+ ... consequent=[("y", "open")],
187
+ ... t_norm="prod_and",
188
+ ... )
189
+
190
+ Raises:
191
+ ValueError: empty antecedent, wrong consequent size, unknown
192
+ variable/MF, or unknown norm.
193
+ KeyError: a variable or MF is unknown.
194
+ RuntimeError: called after build().
195
+ """
196
+ ...
197
+
198
+ def set_default_t_norm(self, name: TNorms) -> None:
199
+ """Set default t-norm (for AND) used when a rule has no [<norm>].
200
+
201
+ Args:
202
+ name: one of "min", "prod_and", "bounded_diff", "drastic_prod",
203
+ "einstein_prod", "hamacher_prod".
204
+
205
+ Raises:
206
+ ValueError: unknown name or not a t-norm.
207
+ RuntimeError: called after build().
208
+ """
209
+ ...
210
+
211
+ def set_default_s_norm(self, name: SNorms) -> None:
212
+ """Set default s-norm (for OR) used when a rule has no [<norm>].
213
+
214
+ Args:
215
+ name: one of "max", "algebraic_sum", "bounded_sum", "drastic_sum",
216
+ "einstein_sum", "hamacher_sum".
217
+
218
+ Raises:
219
+ ValueError: unknown name or not an s-norm.
220
+ RuntimeError: called after build().
221
+ """
222
+ ...
223
+
224
+ def build(self) -> None:
225
+ """Compile the model.
226
+
227
+ After build() you cannot add more variables, MFs, or rules. Must be
228
+ called before calculate() / set_input() / get_output().
229
+
230
+ Raises:
231
+ RuntimeError: configuration conflict (no inputs, no outputs,
232
+ no rules, duplicate names, etc.).
233
+ """
234
+ ...
235
+
236
+ # ------------------------------------------------------------------------
237
+ # Execution
238
+ # ------------------------------------------------------------------------
239
+
240
+ def set_input(self, name: str, value: float) -> None:
241
+ """Set value of an input variable.
242
+
243
+ Args:
244
+ name: name of a registered input variable.
245
+ value: new crisp value.
246
+
247
+ Raises:
248
+ KeyError: name is not a registered input variable.
249
+ RuntimeError: called before build().
250
+ """
251
+ ...
252
+
253
+ def get_output(self, name: str) -> float:
254
+ """Read value of an output variable.
255
+
256
+ Must be called after calculate() to reflect the latest result.
257
+
258
+ Returns:
259
+ Crisp output value (defuzzified for Mamdani, weighted sum for Sugeno).
260
+
261
+ Raises:
262
+ KeyError: name is not a registered output variable.
263
+ RuntimeError: called before build().
264
+ """
265
+ ...
266
+
267
+ def calculate(self) -> None:
268
+ """Run fuzzy inference.
269
+
270
+ Reads current input values (set via set_input) and writes defuzzified
271
+ results to registered output variables (read via get_output).
272
+
273
+ Raises:
274
+ RuntimeError: called before build().
275
+ """
276
+ ...
277
+
278
+ # ------------------------------------------------------------------------
279
+ # Introspection
280
+ # ------------------------------------------------------------------------
281
+
282
+ def is_built(self) -> bool:
283
+ """Return True if build() has been called successfully."""
284
+ ...