📜 ⬆️ ⬇️

We calculate symbolic expressions with fuzzy triangular numbers in python

Hi, Habr! Today is a miniature tutorial on how to parse a string with a mathematical expression and calculate it using fuzzy triangular numbers. With appropriate code changes, the tutorial will fit to work with other “custom” variables. Reference: fuzzy triangular numbers - a special case of fuzzy numbers (fuzzy variables on the number axis). Read more recommend here and here .

Requirements:


The procedure for solving the problem:

  1. We connect libraries

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

    Connection of fractions is optional, we will use Fraction to store real numbers as a fraction (with the goal of minimal loss of accuracy). The re library will be used to parse a string and automatically generate a list of character variables.

    Using the typing library is optional; we use it to explicitly indicate the types of the parameters of functions. The random library will be used to form test values ​​for fuzzy variables. sympy is an excellent library for symbolic calculations in Python, with its help we will work with the string expression itself.
  2. We describe the class of fuzzy triangular numbers and operations on them. In this example, three operations (addition, subtraction and division) are enough. We will enter operations using the overload of the “magic” methods of the corresponding class:

     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) 

    Forms of representation of fuzzy triangular numbers may be different, we will not go deep. In the presented code we will pay attention to the methods __add__ (addition operator), __sub__ (subtraction operator), __mul__ (multiplication operator). If you try to add a real number to a fuzzy triangular number, it will be converted to a fuzzy triangular number. A similar situation with a tuple or a list of real numbers - the first three numbers will be perceived as fuzzy triangular (and also converted to the class FuzzyTriangular). The __pos__ method overrides the unary operator "+". The __neg__ method is the unary "-". The __eq__ method overrides the "==" operator. If desired, you can additionally override such operations as:

    • division
    • exponentiation
    • the absolute value of a number
    • comparisons (more / less, more or equal / less or equal)
    • scalarization (coercion to int, float, complex numbers, rounding)
    • inversion and others ...

    You can check the adequacy of the entered operations with a small set of tests, for example:

     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) 

    These check operations of addition, division and multiplication are specified in the code and are performed according to the redefinition of the “magic” methods. We would like to be able to perform the same operations using character variables in previously unknown expressions. To do this, you need to enter several auxiliary functions.
  3. We enter auxiliary functions:

    •  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 
      This function will be used to search for character variables in a string-expression (the default pattern is a character from A to Z or from a to z and an integer after it up to 2 characters long (or no number).
    •  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) 

      This function allows you to calculate the value of a string expression with a substitution instead of character variables of variables of any valid type (if the operations contained in the string expression itself are redefined for it). This is possible thanks to the sympy.lambdify function, which transforms the sympy expression into a lambda function that accepts "magic" methods. An important condition for the adequate function of the function is the correct order of the elements in the symbols and values ​​(matching symbols and substituted values).
    • Each time creating a lambda function is expensive. If multiple use of the same expression is required, it is recommended to use the following two functions:

       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) 

      The first returns the most lambda function, and the second allows you to calculate the resulting values ​​using a list of values. Once again, attention is focused on the fact that the values ​​used are not at all required to be triangular fuzzy numbers.

  4. We read the formula string from the file

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

    You can use something like this as a formula line for the expr.txt file:

     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. Pull out character variables from a string expression:

     symbols = symbols_from_expr(expr_str) print('AutoSymbols', symbols) 
  6. We generate test random triangular numbers:

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

    Sorting random values ​​is required to match the order of values ​​of the left "0", center and right "0".
  7. Convert the formula string to the expression:

     func = lambda_func(expr_str, symbols) print('func', '=', func) 
  8. We calculate the value of the formula using the lambda function (we use func_subs and expr_subs to make sure that the results match):

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

Example output:

 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] 

Tutorial over. I hope that you have found here something useful for yourself!

PS: the main "trick" of the described approach is the ability to go beyond the standard for python and sympy types of variables and operations on them. By declaring your class and reloading the “magic” methods, you can calculate previously unknown mathematical expressions with the help of sympy (creating lambda functions that accept both standard and custom types and operations).

Thanks for attention!

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


All Articles