From a98042ca5b0c0c867a6d58fb594b29b35812c136 Mon Sep 17 00:00:00 2001 From: Accusedbold Date: Thu, 30 Apr 2026 15:21:38 -0400 Subject: [PATCH] V1 untested --- __pycache__/algebraic_steps.cpython-313.pyc | Bin 20400 -> 25796 bytes __pycache__/problem_generator.cpython-313.pyc | Bin 13516 -> 13421 bytes __pycache__/steps_generator.cpython-313.pyc | Bin 17227 -> 19285 bytes algebraic_steps.py | 161 ++++++++++++++++-- main.py | 19 ++- problem_generator.py | 10 +- steps_generator.py | 51 +++++- 7 files changed, 223 insertions(+), 18 deletions(-) diff --git a/__pycache__/algebraic_steps.cpython-313.pyc b/__pycache__/algebraic_steps.cpython-313.pyc index a54cb753660ffb4a9e377df01879a74b00b6a55c..061f4cd27d4847d3198a114612e16205a049f442 100644 GIT binary patch delta 9180 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 KxJ-9#UhoGCPQSST delta 1055 zcmZuwOK1~O6rFjQnaoT&4Y5rovH6T`4W^B0O{kw@f+hq-(PBfi8bqs&Ska_?Q^6nF zjS#!BcsC-bC|z`+3kiq|U9`GzQKY3J82oGm6%ndkRYZI@)l$TPdpP&JGjHB~_s-Yj zQ~lb!rl}lzULKh5w?5Eb>HOpJ2mL+Yp0Q>vwOmHd3bkBJ=-|=BaR!&OEi#VFN`KXY zl+$3wlf_aA+kCja%ek%E#@a*k`7>r#wlm5eQ7Y358~uqcpY z%UGypHzn+26nFltA!|hlBRCnHBZba$Lf9}nX_}=!1S4*=Q=QR}LcQqnA@~sj3@YLP z?Jo>fr2LtI-a=mmW>yZV-E5)2!rTa54NvO?rIaiUmX40rkM@pcN^LKNmEiCmZ1!<_ z=R(D`$#gUCL|jSN%af{v*rB97@>3$EJ>_fObv&PeXTHPYUS=C9hx~pb?PHW~hhL2W zIBDz@53uwW6q{UdGiWWpHk`zkMF&~<;e!1B6kVuD7b-}Rage}3O+Q-r- zEX?OHVYs2R!dIRMez)*PVaDl&>6RKk4{uwV`TamzFR1;@`2yXCz^-<|RD%Ov8;Q*-u4SXtlY+6xDo=fu zV769dcPIAbkr$+%KIVQG3EB7-j!l&%Ysn~JXibgk?q!Wz>L^{oPPSO}308HJo9;lt zU=Wh&D&$=VyWnD5T0O<+7Z&EHYT-rIwN0^K^^N$(I>zh9kBlW|_2}Ki_1+u&oy3eD zomFDP$v6Kx;^=tP_+a1dsw+OLB!-jF-F_n6P;IXsU8v}B6G!K^k_yT9BnA_@EftX` ztx75;Pu5GR7)-an4GiGXC_)ioa0#j!g{gH3J6?ZCsE9*n2l6I0!4r*%XPHJ|Pc&)u z&f5Ibf*)=~HzaWgDUJ-q>7dwuimj%&1;yGazDTP-K@Bp6K}Ftts@Qk>WIjR9LtX5i h-NQ@s7LJ7G2xH&Y!;4r+uUpiDa|Bsf<^vQr_zjoA`K$l{ diff --git a/__pycache__/steps_generator.cpython-313.pyc b/__pycache__/steps_generator.cpython-313.pyc index ca800d9276cdb390c8aaaeb2b5606ff5490bf984..57552853ae5d44eeffac50077d3c5e723a25eab8 100644 GIT binary patch delta 3814 zcmbVPZA_cj754kS{{H0U!zKpf*I+QlKp=!K6TZx#NtZDNkF*V19Re4IddAfYaui7%&FIp_?h1uKLNRYGg_ zqQN8xj_hUQqGTyYEk>%$33n`kmJ>8}5mX{<2&@DD{997`m7vVrB(EaS5_CYwTCG=T z>Vq#?YliA+>LKtF)DqMY_z`m2bUMQ}tm@YSB<3G)q*;)l34UyCakbzyZADOu2D^5{joT^el1^%TT#lo33>>^1PXzVpvZ7| zBUD!1N5V4*M;T%&^$DZdQ0WEwlYJtAX_8{}VRS>?ar?XeMZZ_fHH6!(ey2@!Es#8eu$p)}0d3;v3cL z(v^b62Y1b!w2|9R#;Rs>Tki)`At{9`S2~kgx`J$rEhZM?u}qvTrHg2x5`g0PRqh0L zhr7qa%-t~`#~osV>eDdJ(v%nlN+2UFKCdv54f)paBH$;re&J^Jc5O_8XZ?2gOT7<_ zN`-k+6BTv1aZ&=W;(E$5u98k=Je^Oz{+)ZxfgBzyT;8V;Nxdvo%}H}Jvntzb=ro1E z2j6e#8_QW{=HoL9F_ubY(m~sC94@*)I$M}w!(a<|@CHMHfcS$v=PR<;1C2uGU3=Nf zkrxuz4Y%#e+9P{vQ4XN2ZkUThXsXs`n(<8Jy!28! z*6~a{h&sE1I)`HUyLLt2AmG9n#JUwIJN5DD&%?89Nym&dT5yFImm&uWWrvr?qif=Q z%4E9&VQ%^YImPRT<@khw6WukKK|S3*;cWI&_Y4(_w|eSCro~%nbWhP8@)4Dy+Fn7? zM0H8+q;^UNw|ZT=Co~$Io$Pf92FeS)k*h+x$pw8Lr-*AW_4?qKF1w`esF8~fH1Gpq zsG`!M?!(O!b`ZiLP+TSWc(=RCT8y0-Z5k_BfR-;#m8rk5Wa*|Tetbkty<*ZZX#mCS zvY0!mRN+V(;q9=~8nuM+Qm5a+Ek@YamBZWRCb;Z$3r2XW#0&3g-N5?FxOB_uE;2=d zYnA2Dr*jp|8Tkz66q%~eJCQU^)xoQla(mP|MNdB;lP1`3g(Sm7J;+*5nR(J2wS`yF zWK3zC>%yz>YS2?;SOYigU5C@?R}~(IlBQyX#=OE+zb0res!BO+GL?*P2svGf&Bj@w zlINtw__OL8S46`C`6~E>oU0 zi2apX^)H~SMX#XBU6K_N@G58VLr>w6RL&U}r_&kyMv|$m5go+e_=76QANn-)g$JdO zDiQw9x#r@@IGaWz@s&<3C8if+ybrXn=WuzRvil#H-Co;A_-K?~*KcdRcO5ePx4+if zyIs+HBd}8u*)@(pXrSKwk(pF<+u9n4`U3D_b2Y%g^Pu#cffIw%R<-rSn9MAEofo^m z@wWxM!C^R4*9_whqn;D{1^83gWg+wUz%(H4c@!sjUG~x`IlmA!M2(b%aMSO?v~6vw zfqoAW)Fu}xnYa>s5UxZWk1nrchd+m%D0%5XVym8_qR2PKX%lIprR zDYXfm0<2f~;Fg~b`ovrFPz%v}bPG`+8e@1EOB1XhJQu)c09*4p1=<3YSC!R7WcMzRvP6552#0&HcLu9;>$vg?Toh#}QmVyEbxHYsz;er?=(Pn~&|tkzMV`gUw434-4vD+j7_D>W)0Ps~vh! z{SR81GxC9+r*m8G+??C7%LBXG!2^95_ccFB$D6mT9h;AA_HLQ`ceMlet=g4?ulH`Q zZZ!?=$V0o@;e&0TsihKzUK^m6Bxk-jy|@w|Wh}*lqWVHne)50W^0{e0$W8TMvu~op zoAB465hJBi(1|a3`*4?^mk&CWQD(4Kf(XGV!8pMLd}r8miZYtf4KO;1l@QQLY9%n9 zSxU{WEXD`ew^4?PuSqXJZunMHqafdtG-6;+#M%B=1dZt4a<}dXC-+5w;?O&oZt~c%ob^D@UUGeYn*?B=PbaJEq2SR2a AZ2$lO delta 2819 zcmb7GOHf;76u$qxkc7NQ0)aM=C*ct=1ckP|AEm`oTWgEd2TlWo*rp9o{u{=})BzXP zP3sIEwT?5LQ8yh%7j{y|$EKq@M;%+Mqh?{dR2LOwMjc(~Isb(urL;ru;^T1sKj%N+ zIp2To{r(O1`}KGC25NStfFXq}PSBFxJlIn3zfRs>Z>81-=r3@RSxfMbf= zWJ6a*mrP8>$6&@#p1z@pbt^c3wgLN`eA{7HBY6>ilE|V87YZTSKOYz(-I; z5G1HZ$P}n*iZ7bA)9T1A4iAx4PtXAGnZxCcXsSflm-isu-^~G2Kf0S}eUibbJ_4_V zb#UC_O`o(>5~H(Lt1|Zhu7_;5<%(Y3O3+5oL7-($)?E|lowTJ3VS}{Lo2fV&pNjLb zR6IJ7P{HM}v4M1h;~-`9Ldm;@d-G~ck*I;W67wDO?{W|8V4>AL zNX680qTXn^*V2#dcPTCH>?Y_T2op5IH(R^ePO!RMD2D);qLT(zl9zj`jMzAw( zh8>AVqqq}h21<~Z$?s)D=?4EiwgcV|1lf`F)j(891f`JaVNHRW{OOlO>Q_Ne05KcL zwd(e+hz4k_!L1qPXlz20zL%n{-i&{1I@&^(&19d~8w$r_d?%c$bt8+nYHQ``HO-sA z8Zf5yb#+XC!(^M^$89hX3d>0uu2+WOy0r_AZ|jE3rA6u9`paxLGA-JW=|)P#MAzSP zq9T;1)078a+*i@FShN{J*>s2pAYcJ{hTIPMD9hVxvEa1mrVRk4x!=rXSfH*1(uwgLR6g zzKd!M2uX*mS`l(;wyQI})mpH3Z%s)>nszLCN3jarg2v&2bPTWaGKf0x!4L_T)y!2}(}fC)4H| z2*0#?P=xw6KkH0~+G2E-&bC*v-{5K>6dY2f3Wf_ts1f2BX4{YsJ?+m((ik>yL;+hz zIV*$(Z;45#tx3`$11xMQgC4g-#tkPs{4n6P`tKYFujGCV1_yVQD;NZx-{FGG{afK} zkBt?>MUUTqc*MRIe7JDLv1SkH+TOz}-f=tO(EO{_rA!B>j9&Pvzy+V}C=n}HvMc|2z0g@$b`OX9!Cv5ol(&*%=T#v9 zef{g=e^bPk`+!rSPh-lN@(dj4S)8H&1qje$SHQ>x!9`OM7zl*C9cqC)az12 znqtE&C~=xUoYWDLbGjGbQZzfzpK)g2;#ycyDV0BqYcElbRy5`%oKcqIdBg6t!CSQF zLN^_iF&-XE&c=6hKF!yTppTeaj<9Br&_2sf;f6DCb5Fm4_=L*vGpcQH`<&KAV%;s!Eu6V0;;P>oYM({Hm4IbqK3}N>5187yqnLVj|!$H5SJ9j z*or}7aC7j(`XIBfD3V;eB%`&wk4duYimUM&i!947ZK-LAtmRhtdT&+ns;_$0_v%Ac j_t$9KYL;ZQmLH)AZ`NI7Rm*aLwh~+tmRZNPiR}IdOYIJ7 diff --git a/algebraic_steps.py b/algebraic_steps.py index 9e532fa..c17dc4d 100644 --- a/algebraic_steps.py +++ b/algebraic_steps.py @@ -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 - args.append(a) - return Mul(*args, evaluate=False) + elif a == -1: + sign *= -1 + else: + args.append(a) + + 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) diff --git a/main.py b/main.py index 6e1a396..830aa96 100644 --- a/main.py +++ b/main.py @@ -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__": diff --git a/problem_generator.py b/problem_generator.py index 50fdc25..c6a23f7 100644 --- a/problem_generator.py +++ b/problem_generator.py @@ -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() \ No newline at end of file diff --git a/steps_generator.py b/steps_generator.py index c1c3ac7..c152267 100644 --- a/steps_generator.py +++ b/steps_generator.py @@ -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 @@ -310,6 +317,11 @@ def generate_radical_steps (problem): 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 @@ -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