Olá Habr! Hoje é um tutorial em miniatura sobre como analisar uma string com uma expressão matemática e calculá-la usando números triangulares difusos. Com as alterações apropriadas no código, o tutorial funcionará com outras variáveis "personalizadas". Ajuda: números triangulares nebulosos - um caso especial de números nebulosos (variáveis nebulosas no eixo numérico). Eu recomendo me familiarizar
aqui com mais detalhes
aqui e
aqui .
Requisitos:
- A linguagem de programação python 3.x (o código fornecido no artigo foi testado no python 3.5)
- biblioteca sympy , pode ser instalado através do terminal (console):
pip install sympy
O procedimento para resolver o problema:
- Nós conectamos bibliotecas
from fractions import Fraction import re from typing import Iterable from random import random import sympy
As frações de conexão são opcionais; usaremos Fraction para armazenar números reais na forma de uma fração (para minimizar a perda de precisão). Usaremos a re-biblioteca para analisar seqüências de caracteres e gerar automaticamente uma lista de variáveis de caracteres.
O uso da biblioteca de digitação é opcional; usamos para indicar explicitamente os tipos de parâmetros de função. A biblioteca aleatória será usada para gerar valores de teste para variáveis difusas. O sympy é uma ótima biblioteca para cálculos de caracteres em Python, com ele trabalharemos com a própria string de expressão.
- Descrevemos a classe de números triangulares nebulosos e operações sobre eles. Neste exemplo, três operações são suficientes (adição, subtração e divisão). Introduziremos operações usando a sobrecarga de métodos "mágicos" da classe correspondente:
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)
As formas de representação de números triangulares difusos podem ser diferentes, não iremos fundo. No código apresentado, prestaremos atenção aos métodos __add__ (operador de adição), __sub__ (operador de subtração), __mul__ (operador de multiplicação). Se você tentar adicionar um número real a um número triangular nebuloso, ele será convertido em um número triangular nebuloso. Uma situação semelhante com uma tupla ou uma lista de números reais - os três primeiros números serão percebidos como um triangular nebuloso (e também convertido na classe FuzzyTriangular). O método __pos__ substitui o operador unário "+". O método __neg__ é um "-" unário. O método __eq__ substitui o operador ==. Se desejar, você também pode redefinir operações como:
- divisão
- exponenciação
- módulo numérico
- comparações (mais / menos, mais ou igual / menos ou igual)
- escalarização (conversão para int, float, números complexos, arredondamento)
- inversão e outros ...
Você pode verificar a adequação das operações inseridas com um pequeno conjunto de testes, por exemplo:
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))
Essas operações de verificação de adição, divisão e multiplicação são especificadas no código e executadas de acordo com a redefinição dos métodos "mágicos". Gostaríamos de poder realizar as mesmas operações usando variáveis de símbolo em expressões anteriormente desconhecidas. Isso requer a introdução de várias funções auxiliares. - Introduzimos funções 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
Usaremos essa função para procurar variáveis de caracteres em uma sequência de expressões (o modelo padrão é um caractere de A a Z ou de a a z e um número inteiro depois de até 2 caracteres (ou a ausência de um 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 função permite calcular o valor de uma expressão de sequência com substituição em vez de variáveis simbólicas variáveis de qualquer tipo válido (se as operações contidas na própria expressão de sequência forem substituídas). Isso é possível graças à função sympy.lambdify, que converte uma expressão sympy em uma função lambda que aceita métodos "mágicos". Uma condição importante para a função funcionar corretamente é a ordem correta dos elementos em símbolos e valores (correspondência de símbolos e valores substituídos).- Cada vez que a criação de uma função lambda é cara. Se for necessário o uso múltiplo da mesma expressão, é recomendável usar as duas funções a seguir:
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)
O primeiro retorna a própria função lambda e o segundo permite calcular os valores resultantes substituindo uma lista de valores. Mais uma vez, a atenção está focada no fato de que os valores utilizados não precisam ser números nebulosos triangulares.
- Lemos a linha da fórmula do arquivo
with open('expr.txt', 'r') as file: expr_str = file.read() print('expr_str', expr_str)
Algo assim pode ser usado como fórmula de linha para o arquivo 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
- Nós obtemos as variáveis de caractere da expressão string:
symbols = symbols_from_expr(expr_str) print('AutoSymbols', symbols)
- Geramos números triangulares aleatórios de teste:
values = tuple([FuzzyTriangular(sorted([random(),random(),random()]))\ for i in range(len(symbols))])
A classificação de valores aleatórios é necessária para corresponder à ordem dos valores do "0" esquerdo, centro e direito "0". - Converta a string da fórmula em uma expressão:
func = lambda_func(expr_str, symbols) print('func', '=', func)
- Calculamos o valor da fórmula usando a função lambda (usamos func_subs e expr_subs para garantir que os resultados correspondam):
print('func_subs', '=', func_subs(func, values)) print('expr_subs', '=', expr_subs(expr_str, symbols, values))
Exemplo de saída:
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]
O tutorial acabou. Espero que você encontre algo útil aqui!
PS: o principal "recurso" da abordagem descrita é a capacidade de ir além dos tipos padrão de variáveis e operações neles para python e sympy. Ao declarar sua classe e sobrecarregar os métodos "mágicos", é possível calcular expressões matemáticas anteriormente desconhecidas usando o sympy (criando funções lambda que aceitam tipos e operações padrão e de usuário).
Obrigado pela atenção!