#All Rights Reserved John Salguero #Generates steps depending on the problem import algebraic_steps from sympy import * from sympy.parsing.sympy_parser import ( parse_expr, standard_transformations, implicit_multiplication_application ) transformations = standard_transformations + (implicit_multiplication_application,) STEP_GENERATORS = {} def register_steps_generator(problem_type): def decorator(func): STEP_GENERATORS[problem_type] = func return func return decorator @register_steps_generator("linear") def generate_linear_steps(problem): #ax + b = c steps = [] x = symbols('x') current = problem["problem"] left, right = current.split("=") expr = parse_expr(left) a = expr.coeff(x) b = expr.subs(x, 0) ## First Step if b.is_zero == False: if b.is_negative: steps.append(algebraic_steps.add_both_sides(current, -b)) elif b.is_positive: steps.append(algebraic_steps.subtract_both_sides(current, b)) current = steps[-1]["after"] ## Second Step if a != 1 and a != -1: steps.append(algebraic_steps.divide_both_sides(current, a)) elif a == -1: steps.append(algebraic_steps.multiply_both_sides(current, a)) return steps @register_steps_generator("hidden_factor") def generate_hidden_factor_steps(problem): #a(x + b) + c(x + b) = d steps = [] x = symbols('x') current = problem["problem"] left, right = current.split("=") left_expr = parse_expr(left, transformations=transformations, evaluate=False) right_expr = parse_expr(right) terms = left_expr.as_ordered_terms() #factors = [term.as_ordered_factors() for term in terms] #common = set(factors[0]) & set(factors[1]) #base = list(common)[0] base = terms[0].args[1] ## First Step steps.append(algebraic_steps.factor_collect(current)) current = steps[-1]["after"] ## Second Step div = simplify(left_expr / base) steps.append(algebraic_steps.divide_both_sides(current, div)) current = steps[-1]["after"] ## Third Step b = base.subs(x, 0) if b.is_negative: steps.append(algebraic_steps.add_both_sides(current, -b)) elif b.is_positive: steps.append(algebraic_steps.subtract_both_sides(current, b)) current = steps[-1]["after"] left, right = current.split("=") left_expr = parse_expr(left, transformations=transformations) right_expr = parse_expr(right) steps[-1]["after"] = f"{sstr(left_expr)} = {sstr(right_expr)}" return steps @register_steps_generator("distribution") def generate_distribution_steps (problem): #a(x + b) = c steps = [] x = symbols('x') current = problem["problem"] left, right = current.split("=") left_expr = parse_expr(left, transformations=transformations, evaluate=False) right_expr = parse_expr(right) terms = left_expr.as_ordered_terms() base = terms[0].args[1] div = simplify(left_expr / base) ## First Step steps.append(algebraic_steps.divide_both_sides(current, div)) current = steps[-1]["after"] ## Second Step b = base.subs(x, 0) if b.is_negative: steps.append(algebraic_steps.add_both_sides(current, -b)) elif b.is_positive: steps.append(algebraic_steps.subtract_both_sides(current, b)) current = steps[-1]["after"] left, right = current.split("=") left_expr = parse_expr(left, transformations=transformations) right_expr = parse_expr(right) steps[-1]["after"] = f"{sstr(left_expr)} = {sstr(right_expr)}" return steps @register_steps_generator("two_sides") def generate_two_sides_steps (problem): #ax + b = dx + e : a != d steps = [] x = symbols('x') current = problem["problem"] left, right = current.split("=") left_expr = parse_expr(left) right_expr = parse_expr(right) a = left_expr.coeff(x) b = left_expr.subs(x, 0) d = right_expr.coeff(x) e = right_expr.subs(x, 0) ## First Step if d.is_negative: steps.append(algebraic_steps.add_both_sides(current, -d*x)) elif d.is_positive: steps.append(algebraic_steps.subtract_both_sides(current, d*x)) current = steps[-1]["after"] left, right = current.split("=") left_expr = parse_expr(left) right_expr = parse_expr(right) steps[-1]["after"] = f"{sstr(left_expr)} = {sstr(right_expr)}" current = steps[-1]["after"] ## Second Step if b.is_negative: steps.append(algebraic_steps.add_both_sides(current, -b)) elif b.is_positive: steps.append(algebraic_steps.subtract_both_sides(current, b)) current = steps[-1]["after"] left, right = current.split("=") left_expr = parse_expr(left, transformations=transformations) right_expr = parse_expr(right) steps[-1]["after"] = f"{sstr(left_expr)} = {sstr(right_expr)}" current = steps[-1]["after"] ## Third Step new_left, new_right = current.split("=") new_left_expr = parse_expr(left, transformations=transformations, evaluate=False) new_right_expr = parse_expr(right, transformations=transformations, evaluate=False) div = left_expr.coeff(x) if div != 1 and div != -1: steps.append(algebraic_steps.divide_both_sides(current, div)) elif div == -1: steps.append(algebraic_steps.multiply_both_sides(current, div)) return steps @register_steps_generator("like_terms") def generate_like_terms_steps (problem): #ax + bx + c = d steps = [] x = symbols('x') current = problem["problem"] ## First Step steps.append(algebraic_steps.combine_like_terms(current)) current = steps[-1]["after"] ## Second Step left, right = current.split("=") left_expr = parse_expr(left) b = left_expr.subs(x, 0) if b.is_negative: steps.append(algebraic_steps.add_both_sides(current, -b)) elif b.is_positive: steps.append(algebraic_steps.subtract_both_sides(current, b)) current = steps[-1]["after"] left, right = current.split("=") left_expr = parse_expr(left, transformations=transformations) right_expr = parse_expr(right) steps[-1]["after"] = f"{sstr(left_expr)} = {sstr(right_expr)}" current = steps[-1]["after"] ## Third Step div = left_expr.coeff(x) if div != 1 and div != -1: steps.append(algebraic_steps.divide_both_sides(current, div)) elif div == -1: steps.append(algebraic_steps.multiply_both_sides(current, div)) return steps @register_steps_generator("quadratic") def generate_quadratic_steps (problem): #ax² + bx + c = 0 steps = [] x = symbols('x') current = problem["problem"] left, right = current.split("=") left_expr = parse_expr(left, transformations=transformations) right_expr = parse_expr(right) a = left_expr.coeff(x**2) b = left_expr.coeff(x) c = left_expr.subs(x, 0) div = gcd(a, b, c) if a.is_negative: div = -div ## First Step if div != 1 and c.is_zero == False: steps.append(algebraic_steps.factor_out(current, div)) current = steps[-1]["after"] elif c.is_zero: div = gcd(a, b) steps.append(algebraic_steps.factor_out(current, div*x)) current = steps[-1]["after"] if c.is_zero == False: ## Second Steps left, right = current.split("=") left_expr = parse_expr(left, transformations=transformations) inner = left_expr / div steps.extend(algebraic_steps.trinomial_by_grouping(current,inner)) current = steps[-1]["after"] ##Solve the Roots steps.extend(algebraic_steps.solve_roots(current)) current = steps[-1]["after"] # Check for incorrect answers steps.extend(algebraic_steps.check_roots(problem["problem"], current)) return steps @register_steps_generator("difference_squares") def generate_difference_squares_steps (problem): #x² - a² = 0 steps = [] x = symbols('x') current = problem["problem"] left, right = current.split("=") left_expr = parse_expr(left, transformations=transformations) b = left_expr.subs(x, 0) ## Step 1 if b.is_negative: steps.append(algebraic_steps.add_both_sides(current, -b)) elif b.is_positive: steps.append(algebraic_steps.subtract_both_sides(current, b)) current = steps[-1]["after"] left, right = current.split("=") left_expr = parse_expr(left, transformations=transformations) right_expr = parse_expr(right) steps[-1]["after"] = f"{sstr(left_expr)} = {sstr(right_expr)}" current = steps[-1]["after"] ## Step 2 steps.append(algebraic_steps.square_root_both_sides(current)) current = steps[-1]["after"] # Check for incorrect answers steps.extend(algebraic_steps.check_roots(problem["problem"], current)) return steps @register_steps_generator("zero_product") def generate_zero_product_steps (problem): #(x + a)(x + b) = 0 steps = [] current = problem["problem"] steps.extend(algebraic_steps.solve_roots(current)) return steps @register_steps_generator("radical") def generate_radical_steps (problem): #√(x + a) = b steps = [] x = symbols('x') current = problem["problem"] ## First Step steps.append(algebraic_steps.square_both_sides(current)) ## Second Step current = steps[-1]["after"] left, right = current.split("=") left_expr = parse_expr(left, transformations=transformations) b = left_expr.subs(x, 0) if b.is_zero != False: if b.is_negative: steps.append(algebraic_steps.add_both_sides(current, -b)) elif b.is_positive: steps.append(algebraic_steps.subtract_both_sides(current, b)) current = steps[-1]["after"] left, right = current.split("=") left_expr = parse_expr(left, transformations=transformations) right_expr = parse_expr(right) steps[-1]["after"] = f"{sstr(left_expr)} = {sstr(right_expr)}" current = steps[-1]["after"] # Check for incorrect answers steps.extend(algebraic_steps.check_roots(problem["problem"], current)) return steps @register_steps_generator("fraction") def generate_fraction_steps (problem): #(x/a) + b = c steps = [] x = symbols('x') current = problem["problem"] ## First Step left, right = current.split("=") left_expr = parse_expr(left, transformations=transformations) b = left_expr.subs(x, 0) if b.is_zero == False: if b.is_negative: steps.append(algebraic_steps.add_both_sides(current, -b)) elif b.is_positive: steps.append(algebraic_steps.subtract_both_sides(current, b)) current = steps[-1]["after"] left, right = current.split("=") left_expr = parse_expr(left, transformations=transformations) right_expr = parse_expr(right) steps[-1]["after"] = f"{sstr(left_expr)} = {sstr(right_expr)}" current = steps[-1]["after"] ## Second step num, den = fraction(left_expr) if left_expr.subs(x,1).is_negative: steps.append(algebraic_steps.multiply_both_sides(current, -den)) else: steps.append(algebraic_steps.multiply_both_sides(current, den)) return steps @register_steps_generator("binomial") def generate_binomial_steps (problem): #a(x + b) + c(x + d) = e steps = [] x = symbols('x') current = problem["problem"] ## Distribute Terms init_printing(order='none') last_len = -1 while last_len != len(steps): last_len = len(steps) steps.extend(algebraic_steps.distribute_left_step(current)) if len(steps): current = steps[-1]["after"] init_printing(order='lex') ## Combine Like Terms steps.append(algebraic_steps.combine_like_terms(current)) current = steps[-1]["after"] left, right = current.split("=") left_expr = parse_expr(left, transformations=transformations) ## Subtract constant b = left_expr.subs(x, 0) if b.is_zero == False: if b.is_negative: steps.append(algebraic_steps.add_both_sides(current, -b)) elif b.is_positive: steps.append(algebraic_steps.subtract_both_sides(current, b)) current = steps[-1]["after"] left, right = current.split("=") left_expr = parse_expr(left, transformations=transformations) right_expr = parse_expr(right) steps[-1]["after"] = f"{sstr(left_expr)} = {sstr(right_expr)}" current = steps[-1]["after"] ## Divide by coefficient a = left_expr.coeff(x) if a != 1 and a != -1: steps.append(algebraic_steps.divide_both_sides(current, a)) elif a == -1: steps.append(algebraic_steps.multiply_both_sides(current, a)) return steps @register_steps_generator("tricky") def generate_tricky_steps (problem): #(x² - x - a) / (x + b) = c steps = [] x = symbols('x') current = problem["problem"] # Multiply Denom left, right = current.split("=") left_expr = parse_expr(left, transformations=transformations) right_expr = parse_expr(right) num, den = left_expr.as_numer_denom() steps.append(algebraic_steps.multiply_both_sides(current, den)) current = steps[-1]["after"] # Distribute out right side init_printing(order='none') steps.extend(algebraic_steps.distribute_right_step(current)) current = steps[-1]["after"] init_printing(order='lex') # Move Everything to Left steps.append(algebraic_steps.move_all_to_one_side(current)) current = steps[-1]["after"] # Combine Like Terms steps.append(algebraic_steps.combine_like_terms(current)) current = steps[-1]["after"] # Solve Quadratic print(f"calling generate_quadratic_steps with: {current}") steps.extend(generate_quadratic_steps({"problem" : current})) current = steps[-1]["after"] # Check for incorrect answers steps.extend(algebraic_steps.check_roots(problem["problem"], current)) return steps def generate_steps(problem): problem_type = problem["type"] if problem_type not in STEP_GENERATORS: raise ValueError(f"No step generator for type: {problem_type}") return STEP_GENERATORS[problem["type"]](problem)