Optimised the demonstration, Added constraint judge.
Browse files- backend/Example.txt +24 -24
- backend/Sat_cnt.txt +32 -0
- backend/__pycache__/solver.cpython-313.pyc +0 -0
- backend/app.py +2 -2
- backend/solver.py +118 -6
- backend/static/asset-manifest.json +3 -3
- backend/static/index.html +1 -1
- backend/static/static/js/{main.f13a7657.js → main.120fc3ad.js} +0 -0
- backend/static/static/js/{main.f13a7657.js.LICENSE.txt → main.120fc3ad.js.LICENSE.txt} +0 -0
- backend/static/static/js/{main.f13a7657.js.map → main.120fc3ad.js.map} +0 -0
- frontend/src/App.js +29 -20
- test.py +146 -0
backend/Example.txt
CHANGED
@@ -50,9 +50,9 @@ categories = [
|
|
50 |
NUM_POSITIONS = len(categories[0][1])
|
51 |
|
52 |
item_to_cat_and_index = {}
|
53 |
-
for cat_idx, (
|
54 |
for item_idx, item_str in enumerate(item_list):
|
55 |
-
item_to_cat_and_index[item_str] = (cat_idx, item_idx)
|
56 |
|
57 |
Vars = []
|
58 |
for cat_idx, (cat_name, item_list) in enumerate(categories):
|
@@ -66,70 +66,70 @@ for cat_idx, (cat_name, item_list) in enumerate(categories):
|
|
66 |
s.add(Vars[cat_idx][item_idx] >= 1, Vars[cat_idx][item_idx] <= NUM_POSITIONS)
|
67 |
s.add(Distinct(Vars[cat_idx]))
|
68 |
|
69 |
-
def pos(item_str):
|
70 |
-
(cat_idx, item_idx) = item_to_cat_and_index[item_str]
|
71 |
return Vars[cat_idx][item_idx]
|
72 |
|
73 |
# All clues here
|
74 |
# The producer whose jam comes from Wild pickings is in the fourth position.
|
75 |
-
s.add(pos("wild pickings") == 4)
|
76 |
|
77 |
# The producer selling Cherry jam is somewhere to the right of the one wearing a Green shirt.
|
78 |
-
s.add(pos("cherry") > pos("green"))
|
79 |
|
80 |
# Isabella is selling Cherry jam.
|
81 |
-
s.add(pos("Isabella") == pos("cherry"))
|
82 |
|
83 |
# The producer wearing a Pink shirt is somewhere between the producer whose jam source is the Backyard and the one selling Watermelon jam, in that order.
|
84 |
-
s.add(And(pos("backyard") < pos("pink"), pos("pink") < pos("watermelon")))
|
85 |
|
86 |
# The producer selling 14 oz jars is somewhere to the right of the one wearing a Green shirt.
|
87 |
-
s.add(pos("14 oz") > pos("green"))
|
88 |
|
89 |
# The producer with 14 oz jars is next to the one selling Raspberry jam.
|
90 |
-
s.add(Abs(pos("14 oz") - pos("raspberry")) == 1)
|
91 |
|
92 |
# The Raspberry jam producer is positioned at one of the ends.
|
93 |
-
s.add(Or(pos("raspberry") == 1, pos("raspberry") == NUM_POSITIONS))
|
94 |
|
95 |
# Barbara is next to the producer whose jam source is the Organic farm.
|
96 |
-
s.add(Abs(pos("Barbara") - pos("organic farm")) == 1)
|
97 |
|
98 |
# Jane is located somewhere between Nicole and Isabella, in that order.
|
99 |
-
s.add(And(pos("Nicole") < pos("Jane"), pos("Jane") < pos("Isabella")))
|
100 |
|
101 |
# The producer of Fig jam sources the fruit from an Organic farm.
|
102 |
-
s.add(pos("fig") == pos("organic farm"))
|
103 |
|
104 |
# The producer with 10 oz jars is at one of the ends.
|
105 |
-
s.add(Or(pos("10 oz") == 1, pos("10 oz") == NUM_POSITIONS))
|
106 |
|
107 |
# The Raspberry jam producer is somewhere to the right of the one wearing a Green shirt.
|
108 |
-
s.add(pos("raspberry") > pos("green"))
|
109 |
|
110 |
# The producer with 12 oz jars is next to the one who has 6 oz jars.
|
111 |
-
s.add(Abs(pos("12 oz") - pos("6 oz")) == 1)
|
112 |
|
113 |
# Isabella is next to the producer wearing a Black shirt.
|
114 |
-
s.add(Abs(pos("Isabella") - pos("black")) == 1)
|
115 |
|
116 |
# The producer with 6 oz jars is next to the one whose source is the Local grocer.
|
117 |
-
s.add(Abs(pos("6 oz") - pos("local grocer")) == 1)
|
118 |
|
119 |
# The producer with 6 oz jars is in the second position.
|
120 |
-
s.add(pos("6 oz") == 2)
|
121 |
|
122 |
# Rachel's source of fruit is the Farmers' coop.
|
123 |
-
s.add(pos("Rachel") == pos("farmers' coop"))
|
124 |
|
125 |
# Barbara is next to Nicole.
|
126 |
-
s.add(Abs(pos("Barbara") - pos("Nicole")) == 1)
|
127 |
|
128 |
# The producer wearing an Orange shirt gets her fruit from the Backyard.
|
129 |
-
s.add(pos("orange") == pos("backyard"))
|
130 |
|
131 |
# The producer with 12 oz jars is in the very first position.
|
132 |
-
s.add(pos("12 oz") == 1)
|
133 |
|
134 |
# Solve the puzzle
|
135 |
if s.check() == sat:
|
|
|
50 |
NUM_POSITIONS = len(categories[0][1])
|
51 |
|
52 |
item_to_cat_and_index = {}
|
53 |
+
for cat_idx, (cat_str, item_list) in enumerate(categories):
|
54 |
for item_idx, item_str in enumerate(item_list):
|
55 |
+
item_to_cat_and_index[(cat_str, item_str)] = (cat_idx, item_idx)
|
56 |
|
57 |
Vars = []
|
58 |
for cat_idx, (cat_name, item_list) in enumerate(categories):
|
|
|
66 |
s.add(Vars[cat_idx][item_idx] >= 1, Vars[cat_idx][item_idx] <= NUM_POSITIONS)
|
67 |
s.add(Distinct(Vars[cat_idx]))
|
68 |
|
69 |
+
def pos(cat_str, item_str):
|
70 |
+
(cat_idx, item_idx) = item_to_cat_and_index[(cat_str, item_str)]
|
71 |
return Vars[cat_idx][item_idx]
|
72 |
|
73 |
# All clues here
|
74 |
# The producer whose jam comes from Wild pickings is in the fourth position.
|
75 |
+
s.add(pos("Source", "wild pickings") == 4)
|
76 |
|
77 |
# The producer selling Cherry jam is somewhere to the right of the one wearing a Green shirt.
|
78 |
+
s.add(pos("Jam", "cherry") > pos("Shirt", "green"))
|
79 |
|
80 |
# Isabella is selling Cherry jam.
|
81 |
+
s.add(pos("Name", "Isabella") == pos("Jam", "cherry"))
|
82 |
|
83 |
# The producer wearing a Pink shirt is somewhere between the producer whose jam source is the Backyard and the one selling Watermelon jam, in that order.
|
84 |
+
s.add(And(pos("Source", "backyard") < pos("Shirt", "pink"), pos("Shirt", "pink") < pos("Jam", "watermelon")))
|
85 |
|
86 |
# The producer selling 14 oz jars is somewhere to the right of the one wearing a Green shirt.
|
87 |
+
s.add(pos("Size", "14 oz") > pos("Shirt", "green"))
|
88 |
|
89 |
# The producer with 14 oz jars is next to the one selling Raspberry jam.
|
90 |
+
s.add(Abs(pos("Size", "14 oz") - pos("Jam", "raspberry")) == 1)
|
91 |
|
92 |
# The Raspberry jam producer is positioned at one of the ends.
|
93 |
+
s.add(Or(pos("Jam", "raspberry") == 1, pos("Jam", "raspberry") == NUM_POSITIONS))
|
94 |
|
95 |
# Barbara is next to the producer whose jam source is the Organic farm.
|
96 |
+
s.add(Abs(pos("Name", "Barbara") - pos("Source", "organic farm")) == 1)
|
97 |
|
98 |
# Jane is located somewhere between Nicole and Isabella, in that order.
|
99 |
+
s.add(And(pos("Name", "Nicole") < pos("Name", "Jane"), pos("Name", "Jane") < pos("Name", "Isabella")))
|
100 |
|
101 |
# The producer of Fig jam sources the fruit from an Organic farm.
|
102 |
+
s.add(pos("Jam", "fig") == pos("Source", "organic farm"))
|
103 |
|
104 |
# The producer with 10 oz jars is at one of the ends.
|
105 |
+
s.add(Or(pos("Size", "10 oz") == 1, pos("Size", "10 oz") == NUM_POSITIONS))
|
106 |
|
107 |
# The Raspberry jam producer is somewhere to the right of the one wearing a Green shirt.
|
108 |
+
s.add(pos("Jam", "raspberry") > pos("Shirt", "green"))
|
109 |
|
110 |
# The producer with 12 oz jars is next to the one who has 6 oz jars.
|
111 |
+
s.add(Abs(pos("Size", "12 oz") - pos("Size", "6 oz")) == 1)
|
112 |
|
113 |
# Isabella is next to the producer wearing a Black shirt.
|
114 |
+
s.add(Abs(pos("Name", "Isabella") - pos("Shirt", "black")) == 1)
|
115 |
|
116 |
# The producer with 6 oz jars is next to the one whose source is the Local grocer.
|
117 |
+
s.add(Abs(pos("Size", "6 oz") - pos("Source", "local grocer")) == 1)
|
118 |
|
119 |
# The producer with 6 oz jars is in the second position.
|
120 |
+
s.add(pos("Size", "6 oz") == 2)
|
121 |
|
122 |
# Rachel's source of fruit is the Farmers' coop.
|
123 |
+
s.add(pos("Name", "Rachel") == pos("Source", "farmers' coop"))
|
124 |
|
125 |
# Barbara is next to Nicole.
|
126 |
+
s.add(Abs(pos("Name", "Barbara") - pos("Name", "Nicole")) == 1)
|
127 |
|
128 |
# The producer wearing an Orange shirt gets her fruit from the Backyard.
|
129 |
+
s.add(pos("Shirt", "orange") == pos("Source", "backyard"))
|
130 |
|
131 |
# The producer with 12 oz jars is in the very first position.
|
132 |
+
s.add(pos("Size", "12 oz") == 1)
|
133 |
|
134 |
# Solve the puzzle
|
135 |
if s.check() == sat:
|
backend/Sat_cnt.txt
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Solve the puzzle
|
2 |
+
if s.check() == sat:
|
3 |
+
m = s.model()
|
4 |
+
rows = []
|
5 |
+
header = ["House"] + [cat_name for cat_name, _ in categories]
|
6 |
+
for position in range(1, NUM_POSITIONS + 1):
|
7 |
+
row = [str(position)]
|
8 |
+
for cat_idx, (cat_name, item_list) in enumerate(categories):
|
9 |
+
for item_idx, item_str in enumerate(item_list):
|
10 |
+
if m.evaluate(Vars[cat_idx][item_idx]).as_long() == position:
|
11 |
+
row.append(item_str)
|
12 |
+
break
|
13 |
+
rows.append(row)
|
14 |
+
result_dict = {"header": header, "rows": rows}
|
15 |
+
|
16 |
+
cnt = 1
|
17 |
+
block = []
|
18 |
+
for cat_idx, (cat_name, item_list) in enumerate(categories):
|
19 |
+
for i in range(NUM_POSITIONS):
|
20 |
+
block.append(Vars[cat_idx][i] != m[Vars[cat_idx][i]])
|
21 |
+
s.add(Or(block))
|
22 |
+
while s.check() == sat:
|
23 |
+
m = s.model()
|
24 |
+
cnt += 1
|
25 |
+
block = []
|
26 |
+
for cat_idx, (cat_name, item_list) in enumerate(categories):
|
27 |
+
for i in range(NUM_POSITIONS):
|
28 |
+
block.append(Vars[cat_idx][i] != m[Vars[cat_idx][i]])
|
29 |
+
s.add(Or(block))
|
30 |
+
print(f"sat:{cnt}")
|
31 |
+
else:
|
32 |
+
print(f"error")
|
backend/__pycache__/solver.cpython-313.pyc
CHANGED
Binary files a/backend/__pycache__/solver.cpython-313.pyc and b/backend/__pycache__/solver.cpython-313.pyc differ
|
|
backend/app.py
CHANGED
@@ -10,13 +10,13 @@ example_path = os.path.join(BASE_DIR, "Example.txt")
|
|
10 |
with open(example_path, "r", encoding="utf-8") as f:
|
11 |
DEFAULT_SYS_CONTENT = f.read()
|
12 |
|
13 |
-
#
|
14 |
app = Flask(__name__, static_folder='static', static_url_path='')
|
15 |
CORS(app)
|
16 |
|
17 |
@app.route('/')
|
18 |
def index():
|
19 |
-
#
|
20 |
return send_from_directory(app.static_folder, 'index.html')
|
21 |
|
22 |
@app.route("/get_puzzle", methods=["GET"])
|
|
|
10 |
with open(example_path, "r", encoding="utf-8") as f:
|
11 |
DEFAULT_SYS_CONTENT = f.read()
|
12 |
|
13 |
+
# Only initialize once
|
14 |
app = Flask(__name__, static_folder='static', static_url_path='')
|
15 |
CORS(app)
|
16 |
|
17 |
@app.route('/')
|
18 |
def index():
|
19 |
+
# Return built static index.html
|
20 |
return send_from_directory(app.static_folder, 'index.html')
|
21 |
|
22 |
@app.route("/get_puzzle", methods=["GET"])
|
backend/solver.py
CHANGED
@@ -20,8 +20,8 @@ def solve_puzzle(index, puzzle, expected_solution, sys_content):
|
|
20 |
)
|
21 |
|
22 |
messages = [
|
23 |
-
{"role": "user", "content": sys_content},
|
24 |
-
{"role": "user", "content": puzzle},
|
25 |
]
|
26 |
attempts = 0
|
27 |
current_solution = None
|
@@ -43,7 +43,7 @@ def solve_puzzle(index, puzzle, expected_solution, sys_content):
|
|
43 |
messages.append({"role": "user", "content": "Please write a complete Python code in your response. Try again."})
|
44 |
continue
|
45 |
|
46 |
-
code_to_run = code_blocks[
|
47 |
result = subprocess.run(
|
48 |
[sys.executable, "-c", code_to_run],
|
49 |
stdout=subprocess.PIPE,
|
@@ -53,6 +53,10 @@ def solve_puzzle(index, puzzle, expected_solution, sys_content):
|
|
53 |
output = result.stdout.strip()
|
54 |
# print(output)
|
55 |
|
|
|
|
|
|
|
|
|
56 |
try:
|
57 |
current_solution = json.loads(output)
|
58 |
except json.JSONDecodeError:
|
@@ -66,10 +70,117 @@ def solve_puzzle(index, puzzle, expected_solution, sys_content):
|
|
66 |
"solution": current_solution,
|
67 |
"attempts": attempts,
|
68 |
"generatedCode": code_to_run,
|
69 |
-
"modelResponse": content
|
|
|
70 |
}
|
71 |
else:
|
72 |
-
messages.append({"role": "user", "content": "The solution does not match the expected answer. Please check your categories and constraints and provide the complete code
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
|
74 |
return {
|
75 |
"index": index,
|
@@ -77,5 +188,6 @@ def solve_puzzle(index, puzzle, expected_solution, sys_content):
|
|
77 |
"solution": current_solution,
|
78 |
"attempts": attempts,
|
79 |
"generatedCode": code_to_run,
|
80 |
-
"modelResponse": content
|
|
|
81 |
}
|
|
|
20 |
)
|
21 |
|
22 |
messages = [
|
23 |
+
{"role": "user", "content": sys_content},
|
24 |
+
{"role": "user", "content": puzzle + f'\nCategories: {str(expected_solution["header"][1:])}\n'},
|
25 |
]
|
26 |
attempts = 0
|
27 |
current_solution = None
|
|
|
43 |
messages.append({"role": "user", "content": "Please write a complete Python code in your response. Try again."})
|
44 |
continue
|
45 |
|
46 |
+
code_to_run = code_blocks[-1].strip()
|
47 |
result = subprocess.run(
|
48 |
[sys.executable, "-c", code_to_run],
|
49 |
stdout=subprocess.PIPE,
|
|
|
53 |
output = result.stdout.strip()
|
54 |
# print(output)
|
55 |
|
56 |
+
if result.stderr.strip():
|
57 |
+
messages.append({"role": "user", "content": f"Your code has errors: {result.stderr.strip()}. Please check your code and provide the complete code in the end."})
|
58 |
+
continue
|
59 |
+
|
60 |
try:
|
61 |
current_solution = json.loads(output)
|
62 |
except json.JSONDecodeError:
|
|
|
70 |
"solution": current_solution,
|
71 |
"attempts": attempts,
|
72 |
"generatedCode": code_to_run,
|
73 |
+
"modelResponse": content,
|
74 |
+
"problematicConstraints": None
|
75 |
}
|
76 |
else:
|
77 |
+
messages.append({"role": "user", "content": "The solution does not match the expected answer. Please check your categories and step by step reason your constraints and provide the complete code in the end."})
|
78 |
+
|
79 |
+
if result.stderr.strip():
|
80 |
+
return {
|
81 |
+
"index": index,
|
82 |
+
"success": False,
|
83 |
+
"solution": current_solution,
|
84 |
+
"attempts": attempts,
|
85 |
+
"generatedCode": code_to_run,
|
86 |
+
"modelResponse": content,
|
87 |
+
"problematicConstraints": "Code error:\n" + result.stderr.strip()
|
88 |
+
}
|
89 |
+
|
90 |
+
# if 'rows' not in current_solution.keys():
|
91 |
+
# return {
|
92 |
+
# "index": index,
|
93 |
+
# "success": False,
|
94 |
+
# "solution": current_solution,
|
95 |
+
# "attempts": attempts,
|
96 |
+
# "generatedCode": code_to_run,
|
97 |
+
# "modelResponse": content,
|
98 |
+
# "problematicConstraints": str(current_solution)
|
99 |
+
# }
|
100 |
+
|
101 |
+
code_by_line = code_to_run.split("\n")
|
102 |
+
|
103 |
+
fisrt_cons_line = None
|
104 |
+
last_cons_line = None
|
105 |
+
for i, line in enumerate(code_by_line):
|
106 |
+
if "s.add" in line and "pos" in line:
|
107 |
+
if not fisrt_cons_line:
|
108 |
+
fisrt_cons_line = i
|
109 |
+
last_cons_line = i
|
110 |
+
|
111 |
+
experiment_code_line = code_by_line[:fisrt_cons_line]
|
112 |
+
categories = expected_solution['header']
|
113 |
+
for i, houses in enumerate(expected_solution['rows']):
|
114 |
+
for j in range(1, len(houses)):
|
115 |
+
experiment_code_line.append(f"s.add(pos(\"{categories[j]}\", \"{houses[j]}\") == {i+1})")
|
116 |
+
experiment_code_line.append("")
|
117 |
+
experiment_code_line.append("print(s.check())")
|
118 |
+
|
119 |
+
def satisfied(constraint):
|
120 |
+
experiment_code_line[-2] = constraint
|
121 |
+
experiment_code = "\n".join(experiment_code_line)
|
122 |
+
sat_checker = experiment_code.strip()
|
123 |
+
result = subprocess.run(
|
124 |
+
[sys.executable, "-c", sat_checker],
|
125 |
+
stdout=subprocess.PIPE,
|
126 |
+
stderr=subprocess.PIPE,
|
127 |
+
text=True
|
128 |
+
)
|
129 |
+
output = result.stdout.strip()
|
130 |
+
return output.lower()
|
131 |
+
|
132 |
+
if "error" in current_solution.keys():
|
133 |
+
if "Multiple" in current_solution['error']:
|
134 |
+
problematic_constraints = "Issue: Multiple solutions\n"
|
135 |
+
else:
|
136 |
+
problematic_constraints = "Issue: No solution\n"
|
137 |
+
else:
|
138 |
+
problematic_constraints = "Issue: Wrong answer\n"
|
139 |
+
|
140 |
+
cnt_cons = 0
|
141 |
+
problematic_constraints += "\nSatisfaction judge:\n"
|
142 |
+
for i, line in enumerate(code_by_line):
|
143 |
+
if "s.add" in line and "pos" in line:
|
144 |
+
constraint = line.strip()
|
145 |
+
cnt_cons += 1
|
146 |
+
if satisfied(constraint) == "unsat":
|
147 |
+
problematic_constraints += f"In line {i + 1}, the {cnt_cons}-th constraint: {constraint}. Not satisfied.\n"
|
148 |
+
|
149 |
+
with open("Sat_cnt.txt", "r") as f:
|
150 |
+
sat_cnt = f.read()
|
151 |
+
cnt_cons = 0
|
152 |
+
problematic_constraints += "\nMultiple solutions judge:\n"
|
153 |
+
code_by_line_experiment = code_to_run.split("\n")[:last_cons_line + 1]
|
154 |
+
code_by_line_experiment.append("\n")
|
155 |
+
def run_result():
|
156 |
+
experiment_code = "\n".join(code_by_line_experiment) + sat_cnt
|
157 |
+
sat_checker = experiment_code.strip()
|
158 |
+
result = subprocess.run(
|
159 |
+
[sys.executable, "-c", sat_checker],
|
160 |
+
stdout=subprocess.PIPE,
|
161 |
+
stderr=subprocess.PIPE,
|
162 |
+
text=True
|
163 |
+
)
|
164 |
+
return result.stdout.strip()
|
165 |
+
res = run_result()
|
166 |
+
# print("\n".join(code_by_line_experiment) + sat_cnt)
|
167 |
+
# print(res)
|
168 |
+
if not res or res == "error":
|
169 |
+
problematic_constraints += "Unable to judge."
|
170 |
+
else:
|
171 |
+
cur = int(res.split(':')[1])
|
172 |
+
for i, line in enumerate(code_by_line):
|
173 |
+
if "s.add" in line and "pos" in line:
|
174 |
+
cnt_cons += 1
|
175 |
+
code_by_line_experiment[i] = ""
|
176 |
+
res = run_result()
|
177 |
+
if not res or res == "error":
|
178 |
+
problematic_constraints += "Unable to judge."
|
179 |
+
break
|
180 |
+
now_cnt = int(res.split(':')[1])
|
181 |
+
if now_cnt == cur:
|
182 |
+
problematic_constraints += f"In line {i + 1}, the {cnt_cons}-th constraint: {line}. A suspect redundency.\n"
|
183 |
+
code_by_line_experiment[i] = line
|
184 |
|
185 |
return {
|
186 |
"index": index,
|
|
|
188 |
"solution": current_solution,
|
189 |
"attempts": attempts,
|
190 |
"generatedCode": code_to_run,
|
191 |
+
"modelResponse": content,
|
192 |
+
"problematicConstraints": problematic_constraints
|
193 |
}
|
backend/static/asset-manifest.json
CHANGED
@@ -1,15 +1,15 @@
|
|
1 |
{
|
2 |
"files": {
|
3 |
"main.css": "/static/css/main.e6c13ad2.css",
|
4 |
-
"main.js": "/static/js/main.
|
5 |
"static/js/453.8ab44547.chunk.js": "/static/js/453.8ab44547.chunk.js",
|
6 |
"index.html": "/index.html",
|
7 |
"main.e6c13ad2.css.map": "/static/css/main.e6c13ad2.css.map",
|
8 |
-
"main.
|
9 |
"453.8ab44547.chunk.js.map": "/static/js/453.8ab44547.chunk.js.map"
|
10 |
},
|
11 |
"entrypoints": [
|
12 |
"static/css/main.e6c13ad2.css",
|
13 |
-
"static/js/main.
|
14 |
]
|
15 |
}
|
|
|
1 |
{
|
2 |
"files": {
|
3 |
"main.css": "/static/css/main.e6c13ad2.css",
|
4 |
+
"main.js": "/static/js/main.120fc3ad.js",
|
5 |
"static/js/453.8ab44547.chunk.js": "/static/js/453.8ab44547.chunk.js",
|
6 |
"index.html": "/index.html",
|
7 |
"main.e6c13ad2.css.map": "/static/css/main.e6c13ad2.css.map",
|
8 |
+
"main.120fc3ad.js.map": "/static/js/main.120fc3ad.js.map",
|
9 |
"453.8ab44547.chunk.js.map": "/static/js/453.8ab44547.chunk.js.map"
|
10 |
},
|
11 |
"entrypoints": [
|
12 |
"static/css/main.e6c13ad2.css",
|
13 |
+
"static/js/main.120fc3ad.js"
|
14 |
]
|
15 |
}
|
backend/static/index.html
CHANGED
@@ -1 +1 @@
|
|
1 |
-
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>React App</title><script defer="defer" src="/static/js/main.
|
|
|
1 |
+
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>React App</title><script defer="defer" src="/static/js/main.120fc3ad.js"></script><link href="/static/css/main.e6c13ad2.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
backend/static/static/js/{main.f13a7657.js → main.120fc3ad.js}
RENAMED
The diff for this file is too large to render.
See raw diff
|
|
backend/static/static/js/{main.f13a7657.js.LICENSE.txt → main.120fc3ad.js.LICENSE.txt}
RENAMED
File without changes
|
backend/static/static/js/{main.f13a7657.js.map → main.120fc3ad.js.map}
RENAMED
The diff for this file is too large to render.
See raw diff
|
|
frontend/src/App.js
CHANGED
@@ -2,23 +2,26 @@
|
|
2 |
import React, { useState, useEffect } from 'react';
|
3 |
|
4 |
function App() {
|
5 |
-
//
|
6 |
const [puzzleIndex, setPuzzleIndex] = useState(0);
|
7 |
const [puzzleText, setPuzzleText] = useState("");
|
8 |
const [expectedSolution, setExpectedSolution] = useState(null);
|
9 |
|
10 |
-
// sysContent
|
11 |
const [sysContent, setSysContent] = useState("");
|
12 |
|
13 |
-
//
|
14 |
-
const [modelResponse, setModelResponse] = useState("");
|
15 |
const [generatedCode, setGeneratedCode] = useState("");
|
16 |
const [executionSuccess, setExecutionSuccess] = useState(null);
|
17 |
const [attempts, setAttempts] = useState(0);
|
|
|
|
|
18 |
|
19 |
-
|
|
|
|
|
20 |
useEffect(() => {
|
21 |
-
fetch(
|
22 |
.then(res => res.json())
|
23 |
.then(data => {
|
24 |
if(data.success) {
|
@@ -28,16 +31,16 @@ function App() {
|
|
28 |
.catch(e => console.error(e));
|
29 |
}, []);
|
30 |
|
31 |
-
//
|
32 |
useEffect(() => {
|
33 |
-
fetch(
|
34 |
.then(res => res.json())
|
35 |
.then(data => {
|
36 |
if(data.success) {
|
37 |
setPuzzleText(data.puzzle);
|
38 |
setExpectedSolution(data.expected_solution);
|
39 |
} else {
|
40 |
-
console.error("
|
41 |
setPuzzleText("");
|
42 |
setExpectedSolution(null);
|
43 |
}
|
@@ -47,17 +50,20 @@ function App() {
|
|
47 |
|
48 |
const handleSolve = () => {
|
49 |
if(!puzzleText || !expectedSolution) {
|
50 |
-
alert("puzzle
|
51 |
return;
|
52 |
}
|
53 |
const payload = {
|
54 |
index: puzzleIndex,
|
55 |
puzzle: puzzleText,
|
56 |
expected_solution: expectedSolution,
|
57 |
-
sys_content: sysContent
|
|
|
58 |
};
|
59 |
|
60 |
-
|
|
|
|
|
61 |
method: "POST",
|
62 |
headers: { "Content-Type": "application/json" },
|
63 |
body: JSON.stringify(payload)
|
@@ -65,16 +71,19 @@ function App() {
|
|
65 |
.then(res => res.json())
|
66 |
.then(data => {
|
67 |
if(!data.success) {
|
68 |
-
alert("
|
69 |
return;
|
70 |
}
|
71 |
const result = data.result;
|
72 |
-
setModelResponse(result.modelResponse || "");
|
73 |
setGeneratedCode(result.generatedCode || "");
|
74 |
setExecutionSuccess(result.success);
|
75 |
setAttempts(result.attempts || 0);
|
|
|
76 |
})
|
77 |
-
.catch(e => console.error(e))
|
|
|
|
|
|
|
78 |
};
|
79 |
|
80 |
return (
|
@@ -82,7 +91,7 @@ function App() {
|
|
82 |
<h1>Zebra Puzzle Demo</h1>
|
83 |
|
84 |
<div style={{ marginBottom: 20 }}>
|
85 |
-
<label
|
86 |
<input
|
87 |
type="number"
|
88 |
value={puzzleIndex}
|
@@ -101,7 +110,7 @@ function App() {
|
|
101 |
</div>
|
102 |
|
103 |
<div style={{ marginBottom: 20 }}>
|
104 |
-
<h3>sys_content
|
105 |
<textarea
|
106 |
rows={10}
|
107 |
cols={80}
|
@@ -111,17 +120,17 @@ function App() {
|
|
111 |
</div>
|
112 |
|
113 |
<div style={{ marginBottom: 20 }}>
|
114 |
-
<button onClick={handleSolve}>Solve Puzzle with LLM</button>
|
115 |
</div>
|
116 |
|
117 |
<div>
|
118 |
<h2>Result</h2>
|
119 |
<p>Success: {executionSuccess === null ? "N/A" : executionSuccess ? "✅" : "❌"}</p>
|
120 |
<p>Attempts: {attempts}</p>
|
|
|
|
|
121 |
<h3>Generated Code</h3>
|
122 |
<pre>{generatedCode}</pre>
|
123 |
-
<h3>Model Response</h3>
|
124 |
-
<pre>{modelResponse}</pre>
|
125 |
</div>
|
126 |
</div>
|
127 |
);
|
|
|
2 |
import React, { useState, useEffect } from 'react';
|
3 |
|
4 |
function App() {
|
5 |
+
// For puzzle index and puzzle data
|
6 |
const [puzzleIndex, setPuzzleIndex] = useState(0);
|
7 |
const [puzzleText, setPuzzleText] = useState("");
|
8 |
const [expectedSolution, setExpectedSolution] = useState(null);
|
9 |
|
10 |
+
// sysContent can be editted, default using Example.txt
|
11 |
const [sysContent, setSysContent] = useState("");
|
12 |
|
13 |
+
// Interaction results
|
|
|
14 |
const [generatedCode, setGeneratedCode] = useState("");
|
15 |
const [executionSuccess, setExecutionSuccess] = useState(null);
|
16 |
const [attempts, setAttempts] = useState(0);
|
17 |
+
const [isSolving, setIsSolving] = useState(false);
|
18 |
+
const [problematicConstraints, setProblematicConstraints] = useState("");
|
19 |
|
20 |
+
const FLASK_BASE_URL = 'http://localhost:7860';
|
21 |
+
|
22 |
+
// Frontend fetch sysContent in default
|
23 |
useEffect(() => {
|
24 |
+
fetch(`${FLASK_BASE_URL}/default_sys_content`)
|
25 |
.then(res => res.json())
|
26 |
.then(data => {
|
27 |
if(data.success) {
|
|
|
31 |
.catch(e => console.error(e));
|
32 |
}, []);
|
33 |
|
34 |
+
// When puzzleIndex changing,auto get puzzle
|
35 |
useEffect(() => {
|
36 |
+
fetch(`${FLASK_BASE_URL}/get_puzzle?index=${puzzleIndex}`)
|
37 |
.then(res => res.json())
|
38 |
.then(data => {
|
39 |
if(data.success) {
|
40 |
setPuzzleText(data.puzzle);
|
41 |
setExpectedSolution(data.expected_solution);
|
42 |
} else {
|
43 |
+
console.error("Failed to fetch puzzle", data.error);
|
44 |
setPuzzleText("");
|
45 |
setExpectedSolution(null);
|
46 |
}
|
|
|
50 |
|
51 |
const handleSolve = () => {
|
52 |
if(!puzzleText || !expectedSolution) {
|
53 |
+
alert("puzzle or expectedSolution incomplete");
|
54 |
return;
|
55 |
}
|
56 |
const payload = {
|
57 |
index: puzzleIndex,
|
58 |
puzzle: puzzleText,
|
59 |
expected_solution: expectedSolution,
|
60 |
+
sys_content: sysContent,
|
61 |
+
problematic_constraints: problematicConstraints
|
62 |
};
|
63 |
|
64 |
+
setIsSolving(true);
|
65 |
+
|
66 |
+
fetch(`${FLASK_BASE_URL}/solve`, {
|
67 |
method: "POST",
|
68 |
headers: { "Content-Type": "application/json" },
|
69 |
body: JSON.stringify(payload)
|
|
|
71 |
.then(res => res.json())
|
72 |
.then(data => {
|
73 |
if(!data.success) {
|
74 |
+
alert("Backend error: " + data.error);
|
75 |
return;
|
76 |
}
|
77 |
const result = data.result;
|
|
|
78 |
setGeneratedCode(result.generatedCode || "");
|
79 |
setExecutionSuccess(result.success);
|
80 |
setAttempts(result.attempts || 0);
|
81 |
+
setProblematicConstraints(result.problematicConstraints || "");
|
82 |
})
|
83 |
+
.catch(e => console.error(e))
|
84 |
+
.finally(() => {
|
85 |
+
setIsSolving(false);
|
86 |
+
});
|
87 |
};
|
88 |
|
89 |
return (
|
|
|
91 |
<h1>Zebra Puzzle Demo</h1>
|
92 |
|
93 |
<div style={{ marginBottom: 20 }}>
|
94 |
+
<label>Choose puzzle index (0 - 999): </label>
|
95 |
<input
|
96 |
type="number"
|
97 |
value={puzzleIndex}
|
|
|
110 |
</div>
|
111 |
|
112 |
<div style={{ marginBottom: 20 }}>
|
113 |
+
<h3>sys_content</h3>
|
114 |
<textarea
|
115 |
rows={10}
|
116 |
cols={80}
|
|
|
120 |
</div>
|
121 |
|
122 |
<div style={{ marginBottom: 20 }}>
|
123 |
+
<button onClick={handleSolve} disabled={isSolving}>Solve Puzzle with LLM</button>
|
124 |
</div>
|
125 |
|
126 |
<div>
|
127 |
<h2>Result</h2>
|
128 |
<p>Success: {executionSuccess === null ? "N/A" : executionSuccess ? "✅" : "❌"}</p>
|
129 |
<p>Attempts: {attempts}</p>
|
130 |
+
<h3>Problematic Constraints</h3>
|
131 |
+
<pre>{problematicConstraints}</pre>
|
132 |
<h3>Generated Code</h3>
|
133 |
<pre>{generatedCode}</pre>
|
|
|
|
|
134 |
</div>
|
135 |
</div>
|
136 |
);
|
test.py
ADDED
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from z3 import *
|
2 |
+
import json
|
3 |
+
import sys
|
4 |
+
|
5 |
+
# Define all categories in a single list of tuples:
|
6 |
+
# (House is implicit; each row's first column will be the house number.)
|
7 |
+
categories = [
|
8 |
+
("Name", ["Bob", "Arnold", "Carol", "Alice", "Peter", "Eric"]),
|
9 |
+
("Mother", ["Sarah", "Janelle", "Aniya", "Kailyn", "Holly", "Penny"]),
|
10 |
+
("Children", ["Fred", "Samantha", "Bella", "Meredith", "Alice", "Timothy"]),
|
11 |
+
("Vacation", ["city", "mountain", "camping", "beach", "cruise", "cultural"]),
|
12 |
+
("BookGenre", ["romance", "mystery", "historical fiction", "science fiction", "biography", "fantasy"])
|
13 |
+
]
|
14 |
+
|
15 |
+
# No need to change here, automatically processing
|
16 |
+
NUM_POSITIONS = len(categories[0][1])
|
17 |
+
|
18 |
+
item_to_cat_and_index = {}
|
19 |
+
for cat_idx, (cat_str, item_list) in enumerate(categories):
|
20 |
+
for item_idx, item_str in enumerate(item_list):
|
21 |
+
item_to_cat_and_index[(cat_str, item_str)] = (cat_idx, item_idx)
|
22 |
+
|
23 |
+
Vars = []
|
24 |
+
for cat_idx, (cat_name, item_list) in enumerate(categories):
|
25 |
+
var = IntVector(cat_name, len(item_list))
|
26 |
+
Vars.append(var)
|
27 |
+
|
28 |
+
s = Solver()
|
29 |
+
|
30 |
+
for cat_idx, (cat_name, item_list) in enumerate(categories):
|
31 |
+
for item_idx, item_str in enumerate(item_list):
|
32 |
+
s.add(Vars[cat_idx][item_idx] >= 1, Vars[cat_idx][item_idx] <= NUM_POSITIONS)
|
33 |
+
s.add(Distinct(Vars[cat_idx]))
|
34 |
+
|
35 |
+
def pos(cat_str, item_str):
|
36 |
+
(cat_idx, item_idx) = item_to_cat_and_index[(cat_str, item_str)]
|
37 |
+
return Vars[cat_idx][item_idx]
|
38 |
+
|
39 |
+
# All clues here
|
40 |
+
# 1. The person who loves beach vacations is not in the second house.
|
41 |
+
s.add(pos("Vacation", "beach") != 2)
|
42 |
+
|
43 |
+
# 2. The person who loves fantasy books is somewhere to the left of Peter.
|
44 |
+
s.add(pos("BookGenre", "fantasy") < pos("Name", "Peter"))
|
45 |
+
|
46 |
+
# 3. The person whose mother's name is Sarah is the person who prefers city breaks.
|
47 |
+
s.add(pos("Mother", "Sarah") == pos("Vacation", "city"))
|
48 |
+
|
49 |
+
# 4. The person who enjoys camping trips is somewhere to the right of Peter.
|
50 |
+
s.add(pos("Vacation", "camping") > pos("Name", "Peter"))
|
51 |
+
|
52 |
+
# 5. The person who likes going on cruises is the person's child is named Meredith.
|
53 |
+
s.add(pos("Vacation", "cruise") == pos("Children", "Meredith"))
|
54 |
+
|
55 |
+
# 6. There is one house between the person who is the mother of Timothy and Eric.
|
56 |
+
s.add(Abs(pos("Mother", "Timothy") - pos("Name", "Eric")) == 2)
|
57 |
+
|
58 |
+
# 7. The person whose mother's name is Janelle is not in the second house.
|
59 |
+
s.add(pos("Mother", "Janelle") != 2)
|
60 |
+
|
61 |
+
# 8. The person's child is named Fred is somewhere to the left of Eric.
|
62 |
+
s.add(pos("Children", "Fred") < pos("Name", "Eric"))
|
63 |
+
|
64 |
+
# 9. The person who goes on cultural tours is in the fourth house.
|
65 |
+
s.add(pos("Vacation", "cultural") == 4)
|
66 |
+
|
67 |
+
# 10. The person whose mother's name is Janelle is not in the first house.
|
68 |
+
s.add(pos("Mother", "Janelle") != 1)
|
69 |
+
|
70 |
+
# 11. The person whose mother's name is Holly is somewhere to the right of the person who loves historical fiction books.
|
71 |
+
s.add(pos("Mother", "Holly") > pos("BookGenre", "historical fiction"))
|
72 |
+
|
73 |
+
# 12. The person's child is named Bella is somewhere to the left of Alice.
|
74 |
+
s.add(pos("Children", "Bella") < pos("Name", "Alice"))
|
75 |
+
|
76 |
+
# 13. Arnold is somewhere to the right of the person who loves fantasy books.
|
77 |
+
s.add(pos("Name", "Arnold") > pos("BookGenre", "fantasy"))
|
78 |
+
|
79 |
+
# 14. The person who loves mystery books is in the fourth house.
|
80 |
+
s.add(pos("BookGenre", "mystery") == 4)
|
81 |
+
|
82 |
+
# 15. The person's child is named Alice is the person who enjoys camping trips.
|
83 |
+
s.add(pos("Children", "Alice") == pos("Vacation", "camping"))
|
84 |
+
|
85 |
+
# 16. The person whose mother's name is Kailyn is the person who likes going on cruises.
|
86 |
+
s.add(pos("Mother", "Kailyn") == pos("Vacation", "cruise"))
|
87 |
+
|
88 |
+
# 17. There are two houses between the person who loves fantasy books and The person whose mother's name is Aniya.
|
89 |
+
s.add(Abs(pos("BookGenre", "fantasy") - pos("Mother", "Aniya")) == 3)
|
90 |
+
|
91 |
+
# 18. The person who loves fantasy books is Carol.
|
92 |
+
s.add(pos("BookGenre", "fantasy") == pos("Name", "Carol"))
|
93 |
+
|
94 |
+
# 19. The person who likes going on cruises is the person who loves biography books.
|
95 |
+
s.add(pos("Vacation", "cruise") == pos("BookGenre", "biography"))
|
96 |
+
|
97 |
+
# 20. The person who loves fantasy books is in the third house.
|
98 |
+
s.add(pos("BookGenre", "fantasy") == 3)
|
99 |
+
|
100 |
+
# 21. The person whose mother's name is Aniya is the person who loves romance books.
|
101 |
+
s.add(pos("Mother", "Aniya") == pos("BookGenre", "romance"))
|
102 |
+
|
103 |
+
# 22. The person whose mother's name is Janelle is not in the fourth house.
|
104 |
+
s.add(pos("Mother", "Janelle") != 4)
|
105 |
+
|
106 |
+
# 23. The person's child is named Fred is not in the fourth house.
|
107 |
+
s.add(pos("Children", "Fred") != 4)
|
108 |
+
|
109 |
+
# 24. The person who loves biography books is not in the second house.
|
110 |
+
s.add(pos("BookGenre", "biography") != 2)
|
111 |
+
|
112 |
+
# 25. There are two houses between The person whose mother's name is Holly and Eric.
|
113 |
+
s.add(Abs(pos("Mother", "Holly") - pos("Name", "Eric")) == 3)
|
114 |
+
|
115 |
+
# Solve the puzzle
|
116 |
+
if s.check() == sat:
|
117 |
+
m = s.model()
|
118 |
+
rows = []
|
119 |
+
header = ["House"] + [cat_name for cat_name, _ in categories]
|
120 |
+
for position in range(1, NUM_POSITIONS + 1):
|
121 |
+
row = [str(position)]
|
122 |
+
for cat_idx, (cat_name, item_list) in enumerate(categories):
|
123 |
+
for item_idx, item_str in enumerate(item_list):
|
124 |
+
if m.evaluate(Vars[cat_idx][item_idx]).as_long() == position:
|
125 |
+
row.append(item_str)
|
126 |
+
break
|
127 |
+
rows.append(row)
|
128 |
+
result_dict = {"header": header, "rows": rows}
|
129 |
+
|
130 |
+
cnt = 1
|
131 |
+
block = []
|
132 |
+
for cat_idx, (cat_name, item_list) in enumerate(categories):
|
133 |
+
for i in range(NUM_POSITIONS):
|
134 |
+
block.append(Vars[cat_idx][i] != m[Vars[cat_idx][i]])
|
135 |
+
s.add(Or(block))
|
136 |
+
while s.check() == sat:
|
137 |
+
m = s.model()
|
138 |
+
cnt += 1
|
139 |
+
block = []
|
140 |
+
for cat_idx, (cat_name, item_list) in enumerate(categories):
|
141 |
+
for i in range(NUM_POSITIONS):
|
142 |
+
block.append(Vars[cat_idx][i] != m[Vars[cat_idx][i]])
|
143 |
+
s.add(Or(block))
|
144 |
+
print(f"sat:{cnt}")
|
145 |
+
else:
|
146 |
+
print(f"error")
|