Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions config/general.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@

updates:
iterations_per_quick_update: 1e99 # Non-linear search iterations between every quick update, which just displays the maximum likelihood model fit.
iterations_per_full_update: 1e99 # Non-linear search iterations between every full update, which outputs all visuals and result fits (e.g. model.result, search.summary), this exits the search and can be slow.
iterations_per_full_update: 1e99 # Non-linear search iterations between every full update, which outputs all visuals and result fits (e.g. model.result, search.summary), this exits the search and can be slow.
quick_update_background: false # If True, perform_quick_update runs on a background daemon thread so the sampler is never blocked while visuals render. Requires the Analysis subclass to set `supports_background_update = True`.
live_visual_update: false # If True, quick-update visuals are pushed to a live surface (a Jupyter cell when in a kernel, a matplotlib viewer subprocess otherwise) in addition to being written to disk.
hpc:
hpc_mode: false # If True, use HPC mode, which disables GUI visualization, logging to screen and other settings which are not suited to running on a super computer.
iterations_per_quick_update: 10000 # Non-linear search iterations between every quick update, which just displays the maximum likelihood model fit.
iterations_per_quick_update: 10000 # Non-linear search iterations between every quick update, which just displays the maximum likelihood model fit.
iterations_per_full_update: 1e99 # Non-linear search iterations between every full update, which outputs all visuals and result fits (e.g. model.result, search.summary), this exits the search and can be slow.
quick_update_background: false # Keep off on HPC: background rendering pulls matplotlib into the sampler process even when no display is attached.
live_visual_update: false # Keep off on HPC: nodes are headless with no GUI and no notebook kernel, so the live display surface has nothing to attach to.
inversion:
check_reconstruction: true # If True, the inversion's reconstruction is checked to ensure the solution of a meshs's mapper is not an invalid solution where the values are all the same.
reconstruction_vmax_factor: 0.5 # Plots of an Inversion's reconstruction use the reconstructed data's bright value multiplied by this factor.
Expand Down
116 changes: 94 additions & 22 deletions notebooks/cookbooks/analysis.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -446,31 +446,72 @@
"Non-linear searches in **PyAutoFit** can produce on-the-fly visualization while the fit is still running, so you can\n",
"monitor whether the model is behaving sensibly long before the search finishes.\n",
"\n",
"This is controlled by two parameters on the search:\n",
"This is controlled by three independent parameters on the search. The cadence parameter is mandatory; the two\n",
"boolean flags are optional and can be toggled on independently of each other:\n",
"\n",
"- `iterations_per_quick_update=N` \u2014 every `N` likelihood evaluations the search calls\n",
" `analysis.perform_quick_update(paths, instance)` with the current maximum-likelihood `instance`. The default\n",
" `perform_quick_update` implementation (on subclasses like `AnalysisImaging` in PyAutoGalaxy / PyAutoLens) renders\n",
" the relevant subplot (`subplot_fit.png` for imaging, `subplot_tracer.png` for tracers, etc.) into the output\n",
" folder so users can refresh their file browser and see how the fit is progressing.\n",
" the relevant subplot (`fit.png` for imaging, etc.) into the output folder so users can refresh their file browser\n",
" and see how the fit is progressing.\n",
"- `background_quick_update=True` \u2014 runs `perform_quick_update` on a background daemon thread so the sampler is\n",
" never blocked while matplotlib renders and saves PNGs. A latest-only drop policy applies: if a new best-fit\n",
" arrives before the previous render finishes, the older request is silently replaced (we only ever care about the\n",
" most recent state).\n",
"\n",
"**Live in-cell rendering in Jupyter / Colab.** When the search runs inside a Jupyter or Colab notebook kernel, the\n",
"background worker additionally pushes each freshly-written `subplot_fit.png` into the active cell via\n",
"`IPython.display.update_display` with a stable `display_id`. The result: the cell that ran `search.fit(...)`\n",
"shows a **single self-updating image** during the fit, refreshing every `iterations_per_quick_update` evaluations,\n",
"rather than appending stacked frames or requiring you to open PNGs externally. No code change needed by the user\n",
"\u2014 the worker auto-detects the kernel.\n",
"\n",
"**Script mode is unchanged.** When running outside a kernel (e.g. `python my_fit.py` from a terminal), no\n",
"IPython side effects fire. The PNGs still land on disk under the search's output folder as before. `IPython` is\n",
"only imported when actually running inside a kernel.\n",
"\n",
"**Opt-out.** Set `PYAUTO_DISABLE_IPYTHON_DISPLAY=1` to skip the in-cell display step even inside a kernel \u2014\n",
"useful for `papermill` / automated `nbconvert` pipelines that want PNGs on disk but no display side effects.\n",
" most recent state). Requires the `Analysis` subclass to declare its `perform_quick_update` thread-safe \u2014 see\n",
" `supports_background_update` below.\n",
"- `live_visual_update=True` \u2014 in addition to writing the PNG to disk, push the rendered image to a *live* display\n",
" surface. In a Jupyter / Colab kernel the active cell is refreshed in place via `IPython.display.update_display`\n",
" with a stable `display_id`. In a plain Python script run from a terminal, a small matplotlib viewer subprocess\n",
" is spawned that watches the PNG on disk and redraws when it changes. Independent of `background_quick_update`:\n",
" you can enable either flag, both, or neither.\n",
"\n",
"Both flags default to `False` and can also be set globally via the workspace's `config/general.yaml` under\n",
"`updates:`.\n",
"\n",
"__The Analysis API surface__\n",
"\n",
"The quick-update machinery is built on three hooks on `af.Analysis`. Read the source at\n",
"`autofit/non_linear/analysis/analysis.py` for the canonical signatures; the summary here is what subclasses need\n",
"to know in order to customize behaviour:\n",
"\n",
"- `perform_quick_update(paths, instance)` \u2014 the method the dispatcher calls every\n",
" `iterations_per_quick_update` evaluations. The base class implementation is a no-op; subclasses such as\n",
" `AnalysisImaging` override it to render a fit-specific subplot. Override this in your own `Analysis` subclass\n",
" when you want bespoke on-the-fly visuals (the `Analysis` example at the bottom of this section shows the API\n",
" shape).\n",
"- `supports_background_update` \u2014 class property defaulting to `False`. Override and set to `True` when your\n",
" `perform_quick_update` implementation is safe to call on a background daemon thread (i.e. no shared mutable\n",
" state with the sampler thread). When `False` the dispatcher runs the update inline on the sampler thread,\n",
" which keeps semantics simple at the cost of briefly pausing sampling.\n",
"- `supports_jax_visualization` \u2014 class property defaulting to `False`. Reserved for a forthcoming JAX-native\n",
" visualization path; today's renderers always materialise to NumPy before plotting. Mentioned here so subclass\n",
" authors recognise the property when they encounter it in the base class.\n",
"\n",
"The dispatcher itself lives at `Fitness.manage_quick_update` (in `autofit/non_linear/fitness.py`). After each\n",
"likelihood evaluation it checks whether the iteration counter has crossed `iterations_per_quick_update`, consults\n",
"`analysis.supports_background_update`, and routes either to the `BackgroundQuickUpdate` worker or to an inline\n",
"`perform_quick_update` call. Users do not normally call `manage_quick_update` directly \u2014 it is wired up\n",
"automatically when the search is constructed.\n",
"\n",
"__Jupyter / script display surfaces__\n",
"\n",
"When `live_visual_update=True` and the search runs inside a Jupyter or Colab kernel, the worker pushes each\n",
"freshly-written PNG into the active cell via `IPython.display.update_display` keyed on a stable `display_id`. The\n",
"result is that the cell which ran `search.fit(...)` shows a **single self-updating image** during the fit,\n",
"refreshing every `iterations_per_quick_update` evaluations rather than appending stacked frames. No per-cell\n",
"`clear_output()` call is needed and no code change is required by the user \u2014 the worker auto-detects the kernel.\n",
"\n",
"When `live_visual_update=True` and the search runs as a plain Python script (e.g. `python my_fit.py` from a\n",
"terminal), the worker instead spawns a small matplotlib viewer subprocess (`python -m autofit.non_linear.live_viewer`).\n",
"The subprocess polls the PNG on disk and redraws its matplotlib window whenever the file's mtime changes; it exits\n",
"cleanly on headless backends.\n",
"\n",
"When `live_visual_update=False` (the default) the PNGs still land on disk on the same cadence \u2014 only the live\n",
"display side-effect is skipped. `IPython` is only imported when actually running inside a kernel.\n",
"\n",
"**Opt-out.** Set `PYAUTO_DISABLE_IPYTHON_DISPLAY=1` to skip the in-cell display step even when `live_visual_update`\n",
"is enabled inside a kernel \u2014 useful for `papermill` / automated `nbconvert` pipelines that want PNGs on disk but\n",
"no display side effects.\n",
"\n",
"The API shape looks like this (commented out \u2014 see the workspace `start_here.py` for a runnable end-to-end\n",
"example):\n",
Expand All @@ -480,15 +521,46 @@
"# path_prefix=\"cookbooks/quick_update\",\n",
"# name=\"example\",\n",
"# iterations_per_quick_update=50, # call perform_quick_update every 50 likelihood evals\n",
"# background_quick_update=True, # run rendering on a daemon thread\n",
"# background_quick_update=True, # daemon-thread dispatch (analysis must opt in)\n",
"# live_visual_update=True, # push rendered PNG to a live surface (cell or matplotlib window)\n",
"# number_of_cores=1,\n",
"# )\n",
"# result = search.fit(model=model, analysis=analysis)\n",
"```\n",
"\n",
"When this runs inside a Jupyter cell, the cell output is a single image that refreshes ~once per 50 evaluations\n",
"until the search converges. When run as `python my_fit.py`, the PNGs land at\n",
"`output/.../<search>/image/subplot_fit.png` and update on disk on the same cadence."
"When this runs inside a Jupyter cell with `live_visual_update=True`, the cell output is a single image that\n",
"refreshes ~once per 50 evaluations until the search converges. When run as `python my_fit.py`, a matplotlib\n",
"window appears that refreshes on the same cadence. In both modes the PNGs also land at\n",
"`output/.../<search>/image/fit.png`.\n",
"\n",
"__Custom perform_quick_update example__\n",
"\n",
"The block below sketches a custom `Analysis` subclass that overrides `perform_quick_update` to emit a bespoke\n",
"visual every `iterations_per_quick_update` evaluations, and declares itself thread-safe by setting\n",
"`supports_background_update = True`:\n",
"\n",
"```python\n",
"# class AnalysisWithQuickUpdate(af.Analysis):\n",
"#\n",
"# supports_background_update = True # our perform_quick_update has no shared mutable state\n",
"#\n",
"# def __init__(self, data, noise_map):\n",
"# super().__init__()\n",
"# self.data = data\n",
"# self.noise_map = noise_map\n",
"#\n",
"# def log_likelihood_function(self, instance):\n",
"# ... # as usual\n",
"#\n",
"# def perform_quick_update(self, paths, instance):\n",
"# xvalues = np.arange(self.data.shape[0])\n",
"# model_data = instance.model_data_from(xvalues=xvalues)\n",
"# plt.plot(xvalues, self.data, \"k.\")\n",
"# plt.plot(xvalues, model_data, \"r-\")\n",
"# plt.title(\"Live max-likelihood fit\")\n",
"# plt.savefig(path.join(paths.image_path, \"fit.png\"))\n",
"# plt.clf()\n",
"```"
]
},
{
Expand Down
Loading
Loading