456 lines
15 KiB
Python
456 lines
15 KiB
Python
#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_nonzero:
|
|
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.extend(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, skip_check=False):
|
|
#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_zero:
|
|
return generate_linear_steps(problem)
|
|
if a.is_negative:
|
|
div = -div
|
|
|
|
## First Step
|
|
if div != 1 and c.is_nonzero:
|
|
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_nonzero:
|
|
## 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
|
|
if not skip_check:
|
|
steps.extend(algebraic_steps.check_roots(problem["problem"], current))
|
|
|
|
return steps
|
|
|
|
@register_steps_generator("difference_squares")
|
|
def generate_difference_squares_steps (problem, skip_check=False):
|
|
#x² - a² = 0 : a is not 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
|
|
if not skip_check:
|
|
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, skip_check=False):
|
|
#√(x + a) = b
|
|
steps = []
|
|
x = symbols('x')
|
|
current = problem["problem"]
|
|
|
|
|
|
## Square both sides
|
|
steps.append(algebraic_steps.square_both_sides(current))
|
|
|
|
## Subtract constant
|
|
current = steps[-1]["after"]
|
|
left, right = current.split("=")
|
|
left_expr = parse_expr(left, transformations=transformations)
|
|
b = left_expr.subs(x, 0)
|
|
if b.is_nonzero :
|
|
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
|
|
if not skip_check:
|
|
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_nonzero:
|
|
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.extend(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_nonzero:
|
|
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, skip_check=False):
|
|
#(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.extend(algebraic_steps.combine_like_terms(current))
|
|
current = steps[-1]["after"]
|
|
|
|
# Solve Quadratic
|
|
steps.extend(generate_quadratic_steps({"problem" : current}, True))
|
|
current = steps[-1]["after"]
|
|
|
|
# Check for incorrect answers
|
|
if not skip_check:
|
|
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) |