Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion tests/test_calculus.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from visma.calculus.differentiation import differentiate
from visma.calculus.differentiation import differentiate, differentiationProductRule
from visma.calculus.integration import integrate
from tests.tester import quickTest

Expand All @@ -23,6 +23,41 @@ def test_differentiate():
assert quickTest("z + xy", differentiate, 'z') == "1.0"
assert quickTest("z - xy", differentiate, 'z') == "1.0"
assert quickTest("xy - z", differentiate, 'z') == "-1.0"

assert quickTest("sin(x)", differentiate, 'x') == "cos(x)*1.0"
assert quickTest("sin(x)", differentiate, 'y') == "0.0"
assert quickTest("sin(xxx)", differentiate, 'x') == "cos(x^(3.0))*3.0x^(2.0)"
assert quickTest("sin(log(xx))", differentiate, 'x') == "cos(log(x^(2.0)))*x^(-1.0)*2.0x"

assert quickTest("cos(x)", differentiate, 'x') == "-1.0*sin(x)*1.0"
assert quickTest("cos(x)", differentiate, 'y') == "0.0"
assert quickTest("cos(xxx)", differentiate, 'x') == "-1.0*sin(x^(3.0))*3.0x^(2.0)"
assert quickTest("cos(log(xx))", differentiate, 'x') == "-1.0*sin(log(x^(2.0)))*x^(-1.0)*2.0x"

assert quickTest("tan(x)", differentiate, 'x') == "sec(x)*1.0"
# FIXME: Simplify module simplifies sec^2(x) as sec(x) and cosec^2(x) as cosec(x), however differentiation modules give correct output
assert quickTest("tan(x)", differentiate, 'y') == "0.0"

assert quickTest("cot(x)", differentiate, 'x') == "-1.0*csc(x)*1.0"
# FIXME: Simplify module simplifies sec^2(x) as sec(x) and cosec^2(x) as cosec(x), however differentiation modules give correct output
assert quickTest("cot(x)", differentiate, 'y') == "0.0"

assert quickTest("csc(x)", differentiate, 'x') == "-1.0*csc(x)*cot(x)*1.0"
assert quickTest("csc(x)", differentiate, 'y') == "0.0"

assert quickTest("sec(x)", differentiate, 'x') == "sec(x)*tan(x)*1.0"
assert quickTest("sec(x)", differentiate, 'y') == "0.0"

assert quickTest("log(x)", differentiate, 'x') == "x^(-1.0)"
assert quickTest("log(xx)", differentiate, 'x') == "2.0"

# Tests for Product Rule of Differentiation.
assert quickTest("sin(x)*cos(x)", differentiationProductRule, 'x') == "(cos(x)*1.0)*cos(x)+sin(x)*(-1.0*sin(x)*1.0)"
assert quickTest("sin(x)*x", differentiationProductRule, 'x') == "(cos(x)*1.0)*x+sin(x)*(1.0)"
assert quickTest("sin(x)*y", differentiationProductRule, 'x') == "(cos(x)*1.0)*y+sin(x)*(0.0)"
assert quickTest("sin(x)*cos(x)*sec(x)", differentiationProductRule, 'x') == "(cos(x)*1.0)*cos(x)*sec(x)+sin(x)*(-1.0*sin(x)*1.0)*sec(x)+sin(x)*cos(x)*(sec(x)*tan(x)*1.0)"


########################
# calculus.integration #
########################
Expand All @@ -35,3 +70,10 @@ def test_integrate():
assert quickTest("xyz + xy/z + x + 1 + 1/x", integrate, 'x') == "0.5x^(2.0)yz+0.5x^(2.0)yz^(-1.0)+0.5x^(2.0)+x+1.0*log(x)" # FIXME(integration.py): Ignore coeff if 1
assert quickTest("xyz + xy/z + x + 1 + 1/x", integrate, 'y') == "0.5xy^(2.0)z+0.5xy^(2.0)z^(-1.0)+xy+y+x^(-1.0)y"
assert quickTest("xyz + xy/z + x + 1 + 1/x", integrate, 'z') == "0.5xyz^(2.0)+xy*log(z)+xz+z+x^(-1.0)z"

