대수식을 심볼릭 프로그래밍으로 풀기 위해

자료 구조로 만들기 진행

 

제곱, 수, 심볼 변수 정의하기

설명할 내용없이 단순함

class Power():
    def __init__(self, base, exponent):
        self.base = base
        self.exponent = exponent
    
class Number():
    def __init__(self, number):
        self.number = number

class Variable():
    def __init__(self, symbol):
        self.symbol = symbol

Power(Variable("x"), Number(2))

 

 

곱 클래스 만들기

실제 연산하는 내용은 없지만 단순하게 이것도 구현

이건 3 x^2

class Product():
    def __init__(self, exp1, exp2):
        self.exp1 = exp1
        self.exp2 = exp2

Product(Number(3), Power(Variable("x"), Number(2)))

 

 

약간 복잡한 함수 정의하기

Function은 함수

Apply는 함수와 해당 함수 인자

다음 함수식은

(3x^2 + x) * sin(x)

class Sum():
    def __init__(self, *exps):
        self.exps = exps
    
class Function():
    def __init__(self, name):
        self.name = name

class Apply():
    def __init__(self, function, argument):
        self.function = function
        self.argument = argument

f_expression = Product(
    Sum(
        Product(
            Number(3),
            Power(
                Variable("x"),
                Number(2)
            )
        ),
        Variable("x")
    ),
    Apply(
        Function("sin"),
        Variable('x')
    )
)

 

cos(x^3 - 5)는 다음과 같이 표현

 

Apply(
    Function("cos"),
    Sum(
        Power(
            Variable("x"),
            Number("3")
        ),
        Number(-5)
    )
)

 

 

 

로그 연산 표현해보기

ln(y^z)는 원래 쉽게 표현할수 있으나

지금 구현한 함수로는 다음과 같이 표현가능

from math import log
def f(y, z):
    return log(y ** z)

Apply(
    Function("ln"),
    Power(
        Variable("y"),
        Variable("z")
    )
)

 

 

 

 

 

 

나눗셈 구현하기

(a + b) / 2같은 경우는 다음과 같이 표현

class Quotient():
    def __init__(self, numerator, denominator):
        self.numerator = numerator
        self.denominator = denominator

Quotient(
    Sum(
        Variable("a"),
        Variable("b")
    ),
    Number(2)
)

 

 

차 연산 구현해서 이차 방정식 판별식 구현하기

이차 방정식 y = ax^2 + bx + c의 판별식 D = b  ^ 2 - 4 ac를 다음과 같이 표현 가능

 

class Difference():
    def __init__(self, exp1, exp2):
        self.exp1 = exp1
        self.exp2 = exp2

Difference(
    Power(
        Variable("b"),
        Number(2)
    ),
    Product(
        Number(4),
        Product(
            Variable("a"),
            Variable("c")
        )
    )
)

 

 

차연산 표현하기

차연산도 간단하게 이런식으로

class Negative():
    def __init__(self, exp):
        self.exp = exp

Negative(
    Sum(
        Power(
            Variable("x"),
            Number(2)
        ),
        Variable("y")
    )
)

 

 

 

 

2차 방정식 근의 공식 표현하기

근의 공식은 길어지지만 이런식으로

A = Variable('a')
B = Variable('b')
C = Variable('c')
Sqrt = Function('sqrt')

Quotient(
    Sum(
        Negative(B),
        Apply(
            Sqrt,
            Difference(
                Power(
                    B, Number(2)
                ),
                Product(
                    Number(4),
                    Product(A, C)
                )
            )
        )
    ),
    Product(
        Number(2),
        A
    )
)

 

식에 있는 모든 변수 찾아내기

def f(x):

   return x^3

기존의 파이썬 함수는 결과만 반환하지 안에 뭐가 들어있는지 모르므로

앞서 동작은 안하지만 대수 방정식 구조를 만듬

일단 기존에 만든 것에 어떤 변수가 있는지 반환하는 함수 만듬

 

일단 변수만 찾아내는 함수니까 넣은 변수는 잘 찾는거같다.

def distinct_variables(exp):
    if isinstance(exp, Variable):
        return (set(exp.symbol))
    elif isinstance(exp, Number):
        return set()
    elif isinstance(exp, Sum):
        return set().union(*[distinct_variables(exp) for exp in exp.exps])
    elif isinstance(exp, Product):
        return distinct_variables(exp.exp1).union(distinct_variables(exp.exp2))
    elif isinstance(exp, Power):
        return distinct_variables(exp.base).union(distinct_variables(exp.exponent))
    elif isinstance(exp, Apply):
        return distinct_variables(exp.argument)
    else:
        raise TypeError("Not a valid expression")

print(distinct_variables(Variable("z")))
print(distinct_variables(Number(3)))
print(distinct_variables(Sum(Number(3), Number(4))))
print(distinct_variables(Sum(Number(3), Variable("x"))))
print(distinct_variables(Sum(Variable("x"), Variable("y"))))
print(distinct_variables(Power(Variable("x"), Number(3))))
print(distinct_variables(Product(Power(Variable("x"), Number(3)), Variable("y"))))

 

 

