j-js commited on
Commit
074e4ab
·
verified ·
1 Parent(s): ef5b440

Update quant_solver.py

Browse files
Files changed (1) hide show
  1. quant_solver.py +91 -39
quant_solver.py CHANGED
@@ -50,40 +50,76 @@ def is_quant_question(text: str) -> bool:
50
  def _prepare_expression(expr: str) -> str:
51
  expr = clean_math_text(expr).strip()
52
  expr = expr.replace("^", "**")
 
 
 
 
 
53
  expr = re.sub(r"(\d)\s*\(", r"\1*(", expr)
54
  expr = re.sub(r"\)\s*(\d)", r")*\1", expr)
55
- expr = re.sub(r"(\d)([a-zA-Z])", r"\1*\2", expr)
 
 
 
 
56
  return expr
57
 
58
 
 
 
 
 
 
 
 
 
 
 
 
59
  def _extract_equation(text: str) -> Optional[str]:
60
- cleaned = clean_math_text(text)
61
  if "=" not in cleaned:
62
  return None
63
 
 
64
  patterns = [
65
- r"([A-Za-z0-9\.\+\-\*/\^\(\)\s]*[a-zA-Z][A-Za-z0-9\.\+\-\*/\^\(\)\s]*=[A-Za-z0-9\.\+\-\*/\^\(\)\s]+)",
66
- r"([0-9A-Za-z\.\+\-\*/\^\(\)\s]+=[0-9A-Za-z\.\+\-\*/\^\(\)\s]+)",
67
  ]
68
 
69
  for pattern in patterns:
70
  for m in re.finditer(pattern, cleaned):
71
- candidate = m.group(1).strip()
72
- if re.search(r"[a-z]", candidate.lower()) and not candidate.lower().startswith(
73
- ("how do", "can you", "please", "what is", "solve ")
74
- ):
75
  return candidate
76
 
77
- eq_index = cleaned.find("=")
78
- left = re.findall(r"[A-Za-z0-9\.\+\-\*/\^\(\)\s]+$", cleaned[:eq_index])
79
- right = re.findall(r"^[A-Za-z0-9\.\+\-\*/\^\(\)\s]+", cleaned[eq_index + 1:])
80
- if left and right:
81
- candidate = left[0].strip().split()[-1] + " = " + right[0].strip().split()[0]
82
- if re.search(r"[a-z]", candidate.lower()):
83
- return candidate
 
 
 
 
 
 
 
 
84
  return None
85
 
86
 
 
 
 
 
 
 
 
 
87
  def _parse_number(text: str) -> Optional[float]:
88
  raw = clean_math_text(text).strip().lower()
89
 
@@ -189,7 +225,7 @@ def _solve_successive_percent(text: str) -> Optional[SolverResult]:
189
  topic="percent",
190
  answer_value=f"{magnitude:g}%",
191
  internal_answer=f"net {direction} of {magnitude:g}%",
192
- steps=step_lines + [f"The combined multiplier gives a net {direction} of {magnitude:g}%."],
193
  choices_text=text,
194
  )
195
 
@@ -226,28 +262,24 @@ def _solve_ratio_total(text: str) -> Optional[SolverResult]:
226
 
227
  labels = _extract_ratio_labels(lower)
228
  requested_value = left_value
229
- requested_label = "first quantity"
230
 
231
  if labels:
232
  left_label, right_label = labels
233
  if left_label in lower and re.search(rf"how many {re.escape(left_label)}", lower):
234
  requested_value = left_value
235
- requested_label = left_label
236
  elif right_label in lower and re.search(rf"how many {re.escape(right_label)}", lower):
237
  requested_value = right_value
238
- requested_label = right_label
239
  else:
240
  requested_value = left_value
241
- requested_label = left_label
242
 
243
  return _make_result(
244
  topic="ratio",
245
  answer_value=f"{requested_value:g}",
246
- internal_answer=f"{requested_label} = {requested_value:g}",
247
  steps=[
248
  f"Add the ratio parts: {a} + {b} = {part_sum}.",
249
- f"Each ratio unit is {total} / {part_sum} = {unit:g}.",
250
- f"Multiply by the required ratio part to get {requested_value:g}.",
251
  ],
252
  choices_text=text,
253
  )
@@ -275,7 +307,7 @@ def _solve_remainder(text: str) -> Optional[SolverResult]:
275
  internal_answer=str(r),
276
  steps=[
277
  f"Divide {a} by {b}.",
278
- f"The remainder is {a} mod {b} = {r}.",
279
  ],
280
  choices_text=text,
281
  )
@@ -285,10 +317,27 @@ def _solve_percent(text: str) -> Optional[SolverResult]:
285
  lower = clean_math_text(text).lower()
286
  choices = extract_choices(text)
287
 
