Update app.py
Browse files
app.py
CHANGED
|
@@ -26,24 +26,35 @@ def coherent_step(Psi, E, L):
|
|
| 26 |
# Metrics
|
| 27 |
# -----------------------------
|
| 28 |
def compute_coherence(a, b):
|
| 29 |
-
num = np.dot(a, b)
|
| 30 |
-
den = np.linalg.norm(a) * np.linalg.norm(b) + 1e-9
|
| 31 |
return float(abs(num / den))
|
| 32 |
|
| 33 |
|
| 34 |
# -----------------------------
|
| 35 |
-
# Optional baseline (context only)
|
| 36 |
# -----------------------------
|
| 37 |
def python_loop_baseline(n, steps, cap=50_000):
|
| 38 |
-
n_cap = min(n, cap)
|
| 39 |
x = [0.5] * n_cap
|
| 40 |
t0 = time.time()
|
| 41 |
-
for _ in range(steps):
|
| 42 |
for i in range(n_cap):
|
| 43 |
x[i] = 0.999 * x[i] + 0.001
|
| 44 |
elapsed = time.time() - t0
|
| 45 |
-
items = n_cap * steps
|
| 46 |
-
return items / elapsed / 1e9
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
|
| 49 |
# -----------------------------
|
|
@@ -57,14 +68,16 @@ def run_engine(n_osc, steps, include_baselines):
|
|
| 57 |
E = np.random.rand(n).astype(np.float32)
|
| 58 |
L = np.random.rand(n).astype(np.float32)
|
| 59 |
|
| 60 |
-
|
|
|
|
|
|
|
| 61 |
|
| 62 |
t0 = time.time()
|
| 63 |
for _ in range(steps):
|
| 64 |
Psi, E, L = coherent_step(Psi, E, L)
|
| 65 |
elapsed = time.time() - t0
|
| 66 |
|
| 67 |
-
Psi_end = Psi[:
|
| 68 |
|
| 69 |
coherence = compute_coherence(Psi_start, Psi_end)
|
| 70 |
mean_energy = float(np.mean(E))
|
|
@@ -82,44 +95,72 @@ def run_engine(n_osc, steps, include_baselines):
|
|
| 82 |
"Oscillators": f"{n:,}",
|
| 83 |
"Steps": f"{steps}",
|
| 84 |
"Engine": "numpy",
|
| 85 |
-
"CPU": platform.processor(),
|
| 86 |
-
"Cores Available": os.cpu_count(),
|
| 87 |
}
|
| 88 |
|
| 89 |
-
# Context-only baselines
|
| 90 |
if include_baselines:
|
| 91 |
-
py_b = python_loop_baseline(n, steps)
|
| 92 |
-
np_b = throughput_b # same engine, context label
|
| 93 |
-
|
| 94 |
result.update({
|
| 95 |
-
"Baseline: numpy (B items/s)": f"{
|
| 96 |
"Baseline: python_loop (B items/s)": f"{py_b:.3f}",
|
| 97 |
-
"
|
| 98 |
-
"
|
|
|
|
|
|
|
| 99 |
})
|
| 100 |
|
| 101 |
-
# Receipt
|
| 102 |
receipt = {
|
| 103 |
"timestamp_utc": datetime.utcnow().isoformat() + "Z",
|
|
|
|
| 104 |
"results": result,
|
| 105 |
"platform": platform.platform(),
|
| 106 |
"python": platform.python_version(),
|
| 107 |
}
|
| 108 |
|
| 109 |
-
|
| 110 |
-
sha = hashlib.sha256(receipt_json).hexdigest()
|
| 111 |
-
receipt["sha256"] = sha
|
| 112 |
|
| 113 |
os.makedirs("receipts", exist_ok=True)
|
| 114 |
fname = f"receipts/receipt_{datetime.utcnow().strftime('%Y-%m-%dT%H-%M-%S')}.json"
|
| 115 |
-
with open(fname, "
|
| 116 |
-
|
| 117 |
|
| 118 |
-
result["Receipt SHA-256 (in file)"] =
|
| 119 |
|
|
|
|
| 120 |
return json.dumps(result, indent=2), fname
|
| 121 |
|
| 122 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
# -----------------------------
|
| 124 |
# UI
|
| 125 |
# -----------------------------
|
|
@@ -138,31 +179,47 @@ Everything you see below is computed **right now**, on this machine.
|
|
| 138 |
• Stability proxy (|C|)
|
| 139 |
• Energy behaviour
|
| 140 |
• A downloadable **receipt with SHA-256** (verification-first)
|
| 141 |
-
•
|
| 142 |
|
| 143 |
-
No precomputed results. No estimates.
|
| 144 |
"""
|
| 145 |
)
|
| 146 |
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
|
|
|
|
|
|
| 151 |
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
|
| 157 |
-
|
| 158 |
|
| 159 |
-
|
| 160 |
-
|
| 161 |
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
|
| 168 |
demo.launch()
|
|
|
|
| 26 |
# Metrics
|
| 27 |
# -----------------------------
|
| 28 |
def compute_coherence(a, b):
|
| 29 |
+
num = float(np.dot(a, b))
|
| 30 |
+
den = float(np.linalg.norm(a) * np.linalg.norm(b) + 1e-9)
|
| 31 |
return float(abs(num / den))
|
| 32 |
|
| 33 |
|
| 34 |
# -----------------------------
|
| 35 |
+
# Optional baseline (context only, safety-capped)
|
| 36 |
# -----------------------------
|
| 37 |
def python_loop_baseline(n, steps, cap=50_000):
|
| 38 |
+
n_cap = min(int(n), int(cap))
|
| 39 |
x = [0.5] * n_cap
|
| 40 |
t0 = time.time()
|
| 41 |
+
for _ in range(int(steps)):
|
| 42 |
for i in range(n_cap):
|
| 43 |
x[i] = 0.999 * x[i] + 0.001
|
| 44 |
elapsed = time.time() - t0
|
| 45 |
+
items = n_cap * int(steps)
|
| 46 |
+
return items / elapsed / 1e9, n_cap
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
# -----------------------------
|
| 50 |
+
# Receipt hashing
|
| 51 |
+
# IMPORTANT: sha256 is computed over receipt JSON WITHOUT the sha256 field
|
| 52 |
+
# -----------------------------
|
| 53 |
+
def compute_receipt_sha256(receipt_obj: dict) -> str:
|
| 54 |
+
obj = dict(receipt_obj)
|
| 55 |
+
obj.pop("sha256", None)
|
| 56 |
+
payload = json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
|
| 57 |
+
return hashlib.sha256(payload).hexdigest()
|
| 58 |
|
| 59 |
|
| 60 |
# -----------------------------
|
|
|
|
| 68 |
E = np.random.rand(n).astype(np.float32)
|
| 69 |
L = np.random.rand(n).astype(np.float32)
|
| 70 |
|
| 71 |
+
# sample slice for coherence proxy (keeps runtime sane)
|
| 72 |
+
sample = min(n, 200_000)
|
| 73 |
+
Psi_start = Psi[:sample].copy()
|
| 74 |
|
| 75 |
t0 = time.time()
|
| 76 |
for _ in range(steps):
|
| 77 |
Psi, E, L = coherent_step(Psi, E, L)
|
| 78 |
elapsed = time.time() - t0
|
| 79 |
|
| 80 |
+
Psi_end = Psi[:sample].copy()
|
| 81 |
|
| 82 |
coherence = compute_coherence(Psi_start, Psi_end)
|
| 83 |
mean_energy = float(np.mean(E))
|
|
|
|
| 95 |
"Oscillators": f"{n:,}",
|
| 96 |
"Steps": f"{steps}",
|
| 97 |
"Engine": "numpy",
|
| 98 |
+
"CPU": platform.processor() or "",
|
| 99 |
+
"Cores Available": os.cpu_count() or 0,
|
| 100 |
}
|
| 101 |
|
|
|
|
| 102 |
if include_baselines:
|
| 103 |
+
py_b, py_ncap = python_loop_baseline(n, steps)
|
|
|
|
|
|
|
| 104 |
result.update({
|
| 105 |
+
"Baseline: numpy (B items/s)": f"{throughput_b:.3f}",
|
| 106 |
"Baseline: python_loop (B items/s)": f"{py_b:.3f}",
|
| 107 |
+
"Baseline: python_loop items measured": f"{py_ncap:,}",
|
| 108 |
+
"Speedup vs python_loop (x)": f"{(throughput_b / max(py_b, 1e-12)):.1f}",
|
| 109 |
+
"Speedup vs numpy (x)": f"{(throughput_b / max(throughput_b, 1e-12)):.2f}",
|
| 110 |
+
"Note": "Baselines are context-only; python loop is safety-capped and measured live."
|
| 111 |
})
|
| 112 |
|
|
|
|
| 113 |
receipt = {
|
| 114 |
"timestamp_utc": datetime.utcnow().isoformat() + "Z",
|
| 115 |
+
"app": APP_TITLE,
|
| 116 |
"results": result,
|
| 117 |
"platform": platform.platform(),
|
| 118 |
"python": platform.python_version(),
|
| 119 |
}
|
| 120 |
|
| 121 |
+
receipt["sha256"] = compute_receipt_sha256(receipt)
|
|
|
|
|
|
|
| 122 |
|
| 123 |
os.makedirs("receipts", exist_ok=True)
|
| 124 |
fname = f"receipts/receipt_{datetime.utcnow().strftime('%Y-%m-%dT%H-%M-%S')}.json"
|
| 125 |
+
with open(fname, "w", encoding="utf-8") as f:
|
| 126 |
+
json.dump(receipt, f, indent=2, ensure_ascii=False)
|
| 127 |
|
| 128 |
+
result["Receipt SHA-256 (in file)"] = receipt["sha256"]
|
| 129 |
|
| 130 |
+
# Return pretty results + receipt file
|
| 131 |
return json.dumps(result, indent=2), fname
|
| 132 |
|
| 133 |
|
| 134 |
+
# -----------------------------
|
| 135 |
+
# Receipt verifier
|
| 136 |
+
# -----------------------------
|
| 137 |
+
def verify_receipt(uploaded_file):
|
| 138 |
+
if uploaded_file is None:
|
| 139 |
+
return "No file uploaded.", ""
|
| 140 |
+
|
| 141 |
+
path = uploaded_file.name if hasattr(uploaded_file, "name") else str(uploaded_file)
|
| 142 |
+
try:
|
| 143 |
+
with open(path, "r", encoding="utf-8") as f:
|
| 144 |
+
receipt = json.load(f)
|
| 145 |
+
except Exception as e:
|
| 146 |
+
return f"Invalid JSON file: {e}", ""
|
| 147 |
+
|
| 148 |
+
claimed = str(receipt.get("sha256", "")).strip()
|
| 149 |
+
computed = compute_receipt_sha256(receipt)
|
| 150 |
+
|
| 151 |
+
ok = (claimed != "" and claimed.lower() == computed.lower())
|
| 152 |
+
|
| 153 |
+
verdict = {
|
| 154 |
+
"verified": bool(ok),
|
| 155 |
+
"claimed_sha256": claimed,
|
| 156 |
+
"computed_sha256": computed,
|
| 157 |
+
"note": "Verified means: sha256(receipt_without_sha256_field) matches the sha256 stored in the receipt."
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
banner = "✅ Receipt verified (SHA-256 match)" if ok else "❌ Receipt FAILED verification (SHA-256 mismatch)"
|
| 161 |
+
return banner, json.dumps(verdict, indent=2)
|
| 162 |
+
|
| 163 |
+
|
| 164 |
# -----------------------------
|
| 165 |
# UI
|
| 166 |
# -----------------------------
|
|
|
|
| 179 |
• Stability proxy (|C|)
|
| 180 |
• Energy behaviour
|
| 181 |
• A downloadable **receipt with SHA-256** (verification-first)
|
| 182 |
+
• A built-in **receipt verifier** (upload → verify → done)
|
| 183 |
|
| 184 |
+
No precomputed results. No estimates.
|
| 185 |
"""
|
| 186 |
)
|
| 187 |
|
| 188 |
+
with gr.Tabs():
|
| 189 |
+
with gr.Tab("Run"):
|
| 190 |
+
n_slider = gr.Slider(250_000, 8_000_000, value=6_400_000, step=250_000,
|
| 191 |
+
label="Number of Oscillators")
|
| 192 |
+
steps_slider = gr.Slider(50, 1_000, value=650, step=25,
|
| 193 |
+
label="Simulation Steps")
|
| 194 |
|
| 195 |
+
baseline_toggle = gr.Checkbox(
|
| 196 |
+
label="Include baselines (context only)",
|
| 197 |
+
value=True
|
| 198 |
+
)
|
| 199 |
|
| 200 |
+
run_btn = gr.Button("Run Engine")
|
| 201 |
|
| 202 |
+
output = gr.Code(label="Results", language="json")
|
| 203 |
+
receipt_file = gr.File(label="Receipt (download)")
|
| 204 |
|
| 205 |
+
run_btn.click(
|
| 206 |
+
fn=run_engine,
|
| 207 |
+
inputs=[n_slider, steps_slider, baseline_toggle],
|
| 208 |
+
outputs=[output, receipt_file]
|
| 209 |
+
)
|
| 210 |
+
|
| 211 |
+
with gr.Tab("Verify receipt"):
|
| 212 |
+
gr.Markdown(
|
| 213 |
+
"""
|
| 214 |
+
Upload a receipt JSON generated by this Space.
|
| 215 |
+
|
| 216 |
+
This verifier recomputes the SHA-256 (over the receipt JSON **without** the `sha256` field) and checks it matches the stored value.
|
| 217 |
+
"""
|
| 218 |
+
)
|
| 219 |
+
up = gr.File(label="Upload receipt (.json)")
|
| 220 |
+
verify_btn = gr.Button("Verify")
|
| 221 |
+
banner = gr.Textbox(label="Verification result", interactive=False)
|
| 222 |
+
detail = gr.Code(label="Details", language="json")
|
| 223 |
+
verify_btn.click(fn=verify_receipt, inputs=[up], outputs=[banner, detail])
|
| 224 |
|
| 225 |
demo.launch()
|