Repository navigation
feat(interop): COOLEST observation pixel grid, skip-and-record unsupported profiles, Euclid pipeline output #739
Description
Overview
Aaron's review of al.interop.coolest.to_coolest() found that the written template's observation.pixels block (field_of_view_x/y, num_pix_x/y) is always 0: to_coolest builds lazy.Observation() with no pixel grid and COOLEST's plotting API cannot handle that. The full Euclid DR1 run (~15,000 lenses, next week) must write COOLEST by default from euclid_strong_lens_modeling_pipeline, which today has no COOLEST reference at all — and its fitted models (MGE lens light, Delaunay pixelized source) have no COOLEST analytic equivalent, so to_coolest raises on every result. Decision (user-confirmed): skip + record unsupported components; pixel-grid export is a follow-up prompt.
Plan
- Fix
to_coolestso it writes a realPixelatedRegularGridobservation grid fromshape_native+pixel_sizeor adataset=, and warns when neither is given. - Add
on_unsupported="skip": export the full mass model and any Sersic light, omit MGE/pixelized components, list them undermeta.skipped_profiles. Default stays"raise". - Update the autolens_workspace COOLEST guide to pass a pixel grid and document the skip policy.
- Euclid pipeline: every search writes
files/coolest.jsonfromsave_results, guarded likewcs.json; the inspection bundle collectscoolest.json/coolest_sersic.json; README gains a final "COOLEST output" section with the Galan et al. 2023 JOSS citation. - euclid_dr1 science clone (after the pipeline PR merges): merge main,
submit_reload_coolest, ledger page; RAL reload leg on the human's go.
Detailed implementation plan
Affected Repositories
- PyAutoLens (primary, library — ships first)
- autolens_workspace (workspace)
- euclid_strong_lens_modeling_pipeline (workspace)
Branch Survey
| Repository | Current Branch | Dirty? |
|---|---|---|
| ./PyAutoLens | main | clean |
| ./autolens_workspace | main | clean (claimed by multi-galaxy-j1011-real-data, files disjoint) |
| ./euclid_strong_lens_modeling_pipeline | main | clean (claimed by sersic-variants #75, sed-chain-cpu-route #70, sersic-variants-analysis, simulator-from-result-linear; all file sets disjoint from this task) |
Suggested branch: feature/coolest-observation-grid
Work Classification: Both (library first)
Worktree root: ~/Code/PyAutoLabs-wt/coolest-observation-grid/
Implementation Steps
1. PyAutoLens — autolens/interop/coolest.py
to_coolest(galaxies, file_path, cosmology=None, mode="MAP", pixel_size=0.1, shape_native=None, dataset=None, on_unsupported="raise", metadata=None) -> str
- Observation grid: if
dataset(anything with.shape_nativeand.pixel_scales) is given,pixel_size = dataset.pixel_scales[0],shape_native = dataset.shape_native. Withshape_native=(ny, nx)buildlazy.PixelatedRegularGrid(field_of_view_x=(-nx*ps/2, nx*ps/2), field_of_view_y=(-ny*ps/2, ny*ps/2), num_pix_x=nx, num_pix_y=ny)and passlazy.Observation(pixels=grid).num_pix_xis columns (shape_native[1]). Nofits_path(check_external_files=Falsealready). Neither given → unchanged zeros pluswarnings.warnthat COOLEST plotting needs a pixel grid. - Unsupported components: wrap the
coolest_dict_from_light/coolest_dict_from_masscalls; onValueErrorwithon_unsupported="skip"append{"galaxy": "galaxy_<i>", "profile": <name>}tometadata["skipped_profiles"]and continue;"raise"re-raises (existing behaviour, workspace_test round-trip untouched); other values →ValueError. ABasis(MGE) is itself aLightProfileingalaxy.cls_list_from(cls=LightProfile)— skip it as one entryBasis(Gaussian x N). APixelizationis not aLightProfile; record it viagalaxy.cls_list_from(cls=Pixelization)asPixelization(<mesh cls>). A galaxy with everything skipped still exports as an entity with empty models. - Docstring: new params, the metadata key.
Tests test_autolens/interop/test_coolest.py: observation block from shape_native=(10, 12), pixel_size=0.05 → fov_x (-0.3, 0.3), fov_y (-0.25, 0.25), num_pix 12/10, instrument pixel size == fov_x span / num_pix_x; dataset= path; no grid → warning, zeros; MGE + pixelized galaxy default raises, skip writes the mass model and records both; from_coolest on the skipped template returns the mass-only tracer.
2. autolens_workspace — scripts/guides/coolest_interop.py
Export cell passes shape_native + pixel_size (or dataset=), prints the observation block, one sentence on why COOLEST needs the grid. New short section "Unsupported profiles" after __Supported Profiles__. Regenerate the notebook per this workspace's convention; no unrelated notebook diffs. autolens_workspace_test/scripts/misc/interop/coolest_round_trip.py only if the new warning makes the smoke run noisy.
3. euclid_strong_lens_modeling_pipeline
util.pyAnalysisImaging.save_results: afterwcs.json, a module-level helper (e.g.coolest_json_from(...)) wrapsal.interop.coolest.to_coolest(galaxies=result.max_log_likelihood_tracer, file_path=paths._files_path / "coolest", dataset=self.dataset, on_unsupported="skip", metadata={"pipeline": ..., "search": paths.name, "dataset": <name>}). Guarded like thewcs.jsonblock:ImportErrorand any other exception →logging.warning("coolest.json: ..."), never fails the fit.scripts/tools/build_inspect.pyprocess_dataset: best-effortcoolest.json(frominitial_lens_model/vis_pixmemberfiles/coolest.json) andcoolest_sersic.json(fromsersic_lens_model/vis) via the existing zip/dir member streaming.catalogue/README.mdtable: two new rows;build_inspection_bundle.shheader comment updated.README.md: final section "COOLEST output" (after "Visualization"): every search writesfiles/coolest.json(MAP template: full mass model, Sersic light; MGE and pixelized components undermeta.skipped_profiles); bundle collectscoolest.json/coolest_sersic.json;pip install autolens[coolest](add[coolest]to the Getting Started install line); missing package = logged warning. Citation Galan et al. 2023, JOSS 8(88) 5567, doi:10.21105/joss.05567 — BibTeX copied from the installedcoolestMETADATA; link the workspace guide.AGENTS.md: one line in the output-contract list if present.- Tests:
tests/test_coolest_output.py(not slow, JAX-free) — helper on a Basis+Pixelization+SIE+shear tracer writes JSON whose grid matches a 20x20 / 0.1" mock dataset and whose metadata lists the skipped profiles; monkeypatchedImportError→ warning, no file. Extend the slow run-level test that assertswcs.jsonto assertfiles/coolest.json.smoke_tests.txtunchanged. - Pre-run checklist (human):
pip install coolestin the RAL venv/mnt/ral/jnightin/PyAuto—HPCPullPyAutosyncs library mains, not new pip deps.
4. euclid_dr1 science clone (/mnt/c/Users/Jammy/Science/euclid_dr1), after the pipeline PR merges
git merge origin/main (never push); pytest tests/test_coolest_output.py -q; hpc/batch_cpu/submit_reload_coolest copied from submit_reload_wcs (array 0,2-9, no --seed); output.force_pickle_overwrite: true toggle + revert commits; ledger page wiki/project/2026-09-<dd>-coolest-json-reload-pass.md. RAL leg (push, submit, pull, rebuild dr1_sep1 bundle, confirm coolest.json per lens) only on the human's go.
Key Files
PyAutoLens/autolens/interop/coolest.py—to_coolestobservation grid + skip policyPyAutoLens/test_autolens/interop/test_coolest.py— testsautolens_workspace/scripts/guides/coolest_interop.py— guideeuclid_strong_lens_modeling_pipeline/util.py—save_resultshook + helpereuclid_strong_lens_modeling_pipeline/scripts/tools/build_inspect.py— bundle collectioneuclid_strong_lens_modeling_pipeline/README.md,catalogue/README.md— docs + citationeuclid_strong_lens_modeling_pipeline/tests/test_coolest_output.py— new tests
Verification
- PyAutoLens:
pytest test_autolens/interop -q; load a written template withcoolest's JSON serializer and assertobservation.pixels.num_pix_x == 100,field_of_view_x == (-5.0, 5.0); renderModelPlotteronce ifcoolest.api.plottingimports. - Workspace: run the guide end-to-end (round-trip deflection difference still ~1e-12); notebook regenerated.
- Pipeline:
pytest -q -m "not slow",pytest -q -m slow, smokeinitial_lens_model.pyunderPYAUTO_TEST_MODE, build the bundle on the test-mode output and confirmcoolest.jsonis collected; open onecoolest.jsonand check mass model,meta.skipped_profiles, and grid = shape × pixel scale.
Original Prompt
Click to expand starting prompt
COOLEST: fix the empty Observation pixel grid, emit coolest.json from the Euclid pipeline by default, carry it into euclid_dr1
Type: feature
Target: PyAutoLens
Repos:
- PyAutoLens
- autolens_workspace
- euclid_strong_lens_modeling_pipeline
Themes: - coolest
- interop
- euclid
Difficulty: medium
Autonomy: supervised
Priority: high
Status: formalised
Consequence: judge
Filed: 2026-09-16
Request (verbatim)
Hi Aaron,
The models and results for the 3,000 DR1 lenses described in ECLIPSE C are here: Next week I'm planning to model the full DR1 sample (~15,000 lenses). I've also added COOLEST outputs to PyAutoLens, which are described here:
https://github.com/PyAutoLabs/autolens_workspace/blob/main/scripts/guides/coolest_interop.py
These aren't included in the 3,000 models above. it's a new feature that I'll use for the full run next week.
The COOLEST guide and the conversion code above are fairly vibe-coded and have had less human oversight than I'd normally like. If you could take a look and confirm that everything makes sense before I blitz through the full DR1 sample next week, that'd be massively appreciated!Hey James,
I just wanted to let you know that I checked out the COOLEST conversion code. I noticed one small problem with the json it created. In the "observation/pixels" attribute, "field_of_view_x(/y)" and "num_pix_x(/y)" are all automatically set to 0. It appears that the al.interop.coolest.to_coolest() imports an empty COOLEST Observation object with these values preset to 0.
When I try to plot with these values in the COOLEST api, however, COOLEST doesn't know how to handle them. After editing the values in the json so that they result in a pixel scale equal to the one found in the "instrument" attribute, COOLEST makes a nice-looking plot. This was the only issue I was able to find so far!
Can you 1) Check and fix the issue Aaron brought up; 2) ensure COolest is supported documenting and working on the euclid_strong_lens_modeling_pipeline, dont make it front and centre but ensure runs output coolest by default and that the bottom of the README.md mentions this with citation this includes ensuring its output as a .json file when we do catalogue generation; 3) Add Coolest support to the euclid_dr1 science project and ensure its in the catalogue, if it isnt already!
Scope
- PyAutoLens —
to_coolestbuilds aPixelatedRegularGridforlazy.Observationfrom
shape_native+pixel_size(or adataset=), warns when no grid is given, and gains
on_unsupported="skip"which exports the mass model plus any Sersic light and records the
omitted components (MGEBasis, pixelized source) undermeta.skipped_profiles. Tests. - autolens_workspace — the COOLEST guide passes a pixel grid and documents the skip policy;
notebook regenerated. - euclid_strong_lens_modeling_pipeline — every search writes
files/coolest.jsonfrom
save_results(guarded likewcs.json, missing package is a logged warning); the
inspection bundle collectscoolest.json/coolest_sersic.json; README gains a final
"COOLEST output" section with the Galan et al. 2023 JOSS citation andautolens[coolest]
in the install line; unit + slow tests. - euclid_dr1 science clone (after the pipeline PR merges) — merge pipeline main, add
hpc/batch_cpu/submit_reload_coolest, ledger page; RAL reload leg on the human's go.
Decision (user-confirmed at planning): unsupported light/source components are skipped and
recorded, not converted. Pixel-grid export (MGE → PixelatedRegularGrid, pixelized source →
IrregularGrid) is the follow-up prompt draft/feature/autolens/coolest_pixel_grid_export.md.
Activity
Jammy2211 commentedon Sep 16, 2026
Implemented and pushed — PR-open parked on Heart RED (2026-09-16)
All three legs are implemented, verified and pushed to
feature/coolest-observation-grid; no PRs are open yet becausepyauto-heart readinessis RED on two reasons this diff does not touch:install verification FAILED (testpypi; checks F)release validation FAILED (stage integrate)(A third reason,
PyAutoArray: 4 commit(s) behind origin, was a stale local checkout and is cleared.) PR-open therefore needs either Heart green or an explicit human override.feature/coolest-observation-grid@ 0ee1146test_autolens653 passed, 1 xfailed; interop 13 passed (7 new)coolest.json+coolest_sersic.jsonThe reported defect reproduces and is fixed. A template written without a grid gives
num_pix_x 0,field_of_view_x (0, 0)andcoolest.api.plotting.ModelPlotterfails withIndexError; the same model written withshape_native=(100, 100), pixel_size=0.1loads withnum_pix_x == 100,field_of_view_x == (-5.0, 5.0), passesobservation.check_consistency_with_instrument(), andplot_surface_brightness/plot_convergencerender.Real-code findings that changed the plan slightly
autogalaxy.exc.ProfileException(subclass ofException, notValueError);to_coolestcatches both.Basis(MGE) is both aLightProfileand aMassProfile, so it appears in both loops; it is recorded once, asBasis(Gaussian x N).cls_list_fromreturns no light or mass profiles, so it silently exported an empty entity. That behaviour is kept underon_unsupported="raise";"skip"records it asPixelization(Delaunay).meta.searchcarries the search name (vis_pix/vis), the stage the template belongs to;meta.pipelinecarries the pipeline name.Test-mode pipeline template (
initial_lens_model/vis_pix, collected out of the result zip into the bundle): mass modelSIE+ExternalShear;meta.skipped_profiles = [Basis(Gaussian x 40) @ galaxy_0, Pixelization(Delaunay) @ galaxy_1];observation.pixels100x100 with field of view ±5.0 against a 100x100 / 0.1" cut-out. The Sersic-stage template carriesSersiclight with no skipped profiles.Before the DR1 run (human):
pip install coolestin the RAL venv/mnt/ral/jnightin/PyAuto—HPCPullPyAutosyncs library mains, not new pip dependencies. Without it every run logscoolest.json: the optional coolest package is not installedand writes no template.After the pipeline PR merges: euclid_dr1 clone leg — merge origin/main,
hpc/batch_cpu/submit_reload_coolest(copy ofsubmit_reload_wcs, array 0,2-9),force_pickle_overwritetoggle + revert, ledger page; RAL reload on the human's go.Follow-ups filed in PyAutoMind/draft:
feature/autolens/coolest_pixel_grid_export.md(MGE →PixelatedRegularGrid, pixelized source →IrregularGrid);maintenance/euclid/skip_fit_output_no_longer_gates_save_results_docs_drift.md(the smoke profile still writeswcs.json/coolest.json; AGENTS.md and catalogue/README.md say the gate covers all ofsave_results).Ship order once authorised: PyAutoLens PR first, then the two workspace PRs behind the library-first gate (pipeline CI source-installs library mains, so its legs go green only after the PyAutoLens merge).
Jammy2211 commentedon Sep 16, 2026
Heart RED override — human-authorised 2026-09-16 for PR-open (plain override, not the corrective-PR exception: the diff repairs neither reason). Reasons verbatim from
pyauto-heart readiness --jsonat the time:install verification FAILED (testpypi; checks F)release validation FAILED (stage integrate)PRs opened (all
pending-release):Also authorised and in progress:
pip install coolestin the RAL venv; euclid_dr1 clone prepared for thecoolest.jsonreload pass (script + ledger, run after the PyAutoLens merge + HPCPullPyAuto).Jammy2211 commentedon Sep 16, 2026
CI judged 2026-09-16 (single pass), library merged:
Library-first gate is satisfied for both. Next: merge #82 and #551, then
/prmfor the close-out (Shipped comment, issue close, Mind record, worktree removal). euclid_dr1 clone is prepared (merge of the pipeline branch +submit_reload_coolest+ ledger, commit 9803a8f, not pushed); the RAL reload needsHPCPullPyAutofirst now that PyAutoLens main carries the change.Jammy2211 commentedon Sep 16, 2026
euclid_dr1 reload pass RAN (2026-09-17 00:00 local), clean. SLURM array 343367 on RAL, nine tasks COMPLETED in 29-35 s each; every
.outshows twoForcing pickle overwrite+ twoFit Already Completedlines and nocoolest.json:warning.files/coolest.jsonis in 18/18 result zips (both stages, nine lenses), pulled to the clone, and the rebuilt dr1_sep1 inspection bundle hascoolest.jsonin all nine lens folders (nocoolest_sersic.json, the Sersic stage has not run on this sample).force_pickle_overwriteis back tofalseon RAL and in the clone; backupoutput.prezip.bak.2026-09-16b(108 MB) left on RAL for the human to delete once trusted.Template facts, identical across the nine
vis_pixtemplates: mass model SIE (galaxy_0, z=0.5) + ExternalShear (mass_field_0);meta.skipped_profiles=Basis(Gaussian x 40)@ galaxy_0,Pixelization(Delaunay)@ galaxy_1;observation.pixels100x100, field of view ±5.0";instrument.pixel_size0.1;meta.pipeline/search/datasetset. Ledger: euclid_dr1 clonewiki/project/2026-09-16-coolest-json-reload-pass.md(commit 0cc2004, local only).RAL also has
coolest0.1.11 in the venv and PyAutoLens main at 223132c (HPCPullPyAuto run), so the DR1 run writes COOLEST by default once pipeline #82 is merged and pulled to the project.Jammy2211 commentedon Sep 16, 2026
Shipped
to_coolestwrites the observation pixel grid fromshape_native/pixel_sizeordataset=, warns without one, and gainson_unsupported="skip"withmeta.skipped_profiles. Pending release.files/coolest.json; bundle collectscoolest.json/coolest_sersic.json; README "COOLEST output" section with citation.The reviewer's finding reproduces without a grid (
ModelPlotterIndexError) and is fixed with one. euclid_dr1: reload pass ran on RAL (SLURM 343367, 9/9),coolest.jsonin all nine dr1_sep1 bundle folders; RAL venv hascoolest0.1.11 and PyAutoLens main at the fix.Follow-ups filed in PyAutoMind/draft:
feature/autolens/coolest_pixel_grid_export.md,maintenance/euclid/skip_fit_output_no_longer_gates_save_results_docs_drift.md. Human: delete the two RALoutput.prezip.bak.2026-09-16*backups once trusted.Completion record:
PyAutoMind/complete/2026/09/coolest-observation-grid.md.