Skip to content

Commit

Permalink
Fix a few testing gaps for math evaluation
Browse files Browse the repository at this point in the history
This does the following:

- Split up tests that test more than one thing
- Remove dependencey on numpy
- Fix display formatting for exponents
  • Loading branch information
gunthercox committed Jan 15, 2017
1 parent 8171475 commit ca14518
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 37 deletions.
74 changes: 44 additions & 30 deletions chatterbot/logic/mathematical_evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import re
import os
import json
import math
import decimal
import numpy


class MathematicalEvaluation(LogicAdapter):
Expand All @@ -22,7 +22,12 @@ class MathematicalEvaluation(LogicAdapter):
4) Simplify the equation
5) Solve the equation & return result
"""
functions = ['log', 'sqrt']
functions = {
'sqrt': math.sqrt,

# Most people assume a log of base 10 when a base is not specified
'log': math.log10
}

def __init__(self, **kwargs):
super(MathematicalEvaluation, self).__init__(**kwargs)
Expand Down Expand Up @@ -67,7 +72,7 @@ def process(self, statement):
"""
Takes a statement string.
Returns the simplified statement string
with the mathematical terms "solved".
with the mathematical terms solved.
"""
input_text = statement.text

Expand All @@ -80,38 +85,46 @@ def process(self, statement):
# Getting the mathematical terms within the input statement
expression = str(self.simplify_chunks(self.normalize(input_text)))

# Returning important information
response = Statement(text=expression)

try:
expression += '= ' + str(
eval(expression, {f: getattr(numpy, f) for f in self.functions})
response.text += '= ' + str(
eval(expression, {f: self.functions[f] for f in self.functions})
)

response = Statement(expression)
response.confidence = 1
# Replace '**' with '^' for evaluated exponents
response.text = response.text.replace('**', '^')

# return a confidence of 1 if the expression could be evaluated
return 1, response
# The confidence is 1 if the expression could be evaluated
response.confidence = 1
except:
response = Statement(expression)
response.confidence = 0
return 0, response

return response.confidence, response

def simplify_chunks(self, input_text):
"""
Separates the incoming text.
"""
string = ''
chunks = re.split(r"([\w\.-]+|[\(\)\*\+])", input_text)
chunks = re.split(r'([\w\.-]+|[\(\)\*\+])', input_text)
chunks = [chunk.strip() for chunk in chunks]
chunks = [chunk for chunk in chunks if chunk != '']

classifiers = [
'is_integer', 'is_float', 'is_operator', 'is_constant', 'is_function'
]

for chunk in chunks:
for checker in ['is_integer', 'is_float', 'is_operator', 'is_constant', 'is_function']:
result = getattr(self, checker)(chunk)
for classifier in classifiers:
result = getattr(self, classifier)(chunk)
if result is not False:
string += str(result) + ' '
break

# Replace '^' with '**' to evaluate exponents
string = string.replace('^', '**')

return string

def is_float(self, string):
Expand Down Expand Up @@ -142,8 +155,8 @@ def is_constant(self, string):
said constant. Otherwise, it returns False.
"""
constants = {
"pi": 3.141693,
"e": 2.718281
'pi': 3.141693,
'e': 2.718281
}
return constants.get(string, False)

Expand All @@ -162,7 +175,7 @@ def is_operator(self, string):
If the string is an operator, returns
said operator. Otherwise, it returns false.
"""
if string in "+-/*^()":
if string in '+-/*^()':
return string
else:
return False
Expand Down Expand Up @@ -196,30 +209,30 @@ def substitute_words(self, string):
"""
condensed_string = '_'.join(string.split())

for word in self.math_words["words"]:
for word in self.math_words['words']:
condensed_string = re.sub(
'_'.join(word.split(' ')),
self.math_words["words"][word],
self.math_words['words'][word],
condensed_string
)

for number in self.math_words["numbers"]:
for number in self.math_words['numbers']:
condensed_string = re.sub(
number,
str(self.math_words["numbers"][number]),
str(self.math_words['numbers'][number]),
condensed_string
)

