lsapy 0.1.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,372 @@
1
+ """Membership Function Module."""
2
+
3
+ import warnings
4
+
5
+ import matplotlib.pyplot as plt
6
+ import numpy as np
7
+ from scipy.optimize import curve_fit
8
+
9
+ from lsapy.core.functions import declare_equation, equations, get_function_from_name
10
+
11
+ __all__ = [
12
+ "fit_membership",
13
+ "logistic",
14
+ "sigmoid",
15
+ "vetharaniam2022_eq3",
16
+ "vetharaniam2022_eq5",
17
+ "vetharaniam2024_eq8",
18
+ "vetharaniam2024_eq10",
19
+ ]
20
+
21
+ EQUATION_TYPES = ["sigmoid", "gaussian"]
22
+
23
+
24
+ @declare_equation("sigmoid")
25
+ def logistic(x, a, b):
26
+ r"""
27
+ Logistic function.
28
+
29
+ Parameters
30
+ ----------
31
+ x : any
32
+ Input values to map.
33
+ a : float | int
34
+ Steepness of the function parameter.
35
+ b : float | int
36
+ Value of the function's midpoint.
37
+
38
+ Returns
39
+ -------
40
+ float
41
+ Suitability values.
42
+
43
+ Notes
44
+ -----
45
+ The logistic function is defined as:
46
+
47
+ .. math::
48
+
49
+ f(x) = \frac{1}{1 + e^{-a(x - b)}}
50
+ """
51
+ return 1 / (1 + np.exp(-a * (x - b)))
52
+
53
+
54
+ @declare_equation("sigmoid")
55
+ def sigmoid(x):
56
+ r"""
57
+ Sigmoid function.
58
+
59
+ Parameters
60
+ ----------
61
+ x : any
62
+ Input values to map.
63
+
64
+ Returns
65
+ -------
66
+ float
67
+ Suitability values.
68
+
69
+ Notes
70
+ -----
71
+ The sigmoid function is defined as:
72
+
73
+ .. math::
74
+
75
+ f(x) = \frac{1}{1 + e^{-x}}
76
+ """
77
+ return logistic(x, 1, 0)
78
+
79
+
80
+ @declare_equation("sigmoid", "VTR22_3")
81
+ def vetharaniam2022_eq3(x, a, b):
82
+ r"""
83
+ Sigmoid like function.
84
+
85
+ # TODO: add a more detailed description.
86
+
87
+ Parameters
88
+ ----------
89
+ x : any
90
+ Input values to map.
91
+ a : float | int
92
+ Steepness of the function parameter.
93
+ b : float | int
94
+ Value of the function's midpoint.
95
+
96
+ Returns
97
+ -------
98
+ float
99
+ Suitability values.
100
+
101
+ Notes
102
+ -----
103
+ The sigmoid like function is defined as:
104
+
105
+ .. math::
106
+
107
+ f(x) = \frac{e^{a(x - b)}}{1 + e^{a(x - b)}}
108
+
109
+ References
110
+ ----------
111
+ :cite:cts:`vetharaniam_lsa_2022`
112
+ """
113
+ return np.exp(a * (x - b)) / (1 + np.exp(a * (x - b)))
114
+
115
+
116
+ @declare_equation("sigmoid", "VTR22_5")
117
+ def vetharaniam2022_eq5(x, a, b):
118
+ r"""
119
+ Sigmoid like function.
120
+
121
+ # TODO: add a more detailed description.
122
+
123
+ Parameters
124
+ ----------
125
+ x : any
126
+ Input values to map.
127
+ a : float | int
128
+ Steepness of the function parameter.
129
+ b : float | int
130
+ Value of the function's midpoint.
131
+
132
+ Returns
133
+ -------
134
+ float
135
+ Suitability values.
136
+
137
+ Notes
138
+ -----
139
+ The sigmoid like function is defined as:
140
+
141
+ .. math::
142
+
143
+ f(x) = \frac{1}{1 + e^{a(\sqrt{x} - \sqrt{b})}}
144
+
145
+ References
146
+ ----------
147
+ :cite:cts:`vetharaniam_lsa_2022`
148
+ """
149
+ return 1 / (1 + np.exp(a * (np.sqrt(x) - np.sqrt(b))))
150
+
151
+
152
+ @declare_equation("gaussian", "VTR24_8")
153
+ def vetharaniam2024_eq8(x, a, b, c):
154
+ r"""
155
+ Gaussian like function.
156
+
157
+ # TODO: add a more detailed description.
158
+
159
+ Parameters
160
+ ----------
161
+ x : any
162
+ Input values to map.
163
+ a : float | int
164
+ Steepness of the function parameter.
165
+ b : float | int
166
+ Value of the function's midpoint.
167
+ c : float | int
168
+ Scaling parameter.
169
+
170
+ Returns
171
+ -------
172
+ float
173
+ Suitability values.
174
+
175
+ Notes
176
+ -----
177
+ The Gaussian like function is defined as:
178
+
179
+ .. math::
180
+
181
+ f(x) = e^{-a(x - b)^c}
182
+
183
+ References
184
+ ----------
185
+ :cite:cts:`vetharaniam_lsa_2024`
186
+ """
187
+ return np.exp(-a * np.power(x - b, c))
188
+
189
+
190
+ @declare_equation("gaussian", "VTR24_10")
191
+ def vetharaniam2024_eq10(x, a, b, c):
192
+ r"""
193
+ Gaussian like function.
194
+
195
+ # TODO: add a more detailed description.
196
+
197
+ Parameters
198
+ ----------
199
+ x : any
200
+ Input values to map.
201
+ a : float | int
202
+ Steepness of the function parameter.
203
+ b : float | int
204
+ Value of the function's midpoint.
205
+ c : float | int
206
+ Scaling parameter.
207
+
208
+ Returns
209
+ -------
210
+ float
211
+ Suitability values.
212
+
213
+ Notes
214
+ -----
215
+ The Gaussian like function is defined as:
216
+
217
+ .. math::
218
+
219
+ f(x) = e^{-a(x^c - b^c)}
220
+
221
+ References
222
+ ----------
223
+ :cite:cts:`vetharaniam_lsa_2024`
224
+ """
225
+ return 2 / (1 + np.exp(a * np.power(np.power(x, c) - np.power(b, c), 2)))
226
+
227
+
228
+ def fit_membership(x, y=None, fit_on: str | list[str] = "all", plot: bool = False, verbose: bool = False):
229
+ """
230
+ Fit membership function to data.
231
+
232
+ This function fits membership functions to the provided data. It helps to determine the best membership function
233
+ to use on the data.
234
+
235
+ Parameters
236
+ ----------
237
+ x : any
238
+ Input values to fit the functions on.
239
+ y : any, optional
240
+ Target suitability values to fit the functions. Should be the same length as `x`. If not provided,
241
+ the default values are used (0, 0.25, 0.5, 0.75, 1).
242
+ fit_on : str | list[str], optional
243
+ List of equation or equation types to fit. `all, `sigmoid_like` and `gaussian_like` can also be used.
244
+ If 'all', all available equations are fitted. If '{TYPES}_like', all equations corresponding to the
245
+ type are fitted. Default is 'all'.
246
+ plot : bool, optional
247
+ Whether to plot the fitted functions. Default is False.
248
+ verbose : bool, optional
249
+ Whether to print the fitting results. Default is False.
250
+
251
+ Returns
252
+ -------
253
+ tuple
254
+ A tuple containing the best fitting function and its parameters.
255
+
256
+ Examples
257
+ --------
258
+ >>> from lsapy.functions.membership import fit_membership
259
+ >>> fit_membership([1, 3, 5, 7, 10]) # doctest: +ELLIPSIS
260
+ (<function vetharaniam2022_eq5 at 0x...>, array([-2.78959209, 4.86485647]))
261
+
262
+ By default, the function will fit all available membership equations. If you want to fit only specific equations,
263
+ you can specify it using the `fit_on` parameter: "all", "sigmoid_like", "gaussian_like", or a list of equations.
264
+ The default `y` values can also be changed.
265
+
266
+ >>> fit_membership(x=[1, 3, 5, 5, 7, 9], y=[0, 0.5, 1, 1, 0.5, 0], fit_on="gaussian_like") # doctest: +ELLIPSIS
267
+ (<function vetharaniam2024_eq10 at 0x...>, array([0.38213219, 4.97273138, 0.93922461]))
268
+ """
269
+ if y is None:
270
+ y = [0, 0.25, 0.5, 0.75, 1]
271
+ y = np.array(y)
272
+ functions, skipped = _check_fitting(fit_on)
273
+
274
+ x_ = np.linspace(min(x), max(x), 100)
275
+ rms_errors = []
276
+ f_params = []
277
+ for func in functions:
278
+ try:
279
+ f = get_function_from_name(func)
280
+ p0 = _get_function_p0(func, x)
281
+ popt, _ = curve_fit(f, x, y, p0=p0, maxfev=15000)
282
+ y_ = f(x_, *popt)
283
+ f_params.append(popt)
284
+ rmse = _rmse(y, f(x, *popt))
285
+ rms_errors.append(rmse)
286
+ if plot:
287
+ plt.plot(x_, y_, label=func + f" (RMSE={rmse:.2f})")
288
+ except Exception:
289
+ skipped.append(func)
290
+ warnings.warn(f"Failed to fit `{func}`. Skipped.", stacklevel=2)
291
+
292
+ if all([f in skipped for f in functions]):
293
+ warnings.warn(f"No methods to fit. fitting for the following methods: {', '.join(skipped)}.", stacklevel=2)
294
+ return None, None
295
+
296
+ if plot:
297
+ plt.scatter(x, y, c="r")
298
+ plt.legend()
299
+ plt.show()
300
+
301
+ f_best, p_best = _get_best_fit([m for m in functions if m not in skipped], rms_errors, f_params, verbose=verbose)
302
+ return get_function_from_name(f_best), p_best
303
+
304
+
305
+ def _check_fitting(fit_on: str | list[str] = "all"):
306
+ _types = [t + "_like" for t in EQUATION_TYPES]
307
+ _skipped = []
308
+
309
+ if not isinstance(fit_on, str) and not isinstance(fit_on, list):
310
+ raise ValueError(f"`fit_on` should be a str or a list of string. Got {type(fit_on)}")
311
+
312
+ functions = []
313
+ if isinstance(fit_on, str):
314
+ if fit_on == "all":
315
+ for t in EQUATION_TYPES:
316
+ functions.extend(equations[t].keys())
317
+ fit_on = None
318
+ else:
319
+ fit_on = [fit_on]
320
+ if fit_on is not None:
321
+ for func in fit_on:
322
+ if not isinstance(func, str):
323
+ continue
324
+ if func in _types:
325
+ for f in equations[func.replace("_like", "")].keys():
326
+ if f not in functions:
327
+ functions.append(f)
328
+ else:
329
+ try:
330
+ get_function_from_name(func)
331
+ if func not in functions:
332
+ functions.append(func)
333
+ except Exception:
334
+ _skipped.append(func)
335
+ warnings.warn(f"`{func}` not found in implemented equations. Skipped.", stacklevel=3)
336
+
337
+ for f in ["sigmoid", "vetharaniam2024_eq8"]:
338
+ if f in functions:
339
+ functions.remove(f)
340
+ _skipped.append(f)
341
+ if f == "sigmoid":
342
+ warnings.warn("No parameters to determine for `sigmoid`. Skipped.", stacklevel=3)
343
+ if f == "vetharaniam2024_eq8":
344
+ warnings.warn("Fitting does not support `vetharaniam2024_eq8`. Skipped.", stacklevel=3)
345
+
346
+ if len(functions) == 0:
347
+ raise ValueError("No functions to fit. Try to modify `fit_on` parameter.")
348
+ return functions, _skipped
349
+
350
+
351
+ def _get_function_p0(func: str, x: np.ndarray) -> list[float]:
352
+ if func in equations["sigmoid"]:
353
+ return [1, np.median(x)]
354
+ if func in equations["gaussian"]:
355
+ return [1, np.median(x), 1]
356
+ return []
357
+
358
+
359
+ def _rmse(y_true, y_pred):
360
+ diff = abs(y_true - y_pred)
361
+ return np.sqrt(np.mean(diff**2))
362
+
363
+
364
+ def _get_best_fit(functions, rmse, params, verbose=True):
365
+ best_fit = np.nanargmin(rmse)
366
+ if verbose:
367
+ print(f"""
368
+ Best fit: {functions[best_fit]}
369
+ RMSE: {rmse[best_fit]:.5f}
370
+ Params: a={params[best_fit][0]}, b={params[best_fit][1]}
371
+ """)
372
+ return functions[best_fit], params[best_fit]