print statements in places

This commit is contained in:
2026-04-30 00:39:44 -04:00
parent 680c59513c
commit 4e4440673b
7 changed files with 505 additions and 13 deletions

View File

@@ -56,8 +56,8 @@ def subtract_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 = 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"Subtract both sides by {sstr(value)}"
@@ -101,6 +101,44 @@ def multiply_both_sides(equation, value):
step["step"] = f"Multiply both sides by {sstr(value)}"
step["rule"] = "Multiplication Property of Equality"
return step
def square_root_both_sides(equation):
step = {}
current = equation
step["before"] = current
left, right = current.split("=")
x_generic = symbols('x')
x_pos = symbols('x', positive=True)
left_expr = parse_expr(left, transformations=transformations, evaluate=False)
right_expr = parse_expr(right, transformations=transformations, evaluate=False)
new_left_expr = sqrt(left_expr.subs(x_generic, x_pos))
new_right_expr = sqrt(right_expr)
step["after"] = f"{sstr(new_left_expr)} = {sstr(new_right_expr)}, {sstr(new_left_expr)} = -{sstr(new_right_expr)}"
step["step"] = f"Take the square root of both sides"
step["rule"] = "Square Root Property of Equality"
return step
def square_both_sides(equation):
step = {}
current = equation
step["before"] = current
left, right = current.split("=")
left_expr = parse_expr(left, transformations=transformations, evaluate=False)
right_expr = parse_expr(right, transformations=transformations, evaluate=False)
new_left_expr = clean(left_expr * left_expr)
new_right_expr = clean(right_expr * right_expr)
step["after"] = f"{sstr(new_left_expr)} = {sstr(new_right_expr)}"
step["step"] = f"Square both sides"
step["rule"] = "Multiplication property of equality"
return step
def factor_collect(equation):
step = {}
@@ -122,6 +160,7 @@ def factor_collect(equation):
return step
def factor_form_collection(equation, factor):
# Collect factors of factor
step = {}
current = equation
@@ -140,6 +179,180 @@ def factor_form_collection(equation, factor):
step["rule"] = "Factor by grouping"
return step
def factor_out(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 = clean(cancel(left_expr / factor))
new_left_expr = Mul(factor, new_left_expr, evaluate = False)
step["after"] = f"{sstr(new_left_expr)} = {sstr(right_expr)}"
step["step"] = f"Factor out the Greatest Common Factor, {sstr(factor)}"
step["rule"] = "Reverse Distributive Property"
return step
def trinomial_by_grouping(equation, inner):
# expects n (ax**2+bx+c) = rhs : inner = (ax**2+bx+c), b != 0, c != 0
#4 steps
steps = [{}]
current = equation
steps[-1]["before"] = current
left, right = current.split("=")
x = symbols('x')
left_expr = parse_expr(left, transformations=transformations)
right_expr = parse_expr(right, transformations=transformations, evaluate=False)
n = simplify(left_expr / inner)
poly = inner.as_poly(x)
a = poly.coeff_monomial(x**2)
b = poly.coeff_monomial(x)
c = poly.coeff_monomial(1)
ac = Abs(a * c)
## Split Coeficients
factor1 = Integer(1)
factor2 = ac
while factor1 < factor2:
if ac % factor1 == 0:
factor2 = ac / factor1
if c.is_negative:
if Abs(factor1 - factor2) == Abs(b):
break
else:
if factor1 + factor2 == Abs(b):
break
factor1 = factor1 + 1
if factor1 > factor2:
return []
if c.is_negative:
action = "differ by"
if b.is_negative:
left_expr = Add(a * x**2, factor1 * x, -factor2 * x, c, evaluate=False)
else:
left_expr = Add(a * x**2, -factor1 * x, factor2 * x, c, evaluate=False)
else:
action = "add up to"
if b.is_negative:
left_expr = Add(a * x**2, -factor1 * x, -factor2 * x, c, evaluate=False)
else:
left_expr = Add(a * x**2, factor1 * x, factor2 * x, c, evaluate=False)
if n != 1:
new_left_expr = Mul(n, left_expr, evaluate=False)
else:
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"
## Factor Out X on left term
steps.append({})
steps[-1]["before"] = steps[-2]["after"]
terms = left_expr.as_ordered_terms()
t1, t2, t3, t4 = terms[0], terms[1], terms[2], terms[3]
factored_part1 = factor(t1 + t2)
factored_part2 = factor(t3 + t4)
rest = sum(terms[2:])
new_left_expr = Add(factored_part1, rest, evaluate=False)
new_left_expr = Mul(n, new_left_expr, evaluate=False)
steps[-1]["after"] = f"{sstr(new_left_expr)} = {sstr(right_expr)}"
steps[-1]["step"] = f"Factor out the x from the left two terms of the inner polynomial"
steps[-1]["rule"] = "Reverse Distributive Property"
## Factor Out GCD on right term
steps.append({})
steps[-1]["before"] = steps[-2]["after"]
left_expr = Add(factored_part1, factored_part2, evaluate=False)
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]["rule"] = "Reverse Distributive Property"
## Add Like Terms
if steps[-1]["before"] != steps[-1]["after"]:
steps.append({})
steps[-1]["before"] = steps[-2]["after"]
terms = left_expr.as_ordered_terms()
factors = [set(t.as_ordered_factors()) for t in terms]
common = set.intersection(*factors)
base = list(common)[0]
coeffs = [t.coeff(base) for t in terms]
new_expr = sum(coeffs) * base
new_left_expr = flatten_mul(Mul(n, new_expr, evaluate=False))
steps[-1]["after"] = f"{sstr(new_left_expr)} = {sstr(right_expr)}"
steps[-1]["step"] = f"Collect the like terms"
steps[-1]["rule"] = "Combine the like terms"
return steps
def solve_roots(equation):
# expects n(ax+b)(x+c) = 0
#4 steps
steps = []
left, right = equation.split("=")
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)]
solutions = ""
for factor in x_factors:
clean_factor = clean(factor)
steps.append({})
if solutions:
solutions = solutions + ", "
steps[-1]["before"] = equation
steps[-1]["after"] = f"{sstr(clean_factor)} = 0"
steps[-1]["step"] = f"Focus on a root"
steps[-1]["rule"] = "Root of a polynomial"
current = steps[-1]["after"]
a = clean_factor.coeff(x)
b = clean_factor.subs(x, 0)
if b.is_zero == False:
if b.is_negative:
steps.append(add_both_sides(current, -b))
elif b.is_positive:
steps.append(subtract_both_sides(current, b))
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"]
if a != 1 and a != -1:
steps.append(divide_both_sides(current, a))
elif a == -1:
steps.append(multiply_both_sides(current, a))
solutions += steps[-1]["after"]
steps.append({})
steps[-1]["before"] = equation
steps[-1]["after"] = solutions
steps[-1]["step"] = f"Solved The Roots"
steps[-1]["rule"] = "Root of a polynomial"
return steps
def combine_like_terms(equation):
step = {}
@@ -186,6 +399,110 @@ def combine_like_terms(equation):
step["rule"] = "Combine the like terms"
return step
def distribute_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_left_expr, distributed = distribute_once(left_expr)
if distributed != None:
steps.append({})
steps[-1]["before"] = current
steps[-1]["after"] = f"{sstr(safe_format(new_left_expr))} = {sstr(right_expr)}"
steps[-1]["step"] = f"Distribute out {sstr(distributed)}"
steps[-1]["rule"] = "Distributive Law of Multiplication"
return steps
def build_ordered_add(args):
expr = args[0]
for a in args[1:]:
expr = Add(expr, a, evaluate=False)
return expr
def distribute_once(expr):
expr = flatten_mul(expr)
# ------------------------------------------------------------
# STEP 1: ONLY HANDLE DIRECT DISTRIBUTION CASES
# (i.e. Mul where one factor is Add)
# ------------------------------------------------------------
if expr.is_Mul:
print(f"expr: {sstr(expr)}")
add_part = None
other_parts = []
# extract Add factor + everything else
for arg in expr.args:
print(f"arg: {sstr(arg)}")
if arg.is_Add and add_part is None:
add_part = arg
else:
other_parts.append(arg)
# --------------------------------------------------------
# DISTRIBUTION RULE
# --------------------------------------------------------
if add_part is not None:
print(f"expr used: {sstr(expr)}, add used: {sstr(add_part)}")
distributed_value = Mul(*other_parts)
distributed_terms = [
Mul(*other_parts, term)
for term in add_part.args
]
new_expr = build_ordered_add(distributed_terms)
return new_expr, distributed_value
# ------------------------------------------------------------
# STEP 2: PRIORITY-BASED RECURSION (IMPORTANT FIX)
# ------------------------------------------------------------
if expr.args:
print(f"step2 args:{expr.args}")
# PASS 1: ONLY distributable Mul(Add(...))
for i, arg in enumerate(expr.args):
if arg.is_Mul and arg.has(Add):
new_arg, distributed = distribute_once(arg)
if new_arg != arg:
new_args = list(expr.args)
new_args[i] = new_arg
return build_ordered_add(new_args), distributed
# PASS 2: ONLY recurse into Add or structured nodes
for i, arg in enumerate(expr.args):
# IMPORTANT FILTER: skip pure Mul like -4*x
if arg.is_Mul and not any(a.is_Add for a in arg.args):
continue
new_arg, distributed = distribute_once(arg)
if new_arg != arg:
new_args = list(expr.args)
new_args[i] = new_arg
return build_ordered_add(new_args), distributed
# ------------------------------------------------------------
# STEP 3: NO CHANGE
# ------------------------------------------------------------
return expr, None
def clean(expr):
# remove explicit 1 multipliers
@@ -194,4 +511,30 @@ def clean(expr):
lambda e: Mul(*[arg for arg in e.args if arg != 1])
)
return expr
def safe_format(expr):
if expr.is_Mul:
args = []
for a in expr.args:
a = safe_format(a)
if a == 1:
continue
args.append(a)
return Mul(*args, evaluate=False)
if expr.is_Add:
return expr.func(*[safe_format(a) for a in expr.args], evaluate=False)
return expr
def flatten_mul(expr):
if expr.is_Mul:
args = []
for arg in expr.args:
if arg.is_Mul:
args.extend(arg.args)
else:
args.append(arg)
return Mul(*args, evaluate=False)
return expr

