# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['inferfuzzy',
 'inferfuzzy.defuzzifications',
 'inferfuzzy.memberships',
 'inferfuzzy.rules',
 'inferfuzzy.systems']

package_data = \
{'': ['*']}

install_requires = \
['matplotlib>=3.3.3,<4.0.0', 'numpy>=1.19.4,<2.0.0', 'typer>=0.3.2,<0.4.0']

entry_points = \
{'console_scripts': ['inferfuzzy = inferfuzzy.main:app']}

setup_kwargs = {
    'name': 'inferfuzzy',
    'version': '0.1.3',
    'description': 'Fuzzy Inference System in Python',
    'long_description': '# Inferfuzzy\n\n> **Autor:** [Leynier Gutiérrez González](https://leynier.github.io)\n> **Grupo:** C412\n> **Correo**: [l.gutierrez@estudiantes.matcom.uh.cu](mailto:l.gutierrez@estudiantes.matcom.uh.cu)\n> **Tutor:** Dr. Yudivian Almeida, *Universidad de La Habana*\n> **Código:** <https://github.com/leynier/inferfuzzy>\n\n**Inferfuzzy** es una biblioteca de **Python** para implementar **Sistemas de Inferencia Difusa**.\n\n## Características del Sistema de Inferencia\n\nLa biblioteca contiene implementados los métodos de inferencia *Mamdani* y *Larsen*. Pero es posible implementar partiendo de una base común otros métodos de inferencia.\n\nLos métodos de inferencia reciben una función de *defuzzificación*. La biblioteca contiene implementadas *Centroide*, *Bisectriz*, *Máximo Central*, *Máximo más pequeño* y *Máximo más grande*.\n\nDurante el proceso de definición de los conjuntos difusos esto requieren una función de membresía que puede ser implementada o utilizar una de las disponibles en la biblioteca.\n\nFunciones de membresía implementadas en **Inferfuzzy**:\n\n* Función Gamma\n* Función Lambda o Triangular\n* Función Pi o Trapezoidal\n* Función S\n* Función Z\n* Función Gaussiana\n\nLa *T-conorm* y *T-norm* utilizadas en las reglas de inferencia, así como el método de agregación de los conjuntos son posibles de sobrescribir, por defecto, son *mínimo*, *máximo* y *máximo* respectivamente.\n\nEs posible definir más de una variable de salida para el sistema de inferencia difusa implementado en la biblioteca.\n\n## Estructura de la Implementación\n\nLa implementación se sostiene sobre 7 clases fundamentales:\n\n* `Membership`\n* `BaseSet`\n* `BaseVar`\n* `BaseRule`\n* `Predicate`\n* `VarSet`\n* `InferenceSystem`\n\n### Membership\n\nEs la clase encargada de representar una función de membresía junto a los puntos (llamados `items` internamente)\n\n```python\nclass Membership:\n    def __init__(self, function: Callable[[Any], Any], items: list):\n        self.function = function\n        self.items = items\n\n    def __call__(self, value: Any):\n        return self.function(value)\n```\n\n### BaseSet\n\nEs la clase encargada de representar un conjunto difuso. Recibe como parámetros un objeto de tipo `Membership` representando la función de membresía del conjunto y un método de agregación.\n\n```python\nclass BaseSet:\n    def __init__(\n        self,\n        name: str,\n        membership: Membership,\n        aggregation: Callable[[Any, Any], Any],\n    ):\n        self.name = name\n        self.membership = membership\n        self.aggregation = aggregation\n\n    def __add__(self, arg: "BaseSet"):\n        memb = Membership(\n            lambda x: self.aggregation(\n                self.membership(x),\n                arg.membership(x),\n            ),\n            self.membership.items + arg.membership.items,\n        )\n        return BaseSet(\n            f"({self.name})_union_({arg.name})",\n            memb,\n            aggregation=self.aggregation,\n        )\n```\n\n### BaseVar\n\nEs la clase encargada de representar una variable lingüística. Recibe como parámetros una función de unión, una función de intercepción y una lista de objetos de tipo `BaseSet` representando los conjuntos difusos de la variable.\n\n```python\nclass BaseVar:\n    def __init__(\n        self,\n        name: str,\n        union: Callable[[Any, Any], Any],\n        inter: Callable[[Any, Any], Any],\n        sets: Optional[List[BaseSet]] = None,\n    ):\n        self.name = name\n        self.sets = {set.name: set for set in sets} if sets else {}\n        self.union = union\n        self.inter = inter\n\n    def into(self, set: Union[BaseSet, str]) -> VarSet:\n        set_name = set.name if isinstance(set, BaseSet) else set\n        if set_name not in self.sets:\n            raise KeyError(f"Set {set_name} not found into var {self.name}")\n        temp_set = self.sets[set_name]\n        return VarSet(self, temp_set, self.union, self.inter)\n```\n\n### BaseRule\n\nEs la clase encargada de representar una regla de inferencia. Recibe como parámetro un objeto de tipo `Predicate` representando el antecedente de la regla.\n\n```python\nclass BaseRule:\n    def __init__(self, antecedent: Predicate):\n        self.antecedent = antecedent\n\n    def __call__(self, values: dict):\n        raise NotImplementedError()\n```\n\n`BaseRule` no contiene consecuencias porque las consecuencias de todos los tipos de reglas no son de la misma estructura. La clase `Rule` hereda de `BaseRule` y representa las reglas en los que el sistema produce un conjunto o más como resultado.\n\n```python\nclass Rule(BaseRule):\n    def __init__(self, antecedent: Predicate, consequences: List[VarSet]):\n        super(Rule, self).__init__(antecedent)\n        self.consequences = consequences\n\n    def aggregate(self, set: BaseSet, value: Any) -> BaseSet:\n        raise NotImplementedError()\n\n    def __call__(self, values: dict):\n        value = self.antecedent(values)\n        return {\n            consequence.var.name: self.aggregate(\n                consequence.set,\n                value,\n            )\n            for consequence in self.consequences\n        }\n```\n\n### Predicate\n\nEs la clase encargada de representar a los antecedentes. De ella heredan cuatro clases: `AndPredicate`, `OrPredicate`, `NotPredicate` y `VarSet`. Las primeras tres para representar las relaciones lógicas de unión, intercepción y negación; y la última representa la inclusión de una variable en un determinado conjunto, siendo esta la clase básica para representar a los antecedentes.\n\n```python\nclass Predicate:\n    def __init__(\n        self,\n        union: Callable[[Any, Any], Any],\n        inter: Callable[[Any, Any], Any],\n    ) -> None:\n        self.union = union\n        self.inter = inter\n\n    def __call__(self, values: dict):\n        raise NotImplementedError()\n\n    def __and__(self, other: "Predicate"):\n        return AndPredicate(self, other, self.union, self.inter)\n\n    def __or__(self, other: "Predicate"):\n        return OrPredicate(self, other, self.union, self.inter)\n\n    def __invert__(self):\n        return NotPredicate(self, self.union, self.inter)\n```\n\n#### VarSet\n\n```python\nclass VarSet(Predicate):\n    def __init__(\n        self,\n        var: "BaseVar",\n        set: BaseSet,\n        union: Callable[[Any, Any], Any],\n        inter: Callable[[Any, Any], Any],\n    ):\n        super(VarSet, self).__init__(union, inter)\n        self.var = var\n        self.set = set\n\n    def __call__(self, values: dict):\n        return self.set.membership(values[self.var.name])\n```\n\n### InferenceSystem\n\nEs la clase encargada de representar el sistema de inferencia. Recibe como parámetros las reglas y una función de defuzzificación y con el método `infer` permite realizar la inferencia según los valores proveídos.\n\n```python\nclass InferenceSystem:\n    def __init__(\n        self,\n        rules: Optional[List[BaseRule]] = None,\n        defuzz_func: Optional[Callable[[BaseSet], Any]] = None,\n    ):\n        self.rules = rules if rules else []\n        self.defuzz_func = defuzz_func\n\n    def infer(\n        self,\n        values: dict,\n        defuzz_func: Optional[Callable[[BaseSet], Any]] = None,\n    ) -> Dict[str, Any]:\n        if not self.rules:\n            raise Exception("Empty rules")\n        if self.defuzz_func is None and defuzz_func is None:\n            raise Exception("Defuzzification not found")\n        func = self.defuzz_func if defuzz_func is None else defuzz_func\n        set: Dict[str, BaseSet] = self.rules[0](values)\n        for rule in self.rules[1:]:\n            temp: Dict[str, BaseSet] = rule(values)\n            for key in temp:\n                set[key] += temp[key]\n        result: Dict[str, Any] = {}\n        for key in set:\n            result[key] = func(set[key])\n        return result\n```\n\n## Ejemplo de como utilizar Inferfuzzy\n\nComo ejemplo se utilizará el siguiente problema.\n\nSe desea inferir el por ciento de la cantidad de un determinado producto que se ha vendido en un día en un restaurante, cafetería, etc.\n\nPor ejemplo, el producto *Pollo*, se desea conocer bajo determinadas condiciones que por ciento del *Pollo* sacado del almacén dispuesto para venderse ese día se termina vendiendo.\n\nPara la implementación se seleccionaron `4` variables lingüísticas. Las primeras `3` de entrada y la última de salida.\n\n1. Cantidad de platos o derivados del producto que se vende. Por ejemplo, retomando el ejemplo del *Pollo*, si se vendería *Pollo Frito* y *Pollo Asado*, la variable valdría `2`. A esta variable le llamaremos `variety`.\n    * Baja: `low <= 2`. Función de Membresía: Z\n    * Normal: `1 <= normal <= 5`. Función de Membresía: Gaussiana\n    * Alta: `high >= 4`. Función de Membresía: S\n2. Por ciento que representa la variable `variety` del total de platos o derivados de productos que se vende. Por ejemplo, si se vende *Pollo Frito*, *Pollo Asado*, *Pescado* y *Cerdo* la variable valdría `50`. A esta variable se le llamará `diversity`.\n    * Baja: `low >= 70`. Función de Membresía: Gamma\n    * Normal: `40 <= normal <= 80`. Función de Membresía: Lambda\n    * Alta: `high <= 50`. Función de Membresía: L\n3. Por ciento de la utilización del local, si es `100` es que el local siempre está lleno, si es `0` es que no asiste ningún cliente al establecimiento. A esta variable se le llamará `clients`.\n    * Baja: `low <= 40`. Función de Membresía: L\n    * Normal: `30 <= normal <= 90`. Función de Membresía: Lambda\n    * Alta: `high >= 80`. Función de Membresía: Gamma\n4. Por ciento de la cantidad del producto que se vendió en el día, si es `100` fue se vendió todo al final del día, si es `50` fue que no se vendió la mitad de la cantidad. A esta variable se le llamará `sales`.\n    * Baja: `low <= 60`. Función de Membresía: L\n    * Normal: `30 <= normal <= 90`. Función de Membresía: Lambda\n    * Alta: `high >= 90`. Función de Membresía: Gamma\n\n### Declaración de las variables lingüísticas y sus conjuntos difusos en Inferfuzzy\n\n```python\nvariety_var = Var("variety")\nvariety_var += "low", ZMembership(1, 2)\nvariety_var += "normal", GaussianMembership(3, 2)\nvariety_var += "high", SMembership(4, 6)\n\ndiversity_percent_var = Var("diversity")\ndiversity_percent_var += "low", GammaMembership(70, 100)\ndiversity_percent_var += "normal", LambdaMembership(40, 60, 80)\ndiversity_percent_var += "high", LMembership(30, 50)\n\nclients_percent_var = Var("clients")\nclients_percent_var += "low", LMembership(20, 40)\nclients_percent_var += "normal", LambdaMembership(30, 60, 90)\nclients_percent_var += "high", GammaMembership(80, 100)\n\nsales_percent_var = Var("sales")\nsales_percent_var += "low", LMembership(20, 60)\nsales_percent_var += "normal", LambdaMembership(30, 60, 90)\nsales_percent_var += "high", GammaMembership(90, 100)\n```\n\n### Gráficos de pertenencia de los conjuntos por cada variable\n\n![Gráfico de la variable `variety`](images/var_variety.png)\n\n![Gráfico de la variable `diversity`](images/var_diversity.png)\n\n![Gráfico de la variable `clients`](images/var_clients.png)\n\n![Gráfico de la variable `sales`](images/var_sales.png)\n\n### Reglas de Inferencia\n\n| variety | diversity | clients |  sales |\n|:-------:|:---------:|:-------:|:------:|\n|   low   |    low    |   low   |   low  |\n|   low   |    low    |  normal | normal |\n|   low   |    low    |   high  |  high  |\n|   low   |   normal  |   low   |   low  |\n|   low   |   normal  |  normal |   low  |\n|   low   |   normal  |   high  | normal |\n|   low   |    high   |   low   |   low  |\n|   low   |    high   |  normal |   low  |\n|   low   |    high   |   high  | normal |\n|  normal |    low    |   low   |   low  |\n|  normal |    low    |  normal | normal |\n|  normal |    low    |   high  |  high  |\n|  normal |   normal  |   low   |   low  |\n|  normal |   normal  |  normal | normal |\n|  normal |   normal  |   high  | normal |\n|  normal |    high   |   low   |   low  |\n|  normal |    high   |  normal |   low  |\n|  normal |    high   |   high  | normal |\n|   high  |    low    |   low   |   low  |\n|   high  |    low    |  normal | normal |\n|   high  |    low    |   high  |  high  |\n|   high  |   normal  |   low   |   low  |\n|   high  |   normal  |  normal |   low  |\n|   high  |   normal  |   high  |  high  |\n|   high  |    high   |   low   |   low  |\n|   high  |    high   |  normal |   low  |\n|   high  |    high   |   high  | normal |\n\n### Declaración de las Reglas de Inferencia en Inferfuzzy\n\n```python\nmamdani = MamdaniSystem(\n    defuzz_func=centroid_defuzzification,\n)\nmamdani += (\n    variety_var.into("low")\n    & diversity_percent_var.into("low")\n    & clients_percent_var.into("low")\n), sales_percent_var.into("low")\nmamdani += (\n    variety_var.into("low")\n    & diversity_percent_var.into("low")\n    & clients_percent_var.into("normal")\n), sales_percent_var.into("normal")\nmamdani += (\n    variety_var.into("low")\n    & diversity_percent_var.into("low")\n    & clients_percent_var.into("high")\n), sales_percent_var.into("high")\nmamdani += (\n    variety_var.into("low")\n    & diversity_percent_var.into("normal")\n    & clients_percent_var.into("low")\n), sales_percent_var.into("low")\nmamdani += (\n    variety_var.into("low")\n    & diversity_percent_var.into("normal")\n    & clients_percent_var.into("normal")\n), sales_percent_var.into("low")\nmamdani += (\n    variety_var.into("low")\n    & diversity_percent_var.into("normal")\n    & clients_percent_var.into("high")\n), sales_percent_var.into("normal")\nmamdani += (\n    variety_var.into("low")\n    & diversity_percent_var.into("high")\n    & clients_percent_var.into("low")\n), sales_percent_var.into("low")\nmamdani += (\n    variety_var.into("low")\n    & diversity_percent_var.into("high")\n    & clients_percent_var.into("normal")\n), sales_percent_var.into("low")\nmamdani += (\n    variety_var.into("low")\n    & diversity_percent_var.into("high")\n    & clients_percent_var.into("high")\n), sales_percent_var.into("normal")\nmamdani += (\n    variety_var.into("normal")\n    & diversity_percent_var.into("low")\n    & clients_percent_var.into("low")\n), sales_percent_var.into("low")\nmamdani += (\n    variety_var.into("normal")\n    & diversity_percent_var.into("low")\n    & clients_percent_var.into("normal")\n), sales_percent_var.into("normal")\nmamdani += (\n    variety_var.into("normal")\n    & diversity_percent_var.into("low")\n    & clients_percent_var.into("high")\n), sales_percent_var.into("high")\nmamdani += (\n    variety_var.into("normal")\n    & diversity_percent_var.into("normal")\n    & clients_percent_var.into("low")\n), sales_percent_var.into("low")\nmamdani += (\n    variety_var.into("normal")\n    & diversity_percent_var.into("normal")\n    & clients_percent_var.into("normal")\n), sales_percent_var.into("normal")\nmamdani += (\n    variety_var.into("normal")\n    & diversity_percent_var.into("normal")\n    & clients_percent_var.into("high")\n), sales_percent_var.into("normal")\nmamdani += (\n    variety_var.into("normal")\n    & diversity_percent_var.into("high")\n    & clients_percent_var.into("low")\n), sales_percent_var.into("low")\nmamdani += (\n    variety_var.into("normal")\n    & diversity_percent_var.into("high")\n    & clients_percent_var.into("normal")\n), sales_percent_var.into("low")\nmamdani += (\n    variety_var.into("normal")\n    & diversity_percent_var.into("high")\n    & clients_percent_var.into("high")\n), sales_percent_var.into("normal")\nmamdani += (\n    variety_var.into("high")\n    & diversity_percent_var.into("low")\n    & clients_percent_var.into("low")\n), sales_percent_var.into("low")\nmamdani += (\n    variety_var.into("high")\n    & diversity_percent_var.into("low")\n    & clients_percent_var.into("normal")\n), sales_percent_var.into("normal")\nmamdani += (\n    variety_var.into("high")\n    & diversity_percent_var.into("low")\n    & clients_percent_var.into("high")\n), sales_percent_var.into("high")\nmamdani += (\n    variety_var.into("high")\n    & diversity_percent_var.into("normal")\n    & clients_percent_var.into("low")\n), sales_percent_var.into("low")\nmamdani += (\n    variety_var.into("high")\n    & diversity_percent_var.into("normal")\n    & clients_percent_var.into("normal")\n), sales_percent_var.into("low")\nmamdani += (\n    variety_var.into("high")\n    & diversity_percent_var.into("normal")\n    & clients_percent_var.into("high")\n), sales_percent_var.into("high")\nmamdani += (\n    variety_var.into("high")\n    & diversity_percent_var.into("high")\n    & clients_percent_var.into("low")\n), sales_percent_var.into("low")\nmamdani += (\n    variety_var.into("high")\n    & diversity_percent_var.into("high")\n    & clients_percent_var.into("normal")\n), sales_percent_var.into("low")\nmamdani += (\n    variety_var.into("high")\n    & diversity_percent_var.into("high")\n    & clients_percent_var.into("high")\n), sales_percent_var.into("normal")\n```\n\nDe manera análoga sería para Larsen utilizando la clase `LarsenSystem`.\n\n### Resultados\n\n```bash\nVariety Value: 10\nDiversity Percent: 50\nClients Percent: 50\nMamdani: 35.11%\nLarsen 32.82%\n\nVariety Value: 2\nDiversity Percent: 100\nClients Percent: 100\nMamdani: 96.22%\nLarsen 100.00%\n\nVariety Value: 4\nDiversity Percent: 40\nClients Percent: 100\nMamdani: 60.00%\nLarsen 60.00%\n```\n\n#### Análisis de los Resultados\n\nDe los resultados, se puede observar que los métodos de Mamdani y Larsen obtienen resultados similares. A primera vista no es posible validar si los resultados se asemejan a la realidad, para esto es imprescindible la colaboración de un experto en el tema para la correcta definición de las variables, la asignación de las funciones de membresía más correctas así́ como la definición de las reglas asociadas.\n\n## Conclusiones\n\nEn este escrito se muestra las líneas generales de cómo utilizar Inferfuzzy, además de que muestra la capacidad de los sistemas de inferencia difusos para afrontar problemáticas donde la definición utilizando la lógica clásica no esté clara o que la solución utilizando esta sea demasiado engorrosa.\n\n## Referencias\n\n1. Sistemas de Control con Lógica Difusa: Métodos de Mamdani y de Takagi-Sugeno-Kang (TSK). Autor: Samuel Diciembre Sanahuja\n2. Temas de Simulación. Autor: Dr. Luciano García Garrido\n3. First Course on Fuzzy Theory and Applications. Autor: Kwang H. Lee\n',
    'author': 'leynier',
    'author_email': 'leynier41@gmail.com',
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://github.com/leynier/inferfuzzy',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'entry_points': entry_points,
    'python_requires': '>=3.8,<4.0',
}


setup(**setup_kwargs)