288
- m = re.search(r"(\d+(?:\.\d+)?)\s*(?:%|percent)\s+of\s+(?:a\s+)?number\s+is\s+(\d+(?:\.\d+)?)", lower)
289
- if m:
290
- p = float(m.group(1))
291
- value = float(m.group(2))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
  ans = value / (p / 100.0)
293
  answer_letter = _best_choice(ans, choices) if choices else None
294
 
@@ -300,9 +349,10 @@ def _solve_percent(text: str) -> Optional[SolverResult]:
300
  answer_letter=answer_letter,
301
  internal_answer=f"{ans:g}",
302
  steps=[
303
- "Let the number be n.",
304
- f"Write {p}% of n as {p / 100:g}n.",
305
- f"Set {p / 100:g}n = {value} and solve for n.",
 
306
  ],
307
  )
308
 
@@ -321,8 +371,8 @@ def _solve_percent(text: str) -> Optional[SolverResult]:
321
  answer_letter=answer_letter,
322
  internal_answer=f"{ans:g}",
323
  steps=[
324
- f"Convert {p}% to {p / 100:g}.",
325
- f"Multiply by {n}.",
326
  ],
327
  )
328
 
@@ -370,16 +420,18 @@ def _solve_linear_equation(text: str) -> Optional[SolverResult]:
370
 
371
  try:
372
  lhs, rhs = expr.split("=", 1)
373
- symbols = sorted(set(re.findall(r"\b[a-z]\b", expr)))
 
 
 
374
  if not symbols:
375
  return None
376
 
377
  var_name = symbols[0]
378
  var = sp.symbols(var_name)
379
- sol = sp.solve(
380
- sp.Eq(sp.sympify(_prepare_expression(lhs)), sp.sympify(_prepare_expression(rhs))),
381
- var,
382
- )
383
  if not sol:
384
  return None
385
 
@@ -401,7 +453,7 @@ def _solve_linear_equation(text: str) -> Optional[SolverResult]:
401
  steps=[
402
  "Treat the statement as an equation.",
403
  "Undo operations on both sides to isolate the variable.",
404
- f"That gives {var_name} = {value}.",
405
  ],
406
  )
407
  except Exception:
 
50
  def _prepare_expression(expr: str) -> str:
51
  expr = clean_math_text(expr).strip()
52
  expr = expr.replace("^", "**")
53
+
54
+ # remove common prompt wrappers
55
+ expr = re.sub(r"(?i)^\s*(solve|simplify|evaluate|find|what is|compute)\s*:?\s*", "", expr)
56
+
57
+ # implicit multiplication
58
  expr = re.sub(r"(\d)\s*\(", r"\1*(", expr)
59
  expr = re.sub(r"\)\s*(\d)", r")*\1", expr)
60
+ expr = re.sub(r"(\d)\s*([a-zA-Z])", r"\1*\2", expr)
61
+ expr = re.sub(r"([a-zA-Z])\s*\(", r"\1*(", expr)
62
+ expr = re.sub(r"\)\s*([a-zA-Z])", r")*\1", expr)
63
+ expr = re.sub(r"([a-zA-Z])\s+([a-zA-Z])", r"\1*\2", expr)
64
+
65
  return expr
66
 
67
 
68
+ def _clean_equation_candidate(text: str) -> str:
69
+ s = clean_math_text(text).strip()
70
+
71
+ # remove leading prompt phrases but keep equation content
72
+ s = re.sub(r"(?i)^\s*(solve|simplify|evaluate|find)\s*:?\s*", "", s)
73
+ s = re.sub(r"(?i)^\s*how do i solve\s*:?\s*", "", s)
74
+ s = re.sub(r"(?i)^\s*what is\s+", "", s)
75
+ s = normalize_spaces(s)
76
+ return s
77
+
78
+
79
  def _extract_equation(text: str) -> Optional[str]:
80
+ cleaned = _clean_equation_candidate(text)
81
  if "=" not in cleaned:
82
  return None
83
 
84
+ # first try: take the full equation-looking span
85
  patterns = [
86
+ r"([A-Za-z0-9\.\+\-\*/\^\(\)\s]+=[A-Za-z0-9\.\+\-\*/\^\(\)\s]+)",
 
87
  ]
88
 
89
  for pattern in patterns:
90
  for m in re.finditer(pattern, cleaned):
91
+ candidate = normalize_spaces(m.group(1))
92
+ if "=" not in candidate:
93
+ continue
94
+ if re.search(r"[a-z]", candidate.lower()):
95
  return candidate
96
 
97
+ # fallback: split once on equals and trim to likely expression zones
98
+ parts = cleaned.split("=", 1)
99
+ if len(parts) != 2:
100
+ return None
101
+
102
+ lhs = parts[0].strip(" .,:;!?")
103
+ rhs = parts[1].strip(" .,:;!?")
104
+
105
+ if not lhs or not rhs:
106
+ return None
107
+
108
+ candidate = f"{lhs} = {rhs}"
109
+ if re.search(r"[a-z]", candidate.lower()):
110
+ return candidate
111
+
112
  return None