View File

@@ -3,9 +3,11 @@
from problem_generator import generate_problem
from steps_generator import generate_steps
from sympy import init_printing
#define the entry point to the programs
def main():
init_printing(order='none')
problem = generate_problem()
steps = generate_steps(problem);

View File

@@ -110,18 +110,24 @@ def generate_like_terms ():
@register_problem_generator("quadratic")
def generate_quadratic ():
#ax² + bx + c = 0
r1 = random.choice([i for i in range(-10, 16)])
r2 = random.choice([i for i in range(-10, 16)])
r1 = 0
r2 = 0
while 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])
s = random.choice([i for i in range(-5, 6) if i != 0])
x = symbols('x')
expr = n *(x - r1) * (x - r2)
expr = n *(s * x - r1) * (x - r2)
print(f"n:{n}, s:{s}")
expr = expand(expr)
if r1 == r2:
solution = r1
root1 = Rational(r1, s)
root2 = Integer(r2)
if root1 == root2:
solution = sstr(root1)
else:
solution = [r1, r2]
solution = [sstr(root1), sstr(root2)]
return {
"type": "quadratic",
@@ -210,8 +216,14 @@ def generate_binomial ():
e = a * (ans + b) + c * (ans + d)
x = symbols('x')
left_expr = Mul(a, x + b, evaluate=False)
right_expr = Mul(c, x + d, evaluate=False)
if a != 1:
left_expr = Mul(a, x + b, evaluate=False)
else:
left_expr = x+b
if c != 1:
right_expr = Mul(c, x + d, evaluate=False)
else:
right_expr = x+d
expr = Add(left_expr, right_expr, evaluate=False)
return {
@@ -251,6 +263,6 @@ def generate_problem():
problem_type = random.choices(types, weights=weights)[0]
template = TEMPLATES[problem_type]
return generate_like_terms()
return generate_binomial()
#return template()

View File

@@ -37,9 +37,11 @@ def generate_linear_steps(problem):
elif b.is_positive:
steps.append(algebraic_steps.subtract_both_sides(current, b))
current = steps[-1]["after"]
## Second Step
if a != 1:
## 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
@@ -173,6 +175,7 @@ def generate_like_terms_steps (problem):
#ax + bx + c = d
steps = []
x = symbols('x')
current = problem["problem"]
## First Step
@@ -183,6 +186,23 @@ def generate_like_terms_steps (problem):
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
@@ -191,6 +211,38 @@ 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))
return steps
@register_steps_generator("difference_squares")
@@ -198,12 +250,37 @@ 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))
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
@@ -211,6 +288,28 @@ def generate_zero_product_steps (problem):
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)}"
return steps
@@ -218,6 +317,31 @@ def generate_radical_steps (problem):
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
@@ -225,6 +349,17 @@ def generate_fraction_steps (problem):
def generate_binomial_steps (problem):
#a(x + b) + c(x + d) = e
steps = []
current = problem["problem"]
last_len = -1
while last_len != len(steps):
last_len = len(steps)
steps.extend(algebraic_steps.distribute_step(current))
if len(steps):
current = steps[-1]["after"]
steps.append(algebraic_steps.combine_like_terms(current))
current = steps[-1]["after"]
return steps