Skip to content

feat(interop): COOLEST observation pixel grid, skip-and-record unsupported profiles, Euclid pipeline output #739

Closed

Description

@Jammy2211

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_coolest so it writes a real PixelatedRegularGrid observation grid from shape_native + pixel_size or a dataset=, 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 under meta.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.json from save_results, guarded like wcs.json; the inspection bundle collects coolest.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_native and .pixel_scales) is given, pixel_size = dataset.pixel_scales[0], shape_native = dataset.shape_native. With shape_native=(ny, nx) build lazy.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 pass lazy.Observation(pixels=grid). num_pix_x is columns (shape_native[1]). No fits_path (check_external_files=False already). Neither given → unchanged zeros plus warnings.warn that COOLEST plotting needs a pixel grid.
  • Unsupported components: wrap the coolest_dict_from_light / coolest_dict_from_mass calls; on ValueError with on_unsupported="skip" append {"galaxy": "galaxy_<i>", "profile": <name>} to metadata["skipped_profiles"] and continue; "raise" re-raises (existing behaviour, workspace_test round-trip untouched); other values → ValueError. A Basis (MGE) is itself a LightProfile in galaxy.cls_list_from(cls=LightProfile) — skip it as one entry Basis(Gaussian x N). A Pixelization is not a LightProfile; record it via galaxy.cls_list_from(cls=Pixelization) as Pixelization(<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.py AnalysisImaging.save_results: after wcs.json, a module-level helper (e.g. coolest_json_from(...)) wraps al.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 the wcs.json block: ImportError and any other exception → logging.warning("coolest.json: ..."), never fails the fit.
  • scripts/tools/build_inspect.py process_dataset: best-effort coolest.json (from initial_lens_model/vis_pix member files/coolest.json) and coolest_sersic.json (from sersic_lens_model/vis) via the existing zip/dir member streaming. catalogue/README.md table: two new rows; build_inspection_bundle.sh header comment updated.
  • README.md: final section "COOLEST output" (after "Visualization"): every search writes files/coolest.json (MAP template: full mass model, Sersic light; MGE and pixelized components under meta.skipped_profiles); bundle collects coolest.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 installed coolest METADATA; 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; monkeypatched ImportError → warning, no file. Extend the slow run-level test that asserts wcs.json to assert files/coolest.json. smoke_tests.txt unchanged.
  • Pre-run checklist (human): pip install coolest in the RAL venv /mnt/ral/jnightin/PyAuto — HPCPullPyAuto syncs 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_coolest observation grid + skip policy
  • PyAutoLens/test_autolens/interop/test_coolest.py — tests
  • autolens_workspace/scripts/guides/coolest_interop.py — guide
  • euclid_strong_lens_modeling_pipeline/util.py — save_results hook + helper
  • euclid_strong_lens_modeling_pipeline/scripts/tools/build_inspect.py — bundle collection
  • euclid_strong_lens_modeling_pipeline/README.md, catalogue/README.md — docs + citation
  • euclid_strong_lens_modeling_pipeline/tests/test_coolest_output.py — new tests

Verification

  • PyAutoLens: pytest test_autolens/interop -q; load a written template with coolest's JSON serializer and assert observation.pixels.num_pix_x == 100, field_of_view_x == (-5.0, 5.0); render ModelPlotter once if coolest.api.plotting imports.
  • 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, smoke initial_lens_model.py under PYAUTO_TEST_MODE, build the bundle on the test-mode output and confirm coolest.json is collected; open one coolest.json and 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

  1. PyAutoLens — to_coolest builds a PixelatedRegularGrid for lazy.Observation from
    shape_native + pixel_size (or a dataset=), 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 (MGE Basis, pixelized source) under meta.skipped_profiles. Tests.
  2. autolens_workspace — the COOLEST guide passes a pixel grid and documents the skip policy;
    notebook regenerated.
  3. euclid_strong_lens_modeling_pipeline — every search writes files/coolest.json from
    save_results (guarded like wcs.json, missing package is a logged warning); the
    inspection bundle collects coolest.json / coolest_sersic.json; README gains a final
    "COOLEST output" section with the Galan et al. 2023 JOSS citation and autolens[coolest]
    in the install line; unit + slow tests.
  4. 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

  1. Jammy2211 commented on Sep 16, 2026

    @Jammy2211
    CollaboratorAuthor

    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 because pyauto-heart readiness is 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.

    Repo Branch @ commit Verification
    PyAutoLens feature/coolest-observation-grid @ 0ee1146 test_autolens 653 passed, 1 xfailed; interop 13 passed (7 new)
    autolens_workspace @ 52093ce guide runs end to end, round-trip max deflection diff 8.9e-16; notebook regenerated
    euclid_strong_lens_modeling_pipeline @ 60133f8 fast 131 passed, slow 10 passed, smoke 9/9; bundle collects coolest.json + coolest_sersic.json

    The reported defect reproduces and is fixed. A template written without a grid gives num_pix_x 0, field_of_view_x (0, 0) and coolest.api.plotting.ModelPlotter fails with IndexError; the same model written with shape_native=(100, 100), pixel_size=0.1 loads with num_pix_x == 100, field_of_view_x == (-5.0, 5.0), passes observation.check_consistency_with_instrument(), and plot_surface_brightness / plot_convergence render.

    Real-code findings that changed the plan slightly

    • The converters raise autogalaxy.exc.ProfileException (subclass of Exception, not ValueError); to_coolest catches both.
    • A Basis (MGE) is both a LightProfile and a MassProfile, so it appears in both loops; it is recorded once, as Basis(Gaussian x N).
    • A pixelized galaxy raised nothing before: cls_list_from returns no light or mass profiles, so it silently exported an empty entity. That behaviour is kept under on_unsupported="raise"; "skip" records it as Pixelization(Delaunay).
    • Pipeline meta.search carries the search name (vis_pix / vis), the stage the template belongs to; meta.pipeline carries the pipeline name.

    Test-mode pipeline template (initial_lens_model/vis_pix, collected out of the result zip into the bundle): mass model SIE + ExternalShear; meta.skipped_profiles = [Basis(Gaussian x 40) @ galaxy_0, Pixelization(Delaunay) @ galaxy_1]; observation.pixels 100x100 with field of view ±5.0 against a 100x100 / 0.1" cut-out. The Sersic-stage template carries Sersic light with no skipped profiles.

    Before the DR1 run (human): pip install coolest in the RAL venv /mnt/ral/jnightin/PyAuto — HPCPullPyAuto syncs library mains, not new pip dependencies. Without it every run logs coolest.json: the optional coolest package is not installed and writes no template.

    After the pipeline PR merges: euclid_dr1 clone leg — merge origin/main, hpc/batch_cpu/submit_reload_coolest (copy of submit_reload_wcs, array 0,2-9), force_pickle_overwrite toggle + 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 writes wcs.json/coolest.json; AGENTS.md and catalogue/README.md say the gate covers all of save_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).

  2. Jammy2211 commented on Sep 16, 2026

    @Jammy2211
    CollaboratorAuthor

    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 --json at 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 coolest in the RAL venv; euclid_dr1 clone prepared for the coolest.json reload pass (script + ledger, run after the PyAutoLens merge + HPCPullPyAuto).

  3. Jammy2211 commented on Sep 16, 2026

    @Jammy2211
    CollaboratorAuthor

    CI judged 2026-09-16 (single pass), library merged:

    • PyAutoLens#740 — Tests (3.12, 3.13, nojax) + Docs all green → MERGED (223132c).
    • euclid_strong_lens_modeling_pipeline#82 — unit / slow / smoke on 3.12 + 3.13 all green (131 unit tests ran against the same-named PyAutoLens branch), mergeable CLEAN → awaiting human merge (the agent's merge was blocked by the local permission classifier, not by CI).
    • autolens_workspace#551 — smoke (3.12, 3.13), navigator, size-guard all green, CLEAN → awaiting human merge.

    Library-first gate is satisfied for both. Next: merge #82 and #551, then /prm for 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 needs HPCPullPyAuto first now that PyAutoLens main carries the change.

  4. Jammy2211 commented on Sep 16, 2026

    @Jammy2211
    CollaboratorAuthor

    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 .out shows two Forcing pickle overwrite + two Fit Already Completed lines and no coolest.json: warning. files/coolest.json is in 18/18 result zips (both stages, nine lenses), pulled to the clone, and the rebuilt dr1_sep1 inspection bundle has coolest.json in all nine lens folders (no coolest_sersic.json, the Sersic stage has not run on this sample). force_pickle_overwrite is back to false on RAL and in the clone; backup output.prezip.bak.2026-09-16b (108 MB) left on RAL for the human to delete once trusted.

    Template facts, identical across the nine vis_pix templates: 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.pixels 100x100, field of view ±5.0"; instrument.pixel_size 0.1; meta.pipeline/search/dataset set. Ledger: euclid_dr1 clone wiki/project/2026-09-16-coolest-json-reload-pass.md (commit 0cc2004, local only).

    RAL also has coolest 0.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.

  5. Jammy2211 commented on Sep 16, 2026

    @Jammy2211
    CollaboratorAuthor

    Shipped

    • PyAutoLens#740 — MERGED (223132c): to_coolest writes the observation pixel grid from shape_native/pixel_size or dataset=, warns without one, and gains on_unsupported="skip" with meta.skipped_profiles. Pending release.
    • autolens_workspace#551 — MERGED (2c7a184): guide passes the grid, new "Unsupported Profiles" section, notebook regenerated.
    • euclid_strong_lens_modeling_pipeline#82 — MERGED (d83f05ca): every search writes files/coolest.json; bundle collects coolest.json / coolest_sersic.json; README "COOLEST output" section with citation.

    The reviewer's finding reproduces without a grid (ModelPlotter IndexError) and is fixed with one. euclid_dr1: reload pass ran on RAL (SLURM 343367, 9/9), coolest.json in all nine dr1_sep1 bundle folders; RAL venv has coolest 0.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 RAL output.prezip.bak.2026-09-16* backups once trusted.

    Completion record: PyAutoMind/complete/2026/09/coolest-observation-grid.md.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions