Calcular expresiones simbólicas con números triangulares difusos en python

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:

  1. 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.
  2. 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) # "0" self._b = Fraction(floatdigit) # ("1") self._c = Fraction(floatdigit) # "0" elif isinstance(floatdigit, (tuple,list)): if len(floatdigit) == 2: #    self._a = Fraction(floatdigit[0] - abs(floatdigit[1])) # "0" self._b = Fraction(floatdigit[0]) # ("1") self._c = Fraction(floatdigit[0] + abs(floatdigit[1])) # "0" else: #3  ,   3 self._a = Fraction(floatdigit[0]) # "0" self._b = Fraction(floatdigit[1]) # ("1") self._c = Fraction(floatdigit[2]) # "0" else: self._a = Fraction(ABC[0]) # "0" self._b = Fraction(ABC[1]) # ("1") self._c = Fraction(ABC[2]) # "0" self._center = self._b # self._alpha = self._b - self._a #    self._beta = self._c - self._b #    self._d = (self._alpha + self._beta)/2 self._delta = (self._beta - self._alpha)/2 elif CAB: self._center = Fraction(CAB[0]) # self._alpha = Fraction(CAB[1]) #    self._beta = Fraction(CAB[2]) #    self._d = (self._alpha + self._beta)/2 self._delta = (self._beta - self._alpha)/2 self._b = self._center # ("1") self._a = self._center - self._alpha # "0" self._c = self._center + self._beta # "0" elif CDD: self._center = Fraction(CDD[0]) # self._d = Fraction(CDD[1]) self._delta = Fraction(CDD[2]) self._alpha = self._d - self._delta #    self._beta = self._d + self._delta #    self._b = self._center # ("1") self._a = self._center - self._alpha # "0" self._c = self._center + self._beta # "0" else: raise Exception("No input data to create class") def __repr__(self): return str((round(float(self._a), 12), round(float(self._b), 12),\ round(float(self._c), 12))) def __CDD_add(self, other): center = self._center + other._center d = self._d + other._d delta = self._delta + other._delta return FuzzyTriangular(CDD = (center, d, delta)) def __CDD_sub(self, other): center = self._center - other._center d = self._d + other._d delta = self._delta - other._delta return FuzzyTriangular(CDD = (center, d, delta)) def __CDD_mul(self, other): center = self._center*other._center d = abs(self._center)*other._d + abs(other._center)*self._d delta = self._center*other._delta + other._center*self._delta return FuzzyTriangular(CDD = (center, d, delta)) def __add__(self, other): if isinstance(other, FuzzyTriangular): return self.__CDD_add(other) else: return self.__CDD_add(FuzzyTriangular(other)) def __sub__(self, other): if isinstance(other, FuzzyTriangular): return self.__CDD_sub(other) else: return self.__CDD_sub(FuzzyTriangular(other)) def __mul__(self,other): if isinstance(other, FuzzyTriangular): return self.__CDD_mul(other) else: return self.__CDD_mul(FuzzyTriangular(other)) def __pos__(self): return FuzzyTriangular(1)*self def __neg__(self): return FuzzyTriangular(-1)*self def __eq__(self, other): return (self._a == other._a) and (self._b == other._b) and \ (self._c == other._c) 

    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)) #some tests print('\n') print('A + B = ', A + B) print('A + B == B + A', A + B == B + A) #   print('A + C = ', A + C) print('A + C == C + A', A + C == C + A) print('B + C = ', B + C) print('B + C == C + B', B + C == C + B) print('A + B + C = ', A + B + C) print('(A + B) + C == A + (B + C) == (A + C) + B', \ (A + B) + C == A + (B + C) == (A + C) + B) print('C + 1 = ', C + 1) print('1 + C = ', ONE + C) print('\n') print('A - A =', A - A) print('A - A == 0', A - A == ZERO) print('A - B = ', A - B) print('B - A = ', B - A) #   "-"  "+" print('A - B == -(B - A)', A - B == -(B - A)) print('(A + B + C) - (A + B) = ', (A + B + C) - (A + B)) #    print('(A + B + C) - (A + B) == C', (A + B + C) - (A + B) == C) print('1 - A = ', ONE - A) print('A - 1 = ', A - 1) print('1 - A == -(A - 1)', ONE - A == -(A - 1)) print('\n') print('A*B == B*A', A*B == B*A) print('-1*C =', -ONE*C) print('-1*C == -C', -ONE*C == -C) print('-1*C == C*-1', -ONE*C == C*-1) print('C*-1 = ', C*-1) print('C*-1 =', C*-1) print('-C*1 == -C', -C*1 == -C) print('-C*1 =', -C*1) print('-C =', -C) print('C*-1 == -C', C*-1 == -C) print('(A + B)*C == A*C + B*C', (A + B)*C == A*C + B*C) print('(A - B)*C == A*C - B*C', (A - B)*C == A*C - B*C) print('A*C = ', A*C) print('B*C = ', B*C) print('-B*C = ', -B*C) print('-B*C == B*-C', -B*C == B*-C) print('B*C == -B*-C', B*C == -B*-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.
  3. 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.

  4. 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 
  5. Obtenemos las variables de caracteres de la expresión de cadena:

     symbols = symbols_from_expr(expr_str) print('AutoSymbols', symbols) 
  6. 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".
  7. Convierta la cadena de fórmula en una expresión:

     func = lambda_func(expr_str, symbols) print('func', '=', func) 
  8. 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!

Source: https://habr.com/ru/post/443104/


All Articles