.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "howto/plot_watch_running_simulation.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code. .. rst-class:: sphx-glr-example-title .. _sphx_glr_howto_plot_watch_running_simulation.py: Watching a simulation that is still running ============================================ A run that takes an hour raises one question every few minutes: what is it doing? The progress line in the terminal that started it answers for that terminal; a colleague's notebook, a laptop at home, or the same notebook after the kernel was restarted all need another way in. The recipe has two halves. The analysis is given a ``project=`` directory, and streams into it as it marches — every energy sample, every port signal, the run's state. Any other process then opens that directory and lets the project report: as a table that moves, as a picture of the stored energy, or as a live panel in a notebook. .. GENERATED FROM PYTHON SOURCE LINES 17-27 .. code-block:: Python import os import tempfile import threading import time import magnelio as mio from magnelio import geo, ports .. GENERATED FROM PYTHON SOURCE LINES 29-35 A run to watch -------------- The structure does not matter for this page. A length of WR-90 waveguide with a port at each end is enough to make the solver march for a few seconds, which is all the following needs. .. GENERATED FROM PYTHON SOURCE LINES 35-47 .. code-block:: Python a, b, length = 22.86e-3, 10.16e-3, 60e-3 model = mio.GeometryModel(background="pec", boundary_conditions=mio.BoundaryConditions()) model.add(geo.Brick(origin=(-a / 2, -b / 2, 0), size=(a, b, length), material="air")) model.add_port(ports.PortWaveguide(name="p1", plane="zmin")) model.add_port(ports.PortWaveguide(name="p2", plane="zmax")) mesh = mio.Mesh.from_geometry( model, mio.MeshControl(max_cell_size=b / 12), f_max=12e9, verbose=False ) proj_dir = os.path.join(tempfile.mkdtemp(), "wr90") .. GENERATED FROM PYTHON SOURCE LINES 48-56 The solver, somewhere else -------------------------- On a real job the solver runs in another process — a batch job on a cluster, a second notebook, a script left running overnight. This page has to stay inside one interpreter, so the solver runs on a thread here; the reader below opens the directory and sees nothing but the files, exactly as a second process would. .. GENERATED FROM PYTHON SOURCE LINES 56-68 .. code-block:: Python def solve(): analysis = mio.AnalysisScatteringTD(mesh=mesh, f_min=8e9, project=proj_dir, verbose=False) analysis.run(excited=["p1"]) job = threading.Thread(target=solve) job.start() while not os.path.exists(os.path.join(proj_dir, "project.json")): time.sleep(0.05) .. GENERATED FROM PYTHON SOURCE LINES 69-79 Following the run ----------------- :func:`~magnelio.open_project` opens the directory; ``watch()`` looks at it every ``interval`` seconds and hands the project back whenever something changed — a run starting, a new energy sample, a run ending — until the project is finished. Each report here is one line: the status, the step the solver has reached, and the stored energy in dB below its peak, which is the number the run's stop criterion watches. .. GENERATED FROM PYTHON SOURCE LINES 79-91 .. code-block:: Python proj = mio.open_project(proj_dir) for snapshot in proj.watch(interval=0.25): run = snapshot.runs.get("p1_mode0") if run is None or run.state == "pending": print(f"{snapshot.status:8s} planned") continue level = "—" if run.energy_db is None else f"{run.energy_db:6.1f} dB below peak" print(f"{snapshot.status:8s} step {run.n_steps:6d} {level}") job.join() .. rst-class:: sphx-glr-script-out .. code-block:: none running planned running step 601 0.0 dB below peak running step 1501 -47.7 dB below peak running step 2401 -57.8 dB below peak running step 3301 -63.2 dB below peak running step 4201 -67.0 dB below peak done step 5101 -70.1 dB below peak .. GENERATED FROM PYTHON SOURCE LINES 92-107 The loop body *prints* what it wants seen — inside a loop a bare expression such as ``run.energy_db`` shows nothing, in a notebook as anywhere else. When the whole table is what you want, ``follow()`` is that loop ready-made: it shows the summary at every change and replaces it in place instead of scrolling — in a notebook the cell output is cleared and redrawn, on a terminal the table is redrawn over its own lines. On a finished project it shows the final state once: what ran, how long it took, and why each run stopped. ``follow(plot=True)`` adds the energy plot below the table, redrawn with it — a figure drawn inside a loop of your own would not show until the cell ends, because the notebook's inline backend flushes a cell's figures when the cell is over. ``plot=`` also takes a callable ``draw(project, ax)`` for a picture of your own, with your own axis limits. .. GENERATED FROM PYTHON SOURCE LINES 107-110 .. code-block:: Python proj.follow(interval=0.25) .. rst-class:: sphx-glr-script-out .. code-block:: none Project /tmp/tmpd0j04vta/wr90 analysis AnalysisScatteringTD status done last call started 2026-09-09 09:38:56, 2.8 s ago created 2026-09-09 09:38:56 runs 1 run excited state steps energy elapsed stop reason ──────── ─────── ───── ───── ──────── ─────── ─────────── p1_mode0 p1:0 done 5101 -70.1 dB 1.4 s energy .. raw:: html
Project /tmp/tmpd0j04vta/wr90
analysisAnalysisScatteringTD
statusdone
last callstarted 2026-09-09 09:38:56, 2.8 s ago
created2026-09-09 09:38:56
runs1
runexcitedstatestepsenergyelapsedstop reason
p1_mode0p1:0done5101-70.1 dB1.4 senergy


