Hola Habr! Hoy es un tutorial en miniatura sobre cómo analizar una cadena con una expresión matemática y calcularla usando números triangulares difusos. Con los cambios apropiados en el código, el tutorial funcionará con otras variables "personalizadas". Ayuda: números triangulares difusos: un caso especial de números difusos (variables difusas en el eje numérico). Recomiendo familiarizarse
aquí con más detalle
aquí y
aquí .
Requisitos:
- El lenguaje de programación python 3.x (el código provisto en el artículo fue probado en python 3.5)
- biblioteca sympy , se puede instalar a través del terminal (consola):
pip install sympy
El procedimiento para resolver el problema:
- Conectamos bibliotecas
from fractions import Fraction import re from typing import Iterable from random import random import sympy
La conexión de fracciones es opcional, usaremos Fracción para almacenar números reales en forma de fracción (para minimizar la pérdida de precisión). Usaremos la biblioteca re para analizar cadenas y generar automáticamente una lista de variables de caracteres.
El uso de la biblioteca de mecanografía es opcional; lo usamos para indicar explícitamente los tipos de parámetros de función. La biblioteca aleatoria se utilizará para generar valores de prueba para variables difusas. sympy es una gran biblioteca para el cálculo de caracteres en Python, con ella trabajaremos con la cadena de expresión misma.
- Describimos la clase de números triangulares difusos y operaciones en ellos. En este ejemplo, tres operaciones son suficientes (suma, resta y división). Introduciremos operaciones utilizando la sobrecarga de métodos "mágicos" de la clase correspondiente:
class FuzzyTriangular(object): """ FuzzyTriangular""" def __init__(self, floatdigit = None, ABC = None, CAB = None, CDD = None): super(FuzzyTriangular, self).__init__() if ABC or floatdigit: if isinstance(floatdigit, (int, float)): self._a = Fraction(floatdigit)
Las formas de representación de números triangulares difusos pueden ser diferentes, no profundizaremos. En el código presentado, prestaremos atención a los métodos __add__ (operador de suma), __sub__ (operador de resta), __mul__ (operador de multiplicación). Si intenta agregar un número real a un número triangular difuso, se convertirá en un número triangular difuso. Una situación similar con una tupla o una lista de números reales: los primeros tres números se percibirán como un triángulo difuso (y también se convertirán a la clase Triangular difuso). El método __pos__ anula el operador unario "+". El método __neg__ es un "-" unario. El método __eq__ anula el operador ==. Si lo desea, también puede redefinir operaciones tales como:
- división
- exponenciación
- módulo de número
- comparaciones (más / menos, más o igual / menos o igual)
- escalarización (conversión a int, flotante, números complejos, redondeo)
- inversión y otros ...
Puede verificar la idoneidad de las operaciones ingresadas con un pequeño conjunto de pruebas, por ejemplo, tales como:
ZERO = FuzzyTriangular((0,0,0)) ONE = FuzzyTriangular((1,1,1)) A = FuzzyTriangular((0.3,0.5,0.9)) B = FuzzyTriangular((0.2,0.4,0.67)) C = FuzzyTriangular((0,0.33,0.72)) print('ZERO = '+str(ZERO)) print('ONE = '+str(ONE)) print('A = '+str(A)) print('B = '+str(B)) print('C = '+str(C))
Estas operaciones de verificación de suma, división y multiplicación se especifican en el código y se realizan de acuerdo con la redefinición de los métodos "mágicos". Nos gustaría poder llevar a cabo las mismas operaciones utilizando variables de símbolos en expresiones previamente desconocidas. Esto requiere la introducción de varias funciones auxiliares. - Introducimos funciones auxiliares:
def symbols_from_expr(expr_str: str, pattern=r"[A-Za-z]\d{,2}") -> tuple: """ """ symbols_set = set(re.findall(pattern, expr_str)) symbols_set = sorted(symbols_set) symbols_list = tuple(sympy.symbols(symbols_set)) return symbols_list
Utilizaremos esta función para buscar variables de caracteres en una cadena de expresión (la plantilla predeterminada es un carácter de la A a la Z o de la a a la z y un número entero después de 2 caracteres de largo (o la ausencia de un número). def expr_subs(expr_str: str, symbols: Iterable, values: Iterable): """ values symbols - expr_str""" expr = sympy.sympify(expr_str) func = sympy.lambdify(tuple(symbols), expr, 'sympy') return func(*values)
Esta función le permite calcular el valor de una expresión de cadena con sustitución en lugar de variables de variables simbólicas de cualquier tipo válido (si las operaciones contenidas en la expresión de cadena se anulan). Esto es posible gracias a la función sympy.lambdify, que convierte una expresión sympy en una función lambda que acepta métodos "mágicos". Una condición importante para que la función funcione correctamente es el orden correcto de los elementos en símbolos y valores (correspondencia de símbolos y valores sustituidos).- Cada vez que crea una función lambda es costoso. Si se requiere el uso múltiple de la misma expresión, se recomienda utilizar las dos funciones siguientes:
def lambda_func(expr_str: str, symbols: Iterable) -> callable: """ -, - expr_str symbols""" expr = sympy.sympify(expr_str) func = sympy.lambdify(tuple(symbols), expr, 'sympy') return func def func_subs(expr_func: callable, values: Iterable): """ - expr_func values""" return expr_func(*values)
El primero devuelve la función lambda en sí, y el segundo le permite calcular los valores resultantes mediante la sustitución de una lista de valores. Una vez más, la atención se centra en el hecho de que los valores utilizados no tienen que ser números difusos triangulares.
- Leemos la línea de fórmula del archivo
with open('expr.txt', 'r') as file: expr_str = file.read() print('expr_str', expr_str)
Algo como esto se puede usar como la fórmula de línea para el archivo expr.txt:
p36*q67*p57*p26*p25*p13*q12*q15 + + p36*q67*p47*p26*p24*p13*q12 + + p67*q57*p26*p25*q12*p15 + + q57*p47*p25*p24*q12*p15 + + p57*p25*p12*q15 + + p36*p67*p13 + + p67*p26*p12 + + p47*p24*p12 + + p57*p15 - - p57*p47*p24*p12*p15 - - p67*p47*p26*p24*p12 - - p67*p57*p26*p12*p15 + + p67*p57*p47*p26*p24*p12*p15 - - p36*p67*p26*p13*p12 - - p36*p67*p47*p24*p13*p12 - - p36*p67*p57*p13*p15 + + p36*p67*p57*p47*p24*p13*p12*p15 + + p36*p67*p47*p26*p24*p13*p12 + + p36*p67*p57*p26*p13*p12*p15 - - p36*p67*p57*p47*p26*p24*p13*p12*p15 - - p36*p67*p57*p25*p13*p12*q15 - - p67*p57*p26*p25*p12*q15 - - p57*p47*p25*p24*p12*q15 + + p67*p57*p47*p26*p25*p24*p12*q15 + + p36*p67*p57*p26*p25*p13*p12*q15 + + p36*p67*p57*p47*p25*p24*p13*p12*q15 - - p36*p67*p57*p47*p26*p25*p24*p13*p12*q15 - - p36*p67*q57*p47*q26*p25*p24*p13*q12*p15 - - p67*q57*p47*p26*p25*p24*q12*p15 - - p36*p67*q57*p26*p25*p13*q12*p15 - - p36*q67*q57*p47*p26*p25*p24*p13*q12*p15 - - p36*q67*p57*p47*p26*p24*p13*q12*p15 - - p36*q67*p57*p47*p26*p25*p24*p13*q12*q15
- Obtenemos las variables de caracteres de la expresión de cadena:
symbols = symbols_from_expr(expr_str) print('AutoSymbols', symbols)
- Generamos números triangulares aleatorios de prueba:
values = tuple([FuzzyTriangular(sorted([random(),random(),random()]))\ for i in range(len(symbols))])
Se requiere la clasificación de valores aleatorios para que coincida con el orden de los valores de la izquierda "0", centro y derecha "0". - Convierta la cadena de fórmula en una expresión:
func = lambda_func(expr_str, symbols) print('func', '=', func)
- Calculamos el valor de la fórmula usando la función lambda (usamos func_subs y expr_subs para asegurarnos de que los resultados coincidan):
print('func_subs', '=', func_subs(func, values)) print('expr_subs', '=', expr_subs(expr_str, symbols, values))
Ejemplo de salida:
expr_str p36*q67*p57*p26*p25*p13*q12*q15 + + p36*q67*p47*p26*p24*p13*q12 + + p67*q57*p26*p25*q12*p15 + + q57*p47*p25*p24*q12*p15 + + p57*p25*p12*q15 + + p36*p67*p13 + + p67*p26*p12 + + p47*p24*p12 + + p57*p15 - - p57*p47*p24*p12*p15 - - p67*p47*p26*p24*p12 - - p67*p57*p26*p12*p15 + + p67*p57*p47*p26*p24*p12*p15 - - p36*p67*p26*p13*p12 - - p36*p67*p47*p24*p13*p12 - - p36*p67*p57*p13*p15 + + p36*p67*p57*p47*p24*p13*p12*p15 + + p36*p67*p47*p26*p24*p13*p12 + + p36*p67*p57*p26*p13*p12*p15 - - p36*p67*p57*p47*p26*p24*p13*p12*p15 - - p36*p67*p57*p25*p13*p12*q15 - - p67*p57*p26*p25*p12*q15 - - p57*p47*p25*p24*p12*q15 + + p67*p57*p47*p26*p25*p24*p12*q15 + + p36*p67*p57*p26*p25*p13*p12*q15 + + p36*p67*p57*p47*p25*p24*p13*p12*q15 - - p36*p67*p57*p47*p26*p25*p24*p13*p12*q15 - - p36*p67*q57*p47*q26*p25*p24*p13*q12*p15 - - p67*q57*p47*p26*p25*p24*q12*p15 - - p36*p67*q57*p26*p25*p13*q12*p15 - - p36*q67*q57*p47*p26*p25*p24*p13*q12*p15 - - p36*q67*p57*p47*p26*p24*p13*q12*p15 - - p36*q67*p57*p47*p26*p25*p24*p13*q12*q15 AutoSymbols (p12, p13, p15, p24, p25, p26, p36, p47, p57, p67, q12, q15, q26, q57, q67) func = <function <lambda> at 0x06129C00> func_subs = (-0.391482058715, 0.812813114469, 2.409570627378) expr_subs = (-0.391482058715, 0.812813114469, 2.409570627378) [Finished in 1.5s]
El tutorial ha terminado. ¡Espero que encuentres algo útil aquí!
PD: la "característica" principal del enfoque descrito es la capacidad de ir más allá de los tipos estándar de variables y operaciones en python y sympy. Al declarar su clase y sobrecargar los métodos "mágicos", puede calcular expresiones matemáticas previamente desconocidas utilizando sympy (creando funciones lambda que aceptan operaciones y tipos estándar y de usuario).
Gracias por su atencion!