Source code for lsapy.functions._suitability
"""Suitability Function Module."""
from __future__ import annotations
import warnings
from collections.abc import Callable
from typing import Any
import matplotlib.pyplot as plt
import numpy as np
import lsapy.core.formatting as fmt
from lsapy.core.functions import get_function_from_name
__all__ = [
"SuitabilityFunction",
]
[docs]
class SuitabilityFunction:
"""
Suitability Function.
Suitability function define how the criteria indicator is transformed into a suitability value. The suitability
function are available for continuous and discrete indicators. For continuous indicators, a membership function
is applied to convert indicator values into a suitability. For discrete indicators, a set of rules is mapped
on the indicator.
Parameters
----------
func : Callable | None, optional
Function to compute the suitability value.
name : str | None, optional
Name of the implemented function to use (see Notes for available functions).
If `func` is provided, this parameter is ignored.
params : dict[str, Any], optional
Parameters of the function.
Notes
-----
The implemented functions are (in parentheses the alternative names): ``discrete``, ``logistic``, ``sigmoid``,
``vetharaniam2022_eq3`` (VTR22_eq3), ``vetharaniam2022_eq5`` (VTR22_eq5), ``vetharaniam2024_eq8`` (VTR24_eq8),
``vetharaniam2024_eq10`` (VTR24_eq10).
Examples
--------
>>> func = SuitabilityFunction(name="logistic", params={"a": 1, "b": 5})
>>> func(3)
array(0.11920292, dtype=float32)
``SuitabilityFunction`` can also be used for discrete functions.
>>> func = SuitabilityFunction(name="discrete", params={"rules": {1: 0, 2: 0.1, 3: 0.5, 4: 0.9, 5: 1}})
>>> func(3)
array(0.5, dtype=float32)
"""
[docs]
def __init__(self, func: Callable | None = None, name: str | None = None, params: dict[str, Any] | None = None):
if func is None:
if name is None:
raise ValueError("Either `func` or `name` must be provided to define the suitability function.")
elif not isinstance(name, str):
raise TypeError("`name` must be a string when `func` is not provided.")
else:
self.func = get_function_from_name(name)
else:
if not callable(func):
raise TypeError("`func` must be a callable function.")
if name is not None:
warnings.warn("`name` is ignored when `func` is provided", stacklevel=2)
self.func = func
self.params = params
def __call__(self, x):
"""Call the suitability function."""
if self.func is None:
raise ValueError("No function has been provided.")
if self.params:
def func(val):
"""Wrap the function to include parameters."""
return self.func(val, **self.params)
else:
def func(val):
"""Wrap the function without parameters."""
return self.func(val)
return np.vectorize(func, otypes=[np.float32])(x)
def __repr__(self):
return fmt.sf_repr(self)
@property
def attrs(self) -> dict[str, Any]:
"""
Dictionary of the suitability function attributes.
Returns
-------
dict
Dictionary containing the function name and parameters. If both are undefined, an empty dictionary
is returned.
"""
return {k: v for k, v in {"func": self.func, "params": self.params}.items() if v is not None}
def plot(self, x) -> None:
"""
Basic plot of the suitability function.
Parameters
----------
x : any
Input values to plot.
Examples
--------
>>> import numpy as np # doctest: +SKIP
>>> from lsapy.functions import logistic
>>> sf = SuitabilityFunction(func=logistic, params={"a": 1, "b": 5})
>>> sf.plot(np.linspace(0, 10, 100)) # doctest: +SKIP
"""
plt.plot(x, self(x))