Skip to content

Logging runs

Everything in sillonpy, the client you import inside a simulation script.

import sillonpy as sp

Opening a run

A context manager. Opens a run and seals it on the way out, including when the block raises.

with sp.track_run(run_name="my_fit", project_name="demo", author="you"):
    sp.log_param("degree", 1)

Use it in a loop for a sweep — each iteration is its own run:

for degree in range(1, 6):
    with sp.track_run(project_name="sweep"):
        sp.log_param("degree", degree)
        ...

init — for a script that is one run

sp.init(run_name="my_fit", project_name="demo")
sp.log_param("degree", 1)
# sealed automatically when the interpreter exits

Call sp.force_dump() to seal it early, for instance before starting a second run in the same script.

All arguments are optional:

Argument Default
run_name a generated name, unique in the project
project_name empty
project_path the current directory
author, organisation empty
inherit no parent — see Provenance

@track — logging a function's call

Records a decorated function's arguments, duration and return value under generated keys. Handy for quick instrumentation, noisy for a real experiment:

@sp.track
def simulate(alpha, beta):
    return alpha * beta

Logging values

Parameters — what you chose

sp.log_param("degree", 3)                       # one
sp.log_param({"degree": 3, "solver": "lu"})     # a dict
sp.log_param(degree=3, solver="lu")             # keywords

Results — what came out

sp.log_result("rmse", 0.043)
sp.log_result("field", big_array)      # offloaded to HDF5 automatically

Large arrays are staged to disk and handed to the daemon without ever being serialised into the message, so logging a gigabyte costs you a file write, not memory.

Artifacts — files you produced

sp.log_result("mesh", path="out/mesh.vtk")                    # copied into the store
sp.log_result("scratch", path="tmp/big.dat", save_result=False)  # path recorded only

save_result=False records the path and its hash without copying the bytes — right for something huge that already lives somewhere durable.

Figures

fig, ax = plt.subplots()
ax.plot(x, y)

sp.log_figure(fig, name="fit", used=["coef", "degree"],
              caption="Linear fit over the noisy sample")

used= is the part worth using. It records which logged values produced the plot, so sillon show my_fit -f can tell you later:

fit  ← built from: coef, degree

You can log an existing image instead:

sp.log_figure(path="figures/fit.png", name="fit")

Metadata, tags and notes

sp.add_metadata("solver_version", "4.2")
sp.add_metadata({"cluster": "atlas", "queue": "long"})

sp.add_tag("baseline")
sp.add_tag("gpu", "overnight")          # several at once
sp.add_note("Re-ran after fixing the boundary condition")

log_metadata is an alias of add_metadata.

Some metadata is recorded for you on every run: hostname, working directory, the source of your main script, the imported modules, the runtime, and the final status.

What types can I log?

Anything JSON-serialisable, plus numpy arrays and scalars, complex numbers and Path objects. Arrays go to HDF5, the rest inline.

A custom object will be refused with a message naming the type:

TypeError: Object of type Simulation is not JSON serializable.
Convert it to a dict, list, or string before logging.

When something goes wrong

Failed log calls raise. If the daemon rejects a command, your script hears about it rather than continuing with an incomplete record:

Exception: UnknownCommand: no_such_command

That is deliberate — a silently dropped value would mean a run that claims data it does not have. See Troubleshooting.

Multiple runs in one script

for config in configs:
    with sp.track_run(project_name="sweep"):
        ...

track_run is the simple answer. With init, seal each run with sp.force_dump() before starting the next.

Logging to another directory

sp.init(project_path="/scratch/experiments/run-42")

Handy on a cluster where the job runs somewhere other than your project folder.