V1 untested

This commit is contained in:
2026-04-30 15:21:38 -04:00
parent c31cb1c699
commit a98042ca5b
7 changed files with 223 additions and 18 deletions

View File

@@ -2,6 +2,7 @@
#Steps that are generated
from sympy import *
import re
from sympy.parsing.sympy_parser import (
parse_expr,
standard_transformations,
@@ -94,8 +95,8 @@ def multiply_both_sides(equation, value):
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
new_left_expr = cancel(left_expr * value)
new_right_expr = Mul(right_expr, value, evaluate=False)
step["after"] = f"{sstr(new_left_expr)} = {sstr(new_right_expr)}"
step["step"] = f"Multiply both sides by {sstr(value)}"
@@ -253,8 +254,12 @@ def trinomial_by_grouping(equation, inner):
new_left_expr = left_expr
steps[-1]["after"] = f"{sstr(new_left_expr)} = {sstr(right_expr)}"
steps[-1]["step"] = f"Seperate the x coeficients equal to the factors of {sstr(ac)} that {action} {sstr(Abs(b))}"
steps[-1]["rule"] = "Split Coeficients"
steps[-1]["step"] = (
f"Seperate the x coefficient to equal the factors of the first times last coefficients"
f"({sstr(Abs(a))} x {sstr(Abs(c))} = {sstr(ac)}) that {action} {sstr(Abs(b))}"
f"({sstr(factor1)},{sstr(factor2)})"
)
steps[-1]["rule"] = "Split Coefficients"
## Factor Out X on left term
steps.append({})
@@ -262,6 +267,7 @@ def trinomial_by_grouping(equation, inner):
terms = left_expr.as_ordered_terms()
t1, t2, t3, t4 = terms[0], terms[1], terms[2], terms[3]
div = gcd(t3, t4)
factored_part1 = factor(t1 + t2)
factored_part2 = factor(t3 + t4)
rest = sum(terms[2:])
@@ -280,7 +286,7 @@ def trinomial_by_grouping(equation, inner):
new_left_expr = Mul(n, left_expr, evaluate=False)
steps[-1]["after"] = f"{sstr(new_left_expr)} = {sstr(right_expr)}"
steps[-1]["step"] = f"Factor out the GCD from the right two terms of the inner polynomial"
steps[-1]["step"] = f"Factor out the GCD({sstr(div)}) from the right two terms of the inner polynomial"
steps[-1]["rule"] = "Reverse Distributive Property"
## Add Like Terms
@@ -311,9 +317,30 @@ def solve_roots(equation):
x = symbols('x')
left_expr = parse_expr(left, transformations=transformations, evaluate=False)
factors = left_expr.as_ordered_factors()
x_factors = [f for f in factors if f.has(x)]
## Get the roots
factors = left_expr.as_ordered_factors()
x_factors = []
i = 0
while i < len(factors):
f = factors[i]
if f.has(x):
# If it's already something like 2*x or (x+...)
x_factors.append(f)
i += 1
else:
# Check if next factor has x → combine them
if i + 1 < len(factors) and factors[i + 1].has(x) and not factors[i + 1].is_Add:
combined = Mul(f, factors[i + 1], evaluate=False)
x_factors.append(combined)
i += 2
else:
i += 1
## Iterate through the roots
solutions = ""
for factor in x_factors:
clean_factor = clean(factor)
@@ -399,7 +426,7 @@ def combine_like_terms(equation):
step["rule"] = "Combine the like terms"
return step
def distribute_step(equation):
def distribute_left_step(equation):
steps = []
current = equation
@@ -416,12 +443,101 @@ def distribute_step(equation):
if distributed != None:
steps.append({})
steps[-1]["before"] = current
steps[-1]["after"] = f"{sstr(safe_format(new_left_expr))} = {sstr(right_expr)}"
steps[-1]["after"] = f"{sstr(safe_format(new_left_expr))} = {sstr(safe_format(right_expr))}"
steps[-1]["step"] = f"Distribute out {sstr(distributed)}"
steps[-1]["rule"] = "Distributive Law of Multiplication"
return steps
def distribute_right_step(equation):
steps = []
current = equation
left, right = current.split("=")
left = left.replace("-(", "-1*(")
x = symbols('x')
left_expr = parse_expr(left, transformations=transformations, evaluate=False)
right_expr = parse_expr(right, transformations=transformations, evaluate=False)
#print(f"calling distribute_once with expression: {sstr(left_expr)}")
new_right_expr, distributed = distribute_once(right_expr)
if distributed != None:
steps.append({})
steps[-1]["before"] = current
steps[-1]["after"] = f"{sstr(safe_format(left_expr))} = {sstr(safe_format(new_right_expr))}"
steps[-1]["step"] = f"Distribute out {sstr(distributed)}"
steps[-1]["rule"] = "Distributive Law of Multiplication"
return steps
def check_roots(equation, roots):
steps = []
valid_roots = ""
str_values = [r.split("=")[1].strip() for r in roots.split(",")]
values = [sympify(value) for value in str_values]
current = equation
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)
# Check the roots
for value in values:
## Substitution
left_subbed = substitute_var(left, 'x', f"{value}")
right_subbed = substitute_var(right, 'x', f"{value}")
left_subbed_exp = parse_expr(left_subbed)
right_subbed_exp = parse_expr(right_subbed)
steps.append({})
steps[-1]["before"] = equation
steps[-1]["after"] = f"{left_subbed} = {right_subbed}"
steps[-1]["step"] = f"Substitute x with {value}"
steps[-1]["rule"] = "Substitution"
## Check
l_result = simplify(left_subbed_exp)
r_result = simplify(right_subbed_exp)
if l_result in (zoo, oo, -oo, nan) or r_result in (zoo, oo, -oo, nan):
steps.append({})
steps[-1]["before"] = steps[-2]["after"]
steps[-1]["after"] = f"Undefined"
steps[-1]["step"] = f"Found Extraneous Root, {value} is incorrect"
steps[-1]["rule"] = "Extraneous Root"
continue
if l_result != r_result:
steps.append({})
steps[-1]["before"] = steps[-2]["after"]
steps[-1]["after"] = f"{sstr(l_result)}{sstr(r_result)}"
steps[-1]["step"] = f"Found Extraneous Root, {value} is incorrect"
steps[-1]["rule"] = "Extraneous Root"
continue
else:
steps.append({})
steps[-1]["before"] = steps[-2]["after"]
steps[-1]["after"] = f"{sstr(l_result)} = {sstr(r_result)}"
steps[-1]["step"] = f"{value} is correct"
steps[-1]["rule"] = "Found Root"
if len(valid_roots) > 0:
valid_roots += ","
valid_roots += f"x = {value}"
steps.append({})
steps[-1]["before"] = equation
steps[-1]["after"] = valid_roots
steps[-1]["step"] = f"Found Valid Roots"
steps[-1]["rule"] = "Found Solution"
return steps
def substitute_var(expr, var, value):
pattern = rf'\b{re.escape(var)}\b'
return re.sub(pattern, f'({value})', expr)
def build_ordered_add(args):
flat_args = []
@@ -521,12 +637,35 @@ def clean(expr):
def safe_format(expr):
if expr.is_Mul:
args = []
sign = 1
for a in expr.args:
a = safe_format(a)
if a == 1:
continue
elif a == -1:
sign *= -1
else:
args.append(a)
return Mul(*args, evaluate=False)
if not args:
return -1 if sign == -1 else 1
# if everything is numeric → evaluate fully
if all(a.is_Number for a in args):
val = Mul(*args)
return -val if sign == -1 else val
if len(args) == 1:
result = args[0]
else:
result = Mul(*args, evaluate=False)
if sign == -1:
return Mul(-1, result, evaluate=False)
return result
if expr.is_Add:
return expr.func(*[safe_format(a) for a in expr.args], evaluate=False)