방정식 동작하게 하기

아까 구현한 변수, 곱같은 클래스에 evaluate 함수 추가

evaluate로 변수를 바인딩해서 계산 수행

from abc import ABC, abstractmethod

class Expression(ABC):
    @abstractmethod
    def evaluate(self, **bindings):
        pass

class Number(Expression):
    def __init__(self, number):
        self.number = number
    def evaluate(self, **bindings):
        return self.number

class Variable(Expression):
    def __init__(self, symbol):
        self.symbol = symbol
    def evaluate(self, **bindings):
        try:
            return bindings[self.symbol]
        except:
            raise KeyError("Variable '{}' is not bound.".format(self.symbol))

class Product(Expression):
    def __init__(self, exp1, exp2):
        self.exp1 = exp1
        self.exp2 = exp2
    def evaluate(self, **bindings):
        return self.exp1.evaluate(**bindings) * self.exp2.evaluate(**bindings)

Product(Variable('x'), Variable('y')).evaluate(x=2, y=5)

 

 

타 연산 구현하기

방금 변수와 곱을 구현했고

나머지 연산들을 구현해보자

sin, cos, ln 같은 초월함수는 따로 Apply에서 바인딩 되도록 하고

합, 제곱, 차, 나눗셈 연산은 기본 연산대로 evaluate 추가

 

import math
from math import sin, cos, log

_function_bindings = {
    "sin":math.sin,
    "cos":math.cos,
    "ln":math.log
}

class Apply(Expression):
    def __init__(self, function, argument):
        self.function = function
        self.argument = argument
    def evaluate(self, **bindings):
        return _function_bindings[self.function.name](self.argument.evaluate(**bindings))


class Sum(Expression):
    def __init__(self, *exps):
        self.exps = exps
    def evaluate(self, **bindings):
        return sum([exp.evaluate(**bindings) for exp in self.exps])

class Power(Expression):
    def __init__(self, base, exponent):
        self.base = base
        self.exponent = exponent
    def evaluate(self, **bindings):
        return self.base.evaluate(**bindings) ** self.exponent.evaluate(**bindings)

class Difference(Expression):
    def __init__(self, exp1, exp2):
        self.exp1 = exp1
        self.exp2 = exp2
    def evaluate(self, **bindings):
        return self.exp1.evaluate(**bindings) - self.exp2.evaluate(**bindings)

class Quotient(Expression):
    def __init__(self, numerator, denominator):
        self.numerator = numerator
        self.denominator = denominator
    def evaluate(self, **bindings):
        return self.numerate.evaluate(**bindings) / self.denominator.evaluate(**bindings)

 

 

 

(3 x ^2 + x) * sin(x)

이 식을 심볼릭 방법으로 한것과

그냥 파이썬 함수에 대입한  결과를 보면 동일하게 잘 연산되었음

f_expression = Product(
                Sum(
                    Product(
                        Number(3),
                        Power(
                            Variable("x"),
                            Number(2)
                        )
                    ),
                    Variable("x")
                ),
                Apply(
                    Function("sin"),
                    Variable("x")
                )
            )

print(f_expression.evaluate(x=5))

def f(x):
    return (3 * x ** 2 + x) * sin(x)
print(f(5))

 

 

표현식 전개하기

지금까지 구한 표현식을 그대로 출력하면 클래스랑 주소를 알려주는데

내용을 전개해주면 좋으니 우선 추상 클래스 expression을 다음과 같이 구현

class Expression(ABC):
    @abstractmethod
    def evaluate(self, **bindings):
        pass
    @abstractmethod
    def expand(self):
        pass
    @abstractmethod
    def display(self):
        pass
    def __repr__(self):
        return self.display()

 

 

 

지금까지 구현한

숫자, 변수, 연산들을 다시 구현해보자

먼저 가장 간단한 숫자와 변수

아까와는 달리 주소가 아니라 클래스명과 값이 잘 나온다

class Number(Expression):
    def __init__(self, number):
        self. number = number
    def evaluate(self, **bindings):
        return self.number
    def expand(self):
        return self
    def display(self):
        return "Number({})".format(self.number)

class Variable(Expression):
    def __init__(self, symbol):
        self.symbol = symbol
    def evaluate(self, **bindings):
        return bindings[self.symbol]
    def expand(self):
        return self
    def display(self):
        return "Variable(\"{}\")".format(self.symbol)
print(Number(4))
print(Variable("x"))

 

합, 차연산 구현하고 동작시켜보면 이런식

 

 

class Sum(Expression):
    def __init__(self, *exps):
        self.exps = exps
    def evaluate(self, **bindings):
        return sum([exp.evaluate(**bindings) for exp in self.exps])
    def expand(self):
        return (Sum(*[exp.expand() for exp in self.exps]))
    def display(self):
        return "Sum({})".format(",".join([e.display() for e in self.exps]))

