Laborator commited on
Commit
d2d85c7
·
1 Parent(s): 8db2fd5

Initial QVerify Space - verifier-only CPU Basic

Browse files
Files changed (5) hide show
  1. .gitignore +5 -0
  2. Dockerfile +22 -0
  3. README.md +26 -9
  4. app.py +318 -0
  5. requirements.txt +9 -0
.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .pytest_cache/
5
+ .cache/
Dockerfile ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install verifier-only Python deps. requirements.txt deliberately omits
6
+ # torch / transformers / outlines because the Space does not load Gemma.
7
+ COPY requirements.txt .
8
+ RUN pip install --no-cache-dir -r requirements.txt
9
+
10
+ # Install qverify itself without its declared dependencies (those include
11
+ # torch / transformers, which the verifier-only Space does not need).
12
+ RUN pip install --no-cache-dir --no-deps \
13
+ "qverify @ git+https://github.com/Quantum-Labor/qverify.git@main"
14
+
15
+ COPY app.py .
16
+
17
+ # HuggingFace Spaces expect the app to listen on 0.0.0.0:7860.
18
+ ENV GRADIO_SERVER_NAME=0.0.0.0 \
19
+ GRADIO_SERVER_PORT=7860
20
+ EXPOSE 7860
21
+
22
+ CMD ["python", "app.py"]
README.md CHANGED
@@ -1,14 +1,31 @@
1
  ---
2
- title: Qverify
3
- emoji: 🐢
4
- colorFrom: green
5
- colorTo: purple
6
- sdk: gradio
7
- sdk_version: 6.13.0
8
- app_file: app.py
9
  pinned: false
10
  license: apache-2.0
11
- short_description: Quantum-assisted verification of LLM reasoning
 
 
 
 
 
 
 
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: QVerify
3
+ emoji: 🔬
4
+ colorFrom: blue
5
+ colorTo: orange
6
+ sdk: docker
7
+ app_port: 7860
 
8
  pinned: false
9
  license: apache-2.0
10
+ hardware: cpu-basic
11
+ tags:
12
+ - quantum-computing
13
+ - qiskit
14
+ - pennylane
15
+ - llm-verification
16
+ - logic
17
+ - grover
18
  ---
19
 
