a third through Step Generator

This commit is contained in:
2026-04-29 06:27:05 -04:00
parent 63f32a51cb
commit 680c59513c
9 changed files with 492 additions and 66 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

197
algebraic_steps.py Normal file
View File

@@ -0,0 +1,197 @@
#All Rights Reserved John Salguero
#Steps that are generated
from sympy import *
from sympy.parsing.sympy_parser import (
parse_expr,
standard_transformations,
implicit_multiplication_application
)
transformations = standard_transformations + (implicit_multiplication_application,)
def move_all_to_one_side(equation):
step = {}
current = equation
step["before"] = current
left, right = current.split("=")
x = symbols('x')
left_expr = parse_expr(left, transformations=transformations, evaluate=False)
right_expr = parse_expr(right, transformations=transformations, evaluate=False)
new_expr = left_expr - right_expr
step["after"] = f"{sstr(new_expr)} = 0"
step["step"] = f"Subtract both sides by {sstr(right_expr)}"
step["rule"] = "Subtraction Property of Equality"
return step
def add_both_sides(equation, value):
step = {}
current = equation
step["before"] = current
left, right = current.split("=")
x = symbols('x')
left_expr = parse_expr(left, transformations=transformations, evaluate=False)
right_expr = parse_expr(right, transformations=transformations, evaluate=False)
new_left_expr = left_expr + value
new_right_expr = right_expr + value
step["after"] = f"{sstr(new_left_expr)} = {sstr(new_right_expr)}"
step["step"] = f"Add both sides by {sstr(value)}"
step["rule"] = "Addition Property of Equality"
return step
def subtract_both_sides(equation, value):
step = {}
current = equation
step["before"] = current
left, right = current.split("=")
x = symbols('x')
left_expr = parse_expr(left, transformations=transformations, evaluate=False)
right_expr = parse_expr(right, transformations=transformations, evaluate=False)
new_left_expr = left_expr - value
new_right_expr = right_expr - value
step["after"] = f"{sstr(new_left_expr)} = {sstr(new_right_expr)}"
step["step"] = f"Subtract both sides by {sstr(value)}"
step["rule"] = "Subtraction Property of Equality"
return step
def divide_both_sides(equation, value):
step = {}
current = equation
step["before"] = current
left, right = current.split("=")
x = symbols('x')
left_expr = parse_expr(left, transformations=transformations, evaluate=False)
right_expr = parse_expr(right, transformations=transformations, evaluate=False)
new_left_expr = clean(left_expr / value)
new_right_expr = clean(right_expr / value)
step["after"] = f"{sstr(new_left_expr)} = {sstr(new_right_expr)}"
step["step"] = f"Divide both sides by {sstr(value)}"
step["rule"] = "Division Property of Equality"
return step
def multiply_both_sides(equation, value):
step = {}
current = equation
step["before"] = current
left, right = current.split("=")
x = symbols('x')
left_expr = parse_expr(left, transformations=transformations, evaluate=False)
right_expr = parse_expr(right, transformations=transformations, evaluate=False)
new_left_expr = left_expr * value
new_right_expr = right_expr * value
step["after"] = f"{sstr(new_left_expr)} = {sstr(new_right_expr)}"
step["step"] = f"Multiply both sides by {sstr(value)}"
step["rule"] = "Multiplication Property of Equality"
return step
def factor_collect(equation):
step = {}
current = equation
step["before"] = current
left, right = current.split("=")
x = symbols('x')
left_expr = parse_expr(left, transformations=transformations, evaluate=False)
right_expr = parse_expr(right, transformations=transformations, evaluate=False)
new_left_expr = factor(left_expr)
new_right_expr = factor(right_expr)
step["after"] = f"{sstr(new_left_expr)} = {sstr(new_right_expr)}"
step["step"] = f"Collect the factors"
step["rule"] = "Factoring by grouping"
return step
def factor_form_collection(equation, factor):
step = {}
current = equation
step["before"] = current
left, right = current.split("=")
x = symbols('x')
left_expr = parse_expr(left, transformations=transformations, evaluate=False)
right_expr = parse_expr(right, transformations=transformations, evaluate=False)
new_left_expr = collect(left_expr, factor)
new_right_expr = collect(right_expr, factor)
step["after"] = f"{sstr(new_left_expr)} = {sstr(new_right_expr)}"
step["step"] = f"Collect the factors using factor {sstr(factor)}"
step["rule"] = "Factor by grouping"
return step
def combine_like_terms(equation):
step = {}
current = equation
step["before"] = current
left, right = current.split("=")
x = symbols('x')
left_expr = parse_expr(left, transformations=transformations, evaluate=False)
right_expr = parse_expr(right, transformations=transformations, evaluate=False)
## Combine Left Terms
left_terms = left_expr.as_ordered_terms()
# group by base
left_groups = {}
for t in left_terms:
coeff, rest = t.as_coeff_Mul()
left_groups.setdefault(rest, 0)
left_groups[rest] += coeff
# rebuild manually
new_left_terms = []
for base, coeff in left_groups.items():
if coeff != 0:
new_left_terms.append(coeff * base)
new_left_expr = sum(new_left_terms)
## Comnine Right Terms
right_terms = right_expr.as_ordered_terms()
# group by base
right_groups = {}
for t in right_terms:
coeff, rest = t.as_coeff_Mul()
right_groups.setdefault(rest, 0)
right_groups[rest] += coeff
# rebuild manually
new_right_terms = []
for base, coeff in right_groups.items():
if coeff != 0:
new_right_terms.append(coeff * base)
new_right_expr = sum(new_right_terms)
step["after"] = f"{sstr(new_left_expr)} = {sstr(new_right_expr)}"
step["step"] = "Collect Like Terms"
step["rule"] = "Combine the like terms"
return step
def clean(expr):
# remove explicit 1 multipliers
expr = expr.replace(
lambda e: isinstance(e, Mul),
lambda e: Mul(*[arg for arg in e.args if arg != 1])
)
return expr