19
main.py
View File

@@ -7,7 +7,7 @@ from sympy import init_printing
#define the entry point to the programs
def main():
init_printing(order='none')
init_printing(order='lex')
problem = generate_problem()
steps = generate_steps(problem);
@@ -15,7 +15,22 @@ def main():
print(problem)
print("Steps:")
print(steps)
pretty_print_steps(steps)
def pretty_print_steps(steps):
print("\n" + "=" * 50)
for i, step in enumerate(steps, start=1):
print(f"\nStep {i}")
print("-" * 50)
print(f"Before: {step.get('before', '')}")
print(f"After: {step.get('after', '')}")
for key in step:
if key not in ("before", "after"):
print(f"{key.capitalize()}: {step[key]}")
print("\n" + "=" * 50)
#Starts the program
if __name__ == "__main__":

View File

@@ -120,7 +120,6 @@ def generate_quadratic ():
x = symbols('x')
expr = n *(s * x - r1) * (x - r2)
print(f"n:{n}, s:{s}")
expr = expand(expr)
root1 = Rational(r1, s)
root2 = Integer(r2)
@@ -235,9 +234,12 @@ def generate_binomial ():
@register_problem_generator("tricky")
def generate_tricky ():
#(x² - x - a) / (x + b) = c
r1 = 0
r2 = 0
while r1 == r2 or r1 == 0 and r2 == 0:
r1 = random.choice(range(-10, 13))
r2 = random.choice(range(-10, 13))
n = random.choice([i for i in range(-5, 6) if i != 0])
r1 = random.choice([i for i in range(-10, 16)])
r2 = random.choice([i for i in range(-10, 16) if i != r1])
x = symbols('x')
expr = (x - r1) * (x - r2)
@@ -263,6 +265,6 @@ def generate_problem():
problem_type = random.choices(types, weights=weights)[0]
template = TEMPLATES[problem_type]
return generate_binomial()
return generate_tricky()
#return template()

View File

@@ -242,6 +242,10 @@ def generate_quadratic_steps (problem):
##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
@@ -270,7 +274,10 @@ def generate_difference_squares_steps (problem):
## 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
@@ -311,6 +318,11 @@ def generate_radical_steps (problem):
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")
@@ -353,12 +365,14 @@ def generate_binomial_steps (problem):
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_step(current))
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))
@@ -376,6 +390,9 @@ def generate_binomial_steps (problem):
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)
@@ -390,6 +407,38 @@ def generate_binomial_steps (problem):
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