20
+ # QVerify (verifier-only Space)
21
+
22
+ This Space exposes the QVerify verifier component: a CNF is fed
23
+ straight to Grover's search, on either a CPU-side PennyLane simulator
24
+ or IBM Quantum's Heron r2 processor.
25
+
26
+ The translator (Gemma 4 E4B with grammar-constrained generation)
27
+ requires a GPU and is not loaded here. The full pipeline (translator
28
+ plus grounding plus verifier) runs locally from the GitHub repo.
29
+
30
+ [Code](https://github.com/Quantum-Labor/qverify) ·
31
+ [Documentation](https://github.com/Quantum-Labor/qverify/tree/main/docs)
app.py ADDED
@@ -0,0 +1,318 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """QVerify HuggingFace Space - verifier-only demo, runs on CPU Basic.
2
+
3
+ This Space exposes the QVerify *verifier* component: a CNF is fed
4
+ straight to Grover's search, on either the PennyLane simulator (local
5
+ to this Space's CPU container) or IBM Heron r2 hardware (delegated to
6
+ IBM Cloud over the network, account credentials read from Space
7
+ Secrets).
8
+
9
+ The *translator* component (Gemma 4 E4B with grammar-constrained
10
+ generation) is intentionally not loaded here: it needs a GPU, which
11
+ CPU Basic does not provide. The full pipeline runs locally from the
12
+ GitHub repository, see https://github.com/Quantum-Labor/qverify.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import os
18
+ import time
19
+ from dataclasses import dataclass
20
+ from typing import Any
21
+
22
+ import gradio as gr
23
+
24
+ from qverify.translator.cnf import CNF, Clause, Literal
25
+ from qverify.verifier import verify
26
+ from qverify.verifier._universe import Universe
27
+ from qverify.verifier.backends import IBMQuantumBackend, PennyLaneBackend
28
+ from qverify.verifier.grounding import ground_cnf
29
+
30
+ IBM_CONSOLE_BASE = "https://quantum.cloud.ibm.com/workloads?search="
31
+
32
+
33
+ @dataclass(frozen=True)
34
+ class CnfExample:
35
+ """One ready-to-run CNF example surfaced in the Space's dropdown."""
36
+
37
+ label: str
38
+ description: str
39
+ cnf: CNF
40
+ universe: Universe
41
+
42
+
43
+ def _atom(predicate: str, *args: str, neg: bool = False) -> Literal:
44
+ return Literal(predicate=predicate, args=args, negated=neg)
45
+
46
+
47
+ def _clause(*lits: Literal) -> Clause:
48
+ return Clause(literals=lits)
49
+
50
+
51
+ def _build_examples() -> dict[str, CnfExample]:
52
+ """Three pre-built CNFs covering propositional, ground first-order, and grounded universal."""
53
+ cat_fur_consistency = CnfExample(
54
+ label="Cat and HasFur consistency",
55
+ description=(
56
+ "Two propositional atoms, both asserted: Cat AND HasFur. The "
57
+ "verifier should report contradiction_found=False (the formula "
58
+ "is consistent: setting both to True satisfies it)."
59
+ ),
60
+ cnf=CNF(
61
+ clauses=(
62
+ _clause(_atom("Cat")),
63
+ _clause(_atom("HasFur")),
64
+ )
65
+ ),
66
+ universe=Universe(constants=()),
67
+ )
68
+
69
+ tom_is_a_cat = CnfExample(
70
+ label="Tom is a cat",
71
+ description=(
72
+ "A single ground first-order atom: Cat(Tom). Universe = {Tom}. "
73
+ "Trivially satisfiable; consistency mode reports no contradiction."
74
+ ),
75
+ cnf=CNF(clauses=(_clause(_atom("Cat", "Tom")),)),
76
+ universe=Universe(constants=("Tom",)),
77
+ )
78
+
79
+ universal_rule = CnfExample(
80
+ label="Universal cat-fur rule",
81
+ description=(
82
+ "First-order rule 'forall x. Cat(x) -> HasFur(x)' grounded over "
83
+ "{Tom, Whiskers}. The grounded CNF has two clauses, one per "
84
+ "constant. Consistent: any assignment that makes both Cat(Tom) "
85
+ "and Cat(Whiskers) false satisfies the formula."
86
+ ),
87
+ cnf=CNF(
88
+ clauses=(
89
+ _clause(
90
+ _atom("Cat", "x", neg=True),
91
+ _atom("HasFur", "x"),
92
+ ),
93
+ )
94
+ ),
95
+ universe=Universe(constants=("Tom", "Whiskers")),
96
+ )
97
+
98
+ return {
99
+ cat_fur_consistency.label: cat_fur_consistency,
100
+ tom_is_a_cat.label: tom_is_a_cat,
101
+ universal_rule.label: universal_rule,
102
+ }
103
+
104
+
105
+ EXAMPLES = _build_examples()
106
+ EXAMPLE_LABELS = list(EXAMPLES.keys())
107
+ DEFAULT_LABEL = EXAMPLE_LABELS[0]
108
+
109
+ IBM_TOKEN_PRESENT = bool(os.environ.get("IBM_QUANTUM_TOKEN"))
110
+ IBM_INSTANCE_PRESENT = bool(os.environ.get("IBM_QUANTUM_INSTANCE"))
111
+ IBM_AVAILABLE = IBM_TOKEN_PRESENT and IBM_INSTANCE_PRESENT
112
+
113
+
114
+ def _format_cnf(cnf: CNF) -> str:
115
+ """Render a CNF as a readable multi-line string."""
116
+ if not cnf.clauses:
117
+ return "(empty CNF, trivially satisfied)"
118
+ lines = []
119
+ for c in cnf.clauses:
120
+ lits = []
121
+ for lit in c.literals:
122
+ args = f"({', '.join(lit.args)})" if lit.args else ""
123
+ lits.append(f"{'¬' if lit.negated else ''}{lit.predicate}{args}")
124
+ lines.append("(" + " ∨ ".join(lits) + ")")
125
+ return "\n".join(lines)
126
+
127
+
128
+ def _on_example_change(label: str) -> tuple[str, str, str]:
129
+ """Update the CNF preview, universe preview, and description on dropdown change."""
130
+ ex = EXAMPLES[label]
131
+ universe_str = ", ".join(ex.universe.constants) if ex.universe.constants else "(empty)"
132
+ return _format_cnf(ex.cnf), universe_str, ex.description
133
+
134
+
135
+ def _run_verify(label: str, mode: str, backend_kind: str) -> dict[str, Any]:
136
+ """Ground the example CNF, run Grover's search, and return a JSON-friendly result."""
137
+ if label not in EXAMPLES:
138
+ return {"error": f"unknown example: {label}"}
139
+
140
+ ex = EXAMPLES[label]
141
+ grounded = ground_cnf(ex.cnf, ex.universe)
142
+
143
+ if backend_kind == "pennylane":
144
+ backend = PennyLaneBackend()
145
+ elif backend_kind == "ibm":
146
+ if not IBM_AVAILABLE:
147
+ return {
148
+ "error": (
149
+ "IBM_QUANTUM_TOKEN and/or IBM_QUANTUM_INSTANCE are not "
150
+ "set in this Space's Secrets. The IBM hardware path is "
151
+ "unavailable; use the simulator button."
152
+ )
153
+ }
154
+ backend = IBMQuantumBackend()
155
+ else:
156
+ return {"error": f"unknown backend kind: {backend_kind}"}
157
+
158
+ start = time.monotonic()
159
+ result = verify(grounded, backend=backend, mode=mode)
160
+ wall = round(time.monotonic() - start, 1)
161
+
162
+ counter_model: Any
163
+ if result.counter_model is None:
164
+ if mode == "consistency":
165
+ counter_model = (
166
+ "(consistency mode: no satisfying assignment is surfaced; "
167
+ "this is expected when the formula is consistent because "
168
+ "the verifier does not display a model in this mode, and "
169
+ "when inconsistent because UNSAT means there is none)"
170
+ )
171
+ else:
172
+ counter_model = "no counter-model found (formula is entailed)"
173
+ else:
174
+ counter_model = result.counter_model.assignment
175
+
176
+ out: dict[str, Any] = {
177
+ "example": label,
178
+ "mode": mode,
179
+ "backend": result.backend_name,
180
+ "contradiction_found": result.contradiction_found,
181
+ "counter_model": counter_model,
182
+ "n_variables": result.n_variables,
183
+ "n_clauses": result.n_clauses,
184
+ "n_grover_iterations": result.n_grover_iterations,
185
+ "shots": result.shots,
186
+ "wall_clock_seconds": wall,
187
+ }
188
+
189
+ job_id = result.metadata.get("job_id") if result.metadata else None
190
+ if isinstance(job_id, str) and job_id:
191
+ out["ibm_job_id"] = job_id
192
+ out["ibm_job_url"] = IBM_CONSOLE_BASE + job_id
193
+
194
+ return out
195
+
196
+
197
+ def verify_on_simulator(label: str, mode: str) -> dict[str, Any]:
198
+ return _run_verify(label, mode, "pennylane")
199
+
200
+
201
+ def verify_on_ibm(label: str, mode: str) -> dict[str, Any]:
202
+ return _run_verify(label, mode, "ibm")
203
+
204
+
205
+ _initial_cnf, _initial_universe, _initial_description = _on_example_change(DEFAULT_LABEL)
206
+
207
+ _ibm_status_md = (
208
+ "IBM Quantum credentials detected in Space Secrets. "
209
+ "The hardware button is enabled; runs consume your free-tier "
210
+ "monthly budget (10 minutes shared)."
211
+ if IBM_AVAILABLE
212
+ else (
213
+ "IBM Quantum credentials are not configured. "
214
+ "Add `IBM_QUANTUM_TOKEN` and `IBM_QUANTUM_INSTANCE` to this "
215
+ "Space's Settings -> Variables and secrets to enable hardware runs."
216
+ )
217
+ )
218
+
219
+
220
+ with gr.Blocks(title="QVerify") as demo:
221
+ gr.Markdown(
222
+ "# QVerify · quantum-assisted CNF verifier\n\n"
223
+ "This Space exposes the QVerify **verifier** component: a CNF is "
224
+ "fed directly to Grover's search, on either a CPU-side PennyLane "
225
+ "simulator or IBM Quantum's Heron r2 processor.\n\n"
226
+ "Pick a pre-built example, choose a verification mode, then run."
227
+ )
228
+
229
+ with gr.Row():
230
+ with gr.Column(scale=1):
231
+ gr.Markdown("### 1. Pick a CNF")
232
+ example_dd = gr.Dropdown(
233
+ label="Example",
234
+ choices=EXAMPLE_LABELS,
235
+ value=DEFAULT_LABEL,
236
+ )
237
+ description_box = gr.Markdown(_initial_description)
238
+ cnf_box = gr.Textbox(
239
+ label="CNF (read-only)",
240
+ value=_initial_cnf,
241
+ lines=4,
242
+ interactive=False,
243
+ )
244
+ universe_box = gr.Textbox(
245
+ label="Universe of constants",
246
+ value=_initial_universe,
247
+ interactive=False,
248
+ )
249
+
250
+ gr.Markdown("### 2. Choose a mode")
251
+ mode_radio = gr.Radio(
252
+ label="Mode",
253
+ choices=["consistency", "entailment"],
254
+ value="consistency",
255
+ info=(
256
+ "consistency: SAT means the CNF is consistent. "
257
+ "entailment: SAT yields a counter-model showing the "
258
+ "step is not entailed by the premises."
259
+ ),
260
+ )
261
+
262
+ gr.Markdown("### 3. Run")
263
+ with gr.Row():
264
+ btn_sim = gr.Button("Verify on PennyLane simulator", variant="primary")
265
+ btn_hw = gr.Button(
266
+ "Verify on IBM Heron r2",
267
+ variant="secondary",
268
+ interactive=IBM_AVAILABLE,
269
+ )
270
+ gr.Markdown(_ibm_status_md)
271
+
272
+ with gr.Column(scale=1):
273
+ gr.Markdown("### Result")
274
+ output = gr.JSON(label="Verification result")
275
+
276
+ example_dd.change(
277
+ _on_example_change,
278
+ inputs=[example_dd],
279
+ outputs=[cnf_box, universe_box, description_box],
280
+ )
281
+ btn_sim.click(
282
+ verify_on_simulator,
283
+ inputs=[example_dd, mode_radio],
284
+ outputs=output,
285
+ )
286
+ btn_hw.click(
287
+ verify_on_ibm,
288
+ inputs=[example_dd, mode_radio],
289
+ outputs=output,
290
+ )
291
+
292
+ gr.Markdown("---")
293
+ gr.Markdown(
294
+ "### About\n\n"
295
+ "**This Space demos the verifier component of QVerify.** A CNF "
296
+ "is grounded over its declared universe, encoded into a Grover "
297
+ "circuit, and run on the chosen backend. The classical "
298
+ "post-check walks the most-frequent measured bitstrings until "
299
+ "one satisfies the CNF.\n\n"
300
+ "**The full pipeline (translator + grounding + verifier) "
301
+ "requires a GPU and runs locally**: it loads Gemma 4 E4B with "
302
+ "grammar-constrained generation to convert natural-language "
303
+ "premises into CNF before verification. CPU Basic Spaces cannot "
304
+ "host the translator, so this Space is verifier-only. See the "
305
+ "[GitHub repository](https://github.com/Quantum-Labor/qverify) "
306
+ "for the end-to-end pipeline.\n\n"
307
+ "**Real quantum hardware runs are routed through your IBM "
308
+ "Quantum free-tier account** (Open Plan, ~10 minutes of quantum "
309
+ "time per month, shared across users). The Space orchestrates "
310
+ "the call; the actual circuit executes on `ibm_fez` (Heron r2, "
311
+ "156 qubits) via IBM Cloud. A previously verified hardware run "
312
+ "is documented [here](https://github.com/Quantum-Labor/qverify"
313
+ "#hardware-run)."
314
+ )
315
+
316
+
317
+ if __name__ == "__main__":
318
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ qiskit>=2.0
2
+ qiskit-ibm-runtime>=0.40
3
+ pennylane>=0.39
4
+ pennylane-qiskit>=0.39
5
+ numpy<2.1
6
+ pydantic>=2.9
7
+ pydantic-settings>=2.6
8
+ python-dotenv>=1.0
9
+ gradio>=5.0