Calculer des expressions symboliques avec des nombres triangulaires flous en python

Bonjour, Habr! Aujourd'hui est un tutoriel miniature sur la façon d'analyser une chaîne avec une expression mathématique et de la calculer en utilisant des nombres triangulaires flous. Avec les modifications appropriées du code, le didacticiel fonctionnera avec d'autres variables "personnalisées". Aide: nombres triangulaires flous - un cas spécial de nombres flous (variables floues sur l'axe numérique). Je recommande de vous familiariser ici plus en détail ici et ici .

Prérequis:

  • Le langage de programmation python 3.x (le code fourni dans l'article a été testé sur python 3.5)
  • bibliothèque sympy , peut être installé via le terminal (console):

    pip install sympy 

La procédure pour résoudre le problème:

  1. Nous connectons les bibliothèques

     from fractions import Fraction import re from typing import Iterable from random import random import sympy 

    Les fractions de connexion sont facultatives, nous utiliserons Fraction pour stocker des nombres réels sous la forme d'une fraction (afin de minimiser la perte de précision). Nous utiliserons la bibliothèque re pour analyser les chaînes et générer automatiquement une liste de variables de caractères.

    L'utilisation de la bibliothèque de typage est facultative; nous l'utilisons pour indiquer explicitement les types de paramètres de fonction. La bibliothèque aléatoire sera utilisée pour générer des valeurs de test pour les variables floues. sympy est une excellente bibliothèque pour les calculs de caractères en Python, avec elle, nous travaillerons avec la chaîne d'expression elle-même.
  2. Nous décrivons la classe des nombres triangulaires flous et leurs opérations. Dans cet exemple, trois opérations suffisent (addition, soustraction et division). Nous allons introduire des opérations utilisant la surcharge des méthodes "magiques" de la classe correspondante:

     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) 

    Les formes de représentation des nombres triangulaires flous peuvent être différentes, nous n'irons pas en profondeur. Dans le code présenté, nous prêtons attention aux méthodes __add__ (opérateur d'addition), __sub__ (opérateur de soustraction), __mul__ (opérateur de multiplication). Si vous essayez d'ajouter un nombre réel à un nombre triangulaire flou, il sera converti en nombre triangulaire flou. Une situation similaire avec un tuple ou une liste de nombres réels - les trois premiers nombres seront perçus comme triangulaires flous (et également convertis en classe FuzzyTriangular). La méthode __pos__ remplace l'opérateur unaire "+". La méthode __neg__ est un "-" unaire. La méthode __eq__ remplace l'opérateur ==. Si vous le souhaitez, vous pouvez en outre redéfinir des opérations telles que:

    • division
    • exponentiation
    • module numérique
    • comparaisons (plus / moins, plus ou égal / moins ou égal)
    • scalarisation (transtypé en entier, flottant, nombres complexes, arrondi)
    • inversion et autres ...

    Vous pouvez vérifier l'adéquation des opérations saisies avec un petit ensemble de tests, par exemple, tels que:

     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) 

    Ces opérations de vérification d'addition, de division et de multiplication sont précisées dans le code et effectuées selon la redéfinition des méthodes "magiques". Nous aimerions pouvoir effectuer les mêmes opérations en utilisant des variables de symboles dans des expressions inconnues auparavant. Cela nécessite l'introduction de plusieurs fonctions auxiliaires.
  3. Nous introduisons des fonctions auxiliaires:

    •  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 
      Nous utiliserons cette fonction pour rechercher des variables de caractère dans une chaîne d'expression (le modèle par défaut est un caractère de A à Z ou de a à z et un entier après 2 caractères maximum (ou l'absence de nombre).
    •  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) 

      Cette fonction vous permet de calculer la valeur d'une expression de chaîne avec substitution au lieu des variables de variables symboliques de tout type valide (si les opérations contenues dans l'expression de chaîne elle-même sont remplacées). Ceci est possible grâce à la fonction sympy.lambdify, qui convertit une expression sympy en une fonction lambda qui accepte les méthodes "magiques". Une condition importante pour que la fonction fonctionne correctement est l'ordre correct des éléments dans les symboles et les valeurs (correspondance des symboles et des valeurs substituées).
    • Chaque fois que créer une fonction lambda coûte cher. Si l'utilisation multiple de la même expression est requise, il est recommandé d'utiliser les deux fonctions suivantes:

       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) 

      La première renvoie la fonction lambda elle-même, et la seconde vous permet de calculer les valeurs résultantes en substituant une liste de valeurs. Encore une fois, l'attention est concentrée sur le fait que les valeurs utilisées ne doivent pas nécessairement être des nombres flous triangulaires.

  4. Nous lisons la ligne de formule du fichier

     with open('expr.txt', 'r') as file: expr_str = file.read() print('expr_str', expr_str) 

    Quelque chose comme ça peut être utilisé comme formule de ligne pour le fichier 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. Nous obtenons les variables de caractères de l'expression de chaîne:

     symbols = symbols_from_expr(expr_str) print('AutoSymbols', symbols) 
  6. Nous générons des nombres triangulaires aléatoires de test:

     values = tuple([FuzzyTriangular(sorted([random(),random(),random()]))\ for i in range(len(symbols))]) 

    Le tri des valeurs aléatoires est nécessaire pour correspondre à l'ordre des valeurs du «0» gauche, du centre et du «0» droit.
  7. Convertissez la chaîne de formule en une expression:

     func = lambda_func(expr_str, symbols) print('func', '=', func) 
  8. Nous calculons la valeur de la formule en utilisant la fonction lambda (nous utilisons func_subs et expr_subs pour nous assurer que les résultats correspondent):

     print('func_subs', '=', func_subs(func, values)) print('expr_subs', '=', expr_subs(expr_str, symbols, values)) 

Exemple de sortie:

 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] 

Le tutoriel est terminé. J'espère que vous trouverez quelque chose d'utile ici!

PS: la principale "caractéristique" de l'approche décrite est la possibilité d'aller au-delà des types standard de variables et d'opérations sur celles-ci pour python et sympy. En déclarant votre classe et en surchargeant les méthodes "magiques", vous pouvez calculer des expressions mathématiques auparavant inconnues à l'aide de sympy (création de fonctions lambda qui acceptent à la fois les types et opérations standard et utilisateur).

Merci de votre attention!

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


All Articles