diff --git a/dataset/cluster/simple/data.fits b/dataset/cluster/simple/data.fits index 6e527a11d..a81431924 100644 Binary files a/dataset/cluster/simple/data.fits and b/dataset/cluster/simple/data.fits differ diff --git a/dataset/cluster/simple/dataset_point.png b/dataset/cluster/simple/dataset_point.png deleted file mode 100644 index f1ceb0ae4..000000000 Binary files a/dataset/cluster/simple/dataset_point.png and /dev/null differ diff --git a/dataset/cluster/simple/noise_map.fits b/dataset/cluster/simple/noise_map.fits index 234ec0e15..11666ea11 100644 Binary files a/dataset/cluster/simple/noise_map.fits and b/dataset/cluster/simple/noise_map.fits differ diff --git a/dataset/cluster/simple/point_dataset_0.json b/dataset/cluster/simple/point_dataset_0.json index 3dba503b3..27fbec5d7 100644 --- a/dataset/cluster/simple/point_dataset_0.json +++ b/dataset/cluster/simple/point_dataset_0.json @@ -3,11 +3,6 @@ "class_path": "autolens.point.dataset.PointDataset", "arguments": { "time_delays": null, - "fluxes_noise_map": null, - "fluxes": null, - "time_delays_noise_map": null, - "redshift": 1.0, - "name": "point_0", "positions_noise_map": { "type": "instance", "class_path": "autoarray.structures.arrays.irregular.ArrayIrregular", @@ -15,7 +10,6 @@ "values": { "type": "ndarray", "array": [ - 0.005, 0.005, 0.005 ], @@ -23,6 +17,9 @@ } } }, + "redshift": 1.0, + "name": "point_0", + "fluxes_noise_map": null, "positions": { "type": "instance", "class_path": "autoarray.structures.grids.irregular_2d.Grid2DIrregular", @@ -31,21 +28,19 @@ "type": "ndarray", "array": [ [ - -9.166406249999998, - -19.276733167466432 - ], - [ - 0.0359375, - -0.0739730032399208 + 1.0, + 0.0 ], [ - 1.8125, - 23.34028674178623 + 0.0, + 1.0 ] ], "dtype": "float64" } } - } + }, + "time_delays_noise_map": null, + "fluxes": null } } \ No newline at end of file diff --git a/dataset/cluster/simple/point_dataset_1.json b/dataset/cluster/simple/point_dataset_1.json index b566489dc..2d8b83da6 100644 --- a/dataset/cluster/simple/point_dataset_1.json +++ b/dataset/cluster/simple/point_dataset_1.json @@ -3,11 +3,6 @@ "class_path": "autolens.point.dataset.PointDataset", "arguments": { "time_delays": null, - "fluxes_noise_map": null, - "fluxes": null, - "time_delays_noise_map": null, - "redshift": 2.0, - "name": "point_1", "positions_noise_map": { "type": "instance", "class_path": "autoarray.structures.arrays.irregular.ArrayIrregular", @@ -15,7 +10,6 @@ "values": { "type": "ndarray", "array": [ - 0.005, 0.005, 0.005 ], @@ -23,6 +17,9 @@ } } }, + "redshift": 2.0, + "name": "point_1", + "fluxes_noise_map": null, "positions": { "type": "instance", "class_path": "autoarray.structures.grids.irregular_2d.Grid2DIrregular", @@ -31,21 +28,19 @@ "type": "ndarray", "array": [ [ - -15.96796875, - 19.00429600919258 - ], - [ - 0.68125, - -0.5133004737014016 + 1.0, + 0.0 ], [ - 13.6921875, - -14.30926557794663 + 0.0, + 1.0 ] ], "dtype": "float64" } } - } + }, + "time_delays_noise_map": null, + "fluxes": null } } \ No newline at end of file diff --git a/dataset/cluster/simple/point_datasets.csv b/dataset/cluster/simple/point_datasets.csv index 7c9c3b36a..a460418f0 100644 --- a/dataset/cluster/simple/point_datasets.csv +++ b/dataset/cluster/simple/point_datasets.csv @@ -1,7 +1,5 @@ name,y,x,positions_noise,redshift -point_0,-9.166406249999998,-19.276733167466432,0.005,1.0 -point_0,0.0359375,-0.0739730032399208,0.005,1.0 -point_0,1.8125,23.34028674178623,0.005,1.0 -point_1,-15.96796875,19.00429600919258,0.005,2.0 -point_1,0.68125,-0.5133004737014016,0.005,2.0 -point_1,13.6921875,-14.30926557794663,0.005,2.0 +point_0,1.0,0.0,0.005,1.0 +point_0,0.0,1.0,0.005,1.0 +point_1,1.0,0.0,0.005,2.0 +point_1,0.0,1.0,0.005,2.0 diff --git a/dataset/cluster/simple/psf.fits b/dataset/cluster/simple/psf.fits index 940efcd87..3d342eff1 100644 Binary files a/dataset/cluster/simple/psf.fits and b/dataset/cluster/simple/psf.fits differ diff --git a/dataset/cluster/simple/tracer.json b/dataset/cluster/simple/tracer.json index 8dc008087..82175406a 100644 --- a/dataset/cluster/simple/tracer.json +++ b/dataset/cluster/simple/tracer.json @@ -23,8 +23,8 @@ ] }, "sersic_index": 4.0, - "intensity": 1.5, - "effective_radius": 3.0 + "effective_radius": 3.0, + "intensity": 1.5 } }, "mass": { @@ -38,8 +38,8 @@ 0.0 ] }, - "rs": 20.0, "b0": 3.0, + "rs": 20.0, "ra": 8.0 } } @@ -63,8 +63,8 @@ ] }, "sersic_index": 3.5, - "intensity": 0.8, - "effective_radius": 1.5 + "effective_radius": 1.5, + "intensity": 0.8 } }, "mass": { @@ -78,8 +78,8 @@ 8.0 ] }, - "rs": 12.0, "b0": 1.2, + "rs": 12.0, "ra": 5.0 } } @@ -103,8 +103,8 @@ ] }, "sersic_index": 3.0, - "intensity": 0.4, - "effective_radius": 0.8 + "effective_radius": 0.8, + "intensity": 0.4 } }, "mass": { @@ -118,8 +118,8 @@ -6.5 ] }, - "rs": 10.0, "b0": 0.12, + "rs": 10.0, "ra": 0.1 } } @@ -143,8 +143,8 @@ ] }, "sersic_index": 3.0, - "intensity": 0.32, - "effective_radius": 0.8 + "effective_radius": 0.8, + "intensity": 0.32 } }, "mass": { @@ -158,8 +158,8 @@ 3.0 ] }, - "rs": 8.94427190999916, "b0": 0.1073312629199899, + "rs": 8.94427190999916, "ra": 0.1 } } @@ -183,8 +183,8 @@ ] }, "sersic_index": 3.0, - "intensity": 0.25, - "effective_radius": 0.8 + "effective_radius": 0.8, + "intensity": 0.25 } }, "mass": { @@ -198,8 +198,8 @@ -5.0 ] }, - "rs": 7.905694150420949, "b0": 0.09486832980505139, + "rs": 7.905694150420949, "ra": 0.1 } } @@ -223,8 +223,8 @@ ] }, "sersic_index": 3.0, - "intensity": 0.2, - "effective_radius": 0.8 + "effective_radius": 0.8, + "intensity": 0.2 } }, "mass": { @@ -238,8 +238,8 @@ -9.0 ] }, - "rs": 7.0710678118654755, "b0": 0.08485281374238571, + "rs": 7.0710678118654755, "ra": 0.1 } } @@ -263,8 +263,8 @@ ] }, "sersic_index": 3.0, - "intensity": 0.16, - "effective_radius": 0.8 + "effective_radius": 0.8, + "intensity": 0.16 } }, "mass": { @@ -278,8 +278,8 @@ 13.0 ] }, - "rs": 6.324555320336759, "b0": 0.0758946638440411, + "rs": 6.324555320336759, "ra": 0.1 } } @@ -303,8 +303,8 @@ ] }, "sersic_index": 3.0, - "intensity": 0.13, - "effective_radius": 0.8 + "effective_radius": 0.8, + "intensity": 0.13 } }, "mass": { @@ -318,8 +318,8 @@ 4.0 ] }, - "rs": 5.7008771254956905, "b0": 0.06841052550594828, + "rs": 5.7008771254956905, "ra": 0.1 } } @@ -343,8 +343,8 @@ ] }, "sersic_index": 3.0, - "intensity": 0.1, - "effective_radius": 0.8 + "effective_radius": 0.8, + "intensity": 0.1 } }, "mass": { @@ -358,8 +358,8 @@ 9.0 ] }, - "rs": 5.0, "b0": 0.06, + "rs": 5.0, "ra": 0.1 } } @@ -383,8 +383,8 @@ ] }, "sersic_index": 3.0, - "intensity": 0.08, - "effective_radius": 0.8 + "effective_radius": 0.8, + "intensity": 0.08 } }, "mass": { @@ -398,8 +398,8 @@ -12.0 ] }, - "rs": 4.47213595499958, "b0": 0.05366563145999495, + "rs": 4.47213595499958, "ra": 0.1 } } @@ -423,8 +423,8 @@ ] }, "sersic_index": 3.0, - "intensity": 0.06, - "effective_radius": 0.8 + "effective_radius": 0.8, + "intensity": 0.06 } }, "mass": { @@ -438,8 +438,8 @@ 5.5 ] }, - "rs": 3.872983346207417, "b0": 0.046475800154489, + "rs": 3.872983346207417, "ra": 0.1 } } @@ -463,8 +463,8 @@ ] }, "sersic_index": 3.0, - "intensity": 0.05, - "effective_radius": 0.8 + "effective_radius": 0.8, + "intensity": 0.05 } }, "mass": { @@ -478,8 +478,8 @@ 11.0 ] }, - "rs": 3.5355339059327378, "b0": 0.042426406871192854, + "rs": 3.5355339059327378, "ra": 0.1 } } @@ -502,9 +502,9 @@ 0.0 ] }, - "redshift_object": 0.5, "redshift_source": 2.0, - "mass_at_200": 1995262314968882.8 + "mass_at_200": 1995262314968882.8, + "redshift_object": 0.5 } } } @@ -519,8 +519,9 @@ "type": "instance", "class_path": "autogalaxy.profiles.light.standard.sersic_core.SersicCore", "arguments": { - "intensity": 2.0, - "sersic_index": 1.0, + "alpha": 3.0, + "radius_break": 0.025, + "effective_radius": 0.3, "ell_comps": { "type": "tuple", "values": [ @@ -528,7 +529,7 @@ -0.05555555555555551 ] }, - "effective_radius": 0.3, + "intensity": 2.0, "centre": { "type": "tuple", "values": [ @@ -536,9 +537,8 @@ 0.5 ] }, - "gamma": 0.25, - "alpha": 3.0, - "radius_break": 0.025 + "sersic_index": 1.0, + "gamma": 0.25 } } } @@ -553,8 +553,9 @@ "type": "instance", "class_path": "autogalaxy.profiles.light.standard.sersic_core.SersicCore", "arguments": { - "intensity": 2.0, - "sersic_index": 1.0, + "alpha": 3.0, + "radius_break": 0.025, + "effective_radius": 0.3, "ell_comps": { "type": "tuple", "values": [ @@ -562,7 +563,7 @@ -0.11111111111111108 ] }, - "effective_radius": 0.3, + "intensity": 2.0, "centre": { "type": "tuple", "values": [ @@ -570,9 +571,8 @@ 1.2 ] }, - "gamma": 0.25, - "alpha": 3.0, - "radius_break": 0.025 + "sersic_index": 1.0, + "gamma": 0.25 } } } diff --git a/scripts/cluster/csv_api.py b/scripts/cluster/csv_api.py index 96ec9db71..38451be2c 100644 --- a/scripts/cluster/csv_api.py +++ b/scripts/cluster/csv_api.py @@ -437,6 +437,33 @@ ``simulator.py``; consumed by ``modeling.py`` and ``start_here.py`` via ``al.galaxy_table_from_csv``. +__Lenstool-Parameterized Rows__ + +Because the ``profile_class`` column dispatches against the full ``al.mp`` namespace, a +``mass.csv`` can carry rows in **Lenstool's native parameterization** via ``dPIEMassLenstool`` +— the columns become the ``.par``-file keywords verbatim:: + + galaxy,attr_name,profile_class,y,x,ellipticity,angle_pos,sigma,r_core,r_cut,redshift_object,redshift_source,H0,Om0,redshift + O1,mass,dPIEMassLenstool,1.479,-2.997,0.678,8.971,987.34,18.96,283.54,0.39,11.76,70.0,0.3,0.39 + +``sigma`` is Lenstool's fiducial ``v_disp`` (sigma_LT), radii are in arcsec, and the run's own +cosmology travels as the flat ``H0`` / ``Om0`` columns. ``scripts/cluster/lenstool/`` builds its +entire 149-component published model this way — the ``.par`` file becomes one canonical CSV. Note +the multi-plane convention: ``redshift_source`` must be the tracer's *final* (highest) source +plane. + +Light-profile CSVs (``light.csv``) support the linear / operated variants with qualified class +names (``linear.Sersic``, ``operated.Gaussian``); plain names resolve to the standard profiles. + +__Member Catalogues With Properties__ + +``scaling_galaxies.csv`` / ``al.galaxy_table_from_csv`` accept any extra per-galaxy columns +beyond ``y, x, luminosity[, redshift]`` — numeric columns (``ellipticity``, ``angle_pos``, +``mag``) load as float lists in ``GalaxyTable.properties``, strings (names, notes) as string +lists. Nothing is silently dropped, and two loud guards protect the model CSVs: a typo'd +parameter column raises (instead of silently leaving the profile at its default), as does a +duplicate ``(galaxy, attr_name)`` row pair. + To start modelling your own cluster: 1. Edit (or generate from a light-only fit) the model CSVs and diff --git a/scripts/cluster/lenstool/data.py b/scripts/cluster/lenstool/data.py index e04a14df6..b62669f78 100644 --- a/scripts/cluster/lenstool/data.py +++ b/scripts/cluster/lenstool/data.py @@ -17,9 +17,12 @@ If you are a Lenstool user: each output CSV corresponds to one input you already maintain — - - ``arcs.dat`` → ``point_datasets.csv`` (multiple-image positions + redshifts + noise) - - ``galcat.cat`` → ``members.csv`` (cluster members: centres, shapes, magnitudes) - - ``best.par`` → ``halos.csv`` + ``members_best.csv`` (every optimized ``potential`` section) + - ``arcs.dat`` → ``point_datasets.csv`` (multiple-image positions + redshifts + noise) + - ``galcat.cat`` → ``members.csv`` (member catalogue: centres + shape/mag properties, + the ``al.galaxy_table_from_csv`` schema) + - ``best.par`` → ``mass.csv`` (every optimized ``potential`` section as one + ``dPIEMassLenstool`` row of the canonical named-galaxy model CSV — **the .par file as a + table**, read back with ``al.galaxy_models_from_csv`` like every other cluster dataset) __Attribution__ @@ -76,6 +79,8 @@ import numpy as np +import autolens as al + """ __Paths + URLs__ """ @@ -251,30 +256,53 @@ def parse_best_par(path: Path) -> tuple: for system, y, x, redshift, _ in image_rows: f.write(f"point_{system},{y:.6f},{x:.6f},{SIGPOS_ARCSEC},{redshift}\n") -with open(DATASET_PATH / "members.csv", "w") as f: - f.write("y,x,ellipticity,angle_pos,mag,luminosity\n") - for row in member_rows: - f.write(",".join(f"{v:.6f}" for v in row) + "\n") - -with open(DATASET_PATH / "halos.csv", "w") as f: - f.write("label,y,x,ellipticity,angle_pos,r_core,r_cut,sigma,z_lens\n") - for h in named_halos: - f.write( - f"{h['label']},{h['y']:.6f},{h['x']:.6f},{h['ellipticity']:.6f}," - f"{h['angle_pos']:.6f},{h['r_core']:.6f},{h['r_cut']:.6f}," - f"{h['sigma']:.6f},{h['z_lens']}\n" - ) +al.galaxy_table_to_csv( + centres=[(r[0], r[1]) for r in member_rows], + luminosities=[r[5] for r in member_rows], + file_path=DATASET_PATH / "members.csv", + properties={ + "ellipticity": [r[2] for r in member_rows], + "angle_pos": [r[3] for r in member_rows], + "mag": [r[4] for r in member_rows], + }, +) -with open(DATASET_PATH / "members_best.csv", "w") as f: - f.write("label,y,x,ellipticity,angle_pos,r_core,r_cut,sigma,z_lens\n") - for h in member_halos: - f.write( - f"{h['label']},{h['y']:.6f},{h['x']:.6f},{h['ellipticity']:.6f}," - f"{h['angle_pos']:.6f},{h['r_core']:.6f},{h['r_cut']:.6f}," - f"{h['sigma']:.6f},{h['z_lens']}\n" +# The whole optimized model — 5 named halos + 144 scaling members — becomes ONE canonical +# ``mass.csv``: each ``potential`` section is a ``dPIEMassLenstool`` row whose columns are the +# ``.par`` keywords verbatim (sigma, r_core, r_cut, ellipticity, angle_pos) plus the run's +# redshifts and cosmology as flat values. ``modeling.py`` reads it back with the same +# ``al.galaxy_models_from_csv`` call used throughout ``scripts/cluster/``. +# +# Every profile is normalized against the tracer's FINAL source plane (the multi-plane +# convention modeling.py explains) using the run's own cosmology (H0=70, Om0=0.3). +Z_FINAL_PLANE = max(z_by_system.values()) + +profiles_by_galaxy = {} +for h in halos: + name = h["label"] if h["label"].startswith("O") else f"member_{h['label']}" + profiles_by_galaxy[name] = { + "mass": al.mp.dPIEMassLenstool( + centre=(h["y"], h["x"]), + ellipticity=h["ellipticity"], + angle_pos=h["angle_pos"], + sigma=h["sigma"], + r_core=h["r_core"], + r_cut=h["r_cut"], + redshift_object=h["z_lens"], + redshift_source=Z_FINAL_PLANE, + H0=70.0, + Om0=0.3, ) + } + +al.galaxy_models_to_csv( + profiles_by_galaxy, + DATASET_PATH / "mass.csv", + family="mass", + redshifts={name: 0.39 for name in profiles_by_galaxy}, +) -print("Wrote point_datasets.csv, members.csv, halos.csv, members_best.csv.") +print("Wrote point_datasets.csv, members.csv, mass.csv (149 dPIEMassLenstool rows).") """ __Image Cutout__ @@ -285,7 +313,14 @@ def parse_best_par(path: Path) -> tuple: """ cutout_path = DATASET_PATH / "data.fits" -if not cutout_path.exists(): +import os + +if os.environ.get("PYAUTO_SMALL_DATASETS") == "1": + print( + "PYAUTO_SMALL_DATASETS=1: skipping the 96 MB RELICS mosaic download / cutout " + "(visualization-only product; the modeling data products above are complete)." + ) +elif not cutout_path.exists(): from astropy.io import fits from astropy.wcs import WCS diff --git a/scripts/cluster/lenstool/modeling.py b/scripts/cluster/lenstool/modeling.py index 05446b664..18b81bc1e 100644 --- a/scripts/cluster/lenstool/modeling.py +++ b/scripts/cluster/lenstool/modeling.py @@ -103,13 +103,16 @@ - ``point_datasets.csv`` — the 60 multiple images of 21 sources (``arcs.dat``), with Lenstool's ``sigposArcsec`` as the position noise and per-system redshifts (spectroscopic where they exist, the model-optimized values of ``best.par`` otherwise). - - ``halos.csv`` — the 5 individually-optimized dPIE halos of ``best.par``: the cluster-scale - halo (O1), the BCG (O2), two light-concentration clumps (O3 "dNW", O4 "ICL") and one galaxy - modelled outside the scaling relation (O5 "eCM"). - - ``members_best.csv`` — the 144 cluster members exactly as Lenstool derived them from the - scaling relation (the reconstruction uses these). - - ``members.csv`` — the member *catalogue* (``galcat.cat``: positions, shapes, magnitudes; the - refit derives member masses from these + the shared scaling parameters, as ``potfile`` does). + - ``mass.csv`` — the complete optimized mass model in the **canonical named-galaxy CSV** + (the same ``al.galaxy_models_from_csv`` format every cluster script uses): 149 rows of + ``profile_class = dPIEMassLenstool``, one per ``potential`` section of ``best.par``, whose + columns are the ``.par`` keywords verbatim. The five individually-optimized halos are named + O1 (cluster-scale), O2 (BCG), O3 ("dNW"), O4 ("ICL"), O5 ("eCM"); the 144 scaling members + are ``member_``. + - ``members.csv`` — the member *catalogue* (``galcat.cat``) in the ``al.galaxy_table_from_csv`` + schema: centres + luminosities plus ``ellipticity`` / ``angle_pos`` / ``mag`` property + columns (the refit derives member masses from these + the shared scaling parameters, as + ``potfile`` does). """ dataset_path = Path("dataset") / "cluster" / "smacs0723" @@ -117,15 +120,13 @@ print(f"{len(dataset_list)} point-source systems loaded.") -def rows_from_csv(path): - lines = Path(path).read_text().strip().splitlines() - keys = lines[0].split(",") - return [dict(zip(keys, line.split(","))) for line in lines[1:]] +mass_table = al.galaxy_models_from_csv(dataset_path / "mass.csv", family="mass") +members_table = al.galaxy_table_from_csv(file_path=dataset_path / "members.csv") - -halo_rows = rows_from_csv(dataset_path / "halos.csv") -member_best_rows = rows_from_csv(dataset_path / "members_best.csv") -member_cat_rows = rows_from_csv(dataset_path / "members.csv") +print( + f"mass.csv: {len(mass_table.rows)} dPIEMassLenstool rows | " + f"members.csv: {len(members_table.luminosities)} catalogue members" +) """ __The Published Model, Reconstructed__ @@ -145,29 +146,13 @@ def rows_from_csv(path): to the same beta = D_LS/D_S ratios in the same cosmology. """ Z_REF_SOURCE = max(float(dataset.redshift) for dataset in dataset_list) -print(f"Profiles normalized to the tracer's final plane, z = {Z_REF_SOURCE:.3f}") - - -def dpie_from_row(row): - return al.mp.dPIEMass.from_lenstool( - centre=(float(row["y"]), float(row["x"])), - ellipticity=float(row["ellipticity"]), - angle_pos=float(row["angle_pos"]), - sigma=float(row["sigma"]), - r_core=float(row["r_core"]), - r_cut=float(row["r_cut"]), - redshift_object=float(row["z_lens"]), - redshift_source=Z_REF_SOURCE, - cosmology=cosmology, - ) - -lens_galaxies = [ - al.Galaxy(redshift=Z_LENS, mass=dpie_from_row(row)) - for row in halo_rows + member_best_rows -] +# One call: every mass.csv row instantiates its dPIEMassLenstool with the .par values — +# the redshift_source (final-plane) normalization and the run's H0/Om0 travel inside the +# CSV columns, so nothing here needs to remember them. +lens_galaxies = list(al.galaxies_from_csv_tables(mass_table).values()) -print(f"Reconstructed {len(lens_galaxies)} dPIE mass components from best.par.") +print(f"Reconstructed {len(lens_galaxies)} dPIE mass components from mass.csv.") source_galaxies = [ al.Galaxy( @@ -321,7 +306,7 @@ def dpie_from_row(row): (``vdslope 4`` and ``slope 4`` in ``input.par`` are these fixed exponents; the same reference-anchored convention is the default throughout the PyAutoLens cluster workflow.) - [2 free for all 146 members] + [2 free for all 146 catalogue members] - **Sources**: one ``al.ps.Point`` per system with a free centre initialised from the traced centroid of its observed images; redshifts fixed (spectroscopic or published model values). @@ -338,26 +323,38 @@ def dpie_from_row(row): R_CORE_MEMBER = 0.15 / kpc_per_arcsec # potfile corekpc, converted once member_models = [] -for row in member_cat_rows: - luminosity = float(row["luminosity"]) +for centre, luminosity, ellipticity, angle_pos in zip( + members_table.centres, + members_table.luminosities, + members_table.properties["ellipticity"], + members_table.properties["angle_pos"], +): mass = af.Model(al.mp.dPIEMassLenstool) - mass.centre = (float(row["y"]), float(row["x"])) - mass.ellipticity = float(row["ellipticity"]) - mass.angle_pos = float(row["angle_pos"]) + mass.centre = tuple(centre) + mass.ellipticity = ellipticity + mass.angle_pos = angle_pos mass.r_core = R_CORE_MEMBER mass.r_cut = (r_cut_star_kpc / float(kpc_per_arcsec)) * luminosity**0.5 mass.sigma = sigma_star * luminosity**0.25 mass.redshift_object = Z_LENS mass.redshift_source = Z_REF_SOURCE + mass.H0 = 70.0 + mass.Om0 = 0.3 member_models.append(af.Model(al.Galaxy, redshift=Z_LENS, mass=mass)) +# The named halos start as af.Models straight from mass.csv — the canonical +# al.galaxy_af_models_from_csv_tables call gives every row's values as fixed +# defaults, and we promote exactly the parameters input.par optimized to priors +# (in Lenstool units). Redshifts and H0/Om0 ride in from the CSV already fixed. +halo_af_models = al.galaxy_af_models_from_csv_tables(mass_table) + + def halo_model_from(label, limits): - row = next(r for r in halo_rows if r["label"] == label) - mass = af.Model(al.mp.dPIEMassLenstool) - mass.redshift_object = Z_LENS - mass.redshift_source = Z_REF_SOURCE - best_y, best_x = float(row["y"]), float(row["x"]) + galaxy_model = halo_af_models[label] + mass = galaxy_model.mass + row = next(r for r in mass_table.rows if r.galaxy == label) + best_y, best_x = row.params["centre"] if "centre" in limits: half = limits["centre"] @@ -367,15 +364,10 @@ def halo_model_from(label, limits): mass.centre_1 = af.UniformPrior( lower_limit=best_x - half, upper_limit=best_x + half ) - else: - mass.centre = (best_y, best_x) if "ellipticity" in limits: mass.ellipticity = af.UniformPrior(0.0, limits["ellipticity"]) mass.angle_pos = af.UniformPrior(-90.0, 90.0) - else: - mass.ellipticity = float(row["ellipticity"]) - mass.angle_pos = float(row["angle_pos"]) lo, hi = limits["r_core_kpc"] mass.r_core = af.UniformPrior( @@ -387,12 +379,11 @@ def halo_model_from(label, limits): mass.r_cut = af.UniformPrior( lo / float(kpc_per_arcsec), hi / float(kpc_per_arcsec) ) - else: - mass.r_cut = float(row["r_cut"]) # O1: fixed 1500 kpc, already in arcsec + # else: r_cut stays at its CSV value (O1: fixed 1500 kpc, already in arcsec). lo, hi = limits["sigma"] mass.sigma = af.UniformPrior(lo, hi) - return af.Model(al.Galaxy, redshift=Z_LENS, mass=mass) + return galaxy_model halo_models = [ @@ -490,7 +481,7 @@ def halo_model_from(label, limits): import os -if os.environ.get("PYAUTO_TEST_MODE") or os.environ.get("LENSTOOL_EXAMPLE_RUN_FIT"): +if os.environ.get("LENSTOOL_EXAMPLE_RUN_FIT"): analysis_factor_list = [ af.AnalysisFactor(prior_model=model, analysis=analysis) for analysis in analysis_list @@ -502,8 +493,9 @@ def halo_model_from(label, limits): print("Refit complete — compare result_list max-likelihood values with Table 3.") else: print( - "Refit composition validated; set LENSTOOL_EXAMPLE_RUN_FIT=1 (or PYAUTO_TEST_MODE=2 for " - "a structural pass) to execute the search." + "Refit composition validated (the model.info above is the structural pass); set " + "LENSTOOL_EXAMPLE_RUN_FIT=1 to execute the production-scale search — a 72-parameter " + "factor-graph fit is never smoke-mode material." ) """ diff --git a/scripts/cluster/modeling.py b/scripts/cluster/modeling.py index f5ac81294..379ff8109 100644 --- a/scripts/cluster/modeling.py +++ b/scripts/cluster/modeling.py @@ -104,11 +104,7 @@ If the dataset does not already exist on your system, it will be created by running the corresponding simulator script. This ensures that all example scripts can be run without manually simulating data first. """ -if ( - not (dataset_path / "data.fits").exists() - or not (dataset_path / "scaling_galaxies.csv").exists() - or not (dataset_path / "mass.csv").exists() -): +if al.util.dataset.should_simulate(str(dataset_path)): import subprocess import sys diff --git a/scripts/cluster/start_here.py b/scripts/cluster/start_here.py index 4f6d31a18..d2f1a0df1 100644 --- a/scripts/cluster/start_here.py +++ b/scripts/cluster/start_here.py @@ -124,16 +124,14 @@ - ``mass.csv`` / ``light.csv`` / ``point.csv`` — named-galaxy CSVs carrying the full truth model, including the centres of the main galaxies and host halo (see ``csv_api.py``). -If the dataset is missing on disk, the corresponding simulator script runs automatically. +If the dataset does not already exist on your system (per ``al.util.dataset.should_simulate``, +which also handles the smoke-mode ``PYAUTO_SMALL_DATASETS`` regeneration case), it is created +by running the corresponding simulator script. """ dataset_name = "simple" dataset_path = Path("dataset") / "cluster" / dataset_name -if ( - not (dataset_path / "data.fits").exists() - or not (dataset_path / "scaling_galaxies.csv").exists() - or not (dataset_path / "mass.csv").exists() -): +if al.util.dataset.should_simulate(str(dataset_path)): subprocess.run( [sys.executable, "scripts/cluster/simulator.py"], check=True,