j-js commited on
Commit
850fc95
·
verified ·
1 Parent(s): b63ce3c

Update quant_solver.py

Browse files
Files changed (1) hide show
  1. quant_solver.py +121 -176
quant_solver.py CHANGED
@@ -1,185 +1,130 @@
1
  from __future__ import annotations
2
 
3
- import math
4
- import re
5
- from statistics import mean, median
6
- from typing import Dict, Optional
7
 
8
  try:
9
- import sympy as sp
10
  except Exception:
11
- sp = None
12
-
13
- from models import SolverResult
14
- from utils import clean_math_text, normalize_spaces
15
-
16
-
17
- def extract_choices(text: str) -> Dict[str, str]:
18
- text = text or ""
19
- matches = list(
20
- re.finditer(
21
- r"(?i)\b([A-E])[\)\.:]\s*(.*?)(?=\s+\b[A-E][\)\.:]\s*|$)",
22
- text,
23
- )
24
- )
25
- return {m.group(1).upper(): normalize_spaces(m.group(2)) for m in matches}
26
-
27
-
28
- def _solve_successive_percent(text: str) -> Optional[SolverResult]:
29
-
30
- t = clean_math_text(text).lower()
31
-
32
- percents = re.findall(r"(\d+(?:\.\d+)?)\s*%", t)
33
- if len(percents) < 2:
34
- return None
35
-
36
- values = [float(p) for p in percents]
37
-
38
- mult = 1
39
- for p in values:
40
- if "decrease" in t or "discount" in t:
41
- mult *= 1 - p / 100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  else:
43
- mult *= 1 + p / 100
44
-
45
- net = (mult - 1) * 100
46
-
47
- return SolverResult(
48
- domain="quant",
49
- solved=True,
50
- topic="percent",
51
- answer_value=f"{net:.2f}%",
52
- internal_answer=f"{net:.2f}%",
53
- steps=[
54
- "Convert each percent change to a multiplier.",
55
- "Multiply the successive multipliers.",
56
- "Convert the final multiplier back to a percent change.",
57
- ],
58
- )
59
-
60
-
61
- def _solve_ratio_total(text: str) -> Optional[SolverResult]:
62
-
63
- t = clean_math_text(text)
64
-
65
- ratio = re.search(r"(\d+)\s*:\s*(\d+)", t)
66
- total = re.search(r"total\s*(?:is|=)?\s*(\d+)", t.lower())
67
-
68
- if not ratio or not total:
69
- return None
70
-
71
- a = int(ratio.group(1))
72
- b = int(ratio.group(2))
73
- total_val = int(total.group(1))
74
-
75
- part_sum = a + b
76
- unit = total_val / part_sum
77
-
78
- return SolverResult(
79
- domain="quant",
80
- solved=True,
81
- topic="ratio",
82
- answer_value=f"{a * unit:g}",
83
- internal_answer=f"{a * unit:g}",
84
- steps=[
85
- "Add the ratio parts.",
86
- "Divide the total by the sum of the ratio.",
87
- "Multiply by the requested ratio component.",
88
- ],
89
- )
90
-
91
-
92
- def _solve_remainder(text: str) -> Optional[SolverResult]:
93
-
94
- t = clean_math_text(text).lower()
95
-
96
- m = re.search(r"remainder.*?(\d+).*?divided by (\d+)", t)
97
-
98
- if not m:
99
- return None
100
-
101
- a = int(m.group(1))
102
- b = int(m.group(2))
103
-
104
- r = a % b
105
-
106
- return SolverResult(
107
- domain="quant",
108
- solved=True,
109
- topic="number_theory",
110
- answer_value=str(r),
111
- internal_answer=str(r),
112
- steps=[
113
- "Divide the number by the divisor.",
114
- "The remainder is the leftover after division.",
115
- ],
116
- )
117
-
118
-
119
- def _solve_percent(text: str) -> Optional[SolverResult]:
120
-
121
- lower = clean_math_text(text).lower()
122
-
123
- m = re.search(r"(\d+)% of a number is (\d+)", lower)
124
-
125
- if m:
126
- p = float(m.group(1))
127
- val = float(m.group(2))
128
-
129
- ans = val / (p / 100)
130
-
131
- return SolverResult(
132
- domain="quant",
133
- solved=True,
134
- topic="percent",
135
- answer_value=f"{ans:g}",
136
- internal_answer=f"{ans:g}",
137
  )
138
 
139
- return None
140
-
141
-
142
- def _solve_linear_equation(text: str) -> Optional[SolverResult]:
143
-
144
- if sp is None:
145
- return None
146
-
147
- m = re.search(r"([a-z])\s*/\s*(\d+)\s*=\s*(\d+)", text)
148
-
149
- if not m:
150
- return None
151
-
152
- var = m.group(1)
153
- a = float(m.group(2))
154
- b = float(m.group(3))
155
-
156
- ans = a * b
157
-
158
- return SolverResult(
159
- domain="quant",
160
- solved=True,
161
- topic="algebra",
162
- answer_value=str(ans),
163
- internal_answer=str(ans),
164
- )
165
-
166
-
167
- def solve_quant(text: str) -> SolverResult:
168
-
169
- for fn in (
170
- _solve_successive_percent,
171
- _solve_ratio_total,
172
- _solve_remainder,
173
- _solve_percent,
174
- _solve_linear_equation,
175
- ):
176
- result = fn(text)
177
- if result:
178
- return result
179
 