assert quickTest("sin(x)", integrate, 'x') == "-1.0*cos(x)"
assert quickTest("cos(x)", integrate, 'x') == "sin(x)"
assert quickTest("tan(x)", integrate, 'x') == "-1.0*ln(cos(x))"
assert quickTest("csc(x)", integrate, 'x') == "-1.0*ln((csc(x)+cot(x)))"
assert quickTest("sec(x)", integrate, 'x') == "ln((sec(x)+tan(x)))"
assert quickTest("cot(x)", integrate, 'x') == "ln(sin(x))"
11 changes: 4 additions & 7 deletions tests/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from visma.functions.variable import Variable
from visma.functions.structure import Expression
from visma.functions.operator import Plus, Minus
from visma.io.parser import tokensToString

######################
# functions.constant #
Expand Down Expand Up @@ -184,7 +185,7 @@ def test_Variable():

variable1 = Variable(2, 'x', 3)
assert variable1.__str__() == "2{x}^{3}"
variable1.integrate('x')
variable1, _ = variable1.integrate('x')
assert variable1.__str__() == "0.5{x}^{4}"

constant = Constant(3)
Expand Down Expand Up @@ -275,10 +276,6 @@ def test_Variable():
add2 = exp1 * exp2
assert add2.__str__() == "{({(8{x}^{6}-8{x}^{3})}-{(12{x}^{3}-{12})})}"

# FIXME: Optimize integrate
'''
variable2 = Variable(3, 'x', -1)
variable2.integrate('x')
assert isinstance(variable2, Expression)
assert variable2.__str__() == '{3log{x}}'
'''
variable2, _ = variable2.integrate('x')
assert tokensToString(variable2) == '3 * log(x)'
66 changes: 35 additions & 31 deletions visma/calculus/differentiation.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import copy

from visma.functions.structure import Function
from visma.functions.structure import Function, Expression
from visma.functions.constant import Constant, Zero
from visma.functions.operator import Operator
from visma.functions.operator import Operator, Multiply, Plus
from visma.simplify.simplify import simplify
from visma.functions.variable import Variable
from visma.functions.exponential import Logarithm, Exponential
from visma.functions.trigonometry import Trigonometric
from visma.io.parser import tokensToString

