REPL & Diagnostics
The tables below are generated by reading the interpreter's own builtin dispatch table (fn call_builtin, engine/crates/qu-interp/src/lib.rs) directly — every row reflects a match arm that actually runs today, not aspirational API surface.
Output & Diagnostics
| Function | Signature | Description |
echo | echo(v1, v2, ...) | Alias of print/disp/writeline. Takes any number of positional arguments v1, v2, ... of any printable type (number, string, matrix, struct, ...) and prints each argument's display form space-joined on one line, followed by a newline. Returns none. |
write | write(v1, v2, ...) or write(fileHandle, s) | Dispatches on the type of the first argument. With ordinary values v1, v2, ... (any printable type): prints them space-joined, without a trailing newline (unlike print/echo). With a fileHandle (the handle object returned by fopen) as the first argument and a string s as the second: instead writes the raw string s to that open file, with no newline added and no console output. Returns none in both forms. |
error | error(msg) | Takes one argument, msg (a string, typically built with {...} interpolation), and raises an EvalError carrying that message. Never returns normally — control passes to the nearest enclosing try/catch, where the caught exception object exposes the string as .message; uncaught, it aborts the script. Returns nothing, ever — there is no value to receive, because control leaves the call. |
warn | warn(msg) | Takes one argument, msg (a string), and prints warning: {msg} to the console. Non-fatal — execution continues on the next line, unlike error. Returns Nothing. |
explain | explain("fnName") | Takes one argument, fnName (a string naming a user-defined function), and returns a string report — it does not print by itself, so wrap it in print(...) to see it. The report says whether that function is running the fast register-only ("soft-compiled") tier or falls back to the ordinary tree-walking interpreter, and — when interpreted — which construct disqualified it (a call to another user function, a loop, indexing, or a non-numeric value). This is the M4 "Phase 0" diagnostic surface: calling explain triggers the same lazy eligibility check an actual call would, so it works even before the function has ever been called, letting a script author ask "why is this function still slow" and get a real answer instead of a black box. Returns a Str, the report itself — the call prints none of it for you. |
Overloads: write
| Call | What it does |
write(v1, v2, ...) | Prints the values space-joined to the console, with no trailing newline — the ordinary-value form. |
write(fileHandle, s) | Writes the string s verbatim to the already-open file fileHandle; no console output, no newline added. |
Which form runs is decided entirely by the type of the first argument, not by argument count — passing a fileHandle as the sole argument, or a string as the first of several ordinary values, stays on the console-printing path.
write("a", 1, "b")
print("")
f = fopen("scratch_write_demo.txt", "w")
write(f, "hello file")
close(f)
g = fopen("scratch_write_demo.txt", "r")
print(read_all(g))
close(g)
a 1 b hello file
a 1 b
hello file
Timers
MATLAB-style tic/toc for quick wall-clock timing, plus a timer/ preciseTimer object for cases where a single global slot isn't enough (nested or concurrent timing regions).
Qu actually has four distinct profiling/timing mechanisms, and picking the wrong one is an easy mistake, so here they are side by side: tic/toc is the single global lap timer — simplest, for timing one region at a time. timer()/preciseTimer() is a standalone object for nested, concurrent, or pausable timing, one per variable instead of one shared global. profile_start/profile_end (below) measures a single named region's wall-clock time *and* memory (RSS delta), returning a plain value rather than mutating shared state, so multiple regions can overlap or nest freely. profiling_mode/profile_stats is a different shape entirely — it attributes call count and cumulative time *per builtin function* across a whole recording window, with no memory tracking; reach for it to find which builtin is slow, not to time one block of your own code. See also profiling_mode/profile_stats further below in this chapter for that fourth mechanism.
| Function | Signature | Description |
tic | tic() | Takes no arguments. Starts (or restarts) the single global stopwatch. timer.start() is sugar for this — see below. Returns Nothing. |
toc | toc() | Takes no arguments. Returns a number: elapsed seconds (float, fractional) since the last tic(). Does not clear the start, so repeated toc() calls measure from the same tic() — useful for lap-timing a loop. Errors if called before any tic(). timer.elapsed() and timer.stop() are both sugar for this (see below) — toc() was already a non-resetting peek, so both aliases inherit that for free. |
timer / preciseTimer | timer([interval]) / preciseTimer([interval]) | Takes one optional positional argument, interval (a number, in seconds; irrelevant for plain profiling use) — the tick period on elapsed(t)/on elapsedOnce(t) fire at. Returns a standalone, controllable timer object (a handle, conventionally bound to a variable t) — the fix for tic/toc's single-global-slot limitation. Starts not running; start(t) begins counting. preciseTimer sets an internal precise marker on the returned object but is otherwise identical to timer — both use the same monotonic clock today; the distinct name is for intent-signaling and forward compatibility. Not the same feature as the bare timer.start()/timer.elapsed()/timer.stop() sugar below — this row produces a real, independent, per-variable object (t = timer(), t.start(), t.pause(), ...); the sugar is the single-global-slot tic/toc MATLAB-style convenience wearing nicer verbs. Pick the object form for nested/concurrent/pausable timing, the bare sugar for quick single-shot profiling. |
start | start(t) | Takes one argument, t (a timer object from timer()/preciseTimer()). Begins counting, or resumes from pause. Returns none; idempotent on an already-running timer. |
pause | pause(t) | Takes one argument, t (a timer object). Freezes the count, keeping the accumulated total so start resumes from where it left off. Returns none; idempotent on an already-paused timer. |
elapsed | elapsed(t) | Takes one argument, t (a timer object). Returns a number: elapsed seconds so far. A pure read — never stops or resets the timer, callable regardless of running state. |
restart | restart(t) | Takes one argument, t (a timer object). Returns a number: the elapsed time up to this call (the completed "lap"), then resets the object to zero and immediately starts it running again — a one-call lap/checkpoint pattern for timing each iteration of a loop. |
stop | stop(t) | Takes one argument, t (a timer object). Freezes the timer and resets the count to zero (unlike pause, which keeps it); returns none. Also stops any on elapsed(t)/on elapsedOnce(t) registration from firing until started again. The handle remains valid and reusable. Returns Nothing. |
profile_start | profile_start() | Takes no arguments. Starts a named profiling window and returns a Record handle -- t0 (wall-clock seconds) and mem0 (RSS bytes, NaN if unsupported) -- to pass unmodified to profile_end. t0/mem0 are semi-internal (read by profile_end, not meant to be inspected directly); mem0 is NaN on a platform current_rss_bytes doesn't support (anything but Windows/Linux). Because the handle is an ordinary value rather than mutated global state (unlike tic), multiple windows — p1 = profile_start(), p2 = profile_start() — can overlap or nest without clobbering each other. Uses SystemTime rather than a monotonic clock, trading tic/toc's clock-step immunity for a value that can round-trip through a Record field — prefer tic/toc for sub-millisecond or clock-step-sensitive timing. See also timer/preciseTimer above for a controllable object, and profiling_mode/profile_stats below for per-builtin call-count and time attribution (no memory) across a whole recording window instead of one region's time and memory. |
profile_end | profile_end(handle) | Takes one argument, handle (the Record returned by profile_start(), unmodified). Closes the window and returns a Record with fields time (elapsed seconds) and mem (RSS delta in bytes, NaN if either sample was NaN, e.g. on an unsupported platform). mem is mem_now - mem0, sampled once at each end rather than polled, so a large allocation that also frees before profile_end runs is invisible to it — the same coarse-sampling trade-off qu-cli's own --profile/--max-memory watchdog documents for itself. Errors if handle is missing t0/mem0 or either field isn't a number, naming which field and why. See also tic/toc above for simpler lap timing with no memory tracking, and profiling_mode/profile_stats below for call-count/time attribution across many builtin calls instead of one region. |
estimate_complexity | estimate_complexity(sizes, times) | Takes two same-length numeric vectors/lists: sizes (the problem sizes swept over) and times (the matching elapsed times in seconds, e.g. from toc() at each size) — at least 3 matching points are required in both. Fits several candidate growth models (O(1), O(log n), O(n), O(n log n), O(n^2), O(n^3)) to the data by least squares and picks whichever has the best R², rather than a naive log-log slope (which misclassifies O(log n)/O(n log n)). As a side effect it prints the fit (label, R², coefficient); it also returns the label as a string (e.g. "O(n)"). Meant to pair with tic/toc: sweep over increasing problem sizes, record elapsed time at each, then call this on the two vectors. Returns a Str, the winning model's label (e.g. "O(n)"). |
timer.start() / timer.elapsed() / timer.stop() — Stopwatch-style sugar for tic/toc
Part of the same small, closed table./timer. "namespace-dot" alias set as table.load/table.write (§ ergonomic API layer, 2026-08-31): the parser recognizes exactly timer.start(/timer.elapsed(/timer.stop( and rewrites each, at parse time, straight to tic()/toc()/toc() — so evaluation is byte-identical to calling those directly. timer is not a namespace value; it still works as an ordinary variable (timer = 5), and this is completely unrelated to the real timer([interval]) object constructor above (a bare timer.start() never constructs one).
timer.start() # same as tic()
heavy_computation()
mid = timer.elapsed() # peek — does NOT reset, callable again
more_work()
total = timer.stop() # same as toc() — also a non-resetting read
A small reverse-mode autodiff tape. param/track are the same underlying operation; param is the spec's name for trainable weights and track is for "differentiate through this too" on an ordinary intermediate value — the distinction is documentation, not mechanism, until optimizers exist to read it.
| Function | Signature | Description |
track (alias param) | track(v) | Takes one argument, v — a plain number or a matrix (a bare [...] list literal is promoted to a column, matching to_matrix's convention). Returns a tracked value: internally a fresh leaf node on the gradient tape with no inputs, so grad treats it as a differentiation root, while remaining numerically transparent (a tracked scalar still reports type(...) as number; a tracked matrix prints as tensor([...], node #N)). Calling track on an already-tracked value is a no-op. |
stop_grad | stop_grad(v) | Takes one argument, v — a tracked value produced by track/param. Returns the plain, untracked inner value (same numeric content and shape) detached from the tape, so gradients don't flow through it — e.g. holding a target/label steady, or truncating backprop through time. |
grad | grad(loss, wrt=X) or grad(loss, X, Y, ...) | Takes loss — a tracked scalar that is the result of a computation built from tracked inputs — plus either a keyword argument wrt=X (one tracked tensor X) or one or more positional tracked tensors X, Y, .... Runs the backprop walk over the tape. The keyword form wrt=X returns a single gradient value with the same shape as X (a number for a scalar X, a matrix for a matrix X); the positional form grad(loss, X, Y, ...) returns a List of such gradients, one per argument, in the same order (positional rather than wrt=[X, Y], because Qu's [...] bracket literal is numeric-matrix syntax and would silently flatten a list of tensors down to plain numbers, losing the node identity grad needs). |
Overloads: grad
| Call | Returns |
grad(loss, wrt=X) | A single gradient value shaped like X — a number for a scalar X, a matrix for a matrix X. |
grad(loss, X, Y, ...) | A List of gradient values, one per positional argument, in the same order. |
x = track(3.0)
y = track(2.0)
loss = x * x + y * y
dloss_dx = grad(loss, wrt=x) # 2*x = 6
grads = grad(loss, x, y) # [2*x, 2*y] = (6, 4)
print("wrt=x form: {dloss_dx}")
print("positional form: {grads}")
wrt=x form: 6 positional form: (6, 4)
wrt=x form: 6
positional form: (6, 4)
RNG Control
The base random-number-generator controls. The named distributions built on top of these (rand/randn shape helpers, and the wider distribution library) are documented in the Statistics & ML chapter — not duplicated here.
| Function | Signature | Description |
rand / randn | rand(rows[, cols]) / randn(rows[, cols]) | Takes rows (a number, the row count) and an optional cols (a number, the column count; when omitted, defaults to 1, giving a rows×1 column vector). Draws rows*cols samples — uniform on [0, 1) for rand, standard-normal (mean 0, variance 1) for randn — and returns them as a rows×cols numeric matrix, drawn from the persistent RNG stream. An optional seed= keyword argument (a number) switches to a fresh, independent, reproducible local stream instead of the persistent one, without disturbing the persistent stream's state. |
seed | seed(n) | Takes one argument, n (a number, the seed value). Reseeds the interpreter's persistent random stream and returns none, so subsequent unseeded rand/randn calls (and anything downstream that draws from self.rng, including spawn/pmap worker seeds) become reproducible — two calls to seed(n) with the same n are followed by identical draws. |
Overloads: rand / randn
| Call | Behavior |
rand(rows) / randn(rows) | cols defaults to 1 — returns a rows×1 column vector, drawn from the persistent stream. |
rand(rows, cols) / randn(rows, cols) | Returns a rows×cols matrix, drawn from the persistent stream. |
rand(rows, cols, seed=n) | Draws from a fresh, independent, local stream seeded with n instead — the persistent stream's position is left completely untouched, which is the surprising part. |
a = rand(3)
print("shape a: {size(a)}")
seed(5)
p = rand(2, 2)
junk = rand(2, 2, seed=42)
q = rand(2, 2)
seed(5)
p2 = rand(2, 2)
q2 = rand(2, 2)
print("q == q2: {q == q2}")
shape a: [3, 1] q == q2: [1, 1; 1, 1]
shape a: [3, 1]
q == q2: [1, 1; 1, 1]
The seed=42 draw of junk never touches the persistent stream: q (drawn after it) still matches q2, drawn from a freshly-reseeded stream with no interloping seed= call at all.
Units
Qu numeric literals can carry a physical-unit suffix directly (e.g. 5 kHz), which normalizes the value to SI base units at parse/eval time — opt-in dimensional bookkeeping on plain numbers, not a wrapper type. This table lists every suffix the interpreter's apply_unit function actually recognizes and its normalization scale; anything not listed (dB, degC, cycles, samples, ...) passes through unscaled (factor 1), as confirmed by 3 dB evaluating to plain 3.
| Suffix | Meaning |
Hz | Hertz, base unit (×1) |
kHz | Kilohertz (×1e3) |
MHz | Megahertz (×1e6) |
GHz | Gigahertz (×1e9) |
s | Seconds, base unit (×1) |
ms | Milliseconds (×1e-3) |
us | Microseconds (×1e-6) |
ns | Nanoseconds (×1e-9) |
V | Volts, base unit (×1) |
mV | Millivolts (×1e-3) |
kV | Kilovolts (×1e3) |
A | Amperes, base unit (×1) |
mA | Milliamperes (×1e-3) |
uA | Microamperes (×1e-6) |
Ohm | Ohms, base unit (×1) |
mOhm | Milliohms (×1e-3) |
kOhm | Kiloohms (×1e3) |
MOhm | Megaohms (×1e6) |
F | Farads, base unit (×1) |
uF | Microfarads (×1e-6) |
nF | Nanofarads (×1e-9) |
pF | Picofarads (×1e-12) |
H | Henries, base unit (×1) |
mH | Millihenries (×1e-3) |
uH | Microhenries (×1e-6) |
W | Watts, base unit (×1) |
mW | Milliwatts (×1e-3) |
kW | Kilowatts (×1e3) |
k | Bare SI prefix, no base unit (×1e3) |
M | Bare SI prefix, no base unit (×1e6) |
G | Bare SI prefix, no base unit (×1e9) |
m | Bare SI prefix, no base unit (×1e-3) |
u | Bare SI prefix, no base unit (×1e-6) |
p | Bare SI prefix, no base unit (×1e-12) |
a | Bare SI prefix, no base unit (×1e-18) |
User-defined units
unit <name> = <expr> declares a named scale factor, for a physical unit this table doesn't already cover (ppm, a lab-specific calibration constant, ...). unit is contextual, not a reserved word — a plain identifier everywhere else, only parsed as this declaration when it's immediately followed by another bare identifier and then =.
unit ppm = 1e-6
x = 5 * ppm # 5e-6 -- ordinary arithmetic, works immediately
y = 5 ppm # 5e-6 -- literal-suffix adjacency also works
print(unit_scale("ppm")) # 0.000001 -- reads the declaration back
1e-6
The grammar already parses <number> <identifier> for any trailing identifier, not just the statically-known spellings in this chapter's table above (that widening — and why a truly unknown unit is a runtime error naming it, not a parse failure — predates user-defined units). unit name = value is what makes a PREVIOUSLY-unknown identifier resolve instead of erroring "unknown unit": it registers name in a runtime table that 5 <name> literal adjacency, ordinary * arithmetic, and unit_scale(name) all consult.
| Function | Signature | Description |
unit_scale | unit_scale(name) | name: a string. Returns the scale factor a unit name = ... declaration registered, or errors naming name if nothing was ever declared under it. |
LaTeX Formatting
General LaTeX-formatting of any value — distinct from the plotting subsystem's own figure-export formats. tex mirrors str; printtex mirrors print.
| Function | Signature | Description |
tex | tex(x) | Takes one argument, x — any value (number, complex number, vector, or matrix). Returns a string: a complete LaTeX math-mode snippet wrapped in $...$ delimiters. Numbers/complex values reuse the same scientific-notation splitting as ordinary display formatting but rendered as \times 10^{n} (e.g. 4.7e-8 becomes $4.7 \times 10^{-8}$); vectors and matrices become a bmatrix environment (e.g. [1, 2, 3] becomes $\begin{bmatrix} 1 & 2 & 3 \end{bmatrix}$). |
printtex | printtex(x) | Takes one argument, x — any value, same acceptance as tex. Same rendering as tex(x), printed directly to the console (with a trailing newline) instead of returned. Returns none. |
Overloads: tex
Case: scalar
x a real number. Small/large magnitudes get split into \times 10^{n} scientific notation; otherwise it's printed as an ordinary decimal.
print(tex(4.7e-8))
$4.7 \times 10^{-8}$Case: complex
x a Complex value. Each component is formatted the same way a real scalar would be (including the scientific-notation split when a component is small/large), then joined as re + im i.
print(tex(3 + 4j))
$3 + 4j$
Case: vector or matrix
x a Vec/Mat. Returns a bmatrix environment instead of a bare number, with & separating entries.
print(tex([1, 2, 3]))
$\begin{bmatrix} 1 & 2 & 3 \end{bmatrix}$printtex follows the exact same three cases — it just prints instead of returning the string.
Examples
tic/toc are the quickest way to answer "how long did that take": start the single global stopwatch with tic(), run the code, then read elapsed seconds with toc(). Because toc() is a non-resetting peek, the same tic() can be read multiple times to lap a loop without restarting the clock each iteration. The example below times a million square-root calls and prints the elapsed seconds:
tic()
for i in 1 to 1000000
x = sqrt(i)
end
elapsed_s = toc()
print("took {elapsed_s:.4f}s")
took 0.0079s
track and grad are the entry points to Qu's reverse-mode autodiff tape: wrap the inputs you want a derivative with respect to in track (or its alias param), build an ordinary arithmetic expression from them, and then call grad on the result to walk the tape backwards. Here x is tracked, y = x^2 + 2x is computed from it, and grad returns dy/dx evaluated at x = 3 (analytically 2x + 2 = 8), which is then printed:
x = track(3.0)
y = x * x + 2 * x
dy_dx = grad(y, wrt=x) # 2*x + 2 = 8
print("dy/dx = {dy_dx}")
A numeric literal followed directly by a recognized suffix is normalized to its SI base value at the moment it is parsed — no explicit conversion call needed. 5 kHz becomes 5000 (hertz, the base unit) and 50m uses the bare SI-magnitude prefix form (no base unit attached) to mean 50 * 1e-3. Both results are ordinary numbers from that point on, so they print like any other value:
Fs = 5 kHz # normalizes to 5000
dt = 50m # bare SI-magnitude prefix: 50 * 1e-3 = 0.05 (no base unit)
print("Fs = {Fs}, dt = {dt}")
Fs = 5000, dt = 0.05
estimate_complexity turns a handful of (size, time) measurements into an empirical Big-O label, which is the natural finishing move after lap-timing a sweep with tic/toc. The example below times summing the first n integers at four increasing sizes, collecting each elapsed time into a times list with append, iterating the sizes list by value with for n in sizes rather than by index. estimate_complexity prints its own fit summary as a side effect and also returns the label string, which is printed again explicitly here for clarity:
sizes = [100, 1000, 10000, 100000]
times = []
for n in sizes
tic()
total = 0
for j in 1 to n
total = total + j
end
dt = toc()
times = append(times, dt)
end
label = estimate_complexity(sizes, times) # e.g. "O(n)"
print("estimated complexity label = {label}")
echo prints several values space-joined on one line, which is handy for quick ad-hoc labeled output without building an interpolated string. warn is for a non-fatal notice: it prints a warning:-prefixed line and execution continues immediately afterward, unlike error, which aborts the current call unless caught:
echo("temperature =", 23.5, "C")
warn("input clipped to [0, 1]")
print("done")
error is how a function signals "this input is invalid" in a way a caller can recover from with try/catch. The raised exception's .message field carries the exact interpolated string passed to error, which is pulled out and printed in the catch block below:
function check_positive(x)
if x < 0
error("check_positive: x must be >= 0, got {x}")
end
return x
end
try
check_positive(-3)
catch err
print("caught: {err.message}")
end
explain is the transparency tool for the soft-compile tier: point it at the name of a user-defined function to find out whether calls to it are running the fast register-only path or falling back to the tree-walking interpreter, and why. Here a trivial numeric function is defined and then explained, printing its soft-compile report:
function double(x)
return x * 2
end
print(explain("double"))
A standalone timer() object is the tool for lap-timing when a single global tic/toc slot isn't enough — for example, timing nested or concurrent regions independently. restart returns the elapsed time for the lap that just finished and immediately starts a new one, while pause simply freezes the running total in place without resetting it:
t = timer()
start(t)
lap1 = restart(t)
lap2 = restart(t)
pause(t)
print("lap1={lap1}, lap2={lap2}")
stop_grad detaches a tracked value from the autodiff tape so gradients stop flowing through it — the typical use is holding a label or target value steady while still letting gradients flow through the prediction it is compared against. Below, target is tracked and then immediately detached with stop_grad, so only x (the prediction) contributes to dloss/dx:
target = stop_grad(track(5.0))
x = track(2.0)
loss = (x - target) * (x - target)
dloss_dx = grad(loss, wrt=x)
print("dloss/dx = {dloss_dx}")
seed makes the persistent random stream reproducible: reseeding with the same number before each draw guarantees the same sequence of values out of rand/randn. The example below draws two 1x3 uniform vectors around a re-seed and prints both to show they match exactly:
seed(42)
a = rand(1, 3)
seed(42)
b = rand(1, 3)
print("a = {a}")
print("b = {b}")
tex/printtex are for dropping a Qu value straight into a LaTeX document. A very small or large magnitude number gets rendered in \times 10^{n} scientific notation, and a vector or matrix becomes a bmatrix environment. The example prints the LaTeX string for a tiny number with tex, then prints a vector's LaTeX form directly with printtex:
c = 4.7e-8
print("tex string: {tex(c)}")
printtex([1, 2, 3])
builtins, apropos, and help are the engine's own self-description tools — useful when you know roughly what you want but not the exact name. builtins() lists every name the engine answers to, apropos filters that list by a substring, and help reports what is known about one specific name. The example counts the total builtin surface, searches it for anything filter-related, and asks for help on curve_fit:
names = builtins()
print("total builtins: {len(names)}")
hits = apropos("filter")
print("filter-related: {hits}")
help("curve_fit")
device_used and gpu_probe_info answer "did that actually run on the GPU?" after a computation that's eligible for GPU dispatch, such as a large matrix multiply. device_used reports where the last dispatch actually went; gpu_probe_info reports what the one-time startup probe found about the hardware. Together they catch the failure mode of a GPU that silently fell back to the CPU:
A = randn(200, 200)
B = randn(200, 200)
C = A * B
d = device_used()
print("device = {d.device}, gpu_dispatches = {d.gpu_dispatches}")
p = gpu_probe_info()
print("gpu available = {p.available}, crossover = {p.crossover}")
More functions
| Function | Signature | Description |
builtins | builtins() | Takes no arguments. Returns a List of strings: every name the engine answers to (hundreds of entries; the exact count grows as builtins are added). The builtin index is this list joined against the chapters. |
apropos | apropos("filter") | Takes one argument, a search substring (a string, e.g. "filter"). Returns a List of strings: every builtin name containing that substring, in engine-registration order. Returns a list rather than printing, so a script can count (len(...)) or loop over the hits. |
help | help("curve_fit") | Takes one argument, a builtin name (a string, e.g. "curve_fit"). Prints what is known about that name directly to the console and returns none — that it exists, near matches when it does not (e.g. help("not_a_real_fn") suggests close spellings), and where the worked examples live. Honest about its limits — the engine carries no per-builtin prose. Returns Nothing; the report goes straight to the console. |
sandbox_mode | sandbox_mode(on) | Takes one argument, on (a boolean). Refuses file and network access for the rest of the session and returns none. One way only — passing sandbox_mode(false) does not turn it back off, which is the point; e.g. after sandbox_mode(true), a subsequent fopen(path, "w") raises a catchable error instead of opening the file. Returns Nothing. |
profiling_mode, profile_stats | profiling_mode(true) | profiling_mode takes one argument, a boolean (true to start recording, false to stop), and returns none. profile_stats() takes no arguments and returns a Table value with columns function, calls, and seconds, one row per builtin called while recording was on. Record per-builtin time and call counts, then read them back; off by default, because the recording itself costs something. Returns a Table from profile_stats, with columns function, calls and seconds; profiling_mode hands back no value. See also the Timers section above, which lays out all four of Qu's profiling/timing mechanisms side by side (tic/toc, timer/preciseTimer, profile_start/profile_end, and this one) and says when to reach for which. |
device_used | device_used() | Takes no arguments. Returns a struct-like value with two fields: .device (a string, e.g. "cpu" or a GPU device name) and .gpu_dispatches (a number, the count of operations actually dispatched to the GPU). Reports where the accelerated work actually went — a GPU that quietly fell back to the CPU is the failure this catches. |
gpu_probe_info | gpu_probe_info() | Takes no arguments. Returns a struct-like value with three fields: .available (a boolean, whether a GPU backend was found), .crossover (a number, the measured matrix/problem size above which the GPU wins over the CPU — inf on a build without GPU support), and .samples (a list of the timed sample points used to compute the crossover). Reports what the GPU probe found at startup. |