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")
|