The Studio¶
The Studio shows a running simulation as a film, from a browser or a phone, and lets you pause,
checkpoint, stop or fork it; watching never changes a bit of the computation. The Studio and its
hub are experimental in this release (Experimental features): sim.watch records the telemetry as
documented, the page, the server, the hub and the client may change.
import numpy as np
import conatus as cn
box = cn.Box(x=cn.periodic(2.0), z=cn.walls(-0.5, 0.5))
m = cn.Model(box, name="Rayleigh-Benard 2D", Ra=2e4, Pr=1)
m.velocity("u"), m.pressure("p"), m.scalar("T")
m.equations("""
dt(u) + grad(p) - Pr*lap(u) = Pr*Ra*T*ez - u·grad(u)
div(u) = 0
dt(T) - lap(T) = -u·grad(T)
""")
m.at("bottom", u=0, T=+0.5)
m.at("top", u=0, T=-0.5)
sim = m.simulate(N=(32, 16), scheme="RK443", dt=1e-3)
sim.initial(T=lambda x, z: -z + 1e-3 * np.cos(np.pi * x) * np.cos(np.pi * z))
def kinetic(sim):
"""Mean kinetic energy: one number per call, a time series in the Studio."""
return 0.5 * float(np.mean(sim.grid("u") ** 2))
w = sim.watch("rb_demo",
film={"T": cn.view("T", scale="signed")}, # the first view is the default
stats={"kinetic": kinetic,
"T(z)": cn.profile("T", along="z")}, # a declared curve
every_s=0.5, # wall seconds between frames
spool=spool_dir) # where the Studio reads
print(w.explain().splitlines()[0])
sim.run(steps=50)
sim.unwatch()
Watch 'rb_demo': the callback reads the state every 10 step(s) (never changes it); frames at most every 0.5 s of wall time per name, declared numbers at most every 0.25 s; t, iteration, dt, wall time per step are always sent.
watched run identical to the unwatched one: True
The run is the same with and without the watch, bit for bit: the Studio only reads.
Only what you declare¶
sim.watch(...) says what the Studio may show, and nothing else is sent:
film: the views of the film, by name. A view is an unknown (
"c"), a function of the simulation that returns an array (vorticity(sim)), orcn.view(source, scale=...)to choose the colour scale:smoke(one-signed),signed(zero in black) ordye. A 3D field is shown as an isosurface, a volume or slices (cn.view(q, mode="isosurface")).stats: numbers and curves. A function returns one number per call (a time series);
cn.series,cn.spectrumandcn.profiledeclare curves with their formula.every: the wall-clock time between two frames of a view.
w = sim.watch(...) returns the watch; w.explain() (and sim.explain()) lists what will be
sent and what is lost on the way: frames are stored on 8 bits and decimated, and the numbers of
the computation are never touched.
What you see¶
The gallery: one card per run, live runs first.
The film: the flow in large, on a dark background. Pinch or scroll to zoom, drag to move, double-tap to return to the initial view; the view chips switch between the declared views.
The timeline: play, 1× to 8×, Live (the end of the film is the present of the run), drag to rewind. The film is kept for the whole run, losslessly re-encoded, within a declared size budget; older frames are thinned out, never silently.
Three sheets, closed by default: Stats (the declared curves, synchronised with the playhead), Controls (pause, resume, checkpoint; stop and fork ask for a second tap), Details (provenance, settings,
explain(), logs and what was lost). A time step declared settable (sim.watch(..., settable={"dt": (lo, hi)})) can be changed from Controls, within its bounds.The board: every run with what needs attention (a non-finite value, a silent run, a relay down…), who is piloting it, and the recent commands; read only.
A second screen compares one quantity across two to six runs. On a phone the Studio can be added to the home screen. The link is always shown: live with its round-trip time, reconnecting with the next attempt (tap to retry now), or offline; the Studio reconnects by itself when the page comes back or the network returns, and a light stream (no 3D data, fewer points) can be chosen on the board for a slow connection.
Scripts and agents read the same state and send the same commands: Pilot a run from a script or an agent.
Opening the Studio¶
The computation writes its film and numbers to a spool directory; the Studio reads it. Choose who serves it:
On a cluster. The server runs inside a batch job on a compute node, never on a login node. A tunnel through the login node brings it to your laptop:
ssh -J <login-node> -L 8765:localhost:8765 <compute-node> # then open http://localhost:8765/login with the one-session token of the job's user_token file # (0600; never printed in the job log, which other members of the group can read)
On your own machine.
python -m conatus.studio.hub serve --local --spool <dir> --port 8765. The one-session login token goes to a 0600 file,hub_state/local_user_token(--state DIR, or--user-token-out FILE), written atomically and never through a symbolic link; only its path is printed.--print-tokenprints the token itself (it then lands in any log of the process).
The Studio needs tornado (pip install "conatus[studio]"); the library never
imports it.
Without the native layer¶
An expert time stepper is watched with conatus.telemetry.Telemetry, which takes the same
declarations (views=, stats=) and gives a callback for solver.run(n, callback=..., every=...)
and a wrapper for Newton–Krylov searches.
Limits and timings¶
Every number the Studio, the hub, the relay and the telemetry use on their own (when a quiet run
counts as silent, how long a confirmation or a session lives, how many failed logins are allowed,
how large a request may be, how much film the hub keeps) is declared in one table,
conatus.studio.constants, and printed by
python -c "import conatus.studio.constants as c; print(c.explain())". A test fails if the code and
the table ever differ, or if a new constant is not listed.
Constant |
Value |
Kind |
What it decides |
How to change it |
|---|---|---|---|---|
|
60.0 s |
timing |
a live run with no record for longer gets the alert ‘silent’ |
edit the constant (and this table) |
|
30.0 s |
timing |
a record within the last LIVE_S seconds makes a run ‘running’ (else ‘stale’) |
edit the constant (and this table) |
|
120.0 s |
security |
a confirmation token (stop, fork) is valid this long, and once |
edit the constant (and this table) |
|
120.0 s |
security |
same, for a Confirmations store built by hand |
argument of the constructor (and this table for the default) |
|
4096 bytes |
limit |
fork/set parameters: a small JSON object at most |
edit the constant (and this table) |
|
200 values |
display |
scalars returned by an agent’s ‘last values’ query |
argument |
|
20 lines |
display |
log lines in an agent’s view of a run |
argument |
|
20 events |
display |
events in an agent’s view of a run |
argument |
|
0 factor |
display |
nearest-neighbour upscaling of an agent’s frame PNG (0: to about 256 px across) |
argument |
|
16384 iterations |
security |
scrypt cost parameter N of stored password hashes |
edit the constant (and this table) |
|
8 |
security |
scrypt block size r |
edit the constant (and this table) |
|
1 |
security |
scrypt parallelisation p |
edit the constant (and this table) |
|
16384 iterations |
security |
= SCRYPT_N (default of the argument) |
argument |
|
8 |
security |
= SCRYPT_R |
argument |
|
1 |
security |
= SCRYPT_P |
argument |
|
32 bytes |
security |
random bytes of relay, user and agent tokens and session cookies (256 bits) |
edit the constant (and this table) |
|
12 characters |
security |
shorter passwords are refused at creation |
edit the constant (and this table) |
|
43200 s |
security |
lifetime of a browser session cookie (12 h) |
argument of the constructor (and this table for the default) |
|
5 failures |
security |
failed password logins per client address within the window before refusal (a valid token is never refused; all clients of a compute node share 127.0.0.1) |
argument of the constructor (and this table for the default) |
|
900.0 s |
security |
sliding window of the login throttle (15 min) |
argument of the constructor (and this table for the default) |
|
8388608 bytes |
limit |
spool transport: bytes of a run file read from its end |
edit the constant (and this table) |
|
8388608 bytes |
limit |
= TAIL_BYTES |
argument |
|
8388608 bytes |
limit |
= TAIL_BYTES |
argument of the constructor (and this table for the default) |
|
20000 records |
limit |
scalar records kept per run when reading a spool file |
argument |
|
2000 lines |
limit |
log lines kept per run when reading a spool file |
argument |
|
30.0 s |
timing |
HTTP timeout of a hub client request |
argument of the constructor (and this table for the default) |
|
30.0 s |
timing |
HTTP timeout of conatus.studio.connect() |
argument |
|
8 runs |
display |
runs compared at once (the page’s categorical palette has 8 slots, never cycled) |
edit the constant (and this table) |
|
2000 points |
display |
points per curve of a comparison (uniform stride) |
argument |
|
4096 bytes |
limit |
fork/set parameters accepted by the hub |
edit the constant (and this table) |
|
10.0 s |
timing |
how long the hub waits for the relay to acknowledge a command |
edit the constant (and this table) |
|
720 frames |
limit |
film frames kept per run and name in the hub’s memory (then thinned) |
edit the constant (and this table) |
|
1048576 bytes |
security |
largest HTTP request body (commands and jobs are a few kB) |
edit the constant (and this table) |
|
4 requests |
security |
concurrent long polls per caller (more: HTTP 429) |
edit the constant (and this table) |
|
512 bytes |
limit |
a command’s fields longer than this are not copied whole into the audit log |
edit the constant (and this table) |
|
20000 records |
limit |
scalar records kept per run in the hub’s memory |
argument of the constructor (and this table for the default) |
|
2000 lines |
limit |
log lines kept per run in the hub’s memory |
argument of the constructor (and this table for the default) |
|
200 runs |
limit |
runs kept by the hub (beyond it the stalest run is evicted) |
argument of the constructor (and this table for the default) |
|
43200 s |
security |
browser session lifetime on the hub (12 h) |
argument of the constructor (and this table for the default) |
|
512.0 MiB |
limit |
film memory budget of the hub |
hub serve –film-mb (default 512) |
|
720 frames |
limit |
= FILM_MAX_FRAMES |
argument of the constructor (and this table for the default) |
|
4096 bytes |
security |
parameters of a job prepared from the Studio |
edit the constant (and this table) |
|
16384 bytes |
display |
bytes of a job log shown in the Studio |
edit the constant (and this table) |
|
20 submissions |
security |
Slurm submissions from the Studio per sliding hour (more: HTTP 429) |
argument of the constructor (and this table for the default) |
|
500 rows |
display |
registry rows sent to the page at most |
edit the constant (and this table) |
|
120.0 s |
security |
smallest period of the relay’s job-queue poll (at least 120 s between two scheduler calls) |
edit the constant (and this table) |
|
4096 bytes |
security |
fork parameters accepted by the relay |
edit the constant (and this table) |
|
2097152 bytes |
limit |
film history sent per poll at most (live data goes first) |
edit the constant (and this table) |
|
4194304 bytes |
security |
a single telemetry line longer than this is skipped and logged |
edit the constant (and this table) |
|
20.0 s |
timing |
WebSocket ping period of the relay |
edit the constant (and this table) |
|
20.0 s |
timing |
WebSocket ping timeout of the relay (then reconnect) |
edit the constant (and this table) |
|
1.0 s |
timing |
spool poll period |
relay –poll |
|
256000.0 bytes/s |
limit |
upload rate limit (token bucket) |
argument of the constructor (and this table for the default) |
|
131072 bytes |
limit |
largest message sent at once |
argument of the constructor (and this table for the default) |
|
1.0 s |
timing |
first reconnection delay (doubled up to max_backoff_s) |
argument of the constructor (and this table for the default) |
|
60.0 s |
timing |
longest reconnection delay |
argument of the constructor (and this table for the default) |
|
120.0 s |
timing |
job-queue poll period of the relay (>= SLURM_MIN_PERIOD_S; 0 = off) |
relay –slurm |
|
20.0 s |
timing |
WebSocket connection timeout |
argument of the constructor (and this table for the default) |
|
1048576 bytes |
security |
largest spool header read |
argument |
|
64 frames |
limit |
= conatus.telemetry.film.MAX_FRAMES_PER_REQUEST (re-exported) |
edit the constant (and this table) |
|
2000 records |
limit |
scalars per run in a light snapshot (uniform stride, last kept) |
edit the constant (and this table) |
|
64 frames |
limit |
frames in one HTTP answer of the film endpoint |
edit the constant (and this table) |
|
8388608 bytes |
limit |
about this many bytes per film answer (one frame at least) |
edit the constant (and this table) |
|
16777216 bytes |
limit |
film file indexed per poll (a 256 MiB film: 16 polls) |
edit the constant (and this table) |
|
4 factor |
limit |
film thinning: level j keeps every FILM_GROWTH**j-th frame |
edit the constant (and this table) |
|
1.0 s |
timing |
minimum wall time between two reads of the control file |
argument of the constructor (and this table for the default) |
|
48 points/axis |
limit |
3D frames strided to at most this many points per axis |
argument of the constructor (and this table for the default) |
|
16384 points |
limit |
points of a particle frame |
argument of the constructor (and this table for the default) |
|
0 bytes |
limit |
film budget of a bare Telemetry (0 = no film; sim.watch passes film_mb) |
argument of the constructor (and this table for the default) |
|
4096 points |
limit |
points of a curve record |
argument |
|
6 zlib level |
limit |
deflate level of film frames |
argument |
|
20 lines |
display |
log lines of a run’s full view |
argument |
|
20 events |
display |
events of a run’s full view |
argument |
|
2000 rows |
display |
scalar rows returned at most (uniform stride) |
argument |
|
100 records |
display |
log records and events returned |
argument |
|
25.0 s |
timing |
long-poll period of a wait for a change |
argument |
|
20 lines |
display |
= HubClient.status(log) |
argument |
|
20 events |
display |
= HubClient.status(events) |
argument |
|
2000 rows |
display |
= HubClient.scalars(max) |
argument |
|
100 records |
display |
= HubClient.log(n) |
argument |
|
25.0 s |
timing |
= HubClient.wait(timeout) |
argument |
|
0.5 s |
timing |
spool file poll period of a wait |
argument |
|
3 users |
display |
last distinct senders of a command shown for a run |
argument |
|
20 lines |
display |
= agentapi.run_view(log), on the hub |
argument |
|
20 events |
display |
= agentapi.run_view(events), on the hub |
argument |
|
64 frames |
limit |
= MAX_FRAMES_PER_REQUEST (frames per call) |
argument |
|
30.0 s |
timing |
wait for the writer thread to flush the last records at close |
argument |
|
30.0 s |
timing |
= HubClient(timeout), from the command line |
python -m conatus studio –timeout |
|
20 lines |
display |
= HubClient.status(log) |
studio status –log |
|
20 events |
display |
= HubClient.status(events) |
studio status –events |
|
25.0 s |
timing |
= HubClient.wait(timeout) |
studio watch –timeout |
|
100 records |
display |
= HubClient.log(n) |
studio log -n |
|
2000 rows |
display |
= HubClient.scalars(max) |
studio scalars –max |
|
512.0 MiB |
limit |
= Hub(film_mb) |
hub serve –film-mb |
|
0.0 s |
timing |
job-queue poll period of the local relay of |
hub serve –slurm |
|
1.0 s |
timing |
= Relay(poll_s) |
relay –poll |
|
256000.0 bytes/s |
limit |
= Relay(max_rate) |
relay –max-rate |
|
60.0 s |
timing |
= Relay(max_backoff_s) |
relay –max-backoff |