diff --git a/chatterbot/logic/mathematical_evaluation.py b/chatterbot/logic/mathematical_evaluation.py index ffb5e170f..330e743d3 100644 --- a/chatterbot/logic/mathematical_evaluation.py +++ b/chatterbot/logic/mathematical_evaluation.py @@ -4,8 +4,8 @@ import re import os import json +import math import decimal -import numpy class MathematicalEvaluation(LogicAdapter): @@ -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) @@ -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 @@ -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): @@ -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) @@ -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 @@ -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])) @@ -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 @@ -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) diff --git a/requirements.txt b/requirements.txt index 5dbe8955e..3c0f7e95a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 diff --git a/tests/logic_adapter_tests/test_mathematical_evaluation.py b/tests/logic_adapter_tests/test_mathematical_evaluation.py index f546f6592..0e3fa15e6 100644 --- a/tests/logic_adapter_tests/test_mathematical_evaluation.py +++ b/tests/logic_adapter_tests/test_mathematical_evaluation.py @@ -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) @@ -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)