###################
# Differentiation #
Expand All @@ -24,21 +28,17 @@ def differentiate(tokens, wrtVar):
animation {list} -- equation tokens for step-by-step
comments {list} -- comments for step-by-step
"""

animation = []
comments = []
tokens, availableOperations, token_string, animation, comments = simplify(tokens)

tokens, animNew, commentsNew = (differentiateTokens(tokens, wrtVar))

tokens, animNew, commentsNew = differentiateTokens(tokens, wrtVar)
animation.append(animNew)
comments.append(commentsNew)

tokens, availableOperations, token_string, animation2, comments2 = simplify(tokens)

animation2.pop(0)
comments2.pop(0)
animation.extend(animation2)
comments.extend(comments2)

return tokens, availableOperations, token_string, animation, comments


Expand All @@ -61,43 +61,47 @@ def differentiateTokens(funclist, wrtVar):
if isinstance(func, Operator):
diffFunc.append(func)
else:
newExpression = Expression()
newfunc = []
while(isinstance(func, Function)):
commentsNew[0] += r"$" + "\\frac{d}{d" + wrtVar + "} ( " + func.__str__() + ")" + r"$"
funcCopy = copy.deepcopy(func)
if wrtVar in funcCopy.functionOf():
if not isinstance(funcCopy, Constant):
for i, var in enumerate(funcCopy.value):
if var == wrtVar:
funcCopy.coefficient *= funcCopy.power[i]
funcCopy.power[i] -= 1
if(funcCopy.power[i] == 0):
del funcCopy.power[i]
del funcCopy.value[i]
if funcCopy.value == []:
funcCopy.__class__ = Constant
funcCopy.value = funcCopy.coefficient
funcCopy.coefficient = 1
funcCopy.power = 1
commentsNew[0] += r"$" + r"= " + funcCopy.__str__() + r"\ ;\ " + r"$"
if isinstance(funcCopy, Trigonometric) or isinstance(funcCopy, Logarithm) or isinstance(funcCopy, Variable) or isinstance(funcCopy, Exponential):
funcCopy = funcCopy.differentiate(wrtVar)
newfunc.append(funcCopy)
func.differentiate()
if not(isinstance(func, Constant) and func.value == 1):
newfunc.append(func)
commentsNew[0] += r"$" + r"= " + funcCopy.__str__() + r"\ ;\ " + r"$"
else:
funcCopy = (Zero())
funcCopy = Zero()
newfunc.append(funcCopy)
commentsNew[0] += r"$" + r"= " + funcCopy.__str__() + r"\ ;\ " + r"$"

newfunc.append(Multiply())
if func.operand is None:
break
else:
func = func.operand
if isinstance(func, Constant):
diffFunc = Zero()
break

diffFunc.extend(newfunc)

newfunc.pop()
newExpression.tokens = newfunc
diffFunc.extend([newExpression])
animNew.extend(diffFunc)
return diffFunc, animNew, commentsNew


def differentiationProductRule(tokens, wrtVar):
resultTokens = []
for i in range(0, len(tokens), 2):
currentDiff = Expression()
currentDiffTokens, _, _, _, _ = differentiate([tokens[i]], wrtVar)
currentDiff.tokens = currentDiffTokens
tempTokens = copy.deepcopy(tokens)
tempTokens[i] = currentDiff
resultTokens.extend(tempTokens)
resultTokens.append(Plus())
resultTokens.pop()
token_string = tokensToString(resultTokens)
# TODO: Make simplify module to simplify expressions involving Trigonometric Expressions (to some extent)
# resultTokens, _, token_string, _, _ = simplify(resultTokens)
return tokens, [], token_string, [], []
113 changes: 43 additions & 70 deletions visma/calculus/integration.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import copy
from visma.functions.structure import Function
from visma.functions.constant import Constant, Zero
from visma.functions.constant import Constant
from visma.functions.variable import Variable
from visma.functions.exponential import Logarithm
from visma.functions.operator import Operator, Binary
from visma.simplify.simplify import simplify
from visma.functions.trigonometry import Trigonometric
from visma.calculus.differentiation import differentiate
from visma.io.parser import tokensToString

###############
# Integration #
Expand All @@ -27,27 +28,17 @@ def integrate(tokens, wrtVar):
"""

tokens, availableOperations, token_string, animation, comments = simplify(tokens)

tokens, animNew, commentsNew = (integrateTokens(tokens, wrtVar))

animation.append(animNew)
comments.append(commentsNew)

tokens, availableOperations, token_string, animation2, comments2 = simplify(tokens)

animation2.pop(0)
comments2.pop(0)
animation.extend(animation2)
comments.extend(comments2)

return tokens, availableOperations, token_string, animation, comments


# This is only applicable to Variable and Constant type
# Kind of hacky as of now
# Must be modified to accomodate other function types
# Have to add integrate class method to individual functions