35
main.py
View File

@@ -1,44 +1,19 @@
#All Rights Reserved John Salguero #All Rights Reserved John Salguero
#Starts the backend to my Youtube stream #Starts the backend to my Youtube stream
from problem_generator import generate_problem, normalize from problem_generator import generate_problem
from mathhook import parse, solve from steps_generator import generate_steps
from sympy import sympify
#define the entry point to the programs #define the entry point to the programs
def main(): def main():
problem = generate_problem() problem = generate_problem()
steps = generate_steps(problem);
print("Generated Problem:") print("Generated Problem:")
print(problem) print(problem)
print("Solve:")
equation = apply_strategy(problem)
expr = parse(equation) print("Steps:")
result = solve(expr, "x") print(steps)
print(result)
def square_both_sides(problem):
lhs, rhs = problem["problem"].split("=")
lhs = sympify(lhs.strip())
rhs = sympify(rhs.strip())
lhs = lhs ** 2
rhs = rhs ** 2
return f"{normalize(lhs)} = {normalize(rhs)}"
def factor_or_formula(problem) :
return problem["problem"]
def apply_strategy(problem):
if problem["type"] == "radical":
return square_both_sides(problem)
if problem["type"] == "quadratics":
return factor_or_formula(problem)
return problem["problem"]
#Starts the program #Starts the program
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -4,17 +4,26 @@
import random import random
from sympy import * from sympy import *
TEMPLATES = {}
def register_problem_generator(problem_type):
def decorator(func):
TEMPLATES[problem_type] = func
return func
return decorator
@register_problem_generator("linear")
def generate_linear(): def generate_linear():
#ax + b = c #ax + b = c
a = random.choice([i for i in range(-10, 16) if i != 0]) a = random.choice([i for i in range(-10, 16) if i != 0])
ans = random.choice([i for i in range(-10, 11)]) ans = random.choice([i for i in range(-10, 11)])
b = random.choice([i for i in range(-10, 11)]) b = random.choice([i for i in range(-10, 11) if a != 1 or i != 0])
c = a * ans + b c = a * ans + b
x = symbols('x') x = symbols('x')
expr = a * x + b expr = a * x + b
# expanded = n # expanded = n
s = normalize(expr) s = sstr(expr)
return { return {
"type": "linear", "type": "linear",
@@ -22,20 +31,21 @@ def generate_linear():
"solution": ans "solution": ans
} }
@register_problem_generator("hidden_factor")
def generate_hidden_factor(): def generate_hidden_factor():
#a(x + b) + c(x + b) = d #a(x + b) + c(x + b) = d
ans = random.choice([i for i in range(-10, 16)]) ans = random.choice([i for i in range(-10, 16)])
b = random.choice([i for i in range(-5, 6) if i != 0]) b = random.choice([i for i in range(-5, 6) if i != 0])
a = random.choice([i for i in range(-5, 6) if i != 0]) a = random.choice([i for i in range(-5, 6) if i != 0])
c = random.choice([i for i in range(-5, 6) if i != 0]) c = random.choice([i for i in range(-5, 6) if i != 0 and i != -a])
x = symbols('x') x = symbols('x')
inner_expr = x + b inner_expr = x + b
inner_value = ans + b inner_value = ans + b
right_side = a * inner_value + c * inner_value right_side = a * inner_value + c * inner_value
problem = f"{a}({normalize(inner_expr)}) + {c}({normalize(inner_expr)}) = {right_side}" problem = f"{a}({sstr(inner_expr)}) + {c}({sstr(inner_expr)}) = {right_side}"
return { return {
"type": "hidden_factor", "type": "hidden_factor",
@@ -43,6 +53,7 @@ def generate_hidden_factor():
"solution": ans "solution": ans
} }
@register_problem_generator("distribution")
def generate_distribution (): def generate_distribution ():
#a(x + b) = c #a(x + b) = c
ans = random.choice([i for i in range(-10, 16)]) ans = random.choice([i for i in range(-10, 16)])
@@ -55,10 +66,11 @@ def generate_distribution ():
return { return {
"type": "distribution", "type": "distribution",
"problem": f"{a}({normalize(inner_expr)}) = {c}", "problem": f"{a}({sstr(inner_expr)}) = {c}",
"solution": ans "solution": ans
} }
@register_problem_generator("two_sides")
def generate_two_sides (): def generate_two_sides ():
#ax + b = dx + e : a != d #ax + b = dx + e : a != d
ans = random.choice([i for i in range(-10, 16)]) ans = random.choice([i for i in range(-10, 16)])
@@ -73,10 +85,11 @@ def generate_two_sides ():
return { return {
"type": "two_sides", "type": "two_sides",
"problem": f"{normalize(left_exp)} = {normalize(right_exp)}", "problem": f"{sstr(left_exp)} = {sstr(right_exp)}",
"solution": ans "solution": ans
} }
@register_problem_generator("like_terms")
def generate_like_terms (): def generate_like_terms ():
#ax + bx + c = d #ax + bx + c = d
ans = random.choice([i for i in range(-10, 16)]) ans = random.choice([i for i in range(-10, 16)])
@@ -90,10 +103,11 @@ def generate_like_terms ():
return { return {
"type": "like_terms", "type": "like_terms",
"problem": f"{normalize(expr)} = {d}", "problem": f"{sstr(expr)} = {d}",
"solution": ans "solution": ans
} }
@register_problem_generator("quadratic")
def generate_quadratic (): def generate_quadratic ():
#ax² + bx + c = 0 #ax² + bx + c = 0
r1 = random.choice([i for i in range(-10, 16)]) r1 = random.choice([i for i in range(-10, 16)])
@@ -103,7 +117,7 @@ def generate_quadratic ():
x = symbols('x') x = symbols('x')
expr = n *(x - r1) * (x - r2) expr = n *(x - r1) * (x - r2)
expr = simplify(expr) expr = expand(expr)
if r1 == r2: if r1 == r2:
solution = r1 solution = r1
else: else:
@@ -111,10 +125,11 @@ def generate_quadratic ():
return { return {
"type": "quadratic", "type": "quadratic",
"problem": f"{normalize(expr)} = 0", "problem": f"{sstr(expr)} = 0",
"solution": solution "solution": solution
} }
@register_problem_generator("difference_squares")
def generate_difference_squares (): def generate_difference_squares ():
#x² - a² = 0 #x² - a² = 0
ans = random.choice([i for i in range(0, 13)]) ans = random.choice([i for i in range(0, 13)])
@@ -130,10 +145,11 @@ def generate_difference_squares ():
return { return {
"type": "difference_squares", "type": "difference_squares",
"problem": f"{normalize(expr)} = 0", "problem": f"{sstr(expr)} = 0",
"solution": solution "solution": solution
} }
@register_problem_generator("zero_product")
def generate_zero_product (): def generate_zero_product ():
#(x + a)(x + b) = 0 #(x + a)(x + b) = 0
a = random.choice([i for i in range(-5, 6)]) a = random.choice([i for i in range(-5, 6)])
@@ -144,10 +160,11 @@ def generate_zero_product ():
return { return {
"type": "zero_product", "type": "zero_product",
"problem": f"{normalize(expr)} = 0", "problem": f"{sstr(expr)} = 0",
"solution": [-a, -b] "solution": [-a, -b]
} }
@register_problem_generator("radical")
def generate_radical (): def generate_radical ():
#√(x + a) = b #√(x + a) = b
a = random.choice([i for i in range(-10, 16)]) a = random.choice([i for i in range(-10, 16)])
@@ -159,10 +176,11 @@ def generate_radical ():
return { return {
"type": "radical", "type": "radical",
"problem": f"{normalize(expr)} = {b}", "problem": f"{sstr(expr)} = {b}",
"solution": ans "solution": ans
} }
@register_problem_generator("fraction")
def generate_fraction (): def generate_fraction ():
#(x/a) + b = c #(x/a) + b = c
a = random.choice([i for i in range(-7, 8) if i != 0 and i != 1]) a = random.choice([i for i in range(-7, 8) if i != 0 and i != 1])
@@ -175,10 +193,11 @@ def generate_fraction ():
return { return {
"type": "fraction", "type": "fraction",
"problem": f"{normalize(expr)} = {c}", "problem": f"{sstr(expr)} = {c}",
"solution": ans "solution": ans
} }
@register_problem_generator("binomial")
def generate_binomial (): def generate_binomial ():
#a(x + b) + c(x + d) = e #a(x + b) + c(x + d) = e
ans = random.choice([i for i in range(-15, 16)]) ans = random.choice([i for i in range(-15, 16)])
@@ -197,12 +216,13 @@ def generate_binomial ():
return { return {
"type": "binomial", "type": "binomial",
"problem": f"{normalize(expr)} = {e}", "problem": f"{sstr(expr)} = {e}",
"solution": ans "solution": ans
} }
@register_problem_generator("tricky")
def generate_tricky (): def generate_tricky ():
#generate random numbers #(x² - x - a) / (x + b) = c
n = random.choice([i for i in range(-5, 6) if i != 0]) n = random.choice([i for i in range(-5, 6) if i != 0])
r1 = random.choice([i for i in range(-10, 16)]) r1 = random.choice([i for i in range(-10, 16)])
r2 = random.choice([i for i in range(-10, 16) if i != r1]) r2 = random.choice([i for i in range(-10, 16) if i != r1])
@@ -214,7 +234,7 @@ def generate_tricky ():
expanded = expanded - expr expanded = expanded - expr
expanded = expanded / (x - r1) expanded = expanded / (x - r1)
# expanded = n # expanded = n
s = normalize(expanded) s = sstr(expanded)
return { return {
"type": "tricky", "type": "tricky",
@@ -222,24 +242,15 @@ def generate_tricky ():
"solution": r2 "solution": r2
} }
def normalize(expr):
return sstr(expr).replace("**", "^")
def generate_problem(): def generate_problem():
template = random.choice(TEMPLATES) types = list(TEMPLATES.keys())
return template() #print(types)
# ['linear', 'hidden_factor', 'distribution', 'two_sides', 'like_terms', 'quadratic', 'difference_squares', 'zero_product', 'radical', 'fraction', 'binomial', 'tricky']
weights = [0.80 , 0.90 , 0.70 , 1.00 , 0.9 , 1.0 , 0.80 , 0.70 , 0.70 , 1.00 , 1.00 , 0.65]
TEMPLATES = [ problem_type = random.choices(types, weights=weights)[0]
generate_linear, template = TEMPLATES[problem_type]
generate_hidden_factor, return generate_like_terms()
generate_distribution, #return template()
generate_two_sides,
generate_like_terms,
generate_quadratic,
generate_difference_squares,
generate_zero_product,
generate_radical,
generate_fraction,
generate_binomial,
generate_tricky
]

View File

@@ -1,2 +1 @@
sympy sympy
mathhook

244
steps_generator.py Normal file
View File

@@ -0,0 +1,244 @@
#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:
steps.append(algebraic_steps.divide_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 = []
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)
return steps
@register_steps_generator("quadratic")
def generate_quadratic_steps (problem):
#ax² + bx + c = 0
steps = []
return steps
@register_steps_generator("difference_squares")
def generate_difference_squares_steps (problem):
#x² - a² = 0
steps = []
return steps
@register_steps_generator("zero_product")
def generate_zero_product_steps (problem):
#(x + a)(x + b) = 0
steps = []
return steps
@register_steps_generator("radical")
def generate_radical_steps (problem):
#√(x + a) = b
steps = []
return steps
@register_steps_generator("fraction")
def generate_fraction_steps (problem):
#(x/a) + b = c
steps = []
return steps
@register_steps_generator("binomial")
def generate_binomial_steps (problem):
#a(x + b) + c(x + d) = e
steps = []
return steps
@register_steps_generator("tricky")
def generate_tricky_steps (problem):
#(x² - x - a) / (x + b) = c
steps = []
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)