Pythonでファジー三角数を使用してシンボリック式を計算する

こんにちは、Habr! 今日は、数式を使用して文字列を解析し、ファジー三角形の数値を使用して計算する方法に関するミニチュアチュートリアルです。 コードに適切な変更を加えれば、チュートリアルは他の「カスタム」変数で動作します。 ヘルプ:ファジィ三角数-ファジィ数の特殊なケース(数値軸上のファジィ変数)。 ここここで詳しく知ることをお勧めします



要件:





問題を解決する手順:



  1. 図書館をつなぐ



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





    分数の接続はオプションです。分数を使用して、実数を分数の形式で保存します(精度の損失を最小限に抑えるため)。 reライブラリを使用して、文字列を解析し、シンボル変数のリストを自動的に生成します。



    タイプライブラリの使用はオプションであり、関数パラメータのタイプを明示的に示すために使用します。 ランダムライブラリは、ファジー変数のテスト値を生成するために使用されます。 sympyは、Pythonで文字を計算するための優れたライブラリであり、式文字列自体を使用して作業します。

  2. ファジィ三角数のクラスとその操作について説明します。 この例では、3つの操作で十分です(加算、減算、除算)。 対応するクラスの「マジック」メソッドのオーバーロードを使用した操作を紹介します。



     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)
          
          





    ファジー三角数の表現形式は異なっていてもかまいませんが、深くはいきません。 提示されたコードでは、メソッド__add__(加算演算子)、__ sub__(減算演算子)、__ mul__(乗算演算子)に注意を払います。 ファジー三角数に実数を追加しようとすると、ファジー三角数に変換されます。 タプルまたは実数のリストを使用した同様の状況-最初の3つの数値は、ファジー三角形として認識されます(また、FuzzyTriangularクラスに変換されます)。 __pos__メソッドは、単項演算子「+」をオーバーライドします。 __neg__メソッドは単項「-」です。 __eq__メソッドは==演算子をオーバーライドします。 必要に応じて、次のような操作をさらに再定義できます。



    • 分割
    • べき乗
    • 数モジュラス
    • 比較(多かれ少なかれ、多かれ少なかれ/多かれ少なかれ)
    • スカラー化(int、float、複素数、丸めにキャスト)
    • 反転など...


    入力した操作の妥当性は、たとえば次のような一連のテストで確認できます。



     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)
          
          





    これらの加算、除算、および乗算の検証操作はコードで指定され、「マジック」メソッドの再定義に従って実行されます。 以前は未知の式でシンボル変数を使用して同じ操作を実行できるようにしたいと思います。 これには、いくつかの補助機能の導入が必要です。
  3. 補助機能を紹介します。



    •  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
            
            



      この関数を使用して、式文字列内の文字変数を検索します(デフォルトのテンプレートは、AからZまたはaからzの文字と、その後の2文字までの整数(または数字がない場合)です。
    •  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)
            
            





      この関数を使用すると、任意の有効なタイプのシンボリック変数変数の代わりに、文字列式の値を計算できます(文字列式自体に含まれる操作がオーバーライドされる場合)。 これはsympy.lambdify関数のおかげで可能です。この関数はsympy式を「マジック」メソッドを受け入れるラムダ関数に変換します。 関数が適切に機能するための重要な条件は、シンボルと値の要素の正しい順序です(シンボルと置換値の対応)。
    • ラムダ関数を作成するたびにコストがかかります。 同じ式を複数回使用する必要がある場合は、次の2つの関数を使用することをお勧めします。



       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)
            
            





      最初の関数はラムダ関数自体を返し、2番目の関数は値のリストを置き換えることで結果の値を計算できます。 繰り返しますが、使用される値が三角ファジィ数である必要はないという事実に注意が集中されます。


  4. ファイルから数式行を読み取ります



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





    このようなものは、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. 文字列式から文字変数を取得します。



     symbols = symbols_from_expr(expr_str) print('AutoSymbols', symbols)
          
          



  6. テスト用のランダムな三角数を生成します:



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





    左の「0」、中央、右の「0」の値の順序を一致させるには、ランダムな値を並べ替える必要があります。
  7. 式文字列を式に変換します。



     func = lambda_func(expr_str, symbols) print('func', '=', func)
          
          



  8. ラムダ関数を使用して式の値を計算します(func_subsとexpr_subsを使用して、結果が一致することを確認します)。



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





出力例:



 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]
      
      





チュートリアルは終了しました。 ここで何かお役に立てば幸いです!



PS:説明されたアプローチの主な「トリック」は、Pythonおよびsympyの標準タイプの変数およびそれらに対する操作を超える機能です。 クラスを宣言し、「マジック」メソッドをオーバーロードすることで、sympy(標準とユーザーの両方のタイプと操作を受け入れるラムダ関数を作成)を使用して、以前は未知の数式を計算できます。



ご清聴ありがとうございました!



All Articles