def integrateTokens(funclist, wrtVar):
"""Integrates given tokens wrt given variable

Expand All @@ -68,65 +59,47 @@ def integrateTokens(funclist, wrtVar):
intFunc.append(func)
else:
newfunc = []
while(isinstance(func, Function)):
commentsNew[0] += r"$" + r"\int \ " + r"( " + func.__str__() + ")" + r" d" + wrtVar + r"$"
funcCopy = copy.deepcopy(func)
if wrtVar in funcCopy.functionOf():
if not isinstance(funcCopy, Constant):
for i, var in enumerate(funcCopy.value):
log = False
if var == wrtVar:
if(funcCopy.power[i] == -1):
log = True
funcLog = Logarithm()
funcLog.operand = Variable()
funcLog.operand.coefficient = 1
funcLog.operand.value.append(funcCopy.value[i])
funcLog.operand.power.append(1)
del funcCopy.power[i]
del funcCopy.value[i]
if funcCopy.value == []:
funcCopy.__class__ = Constant
funcCopy.value = funcCopy.coefficient
funcCopy.coefficient = 1
funcCopy.power = 1
funcCopy = [funcCopy]
funcJoin = Binary()
funcJoin.value = '*'
funcCopy.append(funcJoin)
funcCopy.append(funcLog)
else:
funcCopy.power[i] += 1
funcCopy.coefficient /= funcCopy.power[i]

if log:
commentsNew[0] += r"$" + r"= " + funcCopy[0].__str__() + r"*" + funcCopy[2].__str__() + r"\ ;\ " + r"$"
newfunc.extend(funcCopy)
else:
commentsNew[0] += r"$" + r"= " + funcCopy.__str__() + r"\ ;\ " + r"$"
newfunc.append(funcCopy)
else:
if isinstance(funcCopy, Variable):
funcCopy.value.append(wrtVar)
funcCopy.power.append(1)
if isinstance(funcCopy, Constant):
coeff = funcCopy.value
funcCopy = Variable()
funcCopy.coefficient = coeff
funcCopy.value.append(wrtVar)
funcCopy.power.append(1)
commentsNew[0] += r"$" + r"\int \ " + r"( " + func.__str__() + ")" + r" d" + wrtVar + r"$"
funcCopy = copy.deepcopy(func)
if wrtVar in funcCopy.functionOf():
if isinstance(funcCopy, Variable):
log = False
funcCopy, log = funcCopy.integrate(wrtVar)
if log:
commentsNew[0] += r"$" + r"= " + funcCopy[0].__str__() + r"*" + funcCopy[2].__str__() + r"\ ;\ " + r"$"
newfunc.extend(funcCopy)
else:
commentsNew[0] += r"$" + r"= " + funcCopy.__str__() + r"\ ;\ " + r"$"
newfunc.append(funcCopy)
elif isinstance(funcCopy, Trigonometric):
funcCopy = funcCopy.integrate(wrtVar)
newfunc.append(funcCopy)
commentsNew[0] += r"$" + r"= " + funcCopy.__str__() + r"\ ;\ " + r"$"

if func.operand is None:
break
else:
func = func.operand
if isinstance(func, Constant):
intFunc = Zero()
break

else:
if isinstance(funcCopy, Variable):
funcCopy.value.append(wrtVar)
funcCopy.power.append(1)
if isinstance(funcCopy, Constant):
coeff = funcCopy.value
funcCopy = Variable()
funcCopy.coefficient = coeff
funcCopy.value.append(wrtVar)
funcCopy.power.append(1)
newfunc.append(funcCopy)
commentsNew[0] += r"$" + r"= " + funcCopy.__str__() + r"\ ;\ " + r"$"
intFunc.extend(newfunc)

animNew.extend(intFunc)
return intFunc, animNew, commentsNew


def integrationByParts(tokens, wrtVar):
if (isinstance(tokens[1], Binary) and tokens[1].value == '*'):
u = tokens[0]
v = tokens[2]
vIntegral, _, _, _, _ = integrate(v, wrtVar)
uDerivative, _, _, _, _ = differentiate(u, wrtVar)
term1 = u * vIntegral
term2, _, _, _, _ = integrate(uDerivative * vIntegral, 'x')
resultToken = term1 - term2
token_string = tokensToString(resultToken)
return resultToken, [], token_string, [], []
11 changes: 11 additions & 0 deletions visma/functions/exponential.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import math
import copy
from visma.functions.structure import FuncOp

#########################
Expand Down Expand Up @@ -29,6 +30,16 @@ def inverse(self, rToken, wrtVar, inverseFunction=None):
def calculate(self, val):
return self.coefficient * ((math.log(val, self.base)))

def differentiate(self, wrtVar=None):
from visma.functions.variable import Variable
result = copy.deepcopy(self)
result.__class__ = Variable
result.coefficient = 1
result.value = wrtVar
result.power = [-1]
result.operand = None
return result


class NaturalLog(Logarithm):
"""Class for ln function -- ln(...) or use log_e(...)
Expand Down
3 changes: 3 additions & 0 deletions visma/functions/operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ def __str__(self):
represent += str(self.value)
return represent

def differentiate(self):
return self


class Binary(Operator):
"""Binary operator takes two operands
Expand Down
Loading