class Difference(Expression):
    def __init__(self, exp1, exp2):
        self.exp1 = exp1
        self.exp2 = exp2
    def evaluate(self, **bindings):
        return self.exp1.evaluate(**bindings) - self.exp2.evaluate(**bindings)
    def expand(self):
        return self
    def display(self):
        return "Difference({},{})".format(self.exp1.display(), self.exp2.display())

print(Sum(Number(3), Number(5)))
print(Sum(Variable("x"), Number(3)))
print(Difference(Number(3), Number(5)))
print(Difference(Variable("x"), Number(3)))
print(Difference(Variable("x"), Number(3)).evaluate(x=4))
print(Sum(Variable("x"), Difference(Number(3), Number(5))))

tmp_exp = Sum(Variable("x"), Number(3))
print(tmp_exp)
tmp_exp.expand()

 

 

 

 

나머지 연산도 구현

class Product(Expression):
    def __init__(self, exp1, exp2):
        self.exp1 = exp1
        self.exp2 = exp2
    def evaluate(self, **bindings):
        return self.exp1.evaluate(**bindings) * self.exp2.evaluate(**bindings)
    def expand(self):
        expanded1 = self.exp1.expand()
        expanded2 = self.exp2.expand()
        if isinstance(expanded1, Sum):
            return Sum(*[Product(e, expanded2).expand() for e in expanded1.exps])
        elif isinstance(expanded2, Sum):
            return Sum(*[Product(expanded1, e) for e in expanded2.exps])
        else:
            return Product(expanded1, expanded2)
    def display(self):
        return "Product({},{})".format(self.exp1.display(), self.exp2.display())

class Quotient(Expression):
    def __init__(self, numerator, denominator):
        self.numerator = numerator
        self.denominator = denominator
    def evaluate(self, **bindings):
        return self.numerator.evaluate(**bindings) / self.denominator.evaluate(**bindings)
    def expand(self):
        return self
    def display(self):
        return "Quotient({},{})".format(self.numerator.display(),self.denominator.display())

class Negative(Expression):
    def __init__(self, exp):
        self.exp = exp
    def evaluate(self, **bindings):
        return - self.exp.evaluate(**bindings)
    def expand(self):
        return self
    def display(self):
        return "Negative({})".format(self.exp.display())
    
class Power(Expression):
    def __init__(self, base, exponent):
        self.base = base
        self.exponent = exponent
    def evaluate(self, **bindings):
        return self.base.evaluate(**bindings) ** self.exponent.evaluate(**bindings)
    def expand(self):
        return self
    def display(self):
        return "Power({},{})".format(self.base.display(),self.exponent.display())

class Function():
    def __init__(self, name, make_latex=None):
        self.name = name
        self.make_latex = make_latex
    def latex(self, arg_latex):
        if self.make_latex:
            return self.make_latex(arg_latex)
        else:
            return " \\operatorname{{ {} }} \\left( {} \\right)".format(self.name, arg_latex)

class Apply(Expression):
    def __init__(self, function, argument):
        self.function = function
        self.argument = argument
    def evaluate(self, **bindings):
        return _function_bindings[self.function.name](self.argument.evaluate(**bindings))
    def expand(self):
        return Apply(self.function, self.argument.expand())
    def display(self):
        return "Apply(Function(\"{}\"),{})".format(self.function.name, self.argument.display())

 

식 간단하게 만들고 그냥 출력했을때랑

전개했을때 차이는 이런식

(a + b) * (y + z)

= ay + az + by + bz

출력 결과는 길어서 좀 그렇긴한데 결과도 잘 나오긴 함

Y = Variable('y')
Z = Variable('z')
A = Variable('a')
B = Variable('b')
Product(Sum(A, B), Sum(Y, Z))


Product(Sum(A, B), Sum(Y, Z)).expand()




f_expression = Product(
                Sum(
                    Product(
                        Number(3),
                        Power(
                            Variable("x"),
                            Number(2)
                        )
                    ),
                    Variable("x")
                ),
                Apply(
                    Function("sin"),
                    Variable("x")
                )
            )
f_expression.expand()

 

미분, 적분 내용까지 가긴 너무 힘드니 대충 sympy로 마무리

 

Sympy

기호로 수학 푸는 파이썬 lib

지금까지 구현한거랑 거의 비슷

숫자 그대로 써도되고, 심볼 쓸수있고

아래 내용은 방정식에 대입, 미분, 적분 결과

from sympy import *
from sympy.core.core import *

Mul(Symbol('y'), Add(3, Symbol('x')))



y = Symbol('y')
x = Symbol('x')
print(y * (3+x))
print(y * (3+x).subs(x, 1)) # substitude
print((x**2).diff(x)) # derivative
print((3*x**2).integrate(x)) # integrate

 

 

+ Recent posts