Henry Barnes commited on
Commit
87e9176
·
0 Parent(s):

Initial HF Space: Catalyst Cloud demo

Browse files
Files changed (3) hide show
  1. README.md +20 -0
  2. app.py +224 -0
  3. requirements.txt +4 -0
README.md ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Catalyst Cloud — Neuromorphic Simulator
3
+ emoji: 🧠
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: gradio
7
+ sdk_version: "4.0"
8
+ app_file: app.py
9
+ pinned: false
10
+ license: mit
11
+ short_description: Run hardware-accurate spiking neural network simulations
12
+ ---
13
+
14
+ # Catalyst Cloud — Neuromorphic Simulator
15
+
16
+ Run hardware-accurate spiking neural network simulations in the cloud. Full Loihi 2 parity. No hardware required.
17
+
18
+ Sign up for a free API key, configure your network, and visualize spike rasters — all from this demo.
19
+
20
+ [Website](https://catalyst-neuromorphic.com/cloud) | [API Docs](https://catalyst-neuromorphic.com/cloud/docs) | [pip install catalyst-cloud](https://pypi.org/project/catalyst-cloud/)
app.py ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Catalyst Cloud — Interactive Neuromorphic Simulator Demo.
2
+
3
+ Runs spiking neural network simulations via the Catalyst Cloud API
4
+ and displays spike raster plots.
5
+ """
6
+
7
+ import gradio as gr
8
+ import requests
9
+ import numpy as np
10
+
11
+ API_URL = "https://api.catalyst-neuromorphic.com"
12
+
13
+
14
+ def signup(email: str) -> str:
15
+ """Sign up for a free API key."""
16
+ if not email or "@" not in email:
17
+ return "Please enter a valid email address."
18
+ try:
19
+ resp = requests.post(f"{API_URL}/v1/signup", json={"email": email, "tier": "free"}, timeout=15)
20
+ if resp.status_code == 200:
21
+ data = resp.json()
22
+ return f"Your API key: {data['api_key']}\n\nSave this — it's shown only once.\nFree tier: {data['limits']['max_neurons']} neurons, {data['limits']['max_timesteps']} timesteps, {data['limits']['max_jobs_per_day']} jobs/day."
23
+ else:
24
+ return f"Error: {resp.json().get('detail', resp.text)}"
25
+ except Exception as e:
26
+ return f"Connection error: {e}"
27
+
28
+
29
+ def run_simulation(api_key: str, input_size: int, hidden_size: int, output_size: int,
30
+ topology: str, weight: int, sparsity: float, timesteps: int,
31
+ input_current: int):
32
+ """Run a simulation and return results + raster plot."""
33
+ if not api_key or not api_key.startswith("cn_live_"):
34
+ return "Enter a valid API key (starts with cn_live_)", None
35
+
36
+ headers = {"X-API-Key": api_key, "Content-Type": "application/json"}
37
+
38
+ # Build populations
39
+ populations = [{"label": "input", "size": input_size, "params": {"threshold": 1000}}]
40
+ connections = []
41
+
42
+ if hidden_size > 0:
43
+ populations.append({"label": "hidden", "size": hidden_size, "params": {"threshold": 1000}})
44
+ connections.append({
45
+ "source": "input", "target": "hidden",
46
+ "topology": topology, "weight": weight, "p": sparsity,
47
+ })
48
+ if output_size > 0:
49
+ populations.append({"label": "output", "size": output_size, "params": {"threshold": 1000}})
50
+ connections.append({
51
+ "source": "hidden", "target": "output",
52
+ "topology": topology, "weight": weight, "p": sparsity,
53
+ })
54
+ elif output_size > 0:
55
+ populations.append({"label": "output", "size": output_size, "params": {"threshold": 1000}})
56
+ connections.append({
57
+ "source": "input", "target": "output",
58
+ "topology": topology, "weight": weight, "p": sparsity,
59
+ })
60
+
61
+ total = sum(p["size"] for p in populations)
62
+ if total > 1024:
63
+ return f"Total neurons ({total}) exceeds free tier limit (1024). Reduce sizes.", None
64
+
65
+ try:
66
+ # Create network
67
+ resp = requests.post(f"{API_URL}/v1/networks", headers=headers,
68
+ json={"populations": populations, "connections": connections}, timeout=15)
69
+ if resp.status_code != 200:
70
+ return f"Network error: {resp.json().get('detail', resp.text)}", None
71
+
72
+ network_id = resp.json()["network_id"]
73
+
74
+ # Submit job
75
+ resp = requests.post(f"{API_URL}/v1/jobs", headers=headers, json={
76
+ "network_id": network_id,
77
+ "timesteps": timesteps,
78
+ "stimuli": [{"population": "input", "current": input_current}],
79
+ }, timeout=15)
80
+ if resp.status_code != 200:
81
+ return f"Job error: {resp.json().get('detail', resp.text)}", None
82
+
83
+ job_id = resp.json()["job_id"]
84
+
85
+ # Poll for completion
86
+ import time
87
+ for _ in range(60):
88
+ time.sleep(0.5)
89
+ resp = requests.get(f"{API_URL}/v1/jobs/{job_id}", headers=headers, timeout=15)
90
+ job = resp.json()
91
+ if job["status"] == "completed":
92
+ break
93
+ if job["status"] == "failed":
94
+ return f"Simulation failed: {job.get('error_message', 'Unknown error')}", None
95
+ else:
96
+ return "Timeout waiting for simulation to complete.", None
97
+
98
+ result = job["result"]
99
+
100
+ # Get spike trains
101
+ resp = requests.get(f"{API_URL}/v1/jobs/{job_id}/spikes", headers=headers, timeout=15)
102
+ spikes = resp.json()["spike_trains"]
103
+
104
+ # Build raster plot
105
+ import matplotlib
106
+ matplotlib.use("Agg")
107
+ import matplotlib.pyplot as plt
108
+
109
+ fig, ax = plt.subplots(figsize=(10, 5))
110
+ fig.patch.set_facecolor("#0a0a0a")
111
+ ax.set_facecolor("#0a0a0a")
112
+
113
+ colors = {"input": "#4A9EFF", "hidden": "#FF6B6B", "output": "#50C878"}
114
+ neuron_offset = 0
115
+ yticks = []
116
+ yticklabels = []
117
+
118
+ for pop_label in [p["label"] for p in populations]:
119
+ pop_size = next(p["size"] for p in populations if p["label"] == pop_label)
120
+ pop_spikes = spikes.get(pop_label, {})
121
+ color = colors.get(pop_label, "#FFFFFF")
122
+
123
+ for neuron_str, times in pop_spikes.items():
124
+ neuron_idx = int(neuron_str)
125
+ y = neuron_offset + neuron_idx
126
+ ax.scatter(times, [y] * len(times), s=1, c=color, marker="|", linewidths=0.5)
127
+
128
+ mid = neuron_offset + pop_size // 2
129
+ yticks.append(mid)
130
+ yticklabels.append(f"{pop_label}\n({pop_size})")
131
+ neuron_offset += pop_size
132
+
133
+ ax.set_xlabel("Timestep", color="white", fontsize=11)
134
+ ax.set_ylabel("Neuron", color="white", fontsize=11)
135
+ ax.set_title("Spike Raster Plot", color="white", fontsize=13, fontweight="bold")
136
+ ax.set_yticks(yticks)
137
+ ax.set_yticklabels(yticklabels, fontsize=9)
138
+ ax.tick_params(colors="white")
139
+ ax.spines["bottom"].set_color("#333")
140
+ ax.spines["left"].set_color("#333")
141
+ ax.spines["top"].set_visible(False)
142
+ ax.spines["right"].set_visible(False)
143
+ ax.set_xlim(-1, timesteps + 1)
144
+ ax.set_ylim(-1, neuron_offset)
145
+
146
+ plt.tight_layout()
147
+
148
+ summary = (
149
+ f"Total spikes: {result['total_spikes']}\n"
150
+ f"Timesteps: {result['timesteps']}\n"
151
+ f"Compute time: {job['compute_seconds']:.4f}s\n\n"
152
+ f"Firing rates:\n" +
153
+ "\n".join(f" {k}: {v:.4f}" for k, v in result["firing_rates"].items())
154
+ )
155
+
156
+ return summary, fig
157
+
158
+ except Exception as e:
159
+ return f"Error: {e}", None
160
+
161
+
162
+ # -- Gradio UI --
163
+
164
+ with gr.Blocks(
165
+ title="Catalyst Cloud — Neuromorphic Simulator",
166
+ theme=gr.themes.Base(
167
+ primary_hue="blue",
168
+ neutral_hue="slate",
169
+ ),
170
+ ) as demo:
171
+ gr.Markdown("""
172
+ # Catalyst Cloud — Neuromorphic Simulator
173
+ Run hardware-accurate spiking neural network simulations in the cloud.
174
+ Full Loihi 2 parity. No hardware required.
175
+ """)
176
+
177
+ with gr.Tab("Get API Key"):
178
+ email_input = gr.Textbox(label="Email", placeholder="you@lab.edu")
179
+ signup_btn = gr.Button("Sign up (free)")
180
+ signup_output = gr.Textbox(label="Result", lines=4)
181
+ signup_btn.click(signup, inputs=[email_input], outputs=[signup_output])
182
+
183
+ with gr.Tab("Simulate"):
184
+ api_key_input = gr.Textbox(label="API Key", placeholder="cn_live_...", type="password")
185
+
186
+ with gr.Row():
187
+ input_size = gr.Slider(1, 500, value=50, step=1, label="Input neurons")
188
+ hidden_size = gr.Slider(0, 500, value=30, step=1, label="Hidden neurons (0 = skip)")
189
+ output_size = gr.Slider(0, 200, value=20, step=1, label="Output neurons (0 = skip)")
190
+
191
+ with gr.Row():
192
+ topology = gr.Dropdown(
193
+ ["all_to_all", "one_to_one", "random_sparse"],
194
+ value="random_sparse", label="Topology",
195
+ )
196
+ weight = gr.Slider(100, 2000, value=800, step=50, label="Synaptic weight")
197
+ sparsity = gr.Slider(0.01, 1.0, value=0.3, step=0.01, label="Connection probability")
198
+
199
+ with gr.Row():
200
+ timesteps = gr.Slider(10, 1000, value=200, step=10, label="Timesteps")
201
+ input_current = gr.Slider(500, 10000, value=5000, step=500, label="Input current")
202
+
203
+ run_btn = gr.Button("Run simulation", variant="primary")
204
+
205
+ with gr.Row():
206
+ result_text = gr.Textbox(label="Results", lines=8)
207
+ raster_plot = gr.Plot(label="Spike raster")
208
+
209
+ run_btn.click(
210
+ run_simulation,
211
+ inputs=[api_key_input, input_size, hidden_size, output_size,
212
+ topology, weight, sparsity, timesteps, input_current],
213
+ outputs=[result_text, raster_plot],
214
+ )
215
+
216
+ gr.Markdown("""
217
+ ---
218
+ [Website](https://catalyst-neuromorphic.com/cloud) |
219
+ [API Docs](https://catalyst-neuromorphic.com/cloud/docs) |
220
+ [Pricing](https://catalyst-neuromorphic.com/cloud/pricing) |
221
+ [pip install catalyst-cloud](https://pypi.org/project/catalyst-cloud/)
222
+ """)
223
+
224
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ gradio>=4.0
2
+ requests
3
+ numpy
4
+ matplotlib