180
- return SolverResult(
181
- domain="quant",
182
- solved=False,
183
- topic="general_quant",
184
- reply="This looks quantitative but does not match a strong rule-based solver yet.",
185
- )
 
 
 
 
 
 
 
 
 
 
 
 
1
  from __future__ import annotations
2
 
3
+ from typing import List, Optional
 
 
 
4
 
5
  try:
6
+ from transformers import pipeline
7
  except Exception:
8
+ pipeline = None
9
+
10
+ from models import RetrievedChunk
11
+
12
+
13
+ class GeneratorEngine:
14
+ def __init__(self, model_name: str = "google/flan-t5-small"):
15
+ self.model_name = model_name
16
+ self.pipe = None
17
+
18
+ if pipeline is not None:
19
+ try:
20
+ self.pipe = pipeline("text2text-generation", model=model_name)
21
+ except Exception:
22
+ self.pipe = None
23
+
24
+ def available(self) -> bool:
25
+ return self.pipe is not None
26
+
27
+ def _notes_block(self, retrieval_context: List[RetrievedChunk]) -> str:
28
+ if not retrieval_context:
29
+ return ""
30
+ lines = []
31
+ for chunk in retrieval_context[:3]:
32
+ text = (chunk.text or "").strip().replace("\n", " ")
33
+ if len(text) > 220:
34
+ text = text[:217].rstrip() + "…"
35
+ lines.append(f"- {chunk.topic}: {text}")
36
+ return "\n".join(lines)
37
+
38
+ def _template_fallback(
39
+ self,
40
+ user_text: str,
41
+ question_text: Optional[str],
42
+ topic: str,
43
+ intent: str,
44
+ retrieval_context: Optional[List[RetrievedChunk]] = None,
45
+ ) -> str:
46
+ question = (question_text or user_text or "").strip()
47
+ notes = self._notes_block(retrieval_context or [])
48
+
49
+ if intent == "hint":
50
+ base = "Start by identifying the exact relationship between the quantities before doing any arithmetic."
51
+ elif intent in {"instruction", "method"}:
52
+ base = "Translate the wording into an equation, ratio, or percent relationship, then solve one step at a time."
53
+ elif intent in {"walkthrough", "step_by_step", "explain", "concept"}:
54
+ base = "First identify what the question is asking, then map the values into the correct quantitative structure, and only then compute."
55
  else:
56
+ base = "This does not match a strong solver rule yet, so begin by identifying the target quantity and the relationship connecting the numbers."
57
+
58
+ if notes:
59
+ return f"{base}\n\nRelevant notes:\n{notes}"
60
+ return base
61
+
62
+ def _build_prompt(
63
+ self,
64
+ user_text: str,
65
+ question_text: Optional[str],
66
+ topic: str,
67
+ intent: str,
68
+ retrieval_context: Optional[List[RetrievedChunk]] = None,
69
+ ) -> str:
70
+ question = (question_text or user_text or "").strip()
71
+ notes = self._notes_block(retrieval_context or [])
72
+
73
+ prompt = [
74
+ "You are a concise GMAT tutor.",
75
+ f"Topic: {topic or 'general'}",
76
+ f"Intent: {intent or 'answer'}",
77
+ "",
78
+ f"Question: {question}",
79
+ ]
80
+
81
+ if notes:
82
+ prompt.extend(["", "Relevant teaching notes:", notes])
83
+
84
+ prompt.extend(
85
+ [
86
+ "",
87
+ "Respond briefly and clearly.",
88
+ "If the problem is not fully solvable from the parse, give the next best method step.",
89
+ "Do not invent facts.",
90
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  )
92
 
93
+ return "\n".join(prompt)
94
+
95
+ def generate(
96
+ self,
97
+ user_text: str,
98
+ question_text: Optional[str] = None,
99
+ topic: str = "",
100
+ intent: str = "answer",
101
+ retrieval_context: Optional[List[RetrievedChunk]] = None,
102
+ chat_history=None,
103
+ max_new_tokens: int = 96,
104
+ **kwargs,
105
+ ) -> Optional[str]:
106
+ prompt = self._build_prompt(
107
+ user_text=user_text,
108
+ question_text=question_text,
109
+ topic=topic,
110
+ intent=intent,
111
+ retrieval_context=retrieval_context or [],
112
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
+ if self.pipe is not None:
115
+ try:
116
+ out = self.pipe(prompt, max_new_tokens=max_new_tokens, do_sample=False)
117
+ if out and isinstance(out, list):
118
+ text = str(out[0].get("generated_text", "")).strip()
119
+ if text:
120
+ return text
121
+ except Exception:
122
+ pass
123
+
124
+ return self._template_fallback(
125
+ user_text=user_text,
126
+ question_text=question_text,
127
+ topic=topic,
128
+ intent=intent,
129
+ retrieval_context=retrieval_context or [],
130
+ )