113
 
114
 
115
+ def _extract_variable_names(expr: str) -> List[str]:
116
+ # catches x in "3x + 5 = 20" as well as standalone x
117
+ vars_found = sorted(set(re.findall(r"[a-z]", expr.lower())))
118
+ # avoid treating common words as many variables by keeping only likely algebra variables first
119
+ preferred = [v for v in vars_found if v in {"x", "y", "z", "n"}]
120
+ return preferred or vars_found
121
+
122
+
123
  def _parse_number(text: str) -> Optional[float]:
124
  raw = clean_math_text(text).strip().lower()
125
 
 
225
  topic="percent",
226
  answer_value=f"{magnitude:g}%",
227
  internal_answer=f"net {direction} of {magnitude:g}%",
228
+ steps=step_lines + [f"Combine the multipliers to find the overall percent change."],
229
  choices_text=text,
230
  )
231
 
 
262
 
263
  labels = _extract_ratio_labels(lower)
264
  requested_value = left_value
 
265
 
266
  if labels:
267
  left_label, right_label = labels
268
  if left_label in lower and re.search(rf"how many {re.escape(left_label)}", lower):
269
  requested_value = left_value
 
270
  elif right_label in lower and re.search(rf"how many {re.escape(right_label)}", lower):
271
  requested_value = right_value
 
272
  else:
273
  requested_value = left_value
 
274
 
275
  return _make_result(
276
  topic="ratio",
277
  answer_value=f"{requested_value:g}",
278
+ internal_answer=f"{requested_value:g}",
279
  steps=[
280
  f"Add the ratio parts: {a} + {b} = {part_sum}.",
281
+ f"Find the value of one ratio part using the total.",
282
+ f"Multiply by the required ratio part.",
283
  ],
284
  choices_text=text,
285
  )
 
307
  internal_answer=str(r),
308
  steps=[
309
  f"Divide {a} by {b}.",
310
+ "Keep the amount left over after division.",
311
  ],
312
  choices_text=text,
313
  )
 
317
  lower = clean_math_text(text).lower()
318
  choices = extract_choices(text)
319
 
320
+ patterns = [
321
+ r"(\d+(?:\.\d+)?)\s*(?:%|percent)\s+of\s+(?:a\s+)?number\s+is\s+(\d+(?:\.\d+)?)",
322
+ r"(\d+(?:\.\d+)?)\s*(?:%|percent)\s+of\s+([a-z])\s+is\s+(\d+(?:\.\d+)?)",
323
+ r"(\d+(?:\.\d+)?)\s*(?:%|percent)\s+of\s+([a-z])\s*=\s*(\d+(?:\.\d+)?)",
324
+ ]
325
+
326
+ for pat in patterns:
327
+ m = re.search(pat, lower)
328
+ if not m:
329
+ continue
330
+
331
+ if len(m.groups()) == 2:
332
+ p = float(m.group(1))
333
+ value = float(m.group(2))
334
+ else:
335
+ p = float(m.group(1))
336
+ value = float(m.group(3))
337
+
338
+ if p == 0:
339
+ return None
340
+
341
  ans = value / (p / 100.0)
342
  answer_letter = _best_choice(ans, choices) if choices else None
343
 
 
349
  answer_letter=answer_letter,
350
  internal_answer=f"{ans:g}",
351
  steps=[
352
+ "Let the unknown quantity be a variable.",
353
+ f"Convert {p:g}% into its decimal form.",
354
+ "Write an equation for the part and whole relationship.",
355
+ "Solve that equation for the unknown.",
356
  ],
357
  )
358
 
 
371
  answer_letter=answer_letter,
372
  internal_answer=f"{ans:g}",
373
  steps=[
374
+ f"Convert {p:g}% to a decimal.",
375
+ f"Multiply that decimal by {n}.",
376
  ],
377
  )
378
 
 
420
 
421
  try:
422
  lhs, rhs = expr.split("=", 1)
423
+ lhs_prepped = _prepare_expression(lhs)
424
+ rhs_prepped = _prepare_expression(rhs)
425
+
426
+ symbols = _extract_variable_names(expr)
427
  if not symbols:
428
  return None
429
 
430
  var_name = symbols[0]
431
  var = sp.symbols(var_name)
432
+
433
+ equation = sp.Eq(sp.sympify(lhs_prepped), sp.sympify(rhs_prepped))
434
+ sol = sp.solve(equation, var)
 
435
  if not sol:
436
  return None
437
 
 
453
  steps=[
454
  "Treat the statement as an equation.",
455
  "Undo operations on both sides to isolate the variable.",
456
+ "Keep simplifying until the variable is alone.",
457
  ],
458
  )
459
  except Exception: