Update solver_algebra.py
Browse files- solver_algebra.py +326 -101
solver_algebra.py
CHANGED
|
@@ -34,12 +34,13 @@ def solve_algebra(text: str) -> Optional[SolverResult]:
|
|
| 34 |
reply=_format_explanation_only(raw, lower, help_mode, intent),
|
| 35 |
solved=False,
|
| 36 |
help_mode=help_mode,
|
|
|
|
|
|
|
| 37 |
)
|
| 38 |
|
| 39 |
parsed = _parse_request(cleaned, lower)
|
| 40 |
explanation = _explain_what_is_being_asked(parsed, intent)
|
| 41 |
|
| 42 |
-
# Route by structure
|
| 43 |
result = (
|
| 44 |
_handle_systems(parsed, help_mode)
|
| 45 |
or _handle_inequality(parsed, help_mode)
|
|
@@ -56,9 +57,14 @@ def solve_algebra(text: str) -> Optional[SolverResult]:
|
|
| 56 |
explanation,
|
| 57 |
_generic_algebra_guidance(parsed, help_mode, intent),
|
| 58 |
)
|
| 59 |
-
return _mk_result(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
-
# Prefix with decoded question meaning when useful
|
| 62 |
if explanation:
|
| 63 |
result.reply = _join_sections("Let’s work through it.", explanation, result.reply)
|
| 64 |
else:
|
|
@@ -103,7 +109,7 @@ def _looks_like_algebra(raw: str, lower: str) -> bool:
|
|
| 103 |
|
| 104 |
|
| 105 |
def _detect_help_mode(lower: str) -> str:
|
| 106 |
-
if any(x in lower for x in ["hint", "nudge", "small hint"]):
|
| 107 |
return "hint"
|
| 108 |
if any(x in lower for x in ["step by step", "steps", "walkthrough", "work through"]):
|
| 109 |
return "walkthrough"
|
|
@@ -255,6 +261,125 @@ def _degree_of_expr(expr) -> Optional[int]:
|
|
| 255 |
return None
|
| 256 |
|
| 257 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 258 |
# =========================================================
|
| 259 |
# Handlers
|
| 260 |
# =========================================================
|
|
@@ -271,14 +396,11 @@ def _handle_systems(parsed: Dict[str, Any], help_mode: str) -> Optional[SolverRe
|
|
| 271 |
if not symbols:
|
| 272 |
return None
|
| 273 |
|
| 274 |
-
lins = []
|
| 275 |
nonlinear = []
|
| 276 |
for eq in sym_eqs:
|
| 277 |
expr = sp.expand(eq.lhs - eq.rhs)
|
| 278 |
deg = _degree_of_expr(expr)
|
| 279 |
-
if deg =
|
| 280 |
-
lins.append(eq)
|
| 281 |
-
else:
|
| 282 |
nonlinear.append(eq)
|
| 283 |
|
| 284 |
if nonlinear:
|
|
@@ -298,9 +420,10 @@ def _handle_systems(parsed: Dict[str, Any], help_mode: str) -> Optional[SolverRe
|
|
| 298 |
),
|
| 299 |
solved=True,
|
| 300 |
help_mode=help_mode,
|
|
|
|
|
|
|
| 301 |
)
|
| 302 |
|
| 303 |
-
# Linear system classification
|
| 304 |
matrix, vec = sp.linear_eq_to_matrix([eq.lhs - eq.rhs for eq in sym_eqs], symbols)
|
| 305 |
rank_a = matrix.rank()
|
| 306 |
rank_aug = matrix.row_join(vec).rank()
|
|
@@ -308,19 +431,41 @@ def _handle_systems(parsed: Dict[str, Any], help_mode: str) -> Optional[SolverRe
|
|
| 308 |
|
| 309 |
if rank_a != rank_aug:
|
| 310 |
msg = [
|
| 311 |
-
"This system is inconsistent.",
|
| 312 |
-
"That means the equations conflict with each other, so there is no common solution.",
|
| 313 |
-
"A good first move is to eliminate one variable and compare the resulting statements."
|
| 314 |
]
|
| 315 |
-
return _mk_result(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 316 |
|
| 317 |
if rank_a < nvars:
|
| 318 |
msg = [
|
| 319 |
-
"This system does not pin down a unique solution.",
|
| 320 |
-
"That means there are infinitely many solutions or at least one free variable.",
|
| 321 |
-
"On GMAT-style questions, this often means you should solve for a relationship instead of individual values."
|
| 322 |
]
|
| 323 |
-
return _mk_result(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 324 |
|
| 325 |
steps = [
|
| 326 |
"- Choose one variable to eliminate.",
|
|
@@ -338,6 +483,8 @@ def _handle_systems(parsed: Dict[str, Any], help_mode: str) -> Optional[SolverRe
|
|
| 338 |
),
|
| 339 |
solved=True,
|
| 340 |
help_mode=help_mode,
|
|
|
|
|
|
|
| 341 |
)
|
| 342 |
except Exception:
|
| 343 |
return None
|
|
@@ -356,26 +503,29 @@ def _handle_inequality(parsed: Dict[str, Any], help_mode: str) -> Optional[Solve
|
|
| 356 |
|
| 357 |
syms = sorted(list(rel.free_symbols), key=lambda s: s.name)
|
| 358 |
if len(syms) != 1:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 359 |
return _mk_result(
|
| 360 |
reply=_modeled_steps(
|
| 361 |
title="This is an inequality problem.",
|
| 362 |
method="Rearrange so one side becomes 0, then analyze sign changes or isolate the variable.",
|
| 363 |
-
steps=
|
| 364 |
-
"- Collect all terms on one side.",
|
| 365 |
-
"- Factor if possible.",
|
| 366 |
-
"- Mark critical points where the expression is 0 or undefined.",
|
| 367 |
-
"- Test intervals to see where the inequality is true.",
|
| 368 |
-
"- Remember: multiplying or dividing by a negative flips the inequality sign."
|
| 369 |
-
],
|
| 370 |
help_mode=help_mode,
|
| 371 |
),
|
| 372 |
solved=True,
|
| 373 |
help_mode=help_mode,
|
|
|
|
|
|
|
| 374 |
)
|
| 375 |
|
| 376 |
var = syms[0]
|
| 377 |
steps = [
|
| 378 |
-
f"- Isolate {var} as much as possible.",
|
| 379 |
"- Be careful with brackets, fractions, and negative coefficients.",
|
| 380 |
"- If you multiply or divide by a negative quantity, reverse the inequality sign.",
|
| 381 |
"- If the expression factors, use sign analysis instead of treating it like a normal equation."
|
|
@@ -389,6 +539,8 @@ def _handle_inequality(parsed: Dict[str, Any], help_mode: str) -> Optional[Solve
|
|
| 389 |
),
|
| 390 |
solved=True,
|
| 391 |
help_mode=help_mode,
|
|
|
|
|
|
|
| 392 |
)
|
| 393 |
except Exception:
|
| 394 |
return None
|
|
@@ -406,10 +558,21 @@ def _handle_equation(parsed: Dict[str, Any], help_mode: str) -> Optional[SolverR
|
|
| 406 |
deg = _degree_of_expr(expr)
|
| 407 |
|
| 408 |
if not syms:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 409 |
return _mk_result(
|
| 410 |
-
reply=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 411 |
solved=True,
|
| 412 |
help_mode=help_mode,
|
|
|
|
|
|
|
| 413 |
)
|
| 414 |
|
| 415 |
if len(syms) > 1 and deg == 1:
|
|
@@ -427,27 +590,26 @@ def _handle_equation(parsed: Dict[str, Any], help_mode: str) -> Optional[SolverR
|
|
| 427 |
),
|
| 428 |
solved=True,
|
| 429 |
help_mode=help_mode,
|
|
|
|
|
|
|
| 430 |
)
|
| 431 |
|
| 432 |
if deg == 1:
|
| 433 |
var = syms[0]
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
"
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
steps.append("- Be careful: dividing by a variable assumes that variable is not 0.")
|
| 442 |
return _mk_result(
|
| 443 |
-
reply=
|
| 444 |
-
title="This is a linear equation.",
|
| 445 |
-
method="Use inverse operations and keep both sides balanced.",
|
| 446 |
-
steps=steps,
|
| 447 |
-
help_mode=help_mode,
|
| 448 |
-
),
|
| 449 |
solved=True,
|
| 450 |
help_mode=help_mode,
|
|
|
|
|
|
|
|
|
|
| 451 |
)
|
| 452 |
|
| 453 |
if deg == 2:
|
|
@@ -455,8 +617,10 @@ def _handle_equation(parsed: Dict[str, Any], help_mode: str) -> Optional[SolverR
|
|
| 455 |
disc = None
|
| 456 |
try:
|
| 457 |
poly = sp.Poly(expr, var)
|
| 458 |
-
|
| 459 |
-
|
|
|
|
|
|
|
| 460 |
except Exception:
|
| 461 |
pass
|
| 462 |
|
|
@@ -466,7 +630,6 @@ def _handle_equation(parsed: Dict[str, Any], help_mode: str) -> Optional[SolverR
|
|
| 466 |
"- If it does not factor cleanly, use a systematic method such as the quadratic formula or completing the square.",
|
| 467 |
"- After finding candidate roots internally, substitute back to verify."
|
| 468 |
]
|
| 469 |
-
|
| 470 |
if disc is not None:
|
| 471 |
steps.append("- The discriminant tells you whether there are two, one, or no real roots.")
|
| 472 |
|
|
@@ -479,6 +642,8 @@ def _handle_equation(parsed: Dict[str, Any], help_mode: str) -> Optional[SolverR
|
|
| 479 |
),
|
| 480 |
solved=True,
|
| 481 |
help_mode=help_mode,
|
|
|
|
|
|
|
| 482 |
)
|
| 483 |
|
| 484 |
if deg and deg > 2:
|
|
@@ -493,6 +658,7 @@ def _handle_equation(parsed: Dict[str, Any], help_mode: str) -> Optional[SolverR
|
|
| 493 |
steps.append("- This one appears factorable, so the zero-product idea is likely useful.")
|
| 494 |
if parsed["has_integer_constraint"]:
|
| 495 |
steps.append("- Since the variables may be restricted to integers, candidate checking can also be efficient.")
|
|
|
|
| 496 |
return _mk_result(
|
| 497 |
reply=_modeled_steps(
|
| 498 |
title="This is a higher-degree algebra equation.",
|
|
@@ -502,6 +668,8 @@ def _handle_equation(parsed: Dict[str, Any], help_mode: str) -> Optional[SolverR
|
|
| 502 |
),
|
| 503 |
solved=True,
|
| 504 |
help_mode=help_mode,
|
|
|
|
|
|
|
| 505 |
)
|
| 506 |
|
| 507 |
except Exception:
|
|
@@ -523,74 +691,85 @@ def _handle_expression(parsed: Dict[str, Any], help_mode: str, intent: str) -> O
|
|
| 523 |
expr = _sympify_expr(expr_text)
|
| 524 |
|
| 525 |
if intent == "simplify":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 526 |
return _mk_result(
|
| 527 |
reply=_modeled_steps(
|
| 528 |
title="This is a simplification task.",
|
| 529 |
method="Combine like terms, reduce fractions carefully, and use identities where helpful.",
|
| 530 |
-
steps=
|
| 531 |
-
"- Expand only if that helps combine terms.",
|
| 532 |
-
"- Collect like powers and like variable terms.",
|
| 533 |
-
"- Factor common pieces if the expression becomes cleaner that way.",
|
| 534 |
-
"- Check for hidden identities such as a^2-b^2 or perfect squares."
|
| 535 |
-
],
|
| 536 |
help_mode=help_mode,
|
| 537 |
),
|
| 538 |
solved=True,
|
| 539 |
help_mode=help_mode,
|
|
|
|
|
|
|
| 540 |
)
|
| 541 |
|
| 542 |
if intent == "expand":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 543 |
return _mk_result(
|
| 544 |
reply=_modeled_steps(
|
| 545 |
title="This is an expansion task.",
|
| 546 |
method="Distribute carefully across every term in the bracket(s).",
|
| 547 |
-
steps=
|
| 548 |
-
"- Multiply each outside factor by each inside term.",
|
| 549 |
-
"- Watch negative signs.",
|
| 550 |
-
"- Combine like terms at the end."
|
| 551 |
-
],
|
| 552 |
help_mode=help_mode,
|
| 553 |
),
|
| 554 |
solved=True,
|
| 555 |
help_mode=help_mode,
|
|
|
|
|
|
|
| 556 |
)
|
| 557 |
|
| 558 |
if intent == "factor":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 559 |
return _mk_result(
|
| 560 |
reply=_modeled_steps(
|
| 561 |
title="This is a factorization task.",
|
| 562 |
method="Start by pulling out any common factor, then check special identities and quadratic patterns.",
|
| 563 |
-
steps=
|
| 564 |
-
"- Take out the greatest common factor first.",
|
| 565 |
-
"- Check for difference of squares.",
|
| 566 |
-
"- Check for perfect-square trinomials.",
|
| 567 |
-
"- If it is quadratic in form, use sum/product structure."
|
| 568 |
-
],
|
| 569 |
help_mode=help_mode,
|
| 570 |
),
|
| 571 |
solved=True,
|
| 572 |
help_mode=help_mode,
|
|
|
|
|
|
|
| 573 |
)
|
| 574 |
|
| 575 |
if intent == "rearrange":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 576 |
return _mk_result(
|
| 577 |
reply=_modeled_steps(
|
| 578 |
title="This is a rearranging / isolating task.",
|
| 579 |
method="Move all terms involving the target variable together, then factor it out.",
|
| 580 |
-
steps=
|
| 581 |
-
"- Identify which variable must be isolated.",
|
| 582 |
-
"- Move all target-variable terms to one side.",
|
| 583 |
-
"- Move all non-target terms to the other side.",
|
| 584 |
-
"- Factor the target variable if it appears in multiple terms.",
|
| 585 |
-
"- Divide only when you know the divisor is allowed to be nonzero."
|
| 586 |
-
],
|
| 587 |
help_mode=help_mode,
|
| 588 |
),
|
| 589 |
solved=True,
|
| 590 |
help_mode=help_mode,
|
|
|
|
|
|
|
| 591 |
)
|
| 592 |
|
| 593 |
-
# Generic expression handling
|
| 594 |
deg = _degree_of_expr(expr)
|
| 595 |
steps = [
|
| 596 |
"- Decide whether the best move is simplify, expand, factor, or substitute.",
|
|
@@ -609,6 +788,8 @@ def _handle_expression(parsed: Dict[str, Any], help_mode: str, intent: str) -> O
|
|
| 609 |
),
|
| 610 |
solved=True,
|
| 611 |
help_mode=help_mode,
|
|
|
|
|
|
|
| 612 |
)
|
| 613 |
except Exception:
|
| 614 |
return None
|
|
@@ -616,7 +797,6 @@ def _handle_expression(parsed: Dict[str, Any], help_mode: str, intent: str) -> O
|
|
| 616 |
|
| 617 |
def _handle_word_translation(parsed: Dict[str, Any], help_mode: str) -> Optional[SolverResult]:
|
| 618 |
lower = parsed["lower"]
|
| 619 |
-
raw = parsed["raw"]
|
| 620 |
|
| 621 |
triggers = [
|
| 622 |
"more than", "less than", "twice", "times", "sum of", "difference",
|
|
@@ -626,18 +806,18 @@ def _handle_word_translation(parsed: Dict[str, Any], help_mode: str) -> Optional
|
|
| 626 |
return None
|
| 627 |
|
| 628 |
mappings = [
|
| 629 |
-
("more than", "
|
| 630 |
-
("less than", "
|
| 631 |
-
("twice", "'
|
| 632 |
-
("sum of", "'
|
| 633 |
-
("difference", "'
|
| 634 |
-
("product of", "'
|
| 635 |
-
("quotient", "'
|
| 636 |
-
("at least", "
|
| 637 |
-
("at most", "
|
| 638 |
-
("no more than", "
|
| 639 |
-
("no less than", "
|
| 640 |
-
("consecutive", "
|
| 641 |
]
|
| 642 |
|
| 643 |
bullets = []
|
|
@@ -660,6 +840,8 @@ def _handle_word_translation(parsed: Dict[str, Any], help_mode: str) -> Optional
|
|
| 660 |
),
|
| 661 |
solved=True,
|
| 662 |
help_mode=help_mode,
|
|
|
|
|
|
|
| 663 |
)
|
| 664 |
|
| 665 |
|
|
@@ -667,20 +849,23 @@ def _handle_degree_reasoning(parsed: Dict[str, Any], help_mode: str) -> Optional
|
|
| 667 |
if not parsed["mentions_degree"]:
|
| 668 |
return None
|
| 669 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 670 |
return _mk_result(
|
| 671 |
reply=_modeled_steps(
|
| 672 |
title="This question is using degree / polynomial structure.",
|
| 673 |
method="The degree tells you the highest power present and helps narrow the right solving method.",
|
| 674 |
-
steps=
|
| 675 |
-
"- Degree 1 suggests a linear structure.",
|
| 676 |
-
"- Degree 2 suggests a quadratic structure.",
|
| 677 |
-
"- Higher degree often calls for factorization, substitution, or identity spotting.",
|
| 678 |
-
"- A polynomial of degree n can have at most n roots over the reals/complexes combined."
|
| 679 |
-
],
|
| 680 |
help_mode=help_mode,
|
| 681 |
),
|
| 682 |
solved=True,
|
| 683 |
help_mode=help_mode,
|
|
|
|
|
|
|
| 684 |
)
|
| 685 |
|
| 686 |
|
|
@@ -690,20 +875,23 @@ def _handle_integer_restricted(parsed: Dict[str, Any], help_mode: str) -> Option
|
|
| 690 |
if not parsed["equations"] and not parsed["inequalities"]:
|
| 691 |
return None
|
| 692 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 693 |
return _mk_result(
|
| 694 |
reply=_modeled_steps(
|
| 695 |
title="This problem has an integer restriction.",
|
| 696 |
method="That often makes controlled testing or divisibility reasoning much faster than pure symbolic solving.",
|
| 697 |
-
steps=
|
| 698 |
-
"- Use the algebra to narrow the possible forms first.",
|
| 699 |
-
"- Then test only values consistent with the restriction.",
|
| 700 |
-
"- Stop when the restriction makes further values impossible.",
|
| 701 |
-
"- Always check the tested value in the original condition."
|
| 702 |
-
],
|
| 703 |
help_mode=help_mode,
|
| 704 |
),
|
| 705 |
solved=True,
|
| 706 |
help_mode=help_mode,
|
|
|
|
|
|
|
| 707 |
)
|
| 708 |
|
| 709 |
|
|
@@ -760,32 +948,33 @@ def _format_explanation_only(raw: str, lower: str, help_mode: str, intent: str)
|
|
| 760 |
# =========================================================
|
| 761 |
|
| 762 |
def _modeled_steps(title: str, method: str, steps: List[str], help_mode: str) -> str:
|
|
|
|
|
|
|
| 763 |
if help_mode == "hint":
|
| 764 |
return _join_sections(
|
| 765 |
title,
|
| 766 |
f"Hint: {method}",
|
| 767 |
-
|
| 768 |
)
|
| 769 |
|
| 770 |
if help_mode == "explain":
|
| 771 |
return _join_sections(
|
| 772 |
title,
|
| 773 |
f"Method idea: {method}",
|
| 774 |
-
"\n".join(
|
| 775 |
)
|
| 776 |
|
| 777 |
if help_mode == "method":
|
| 778 |
return _join_sections(
|
| 779 |
title,
|
| 780 |
f"Method: {method}",
|
| 781 |
-
"\n".join(
|
| 782 |
)
|
| 783 |
|
| 784 |
-
# walkthrough / answer
|
| 785 |
return _join_sections(
|
| 786 |
title,
|
| 787 |
f"Walkthrough method: {method}",
|
| 788 |
-
"\n".join(
|
| 789 |
)
|
| 790 |
|
| 791 |
|
|
@@ -794,8 +983,18 @@ def _join_sections(*parts: str) -> str:
|
|
| 794 |
return "\n\n".join(clean)
|
| 795 |
|
| 796 |
|
| 797 |
-
def _mk_result(
|
| 798 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 799 |
reply=reply,
|
| 800 |
meta={
|
| 801 |
"domain": "quant",
|
|
@@ -806,5 +1005,31 @@ def _mk_result(reply: str, solved: bool, help_mode: str) -> SolverResult:
|
|
| 806 |
"topic": "algebra",
|
| 807 |
"used_retrieval": False,
|
| 808 |
"used_generator": False,
|
|
|
|
|
|
|
|
|
|
| 809 |
},
|
| 810 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
reply=_format_explanation_only(raw, lower, help_mode, intent),
|
| 35 |
solved=False,
|
| 36 |
help_mode=help_mode,
|
| 37 |
+
steps=[],
|
| 38 |
+
display_steps=[],
|
| 39 |
)
|
| 40 |
|
| 41 |
parsed = _parse_request(cleaned, lower)
|
| 42 |
explanation = _explain_what_is_being_asked(parsed, intent)
|
| 43 |
|
|
|
|
| 44 |
result = (
|
| 45 |
_handle_systems(parsed, help_mode)
|
| 46 |
or _handle_inequality(parsed, help_mode)
|
|
|
|
| 57 |
explanation,
|
| 58 |
_generic_algebra_guidance(parsed, help_mode, intent),
|
| 59 |
)
|
| 60 |
+
return _mk_result(
|
| 61 |
+
reply=reply,
|
| 62 |
+
solved=False,
|
| 63 |
+
help_mode=help_mode,
|
| 64 |
+
steps=[],
|
| 65 |
+
display_steps=[],
|
| 66 |
+
)
|
| 67 |
|
|
|
|
| 68 |
if explanation:
|
| 69 |
result.reply = _join_sections("Let’s work through it.", explanation, result.reply)
|
| 70 |
else:
|
|
|
|
| 109 |
|
| 110 |
|
| 111 |
def _detect_help_mode(lower: str) -> str:
|
| 112 |
+
if any(x in lower for x in ["hint", "nudge", "small hint", "next hint"]):
|
| 113 |
return "hint"
|
| 114 |
if any(x in lower for x in ["step by step", "steps", "walkthrough", "work through"]):
|
| 115 |
return "walkthrough"
|
|
|
|
| 261 |
return None
|
| 262 |
|
| 263 |
|
| 264 |
+
def _safe_str(obj: Any) -> str:
|
| 265 |
+
try:
|
| 266 |
+
return str(sp.simplify(obj))
|
| 267 |
+
except Exception:
|
| 268 |
+
return str(obj)
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
def _format_eq(lhs: Any, rhs: Any) -> str:
|
| 272 |
+
return f"{_safe_str(lhs)} = {_safe_str(rhs)}"
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
def _strip_bullet_prefix(step: str) -> str:
|
| 276 |
+
return re.sub(r"^\s*-\s*", "", (step or "").strip())
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
def _ensure_bullets(steps: List[str]) -> List[str]:
|
| 280 |
+
out = []
|
| 281 |
+
for s in steps:
|
| 282 |
+
s = _strip_bullet_prefix(s)
|
| 283 |
+
if s:
|
| 284 |
+
out.append(f"- {s}")
|
| 285 |
+
return out
|
| 286 |
+
|
| 287 |
+
|
| 288 |
+
# =========================================================
|
| 289 |
+
# Step builders
|
| 290 |
+
# =========================================================
|
| 291 |
+
|
| 292 |
+
def _build_linear_equation_steps(eq, var) -> Tuple[List[str], List[str], Optional[Any]]:
|
| 293 |
+
"""
|
| 294 |
+
Returns:
|
| 295 |
+
all_steps: full internal steps including final solved step if available
|
| 296 |
+
display_steps: safe steps for walkthrough/method/answer without answer leakage
|
| 297 |
+
final_value: solved value if found
|
| 298 |
+
"""
|
| 299 |
+
lhs = sp.simplify(eq.lhs)
|
| 300 |
+
rhs = sp.simplify(eq.rhs)
|
| 301 |
+
|
| 302 |
+
start_step = f"Start with {_format_eq(lhs, rhs)}."
|
| 303 |
+
|
| 304 |
+
try:
|
| 305 |
+
solutions = sp.solve(eq, var)
|
| 306 |
+
except Exception:
|
| 307 |
+
solutions = []
|
| 308 |
+
|
| 309 |
+
final_value = solutions[0] if len(solutions) == 1 else None
|
| 310 |
+
|
| 311 |
+
expr = sp.expand(lhs - rhs)
|
| 312 |
+
coeff = sp.expand(expr).coeff(var, 1)
|
| 313 |
+
const = sp.expand(expr).subs(var, 0)
|
| 314 |
+
|
| 315 |
+
all_steps: List[str] = [start_step]
|
| 316 |
+
display_steps: List[str] = [start_step]
|
| 317 |
+
|
| 318 |
+
# Pattern 1: x / c = k
|
| 319 |
+
if lhs == var / sp.denom(lhs) and rhs.is_number:
|
| 320 |
+
denom = sp.denom(lhs)
|
| 321 |
+
step2_lhs = sp.simplify(lhs * denom)
|
| 322 |
+
step2_rhs = sp.simplify(rhs * denom)
|
| 323 |
+
step2 = f"Multiply both sides by {_safe_str(denom)} to undo the division."
|
| 324 |
+
step3 = f"This gives {_format_eq(step2_lhs, step2_rhs)}."
|
| 325 |
+
all_steps.extend([step2, step3])
|
| 326 |
+
display_steps.extend([step2, step3])
|
| 327 |
+
if final_value is not None:
|
| 328 |
+
all_steps.append(f"So {_safe_str(var)} = {_safe_str(final_value)}.")
|
| 329 |
+
return _ensure_bullets(all_steps), _ensure_bullets(display_steps), final_value
|
| 330 |
+
|
| 331 |
+
# Pattern 2: a*x = b
|
| 332 |
+
try:
|
| 333 |
+
ratio = sp.simplify(lhs / var)
|
| 334 |
+
if lhs == sp.simplify(ratio * var) and ratio.is_number and ratio != 1 and rhs.is_number:
|
| 335 |
+
step2 = f"Divide both sides by {_safe_str(ratio)} to isolate {_safe_str(var)}."
|
| 336 |
+
step3 = f"This leaves {_safe_str(var)} by itself on the left."
|
| 337 |
+
all_steps.extend([step2, step3])
|
| 338 |
+
display_steps.extend([step2, step3])
|
| 339 |
+
if final_value is not None:
|
| 340 |
+
all_steps.append(f"So {_safe_str(var)} = {_safe_str(final_value)}.")
|
| 341 |
+
return _ensure_bullets(all_steps), _ensure_bullets(display_steps), final_value
|
| 342 |
+
except Exception:
|
| 343 |
+
pass
|
| 344 |
+
|
| 345 |
+
# Pattern 3: x + b = c
|
| 346 |
+
try:
|
| 347 |
+
if sp.expand(lhs).coeff(var, 1) == 1 and rhs.is_number:
|
| 348 |
+
extra = sp.simplify(lhs - var)
|
| 349 |
+
if extra.free_symbols == set():
|
| 350 |
+
if extra != 0:
|
| 351 |
+
direction = "subtract" if extra > 0 else "add"
|
| 352 |
+
amount = abs(extra)
|
| 353 |
+
step2 = f"{direction.capitalize()} {_safe_str(amount)} on both sides to undo the constant term."
|
| 354 |
+
step3 = f"That isolates {_safe_str(var)} on the left."
|
| 355 |
+
all_steps.extend([step2, step3])
|
| 356 |
+
display_steps.extend([step2, step3])
|
| 357 |
+
if final_value is not None:
|
| 358 |
+
all_steps.append(f"So {_safe_str(var)} = {_safe_str(final_value)}.")
|
| 359 |
+
return _ensure_bullets(all_steps), _ensure_bullets(display_steps), final_value
|
| 360 |
+
except Exception:
|
| 361 |
+
pass
|
| 362 |
+
|
| 363 |
+
# General linear fallback
|
| 364 |
+
if coeff != 0:
|
| 365 |
+
if const != 0:
|
| 366 |
+
step2 = "Rearrange so the variable term is isolated."
|
| 367 |
+
step3 = "Undo the constant term using the inverse operation on both sides."
|
| 368 |
+
display_steps.extend([step2, step3])
|
| 369 |
+
all_steps.extend([step2, step3])
|
| 370 |
+
if coeff != 1:
|
| 371 |
+
step4 = f"Then divide by the coefficient of {_safe_str(var)} to isolate the variable."
|
| 372 |
+
display_steps.append(step4)
|
| 373 |
+
all_steps.append(step4)
|
| 374 |
+
if final_value is not None:
|
| 375 |
+
all_steps.append(f"So {_safe_str(var)} = {_safe_str(final_value)}.")
|
| 376 |
+
else:
|
| 377 |
+
all_steps.append("There is no variable term left after simplification, so check whether the equation is always true or impossible.")
|
| 378 |
+
display_steps.append("After simplification, check whether the statement is always true or impossible.")
|
| 379 |
+
|
| 380 |
+
return _ensure_bullets(all_steps), _ensure_bullets(display_steps), final_value
|
| 381 |
+
|
| 382 |
+
|
| 383 |
# =========================================================
|
| 384 |
# Handlers
|
| 385 |
# =========================================================
|
|
|
|
| 396 |
if not symbols:
|
| 397 |
return None
|
| 398 |
|
|
|
|
| 399 |
nonlinear = []
|
| 400 |
for eq in sym_eqs:
|
| 401 |
expr = sp.expand(eq.lhs - eq.rhs)
|
| 402 |
deg = _degree_of_expr(expr)
|
| 403 |
+
if deg != 1:
|
|
|
|
|
|
|
| 404 |
nonlinear.append(eq)
|
| 405 |
|
| 406 |
if nonlinear:
|
|
|
|
| 420 |
),
|
| 421 |
solved=True,
|
| 422 |
help_mode=help_mode,
|
| 423 |
+
steps=steps,
|
| 424 |
+
display_steps=steps,
|
| 425 |
)
|
| 426 |
|
|
|
|
| 427 |
matrix, vec = sp.linear_eq_to_matrix([eq.lhs - eq.rhs for eq in sym_eqs], symbols)
|
| 428 |
rank_a = matrix.rank()
|
| 429 |
rank_aug = matrix.row_join(vec).rank()
|
|
|
|
| 431 |
|
| 432 |
if rank_a != rank_aug:
|
| 433 |
msg = [
|
| 434 |
+
"- This system is inconsistent.",
|
| 435 |
+
"- That means the equations conflict with each other, so there is no common solution.",
|
| 436 |
+
"- A good first move is to eliminate one variable and compare the resulting statements."
|
| 437 |
]
|
| 438 |
+
return _mk_result(
|
| 439 |
+
reply=_modeled_steps(
|
| 440 |
+
title="This system is inconsistent.",
|
| 441 |
+
method="Eliminate a variable and compare the resulting statements.",
|
| 442 |
+
steps=msg,
|
| 443 |
+
help_mode=help_mode,
|
| 444 |
+
),
|
| 445 |
+
solved=True,
|
| 446 |
+
help_mode=help_mode,
|
| 447 |
+
steps=msg,
|
| 448 |
+
display_steps=msg,
|
| 449 |
+
)
|
| 450 |
|
| 451 |
if rank_a < nvars:
|
| 452 |
msg = [
|
| 453 |
+
"- This system does not pin down a unique solution.",
|
| 454 |
+
"- That means there are infinitely many solutions or at least one free variable.",
|
| 455 |
+
"- On GMAT-style questions, this often means you should solve for a relationship instead of individual values."
|
| 456 |
]
|
| 457 |
+
return _mk_result(
|
| 458 |
+
reply=_modeled_steps(
|
| 459 |
+
title="This system does not have a unique solution.",
|
| 460 |
+
method="Look for a relationship rather than separate fixed values.",
|
| 461 |
+
steps=msg,
|
| 462 |
+
help_mode=help_mode,
|
| 463 |
+
),
|
| 464 |
+
solved=True,
|
| 465 |
+
help_mode=help_mode,
|
| 466 |
+
steps=msg,
|
| 467 |
+
display_steps=msg,
|
| 468 |
+
)
|
| 469 |
|
| 470 |
steps = [
|
| 471 |
"- Choose one variable to eliminate.",
|
|
|
|
| 483 |
),
|
| 484 |
solved=True,
|
| 485 |
help_mode=help_mode,
|
| 486 |
+
steps=steps,
|
| 487 |
+
display_steps=steps,
|
| 488 |
)
|
| 489 |
except Exception:
|
| 490 |
return None
|
|
|
|
| 503 |
|
| 504 |
syms = sorted(list(rel.free_symbols), key=lambda s: s.name)
|
| 505 |
if len(syms) != 1:
|
| 506 |
+
steps = [
|
| 507 |
+
"- Collect all terms on one side.",
|
| 508 |
+
"- Factor if possible.",
|
| 509 |
+
"- Mark critical points where the expression is 0 or undefined.",
|
| 510 |
+
"- Test intervals to see where the inequality is true.",
|
| 511 |
+
"- Remember: multiplying or dividing by a negative flips the inequality sign."
|
| 512 |
+
]
|
| 513 |
return _mk_result(
|
| 514 |
reply=_modeled_steps(
|
| 515 |
title="This is an inequality problem.",
|
| 516 |
method="Rearrange so one side becomes 0, then analyze sign changes or isolate the variable.",
|
| 517 |
+
steps=steps,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 518 |
help_mode=help_mode,
|
| 519 |
),
|
| 520 |
solved=True,
|
| 521 |
help_mode=help_mode,
|
| 522 |
+
steps=steps,
|
| 523 |
+
display_steps=steps,
|
| 524 |
)
|
| 525 |
|
| 526 |
var = syms[0]
|
| 527 |
steps = [
|
| 528 |
+
f"- Isolate {_safe_str(var)} as much as possible.",
|
| 529 |
"- Be careful with brackets, fractions, and negative coefficients.",
|
| 530 |
"- If you multiply or divide by a negative quantity, reverse the inequality sign.",
|
| 531 |
"- If the expression factors, use sign analysis instead of treating it like a normal equation."
|
|
|
|
| 539 |
),
|
| 540 |
solved=True,
|
| 541 |
help_mode=help_mode,
|
| 542 |
+
steps=steps,
|
| 543 |
+
display_steps=steps,
|
| 544 |
)
|
| 545 |
except Exception:
|
| 546 |
return None
|
|
|
|
| 558 |
deg = _degree_of_expr(expr)
|
| 559 |
|
| 560 |
if not syms:
|
| 561 |
+
steps = [
|
| 562 |
+
"- Simplify both sides fully.",
|
| 563 |
+
"- Then decide whether the statement is always true, never true, or just a numeric identity."
|
| 564 |
+
]
|
| 565 |
return _mk_result(
|
| 566 |
+
reply=_modeled_steps(
|
| 567 |
+
title="No variable remains after simplification.",
|
| 568 |
+
method="Check the resulting statement itself.",
|
| 569 |
+
steps=steps,
|
| 570 |
+
help_mode=help_mode,
|
| 571 |
+
),
|
| 572 |
solved=True,
|
| 573 |
help_mode=help_mode,
|
| 574 |
+
steps=steps,
|
| 575 |
+
display_steps=steps,
|
| 576 |
)
|
| 577 |
|
| 578 |
if len(syms) > 1 and deg == 1:
|
|
|
|
| 590 |
),
|
| 591 |
solved=True,
|
| 592 |
help_mode=help_mode,
|
| 593 |
+
steps=steps,
|
| 594 |
+
display_steps=steps,
|
| 595 |
)
|
| 596 |
|
| 597 |
if deg == 1:
|
| 598 |
var = syms[0]
|
| 599 |
+
all_steps, display_steps, final_value = _build_linear_equation_steps(eq, var)
|
| 600 |
+
reply = _modeled_steps(
|
| 601 |
+
title="This is a linear equation.",
|
| 602 |
+
method="Use inverse operations and keep both sides balanced.",
|
| 603 |
+
steps=display_steps,
|
| 604 |
+
help_mode=help_mode,
|
| 605 |
+
)
|
|
|
|
| 606 |
return _mk_result(
|
| 607 |
+
reply=reply,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 608 |
solved=True,
|
| 609 |
help_mode=help_mode,
|
| 610 |
+
steps=all_steps,
|
| 611 |
+
display_steps=display_steps,
|
| 612 |
+
final_value=final_value,
|
| 613 |
)
|
| 614 |
|
| 615 |
if deg == 2:
|
|
|
|
| 617 |
disc = None
|
| 618 |
try:
|
| 619 |
poly = sp.Poly(expr, var)
|
| 620 |
+
coeffs = poly.all_coeffs()
|
| 621 |
+
if len(coeffs) == 3:
|
| 622 |
+
a, b, c = coeffs
|
| 623 |
+
disc = sp.expand(b**2 - 4 * a * c)
|
| 624 |
except Exception:
|
| 625 |
pass
|
| 626 |
|
|
|
|
| 630 |
"- If it does not factor cleanly, use a systematic method such as the quadratic formula or completing the square.",
|
| 631 |
"- After finding candidate roots internally, substitute back to verify."
|
| 632 |
]
|
|
|
|
| 633 |
if disc is not None:
|
| 634 |
steps.append("- The discriminant tells you whether there are two, one, or no real roots.")
|
| 635 |
|
|
|
|
| 642 |
),
|
| 643 |
solved=True,
|
| 644 |
help_mode=help_mode,
|
| 645 |
+
steps=steps,
|
| 646 |
+
display_steps=steps,
|
| 647 |
)
|
| 648 |
|
| 649 |
if deg and deg > 2:
|
|
|
|
| 658 |
steps.append("- This one appears factorable, so the zero-product idea is likely useful.")
|
| 659 |
if parsed["has_integer_constraint"]:
|
| 660 |
steps.append("- Since the variables may be restricted to integers, candidate checking can also be efficient.")
|
| 661 |
+
|
| 662 |
return _mk_result(
|
| 663 |
reply=_modeled_steps(
|
| 664 |
title="This is a higher-degree algebra equation.",
|
|
|
|
| 668 |
),
|
| 669 |
solved=True,
|
| 670 |
help_mode=help_mode,
|
| 671 |
+
steps=steps,
|
| 672 |
+
display_steps=steps,
|
| 673 |
)
|
| 674 |
|
| 675 |
except Exception:
|
|
|
|
| 691 |
expr = _sympify_expr(expr_text)
|
| 692 |
|
| 693 |
if intent == "simplify":
|
| 694 |
+
steps = [
|
| 695 |
+
"- Expand only if that helps combine terms.",
|
| 696 |
+
"- Collect like powers and like variable terms.",
|
| 697 |
+
"- Factor common pieces if the expression becomes cleaner that way.",
|
| 698 |
+
"- Check for hidden identities such as a^2-b^2 or perfect squares."
|
| 699 |
+
]
|
| 700 |
return _mk_result(
|
| 701 |
reply=_modeled_steps(
|
| 702 |
title="This is a simplification task.",
|
| 703 |
method="Combine like terms, reduce fractions carefully, and use identities where helpful.",
|
| 704 |
+
steps=steps,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 705 |
help_mode=help_mode,
|
| 706 |
),
|
| 707 |
solved=True,
|
| 708 |
help_mode=help_mode,
|
| 709 |
+
steps=steps,
|
| 710 |
+
display_steps=steps,
|
| 711 |
)
|
| 712 |
|
| 713 |
if intent == "expand":
|
| 714 |
+
steps = [
|
| 715 |
+
"- Multiply each outside factor by each inside term.",
|
| 716 |
+
"- Watch negative signs.",
|
| 717 |
+
"- Combine like terms at the end."
|
| 718 |
+
]
|
| 719 |
return _mk_result(
|
| 720 |
reply=_modeled_steps(
|
| 721 |
title="This is an expansion task.",
|
| 722 |
method="Distribute carefully across every term in the bracket(s).",
|
| 723 |
+
steps=steps,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 724 |
help_mode=help_mode,
|
| 725 |
),
|
| 726 |
solved=True,
|
| 727 |
help_mode=help_mode,
|
| 728 |
+
steps=steps,
|
| 729 |
+
display_steps=steps,
|
| 730 |
)
|
| 731 |
|
| 732 |
if intent == "factor":
|
| 733 |
+
steps = [
|
| 734 |
+
"- Take out the greatest common factor first.",
|
| 735 |
+
"- Check for difference of squares.",
|
| 736 |
+
"- Check for perfect-square trinomials.",
|
| 737 |
+
"- If it is quadratic in form, use sum/product structure."
|
| 738 |
+
]
|
| 739 |
return _mk_result(
|
| 740 |
reply=_modeled_steps(
|
| 741 |
title="This is a factorization task.",
|
| 742 |
method="Start by pulling out any common factor, then check special identities and quadratic patterns.",
|
| 743 |
+
steps=steps,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 744 |
help_mode=help_mode,
|
| 745 |
),
|
| 746 |
solved=True,
|
| 747 |
help_mode=help_mode,
|
| 748 |
+
steps=steps,
|
| 749 |
+
display_steps=steps,
|
| 750 |
)
|
| 751 |
|
| 752 |
if intent == "rearrange":
|
| 753 |
+
steps = [
|
| 754 |
+
"- Identify which variable must be isolated.",
|
| 755 |
+
"- Move all target-variable terms to one side.",
|
| 756 |
+
"- Move all non-target terms to the other side.",
|
| 757 |
+
"- Factor the target variable if it appears in multiple terms.",
|
| 758 |
+
"- Divide only when you know the divisor is allowed to be nonzero."
|
| 759 |
+
]
|
| 760 |
return _mk_result(
|
| 761 |
reply=_modeled_steps(
|
| 762 |
title="This is a rearranging / isolating task.",
|
| 763 |
method="Move all terms involving the target variable together, then factor it out.",
|
| 764 |
+
steps=steps,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 765 |
help_mode=help_mode,
|
| 766 |
),
|
| 767 |
solved=True,
|
| 768 |
help_mode=help_mode,
|
| 769 |
+
steps=steps,
|
| 770 |
+
display_steps=steps,
|
| 771 |
)
|
| 772 |
|
|
|
|
| 773 |
deg = _degree_of_expr(expr)
|
| 774 |
steps = [
|
| 775 |
"- Decide whether the best move is simplify, expand, factor, or substitute.",
|
|
|
|
| 788 |
),
|
| 789 |
solved=True,
|
| 790 |
help_mode=help_mode,
|
| 791 |
+
steps=steps,
|
| 792 |
+
display_steps=steps,
|
| 793 |
)
|
| 794 |
except Exception:
|
| 795 |
return None
|
|
|
|
| 797 |
|
| 798 |
def _handle_word_translation(parsed: Dict[str, Any], help_mode: str) -> Optional[SolverResult]:
|
| 799 |
lower = parsed["lower"]
|
|
|
|
| 800 |
|
| 801 |
triggers = [
|
| 802 |
"more than", "less than", "twice", "times", "sum of", "difference",
|
|
|
|
| 806 |
return None
|
| 807 |
|
| 808 |
mappings = [
|
| 809 |
+
("more than", "Be careful with order: '10 more than x' means x + 10."),
|
| 810 |
+
("less than", "Be careful with order: '3 less than x' means x - 3, but '3 less than a number' means number - 3."),
|
| 811 |
+
("twice", "'Twice x' means 2x."),
|
| 812 |
+
("sum of", "'Sum of a and b' means a + b."),
|
| 813 |
+
("difference", "'Difference of a and b' means a - b."),
|
| 814 |
+
("product of", "'Product of a and b' means ab."),
|
| 815 |
+
("quotient", "'Quotient of a and b' means a / b."),
|
| 816 |
+
("at least", "This signals >=."),
|
| 817 |
+
("at most", "This signals <=."),
|
| 818 |
+
("no more than", "This signals <=."),
|
| 819 |
+
("no less than", "This signals >=."),
|
| 820 |
+
("consecutive", "Use n, n+1, n+2, ..."),
|
| 821 |
]
|
| 822 |
|
| 823 |
bullets = []
|
|
|
|
| 840 |
),
|
| 841 |
solved=True,
|
| 842 |
help_mode=help_mode,
|
| 843 |
+
steps=bullets,
|
| 844 |
+
display_steps=bullets,
|
| 845 |
)
|
| 846 |
|
| 847 |
|
|
|
|
| 849 |
if not parsed["mentions_degree"]:
|
| 850 |
return None
|
| 851 |
|
| 852 |
+
steps = [
|
| 853 |
+
"- Degree 1 suggests a linear structure.",
|
| 854 |
+
"- Degree 2 suggests a quadratic structure.",
|
| 855 |
+
"- Higher degree often calls for factorization, substitution, or identity spotting.",
|
| 856 |
+
"- A polynomial of degree n can have at most n roots over the reals/complexes combined."
|
| 857 |
+
]
|
| 858 |
return _mk_result(
|
| 859 |
reply=_modeled_steps(
|
| 860 |
title="This question is using degree / polynomial structure.",
|
| 861 |
method="The degree tells you the highest power present and helps narrow the right solving method.",
|
| 862 |
+
steps=steps,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 863 |
help_mode=help_mode,
|
| 864 |
),
|
| 865 |
solved=True,
|
| 866 |
help_mode=help_mode,
|
| 867 |
+
steps=steps,
|
| 868 |
+
display_steps=steps,
|
| 869 |
)
|
| 870 |
|
| 871 |
|
|
|
|
| 875 |
if not parsed["equations"] and not parsed["inequalities"]:
|
| 876 |
return None
|
| 877 |
|
| 878 |
+
steps = [
|
| 879 |
+
"- Use the algebra to narrow the possible forms first.",
|
| 880 |
+
"- Then test only values consistent with the restriction.",
|
| 881 |
+
"- Stop when the restriction makes further values impossible.",
|
| 882 |
+
"- Always check the tested value in the original condition."
|
| 883 |
+
]
|
| 884 |
return _mk_result(
|
| 885 |
reply=_modeled_steps(
|
| 886 |
title="This problem has an integer restriction.",
|
| 887 |
method="That often makes controlled testing or divisibility reasoning much faster than pure symbolic solving.",
|
| 888 |
+
steps=steps,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 889 |
help_mode=help_mode,
|
| 890 |
),
|
| 891 |
solved=True,
|
| 892 |
help_mode=help_mode,
|
| 893 |
+
steps=steps,
|
| 894 |
+
display_steps=steps,
|
| 895 |
)
|
| 896 |
|
| 897 |
|
|
|
|
| 948 |
# =========================================================
|
| 949 |
|
| 950 |
def _modeled_steps(title: str, method: str, steps: List[str], help_mode: str) -> str:
|
| 951 |
+
clean_steps = _ensure_bullets(steps)
|
| 952 |
+
|
| 953 |
if help_mode == "hint":
|
| 954 |
return _join_sections(
|
| 955 |
title,
|
| 956 |
f"Hint: {method}",
|
| 957 |
+
clean_steps[0] if clean_steps else ""
|
| 958 |
)
|
| 959 |
|
| 960 |
if help_mode == "explain":
|
| 961 |
return _join_sections(
|
| 962 |
title,
|
| 963 |
f"Method idea: {method}",
|
| 964 |
+
"\n".join(clean_steps[:3])
|
| 965 |
)
|
| 966 |
|
| 967 |
if help_mode == "method":
|
| 968 |
return _join_sections(
|
| 969 |
title,
|
| 970 |
f"Method: {method}",
|
| 971 |
+
"\n".join(clean_steps)
|
| 972 |
)
|
| 973 |
|
|
|
|
| 974 |
return _join_sections(
|
| 975 |
title,
|
| 976 |
f"Walkthrough method: {method}",
|
| 977 |
+
"\n".join(clean_steps)
|
| 978 |
)
|
| 979 |
|
| 980 |
|
|
|
|
| 983 |
return "\n\n".join(clean)
|
| 984 |
|
| 985 |
|
| 986 |
+
def _mk_result(
|
| 987 |
+
reply: str,
|
| 988 |
+
solved: bool,
|
| 989 |
+
help_mode: str,
|
| 990 |
+
steps: Optional[List[str]] = None,
|
| 991 |
+
display_steps: Optional[List[str]] = None,
|
| 992 |
+
final_value: Any = None,
|
| 993 |
+
) -> SolverResult:
|
| 994 |
+
steps = steps or []
|
| 995 |
+
display_steps = display_steps or steps
|
| 996 |
+
|
| 997 |
+
result = SolverResult(
|
| 998 |
reply=reply,
|
| 999 |
meta={
|
| 1000 |
"domain": "quant",
|
|
|
|
| 1005 |
"topic": "algebra",
|
| 1006 |
"used_retrieval": False,
|
| 1007 |
"used_generator": False,
|
| 1008 |
+
"steps": steps,
|
| 1009 |
+
"display_steps": display_steps,
|
| 1010 |
+
"internal_answer": _safe_str(final_value) if final_value is not None else None,
|
| 1011 |
},
|
| 1012 |
+
)
|
| 1013 |
+
|
| 1014 |
+
# Add attributes directly in case your formatter / conversation layer reads them.
|
| 1015 |
+
try:
|
| 1016 |
+
setattr(result, "steps", display_steps)
|
| 1017 |
+
except Exception:
|
| 1018 |
+
pass
|
| 1019 |
+
|
| 1020 |
+
try:
|
| 1021 |
+
setattr(result, "display_steps", display_steps)
|
| 1022 |
+
except Exception:
|
| 1023 |
+
pass
|
| 1024 |
+
|
| 1025 |
+
try:
|
| 1026 |
+
setattr(result, "all_steps", steps)
|
| 1027 |
+
except Exception:
|
| 1028 |
+
pass
|
| 1029 |
+
|
| 1030 |
+
try:
|
| 1031 |
+
setattr(result, "internal_answer", final_value)
|
| 1032 |
+
except Exception:
|
| 1033 |
+
pass
|
| 1034 |
+
|
| 1035 |
+
return result
|