for scale in self.math_words["scales"]:
for scale in self.math_words['scales']:
condensed_string = re.sub(
"_" + scale,
" " + self.math_words["scales"][scale],
'_' + scale,
' ' + self.math_words['scales'][scale],
condensed_string
)

condensed_string = condensed_string.split('_')
for chunk_index in range(0, len(condensed_string)):
value = ""
value = ''

try:
value = str(eval(condensed_string[chunk_index]))
Expand All @@ -229,7 +242,8 @@ def substitute_words(self, string):
pass

for chunk_index in range(0, len(condensed_string)):
if self.is_integer(condensed_string[chunk_index]) or self.is_float(condensed_string[chunk_index]):
condensed_chunk = condensed_string[chunk_index]
if self.is_integer(condensed_chunk) or self.is_float(condensed_chunk):
i = 1
start_index = chunk_index
end_index = -1
Expand All @@ -238,10 +252,10 @@ def substitute_words(self, string):
i += 1

for sub_chunk in range(start_index, end_index):
condensed_string[sub_chunk] += " +"
condensed_string[sub_chunk] += ' +'

condensed_string[start_index] = "( " + condensed_string[start_index]
condensed_string[end_index] += " )"
condensed_string[start_index] = '( ' + condensed_string[start_index]
condensed_string[end_index] += ' )'

return ' '.join(condensed_string)

Expand Down
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
jsondatabase>=0.1.7
nltk>=3.2.0,<4.0.0
numpy>=1.11.0,<1.20.0
pymongo>=3.3.0,<4.0.0
python-twitter>=3.0
30 changes: 24 additions & 6 deletions tests/logic_adapter_tests/test_mathematical_evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ def test_division_operator(self):
else:
self.assertEqual(response.text, '( 100 / 20 ) = 5.0')

def test_exponent_operator(self):
statement = Statement('What is 2 ^ 10')
confidence, response = self.adapter.process(statement)
self.assertEqual(response.text, '( 2 ^ 10 ) = 1024')
self.assertEqual(response.confidence, 1)

def test_parenthesized_multiplication_and_addition(self):
statement = Statement('What is 100 + ( 1000 * 2 )?')
confidence, response = self.adapter.process(statement)
Expand Down Expand Up @@ -141,15 +147,27 @@ def test_negative_decimal_multiplication(self):
self.assertEqual(response.text, '( -100.5 * 20 ) = -2010.0')
self.assertEqual(response.confidence, 1)

def test_constants(self):
statement = Statement('What is pi plus e ?')
def test_pi_constant(self):
statement = Statement('What is pi plus one ?')
confidence, response = self.adapter.process(statement)
self.assertEqual(response.text, '3.141693 + ( 1 ) = 4.141693')
self.assertEqual(response.confidence, 1)

def test_e_constant(self):
statement = Statement('What is e plus one ?')
confidence, response = self.adapter.process(statement)
self.assertEqual(response.text, '2.718281 + ( 1 ) = 3.718281')
self.assertEqual(response.confidence, 1)

def test_log_function(self):
statement = Statement('What is log 100 ?')
confidence, response = self.adapter.process(statement)
self.assertEqual(response.text, '3.141693 + 2.718281 = 5.859974')
self.assertEqual(response.text, 'log ( 100 ) = 2.0')
self.assertEqual(response.confidence, 1)

def test_math_functions(self):
statement = Statement('What is log ( 5 + 6 ) * sqrt ( 12 ) ?')
def test_square_root_function(self):
statement = Statement('What is the sqrt 144 ?')
confidence, response = self.adapter.process(statement)
self.assertEqual(response.text, 'log ( ( 5 + ( 6 ) * sqrt ( ( 12 ) ) ) ) = 3.24977779033')
self.assertEqual(response.text, 'sqrt ( 144 ) = 12.0')
self.assertEqual(response.confidence, 1)

0 comments on commit ca14518

Please sign in to comment.