エレファント・ビジュアライザー調査記録

ビジュアルプログラミングで数式の変形を表すことを考えていくブロクです。

人工知能的代数学(9)

マグマの計算(3)

前回の「マグマの左単位元と右単位元」では、等式の集合から推移律によって新しい等式の集合を作っていましたが、等式の数が多くなりすぎるので、「エレファントな群とリー代数(1) - エレファント・ビジュアライザー調査記録」と同様に一つの式から等式によって変形していくようにしました。結果的には「エレファントな群とリー代数(1) - エレファント・ビジュアライザー調査記録」のプログラムを Python で書き換えたようになっています。こうするとブログで書けるぐらいの数になりました。

class Exp:
    pass

# Multiplication クラス
class Multiplication(Exp):
    def __init__(self, left, right):
        self.left = left
        self.right = right
        self.operator = " * "
        self.tex_operator = " "

    def __str__(self):
        if isinstance(self.left, Multiplication):
            left_str = "(" + str(self.left) + ")"
        else:
            left_str = str(self.left)
        if isinstance(self.right, Multiplication):
            right_str = "(" + str(self.right) + ")"
        else:
            right_str = str(self.right)
        return left_str + self.operator + right_str

    def __repr__(self):
        return f"Multiplication({self.left}, {self.right})"

    def texstr(self):
        if isinstance(self.left, Multiplication):
            left_str = "(" + self.left.texstr() + ")"
        else:
            left_str = self.left.texstr()
        if isinstance(self.right, Multiplication):
            right_str = "(" + self.right.texstr() + ")"
        else:
            right_str = self.right.texstr()
        return left_str + self.tex_operator + right_str

    def visit_sub_expressions(self, sub_expressions):
        sub_expressions.append(self)
        self.left.visit_sub_expressions(sub_expressions)
        self.right.visit_sub_expressions(sub_expressions)

    def count_sub_expressions(self):
        count1 = self.left.count_sub_expressions()
        count2 = self.right.count_sub_expressions()
        return count1 + count2 + 1

    def replace_sub_expression(self, index_here, index, exp):
        if index_here == index:
            return exp
        else:
            exp1 = self.left.replace_sub_expression(index_here + 1, index, exp)
            count1 = self.left.count_sub_expressions()
            exp2 = self.right.replace_sub_expression(index_here + count1 + 1, index, exp)
            return Multiplication(exp1, exp2)

# Variable クラス
class Variable(Exp):
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return self.name

    def __repr__(self):
        return f"Variable({self.name})"

    def texstr(self):
        return self.name

    def visit_sub_expressions(self, sub_expressions):
        sub_expressions.append(self)

    def count_sub_expressions(self):
        return 1

    def replace_sub_expression(self, index_here, index, exp):
        if index_here == index:
            return exp
        else:
            return self

# Constant クラス
class Constant(Exp):
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return self.name

    def __repr__(self):
        return f"Constant({self.name})"

    def texstr(self):
        return self.name

    def visit_sub_expressions(self, sub_expressions):
        sub_expressions.append(self)

    def count_sub_expressions(self):
        return 1

    def replace_sub_expression(self, index_here, index, exp):
        if index_here == index:
            return exp
        else:
            return self

class SubExpressions:
    def __init__(self):
        self.sub_expressions = []

    def append(self, node):
            self.sub_expressions.append(node)

class Equation:
    def __init__(self, left, right):
        self.left = left
        self.right = right

    def __str__(self):
        return str(self.left) + " = " + str(self.right)

    def __repr__(self):
        return f"Equation({self.left} = {self.right})"

    def texstr(self):
        return self.left.texstr() + " = " + self.right.texstr()
    
    def reverse(self):
        return Equation(self.right, self.left)
    
from lark import Transformer, v_args

@v_args(inline=True)  # 引数をリストではなく個別の要素として受け取る
class CustomTransformer(Transformer):
    def equation(self, left, right):
        return Equation(left, right)  # 等式

    def multiplication(self, left, right):
        return Multiplication(left, right)  # Multiplication オブジェクトとして返す

    def expression(self, item):
        return item

    def term(self, item):
        return item

    def atom(self, item):
        return item

    def VARIABLE(self, name):
        return Variable(str(name))  # Variable オブジェクトとして返す

    def CONSTANT(self, name):
        return Constant(str(name))  # Constant オブジェクトとして返す
    