.. GENERATED FROM PYTHON SOURCE LINES 111-119 The picture behind the line --------------------------- ``plot_energy`` draws every run's stored energy in dB below its peak — the same figure the progress line and the table report, over the whole run — with the energy criterion as a dashed line. On a project that is still marching it shows the curve so far; repeat the cell to see it grow. .. GENERATED FROM PYTHON SOURCE LINES 119-123 .. code-block:: Python fig, ax = proj.plot_energy() ax.set_title("Stored energy in the grid, one curve per run") .. image-sg:: /howto/images/sphx_glr_plot_watch_running_simulation_001.png :alt: Stored energy in the grid, one curve per run :srcset: /howto/images/sphx_glr_plot_watch_running_simulation_001.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none Text(0.5, 1.0, 'Stored energy in the grid, one curve per run') .. GENERATED FROM PYTHON SOURCE LINES 124-135 A panel that keeps itself current --------------------------------- In a notebook, ``proj.monitor()`` returns a widget: the run table above the energy plot, refreshed from a background thread every few seconds until the project is finished. Leave it as the last expression of a cell; the cell returns at once and the panel keeps moving while you work in other cells. It needs the ``jupyter`` extra (``pip install 'magnelio[jupyter]'``), and it is a notebook thing — this page is built without one, so the panel is only assembled here, not shown. .. GENERATED FROM PYTHON SOURCE LINES 135-143 .. code-block:: Python try: panel = proj.monitor(interval=2.0) except ImportError: panel = None else: panel.stop() .. GENERATED FROM PYTHON SOURCE LINES 144-151 Doing something at every change ------------------------------- Anything that should happen whenever the store changes — redraw a figure, append a line to a log, push a message — is a callable handed to ``watch(on_change=...)``. The loop then runs inside ``watch``, which returns the project when the run is finished: .. GENERATED FROM PYTHON SOURCE LINES 151-156 .. code-block:: Python changes = [] mio.open_project(proj_dir).watch(interval=0.25, on_change=lambda p: changes.append(p.status)) print(f"{len(changes)} report(s) on a finished project; the last says {changes[-1]!r}") .. rst-class:: sphx-glr-script-out .. code-block:: none 1 report(s) on a finished project; the last says 'done' .. GENERATED FROM PYTHON SOURCE LINES 157-177 What to remember ---------------- * **``project=`` is the switch.** Without it a run lives in the memory of the process that computes it; with it, everything lands on disk as it happens, and any process may look. * **The project reads itself.** A project that is not finished re-reads its index whenever the file changes, so typing ``proj`` again shows the current state — no ``refresh()`` needed while it marches. * **``watch`` polls, on purpose.** Every energy sample goes to disk at once and the index is replaced atomically, so a poll every few seconds sees everything and works on any file system a batch job might write to; there is nothing to subscribe to. * **One number to look at.** Stored energy in dB below the peak is what the stop criterion watches, what the progress line shows, what the table lists and what ``plot_energy`` draws. * **A dead run reads ``stale``.** A run whose solver process no longer exists on this host is not ``running``; ``watch`` ends on it the way it ends on ``done``. .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 2.619 seconds) .. _sphx_glr_download_howto_plot_watch_running_simulation.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_watch_running_simulation.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_watch_running_simulation.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_watch_running_simulation.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_