Skip to content

Commit 7e1df46

Browse files
committed
Titles + Padding
1 parent 3764697 commit 7e1df46

3 files changed

Lines changed: 168 additions & 42 deletions

File tree

bindsnet/rendering/app.py

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
class Application():
1818
def __init__(self, network: GUINetwork, width=1400, height=900, title="BindsNET GUI",
1919
header: str | None = None,
20-
step_rate: int | str = 500, draw_fps: float | None = None,
20+
max_steps_per_second: int | float | str | None = None,
21+
draw_fps: float | None = None,
2122
parameters: dict | None = None):
2223
self.width, self.height = width, height
2324
self.network = network
@@ -28,7 +29,14 @@ def __init__(self, network: GUINetwork, width=1400, height=900, title="BindsNET
2829
self.inputs = None # Set when run() is called; Inputs into network during runtime
2930
self.runtime = None # Set when run() is called; Total runtime of network simulation
3031
self.current_time = 0 # Current timestep in network; incremented during runtime
31-
self.step_rate = 1/step_rate
32+
33+
# Cap on how many sim steps run per wall-clock second. `inf` (or "inf"/"max")
34+
# means "go as fast as possible" -- a 0s timer interval, i.e. one step per tick
35+
# with no throttle. Defaults to the draw rate so, out of the box, the sim steps
36+
# roughly in lock-step with the redraws (fall back to 60 if neither is set).
37+
if max_steps_per_second is None:
38+
max_steps_per_second = draw_fps if draw_fps is not None else 60
39+
self.max_steps_per_second = self._coerce_sps(max_steps_per_second)
3240

3341
# Decouple the (expensive) full-canvas redraw from the simulation rate: run the
3442
# sim + cheap per-step data capture (widget.capture) every step, but redraw
@@ -49,6 +57,10 @@ def __init__(self, network: GUINetwork, width=1400, height=900, title="BindsNET
4957
self.step_budget = 0
5058
self._was_active = None # last active-state, to fire play<->pause transitions once
5159

60+
# Rolling measurement of the ACTUAL steps/second, reported to the panel ~2x/sec.
61+
self._sps_count = 0 # steps taken since the last measurement window opened
62+
self._sps_t0 = None # perf_counter at the start of the current window
63+
5264
# Initialize VisPy canvas (GLFW) and grid layout for widget rendering.
5365
self.canvas = scene.SceneCanvas(
5466
title=title,
@@ -62,14 +74,17 @@ def __init__(self, network: GUINetwork, width=1400, height=900, title="BindsNET
6274
# spacer row so the topmost axis tick labels aren't clipped by the canvas edge.
6375
self.layout = self.canvas.central_widget.add_grid(margin=0)
6476
next_row = 0
77+
# Top padding above everything, so the header (or the topmost tick labels when
78+
# there's no header) isn't flush against the canvas edge.
79+
self.layout.add_widget(row=next_row, col=0).height_max = 24
80+
next_row += 1
6581
if header is not None:
6682
self.title_label = scene.Label(header, color='white', font_size=20, bold=True)
6783
self.title_label.height_max = 48
6884
self.layout.add_widget(self.title_label, row=next_row, col=0)
85+
next_row += 1
6986
else:
7087
self.title_label = None
71-
self.layout.add_widget(row=next_row, col=0).height_max = 12 # top padding
72-
next_row += 1
7388

7489
self.grid = self.layout.add_grid(row=next_row, col=0, margin=10)
7590

@@ -80,6 +95,31 @@ def __init__(self, network: GUINetwork, width=1400, height=900, title="BindsNET
8095
# toggle_play/step_once/run_n and reads back via set_time etc.
8196
self.panel = QtControlPanel(self, parameters=self.parameters)
8297

98+
# --- steps-per-second rate -------------------------------------------------
99+
@staticmethod
100+
def _coerce_sps(value: int | float | str) -> float:
101+
# Accept a number, or the words "inf"/"max"/"unlimited"/"" for "as fast as
102+
# possible". Returns a positive float (possibly math.inf).
103+
if isinstance(value, str):
104+
if value.strip().lower() in ("", "inf", "max", "unlimited"):
105+
return float("inf")
106+
value = float(value)
107+
value = float(value)
108+
if value <= 0:
109+
raise ValueError(f"max_steps_per_second must be > 0, got {value}")
110+
return value
111+
112+
@staticmethod
113+
def _interval_for(sps: float) -> float:
114+
# inf steps/sec -> a 0s timer interval (vispy fires it as fast as it can).
115+
return 0.0 if sps == float("inf") else 1.0 / sps
116+
117+
def set_max_steps_per_second(self, value: int | float | str):
118+
# Update the cap live; the running timer's interval is swapped in place.
119+
self.max_steps_per_second = self._coerce_sps(value)
120+
if hasattr(self, "timer"):
121+
self.timer.interval = self._interval_for(self.max_steps_per_second)
122+
83123
def add_widget(self, widget: AbstractWidget, row: int, col: int):
84124
self.widgets.append(widget)
85125
self.grid.add_widget(widget.grid, row, col)
@@ -111,6 +151,8 @@ def reset(self):
111151
self.step_budget = 0
112152
self._was_active = None
113153
self._last_draw = None
154+
self._sps_count = 0
155+
self._sps_t0 = None
114156
self.current_time = 0
115157
self.network.reset_state_variables()
116158
self.network.reset_history()
@@ -133,6 +175,18 @@ def _set_active(self, active: bool):
133175
self._was_active = active
134176

135177
def step(self, event):
178+
# Measure the actual steps/second over a rolling ~0.5s window and report it to
179+
# the panel. Done first (before any early return) so idle/finished states settle
180+
# back to 0 rather than showing a stale rate.
181+
now = time.perf_counter()
182+
if self._sps_t0 is None:
183+
self._sps_t0 = now
184+
elapsed = now - self._sps_t0
185+
if elapsed >= 0.5:
186+
self.panel.set_steps_per_second(self._sps_count / elapsed)
187+
self._sps_count = 0
188+
self._sps_t0 = now
189+
136190
# Check if runtime is over
137191
if self.current_time >= self.runtime:
138192
self.timer.stop()
@@ -162,6 +216,8 @@ def step(self, event):
162216
for widget in self.widgets:
163217
widget.capture(t)
164218

219+
self._sps_count += 1 # count actual advancing steps for the rate readout
220+
165221
manual = not self.running # advancing via Step / Run N, not Play
166222
if self.step_budget > 0:
167223
self.step_budget -= 1
@@ -196,7 +252,9 @@ def run(self, inputs: dict[str, torch.Tensor], runtime: int):
196252
widget.prime(self.network, runtime)
197253
# Start paused: the timer ticks, but the sim only advances once the user hits
198254
# Play / Step / Run N (the plot cameras are interactive while idle).
199-
self.timer = app.Timer(interval=self.step_rate, connect=self.step, start=True)
255+
self.timer = app.Timer(
256+
interval=self._interval_for(self.max_steps_per_second), connect=self.step,
257+
start=True)
200258
# A separate ~60 Hz timer pumps the control panel's event loop when it has one
201259
# (the Qt window); the GLFW loop ticks both timers. The plots are unaffected.
202260
if self.panel.needs_pump:

0 commit comments

Comments
 (0)