# グラマー定義
grammar = """
?start: equation

equation: expression "=" expression

expression: term
    | expression "*" term -> multiplication  # 二項演算子

term: atom
    | "(" expression ")"  # 括弧を使った演算

atom: VARIABLE
    | CONSTANT  # 変数と定数

VARIABLE: /[A-Z][a-zA-Z_]*/  # 大文字で始まる変数
CONSTANT: /[a-z][a-zA-Z_]*/  # 小文字で始まる定数

%ignore " "  # 空白を無視
"""

class Unifier:
    def __init__(self):
        self.substitutions = {}  # 置き換えのマップ

    def unify(self, expr1, x1, expr2, x2):
        if isinstance(expr1, Variable):
            if (expr1.name, x1) in self.substitutions:
                val_expr, val_x = self.substitutions[(expr1.name, x1)]
                return self.unify(val_expr, val_x, expr2, x2)
            else:
                self.substitutions[(expr1.name, x1)] = (expr2, x2)
                return True
        elif isinstance(expr2, Variable):
            return self.unify(expr2, x2, expr1, x1)
        elif isinstance(expr1, Constant):
            return isinstance(expr2, Constant) and expr1.name == expr2.name
        elif isinstance(expr1, Multiplication):
            return isinstance(expr2, Multiplication) and self.unify(expr1.left, x1, expr2.left, x2) and self.unify(expr1.right, x1, expr2.right, x2)
        else:
            return False

    def value(self, expr, x):
        if isinstance(expr, Variable):
            if (expr.name, x) in self.substitutions:
                val_expr, val_x = self.substitutions[(expr.name, x)]
                return self.value(val_expr, val_x)
            else:
                return expr
        elif isinstance(expr, Multiplication):
            return Multiplication(self.value(expr.left, x), self.value(expr.right, x))
        else:
            return expr

class TransitiveTransformer:
    def __init__(self, equations, exp):
        self.equations = equations
        self.expressions = [exp]
        self.history = [[exp]]

    def compose_transitive(self, expr1, eq2, his):
        # 式 expr1 と等式 eq2 を組み合わせる
        # expr1 (の一部)と eq2 の左辺が一致するとき
        # expr1 = eq2.left = eq2.right と変形する
        expr2 = eq2.left
        sub_expressions = SubExpressions()
        expr1.visit_sub_expressions(sub_expressions)
        for index, sub_exp1 in enumerate(sub_expressions.sub_expressions):
            unifier = Unifier()
            if unifier.unify(sub_exp1, 1, expr2, 2):
                # expr1 の一部と eq2 の左辺が一致
                # expr1 の一部を eq2 の右辺で置き換える
                exp_new2 = expr1.replace_sub_expression(0, index, eq2.right)
                exp2 = unifier.value(exp_new2, 2)
                self.expressions.append(exp2)
                self.history.append(his + [exp2])

    def compose_transitive_all(self):
        expressions = self.expressions
        history = self.history
        self.expressions = []
        self.history = []
        for index, exp in enumerate(expressions):
            for eq in self.equations:
                self.compose_transitive(exp, eq, history[index])
                self.compose_transitive(exp, eq.reverse(), history[index])

    def transitive_degree(self, deg):
        for n in range(1, deg + 1):
            transitive_transformer.compose_transitive_all()
            print(f"結果({n}回目)のリスト")
            for his in transitive_transformer.history:
                print(" = ".join([e.texstr() for e in his]))

# 構文解析
from lark import Lark
parser = Lark(grammar, parser="lalr")
custom_transformer = CustomTransformer()

eq1 = custom_transformer.transform(parser.parse("X*e_r=X"))
eq2 = custom_transformer.transform(parser.parse("e_l*X=X"))

# 左辺が最初の式(右辺は使わない)
exp = custom_transformer.transform(parser.parse("e_l=e_r")).left

# 入力として等式のリストを提供
equations = [eq1, eq2]
print("元の等式のリスト")
for eq in equations:
    print(eq.texstr())

transitive_transformer = TransitiveTransformer(equations, exp)

transitive_transformer.transitive_degree(2)