def apply_to(self, expression: Expression) -> List[Expression]: if not expression.is_integral(): return False integral_info = IntegralInfo(expression.sympy_expr.function, parse_expr('x')) subs_rule = substitution_rule(integral_info) if subs_rule is None: # Can not be applied by parts return [] u = subs_rule.u_func du = Derivative(u, subs_rule.symbol).doit() variables = [ ExpressionVariable('u', Expression(u)), ExpressionVariable('du', Expression(du)), ] equivalent_expression = subs_rule.substep.context.xreplace({subs_rule.u_var: parse_expr('u')}) equivalent_expression = equivalent_expression * subs_rule.constant result = Expression(f'Integral({str(equivalent_expression)}, u)', variables, is_latex=False) return [ result ]
def test_get_children(self): exp = Expression("x + x^2") children = exp.get_children() self.assertEqual(2, len(children)) self.assertTrue(Expression("x") in children) self.assertTrue(Expression("x^2") in children)
def test_subtract_should_apply(self): exp = Expression('\\frac{d(x - x^3)}{dx}') expected_result = [ Expression('\\frac{d(x)}{dx} + \\frac{d(-x^3)}{dx}'), ] result = theorem_sum_of_derivatives.apply_to(exp) self.assertTrue(equivalent_solutions(result, expected_result))
def apply_to(self, expression: Expression) -> List[Expression]: if not expression.is_integral(): return False integral_info = IntegralInfo(expression.sympy_expr.function, parse_expr('x')) by_parts_rule = parts_rule(integral_info) if by_parts_rule is None: # Can not be applied by parts return [] u = by_parts_rule.u dv = by_parts_rule.dv v = Integral(dv, by_parts_rule.symbol).doit( ) # TODO Lucas: Buscar si hay una manera menos cochina de hacer esto variables = [ ExpressionVariable('u(x)', Expression(u)), ExpressionVariable('v(x)', Expression(v)), ] equivalent_expression = Expression( '\\int (u(x) * \\frac{d(v(x))}{dx}) dx', variables) #main_expression == Expression('x * cos(x) - \\int (\\frac{d(x)}{dx} * cos(x)) dx') #main_expression = Expression('x * cos(x) - \\int (\\frac{d(x)}{dx} * cos(x)) dx', variables) # TODO: investigar dx #Expression('\\int u * \\frac{d(v)}{dx}', variables) # \\int(x * sen(x)) dx + \\int(x * cos(x)) dx # \\int(x * sen(x)) dx + POR PARTES(\\int(x * cos(x)) dx) # POR PARTES(\\int(x * sen(x)) dx) + \\int(x * cos(x)) dx return [equivalent_expression]
def apply_to(self, expression: Expression) -> List[Expression]: from_side = self.left to_side = self.right # Get the subtrees of the expression that have the same root as from side. # by level means that we also get the level of that subtree to know if the # subtree could match from_side subtrees = expression.get_subtrees_with_root_func_by_level(from_side) from_side_depth = from_side.get_depth() expression_depth = expression.get_depth() results = [] for subtree in subtrees: sub_expression = subtree['expression'] level = subtree['level'] # Example Derivative(x) have depth 2 and Derivative(f(x)+g(x)) have depth 3 # So, if the depth of the subtree is less than the from_side of the theorem # it can't be applied if level <= expression_depth - from_side_depth: application_possibilities = self._apply_to(sub_expression, from_side, to_side) for possibility in application_possibilities: if sub_expression != expression: # Example: # expression cos(x) + Derivative(2*x) # sub_expression = Derivative(2*x) # possibility = 2 * Derivative(x) # replace sub_expression with possibility # result = cos(x) + 2 * Derivative(x) result = expression.get_copy() result.replace(sub_expression, possibility) else: result = possibility results += [result] return results
def apply_to_children(self, expression: Expression, from_side: Expression, to_side: Expression) -> List[Expression]: application_possibilities = [] template = from_side already_tried = set() # try applying to groups of children # for example in x + y + z # try with: x + y ; x + z : y + z if expression.can_group_children(): logger.info("Trying to apply: " + self.name + " to a group children: " + str(expression)) for size in range(2, expression.children_amount() + 1): children_of_size_n_possibilities = expression.get_child_with_size_possibilities( size) for child_of_size_n in children_of_size_n_possibilities: if str(child_of_size_n) not in already_tried: analysis = self.analyzer.analyze( template, self.conditions, child_of_size_n) if analysis.expression_match_template: application_possibilities.append(TheoremApplication( child_of_size_n, self.transform_side(to_side, analysis.equalities))) already_tried.add(str(child_of_size_n)) return application_possibilities
def test_apply_reverse_to(self): theorem = self.derivative_sum_theorem expression = Expression("x^3 + \\frac{d(x)}{dx} + \\frac{d(x^2)}{dx}") possibilities = theorem.apply_reverse_to(expression) expected = Expression("\\frac{d(x + x^2)}{dx} + x^3") print(possibilities) self.assertTrue(is_present(expected, possibilities))
def test_subtract_should_apply(self): exp = Expression('\\int(x - x^3)dx') expected_result = [ Expression('(\\int(x)dx) + (\\int(-x^3)dx)'), ] result = theorem.apply_to(exp) self.assertTrue(equivalent_solutions(result, expected_result))
def solve_exercise_with_solution_tree(self, exercise: SolvedExercise): # get solution tree data = { 'problem_input': exercise.steps[0], } response = self.client.post(path='/results/solution-tree', data=data, format='json') tree_str = json.loads(response.content) resolve_data = { 'problem_input': exercise.steps[0], 'math_tree': tree_str, 'type': 'derivative', } # all steps should be valid for i in range(1, len(exercise.non_result_steps)): previous_steps = [] previous_steps += exercise.non_result_steps[:i] previous_steps.pop() current_step = exercise.non_result_steps[i] resolve_data['step_list'] = previous_steps resolve_data['current_expression'] = current_step response = self.client.post(path='/resolve', data=resolve_data, format='json') result = json.loads(response.content) if result['exerciseStatus'] == 'resolved': print( Expression( current_step['expression']).to_expression_string() + ' - ' + json.dumps(current_step['variables'])) if result['exerciseStatus'] == 'invalid': print( Expression( current_step['expression']).to_expression_string() + ' - ' + json.dumps(current_step['variables'])) self.assertEquals(response.status_code, status.HTTP_200_OK) self.assertEquals(result['exerciseStatus'], 'valid') # the result should be resolved resolve_data['step_list'] = exercise.steps resolve_data['current_expression'] = exercise.steps[-1] response = self.client.post(path='/resolve', data=resolve_data, format='json') result = json.loads(response.content) self.assertEquals(response.status_code, status.HTTP_200_OK) self.assertEquals(result['exerciseStatus'], 'resolved')
def test_sum_of_three_should_return_all_possibilities(self): exp = Expression('\\frac{d(x + x^2 + x^3)}{dx}') expected_result = [ Expression('\\frac{d(x + x^2)}{dx} + \\frac{d( x^3)}{dx}'), Expression('\\frac{d(x)}{dx} + \\frac{d(x^2 + x^3)}{dx}'), Expression('\\frac{d(x + x^3)}{dx} + \\frac{d(x^2)}{dx}') ] result = theorem_sum_of_derivatives.apply_to(exp) self.assertTrue(equivalent_solutions(result, expected_result))
def apply_to(self, expression: Expression) -> List[Expression]: if expression.to_expression_string( ) == 'Integral(u(x)*Derivative(v(x), x), x)': return [ Expression( 'u(x) * v(x) - \\int (\\frac{d(u(x))}{dx} * v(x)) dx', expression.variables) ] return []
def test_sum_of_three_should_return_all_possibilities(self): exp = Expression('\\int(x + x^2 + x^3)dx') expected_result = [ Expression('(\\int(x + x^2)dx) + (\\int( x^3)dx)'), Expression('(\\int(x)dx) + (\\int(x^2 + x^3)dx)'), Expression('(\\int(x + x^3)dx) + (\\int(x^2)dx)') ] result = theorem.apply_to(exp) self.assertTrue(equivalent_solutions(result, expected_result))
def test_analyze_exp_with_user_def_func_diff_sizes(self): analyzer = TemplateMatchAnalyzer() template = Expression("f(x) + g(y)") expression = Expression("x + x^2 + y") analysis = MatchAnalysisReport(template, expression, True, []) result = analyzer.analyze_exp_with_user_def_func_diff_sizes( template, [], expression, analysis) self.assertTrue(result.expression_match_template)
def solution_tree(self, expression: Expression) -> SolutionTreeNode: logger.info("get solution tree for: " + expression.to_string()) theorems = [] if expression.contains_derivative(): theorems += DerivativeTheorems.get_all() if expression.contains_integral(): theorems += IntegrateTheorems.get_all() return self.solution_tree_for(expression, theorems, Theorem('none', None, None, {}))
def test_contains(self): all_steps_contained = True for i in range(0, len(exercise.steps)): step = exercise.steps[i] expression = Expression(step['expression'], step['variables']) all_steps_contained = all_steps_contained and tree.contains( expression) if not all_steps_contained: print('failed: ' + expression.to_string()) self.assertTrue(all_steps_contained)
def analyze_exp_with_user_def_func_eq_sizes( self, template: Expression, template_conditions: List, expression: Expression, analysis: MatchAnalysisReport) -> MatchAnalysisReport: if template.compare_func(expression): if template.is_commutative(): return self.analyze_commutative_children_eq_len( template.get_children(), template_conditions, expression.get_children(), analysis) else: return self.analyze_children_non_commutative_eq_len( template, template_conditions, expression, analysis)
def analyze_children_non_commutative_eq_len( self, template: Expression, template_conditions: List, expression: Expression, analysis: MatchAnalysisReport) -> MatchAnalysisReport: result = analysis template_children = template.get_children() expression_children = expression.get_children() for i in range(0, len(template)): result = self.analyze_rec(template_children[i], template_conditions, expression_children[i], result) return result
def test_parts_replacing_u_v_should_apply(self): exp = Expression('\\int (x * \\cos(x))') variables = [ ExpressionVariable('u(x)', Expression("x")), ExpressionVariable('v(x)', Expression("sen(x)")), ] expected_result = [ Expression('u(x) * v(x) - \\int (\\frac{d(u(x))}{dx} * v(x))', variables), Expression('\\int u(x) * \\frac{d(v(x))}{dx}', variables) ] result = theorem.apply_to(exp) self.assertEqual(result, expected_result)
def solve_exercise_with_solution_tree(self, exercise: SolvedExercise): # get solution tree theorems = load_theorems("test/jsons/integrate_theorems.json") data = { 'problem_input': exercise.steps[0], 'type': 'integrate', 'theorems': theorems } response = self.client.post(path='/results/solution-tree', data=data, format='json') tree_str = json.loads(response.content) resolve_data = { 'problem_input': exercise.steps[0], 'math_tree': tree_str, 'type': 'integrate', 'theorems': theorems } # all steps should be valid for i in range(1, len(exercise.non_result_steps)): previous_steps = exercise.non_result_steps[:i] # remove problem_input previous_steps.pop() current_step = exercise.non_result_steps[i] resolve_data['step_list'] = json.dumps(previous_steps) resolve_data['current_expression'] = current_step response = self.client.post(path='/resolve', data=resolve_data, format='json') result = json.loads(response.content) if result['exerciseStatus'] == 'resolved': print(Expression(current_step).to_string()) if result['exerciseStatus'] == 'invalid': print(Expression(current_step).to_string()) self.assertEquals(response.status_code, status.HTTP_200_OK) self.assertEquals(result['exerciseStatus'], 'valid') # the result should be resolved resolve_data['step_list'] = json.dumps(exercise.steps) resolve_data['current_expression'] = exercise.steps[-1] response = self.client.post(path='/resolve', data=resolve_data, format='json') result = json.loads(response.content) self.assertEquals(response.status_code, status.HTTP_200_OK) self.assertEquals(result['exerciseStatus'], 'resolved')
def test_get_operators_by_level_complex(self): exp = Expression( "cos(x+Derivative(e**x,x)) + Derivative(x + x**(2*x+3), x)", is_latex=False) operators = exp.get_operators_by_level() expected = { 0: [Add], 1: [cos, Derivative], 2: [Add, Add], 3: [Derivative, Pow], 4: [Pow, Add], 5: [Mul] } self.assertEquals(expected, operators)
def solve_derivative(request: Request): if request.method == 'POST': body = json.loads(request.body) expression = Expression(body['expression']) result = result_service.get_derivative_result(expression) logger.info('Returning the following response: {}'.format(result)) return Response(result.to_latex(), status=status.HTTP_200_OK)
def _apply_to(self, expression: Expression, from_side: Expression, to_side: Expression) -> List[Expression]: application_possibilities = [] template = from_side # Apply to general structure logger.debug("Trying to apply: " + self.name + " to the general structure: " + str(expression)) analysis = self.analyzer.analyze(template, self.conditions, expression) if analysis.expression_match_template: application_possibilities.append( self.transform_side(to_side, analysis.equalities)) # Apply to children logger.debug("Trying to apply: " + self.name + " to expression CHILDREN: " + str(expression)) children_transformations = self.apply_to_children( expression, from_side, to_side) for child_transformation in children_transformations: result = expression.get_copy() result.replace(child_transformation.before, child_transformation.after) application_possibilities.append(result) return application_possibilities
def there_is_a_chance_to_apply_to(self, expression: Expression): if not expression.is_integral(): return False integral_info = IntegralInfo(expression.sympy_expr.function, parse_expr('x')) subs_rule = substitution_rule(integral_info) return True if subs_rule is not None else False
def test_get_hints_initial_step_should_return_hints(self): initialStep = Expression( "Derivative(x*exp(x), x) + Derivative(x**2*sin(x), x)", is_latex=False) hints = tree.get_hints(initialStep) hints = list(set(map(lambda h: h.name, hints))) self.assertTrue('derivada del producto' in hints) self.assertTrue('resolver derivadas' in hints)
def analyze_exp_with_user_def_func_diff_sizes( self, template: Expression, template_conditions: List, expression: Expression, analysis: MatchAnalysisReport) -> MatchAnalysisReport: if template.children_amount() >= expression.children_amount(): return self.build_match_analysis_report(False, analysis, template, template_conditions, expression) possible_expression_children = expression.get_children_with_size( template.children_amount()) for children in possible_expression_children: if template.is_commutative(): new_analysis = self.analyze_commutative_children_eq_len( template.get_children(), template_conditions, children, analysis) else: new_analysis = self.analyze_children_non_commutative_eq_len( template.get_children(), template_conditions, children, analysis) if new_analysis.expression_match_template: return new_analysis return self.build_match_analysis_report(False, analysis, template, template_conditions, expression)
def parse_compare_expressions_data( self, request_data: dict) -> (Expression, Expression): self.logger.info( "Parsing validate not in history request data: {}".format( request_data)) try: expression_one_str = request_data['expression_one'] expression_two_str = request_data['expression_two'] expression_one = Expression(expression_one_str) expression_two = Expression(expression_two_str) return (expression_one, expression_two) except Exception as e: self.logger.error( "Error while parsing validate result input: {}".format(e)) raise ValidateMapperException(e)
def there_is_a_chance_to_apply_to(self, expression: Expression): contains_u = False contains_du = False for variable in expression.variables: if variable.tag == 'u': contains_u = True if variable.tag == 'du': contains_du = True return not expression.contains_integral() and contains_du and contains_u
def resolve(request: Request): if request.method == 'POST': body = json.loads(request.body) problem_input = Expression(body['problem_input']['expression'], body['problem_input']['variables']) solution_tree = solutionTreeMapper.parse(body['math_tree']) step_list = [] for step in body['step_list']: # TODO: I will find a more more pythonable to do that step_expr = step['expression'] variables = step['variables'] step_vars = list( map( lambda variable: ExpressionVariable( variable['tag'], Expression(variable['expression']['expression'])), variables if variables is not None else [])) step_list.append(Expression(step_expr, step_vars)) # Current expression body_variables = body['current_expression']['variables'] variables = list( map( lambda variable: ExpressionVariable( variable['tag'], Expression(variable['expression']['expression'])), body_variables if body_variables is not None else [])) current_expression = Expression( body['current_expression']['expression'], variables) (result, hints) = result_service.resolve(problem_input, solution_tree, step_list, current_expression) logger.info('Returning the following response: {} {}'.format( result, json.dumps(hints))) response_data = {'exerciseStatus': result, 'hints': hints} return Response(response_data, status=status.HTTP_200_OK, content_type='application/json')
def is_a_valid_next_step(self, old_expression: Expression, new_expression: Expression, theorems: List[Theorem]) -> bool: logger.info("Starting transition validation") logger.info("Checking if a theorem can be applied") theorems_that_apply = self.theorems_service.possible_theorems_for( old_expression, theorems) logger.info("THEOREMS THAT APPLY:") logger.info(theorems_that_apply) for theorem in theorems_that_apply: theo_applied = theorem.apply_to(old_expression) for possibility in theo_applied: if possibility != None and possibility.simplify( ) == new_expression.simplify(): logger.info("Theorem can be applied") return True theo_applied_reverse = theorem.apply_reverse_to(new_expression) for possibility in theo_applied_reverse: if possibility != None and possibility.simplify( ) == old_expression.simplify(): logger.info("Theorem can be applied") return True # try with derivatives logger.info("Try with derivatives") if old_expression.solve_derivatives().simplify( ) == new_expression.simplify(): logger.info("Derivatives were applied") return True only_one_derivative = old_expression.derivatives_solving_possibilities( ) for derivative_applied in only_one_derivative: if derivative_applied.simplify() == new_expression.simplify(): return True # try simplifying the expression logger.info("Try simplifying") old_simplifications = old_expression.get_simplifications() for simplification in old_simplifications: new_simplifications = new_expression.get_simplifications() if simplification in new_simplifications: logger.info("Simplifications were applied") return True logger.info("Invalid next step: " + str(new_expression) + " - Old expression: " + str(old_expression)) return False
def parse_expression(json_expression): main_expression = json_expression['expression'] json_variables = json_expression['variables'] variables = list( map( lambda variable: ExpressionVariable( variable['tag'], SolutionTreeMapper.parse_expression(variable['expression']) ), json_variables)) return Expression(main_expression, variables, is_latex=False)