Skip to content

cosmotron_mcp.tools.spectra

run_power_spectrum

run_power_spectrum(fits_path: str, nside: int = 64, lmin: int = 2, lmax: int | None = None, n_bandpowers: int = 20, apodisation_scale: float = 3.0, apodise: bool = True, mask_path: str | None = None, full_sky: bool = False, spin: int = 0, sigma_e: float | None = None, e1_col: str = 'E1', e2_col: str = 'E2', z_col: str = 'z_photo', z_min: float | None = None, z_max: float | None = None) -> dict

Load a standardised catalogue and compute its angular power spectrum.

The catalogue must have columns RA, DEC, weight (cosmotron standard format); for spin=2 also E1/E2. When no mask_path is given (and not full_sky), the field's own weighted-occupancy mask is used — for shear this is the weight footprint. Prefer compute_cls_from_session, which reads spin/sigma_e per bin.

Parameters:

Name Type Description Default
fits_path str

Path to the standardised FITS catalogue.

required
nside int

HEALPix resolution parameter.

64
lmin int

Minimum multipole for bandpower binning.

2
lmax int | None

Maximum multipole. None defaults to 3*nside-1 (Nyquist limit). Values above 3*nside-1 are clamped with a warning — this session-less entry point never raises. (The two other Nyquist enforcement points differ: a session's pipeline_config RAISES at bootstrap if lmax > 3*nside-1; pipeline_resolve's clamp-to-session-value path is silent, no warning.)

None
n_bandpowers int

Number of bandpower bins.

20
apodisation_scale float

Apodisation scale in degrees for C1 mask apodisation. Ignored when full_sky=True. Must be

= 2 * pixel_size_deg when full_sky=False.

3.0
apodise bool

Apply mask apodisation. Pass False for an explicitly unapodised masked run; full_sky always skips apodisation.

True
mask_path str | None

Path to a HEALPix mask FITS file. Ignored when full_sky=True.

None
full_sky bool

If True, skip mask apodisation entirely and use a full-sky mask of ones. Returns f_sky=1.0. Mutually exclusive with mask_path.

False
spin int

Field spin weight (0 for galaxy density, 2 for weak-lensing shear). spin=2 builds Q/U from the catalogue's e1/e2 columns via make_shear_map — no separate U-map file is needed.

0
sigma_e float | None

Per-component shear shape-noise RMS (spin-2 only) → EE/BB shot noise N_ℓ = σ_e²·Ω_pix/n̄.

None
e1_col str

Shear first-ellipticity-component column name, used only for spin=2.

'E1'
e2_col str

Shear second-ellipticity-component column name, used only for spin=2.

'E2'
z_col str

Redshift column name used for the optional z selection. Defaults to "z_photo"; pass "Z" for cosmotron standardised catalogues.

'z_photo'
z_min float | None

Optional inclusive-lower redshift bound. When set with z_max, only objects in [z_min, z_max) contribute — used to select one tomographic bin from a shared catalogue.

None
z_max float | None

Optional exclusive-upper redshift bound; see z_min.

None

Returns:

Type Description
dict

This is a HEAVY_TOOLS entry — a full catalogue FITS read + NaMaster

dict

workspace build is compute-node work, so it ALWAYS runs as a

dict

non-blocking local background job, never inline (no jobspec

dict

param — session-less, so there's no site to pin a remote job to;

dict

local job routing only). Returns an async receipt

dict

``{job_id, status, mode: "async", run_dir, session_dir, next:

dict

"check_job", ...}` —session_dir` here is a fixed shared job root

dict

(this tool has no session of its own), NOT a pipeline session; use

dict

the session_dir the receipt itself returns when polling. Poll

dict

check_job(session_dir, job_id); once status == "done", its

dict

result carries: ells, cls, cls_denoised,

dict

noise_bias, f_sky, n_bar, n_objects, nside,

dict

lmin, lmax, n_bandpowers, full_sky, spin. For

dict

spin=2 also includes cls_BB, cls_EB, cls_BE,

dict

cls_BB_denoised, cls_EB_denoised, noise_bias_bb. All

dict

arrays are plain Python lists.

Source code in cosmotron_mcp/server.py
@mcp.tool()
@sync_budget_guard
def run_power_spectrum(
    fits_path: str,
    nside: int = 64,
    lmin: int = 2,
    lmax: int | None = None,
    n_bandpowers: int = 20,
    apodisation_scale: float = 3.0,
    apodise: bool = True,
    mask_path: str | None = None,
    full_sky: bool = False,
    spin: int = 0,
    sigma_e: float | None = None,
    e1_col: str = "E1",
    e2_col: str = "E2",
    z_col: str = "z_photo",
    z_min: float | None = None,
    z_max: float | None = None,
) -> dict:
    """Load a standardised catalogue and compute its angular power spectrum.

    The catalogue must have columns RA, DEC, weight (cosmotron standard format);
    for spin=2 also E1/E2. When no mask_path is given (and not full_sky), the
    field's own weighted-occupancy mask is used — for shear this is the weight
    footprint. Prefer compute_cls_from_session, which reads spin/sigma_e per bin.

    Args:
        fits_path: Path to the standardised FITS catalogue.
        nside: HEALPix resolution parameter.
        lmin: Minimum multipole for bandpower binning.
        lmax: Maximum multipole. `None` defaults to `3*nside-1` (Nyquist
            limit). Values above `3*nside-1` are clamped with a warning —
            this session-less entry point never raises. (The two other
            Nyquist enforcement points differ: a session's `pipeline_config`
            RAISES at bootstrap if `lmax > 3*nside-1`; `pipeline_resolve`'s
            clamp-to-session-value path is silent, no warning.)
        n_bandpowers: Number of bandpower bins.
        apodisation_scale: Apodisation scale in degrees for C1 mask
            apodisation. Ignored when `full_sky=True`. Must be
            >= 2 * pixel_size_deg when `full_sky=False`.
        apodise: Apply mask apodisation. Pass `False` for an explicitly
            unapodised masked run; `full_sky` always skips apodisation.
        mask_path: Path to a HEALPix mask FITS file. Ignored when
            `full_sky=True`.
        full_sky: If `True`, skip mask apodisation entirely and use a
            full-sky mask of ones. Returns `f_sky=1.0`. Mutually exclusive
            with `mask_path`.
        spin: Field spin weight (0 for galaxy density, 2 for weak-lensing
            shear). `spin=2` builds Q/U from the catalogue's e1/e2 columns
            via `make_shear_map` — no separate U-map file is needed.
        sigma_e: Per-component shear shape-noise RMS (spin-2 only) → EE/BB
            shot noise N_ℓ = σ_e²·Ω_pix/n̄.
        e1_col: Shear first-ellipticity-component column name, used only
            for spin=2.
        e2_col: Shear second-ellipticity-component column name, used only
            for spin=2.
        z_col: Redshift column name used for the optional z selection.
            Defaults to ``"z_photo"``; pass ``"Z"`` for cosmotron
            standardised catalogues.
        z_min: Optional inclusive-lower redshift bound. When set with
            `z_max`, only objects in `[z_min, z_max)` contribute — used to
            select one tomographic bin from a shared catalogue.
        z_max: Optional exclusive-upper redshift bound; see `z_min`.

    Returns:
        This is a `HEAVY_TOOLS` entry — a full catalogue FITS read + NaMaster
        workspace build is compute-node work, so it ALWAYS runs as a
        non-blocking local background job, never inline (no `jobspec`
        param — session-less, so there's no site to pin a remote job to;
        local job routing only). Returns an async receipt
        ``{job_id, status, mode: "async", run_dir, session_dir, next:
        "check_job", ...}`` — `session_dir` here is a fixed shared job root
        (this tool has no session of its own), NOT a pipeline session; use
        the `session_dir` the receipt itself returns when polling. Poll
        `check_job(session_dir, job_id)`; once `status == "done"`, its
        `result` carries: ``ells``, ``cls``, ``cls_denoised``,
        ``noise_bias``, ``f_sky``, ``n_bar``, ``n_objects``, ``nside``,
        ``lmin``, ``lmax``, ``n_bandpowers``, ``full_sky``, ``spin``. For
        spin=2 also includes ``cls_BB``, ``cls_EB``, ``cls_BE``,
        ``cls_BB_denoised``, ``cls_EB_denoised``, ``noise_bias_bb``. All
        arrays are plain Python lists.
    """
    from cosmotron_mcp import heavy
    from cosmotron_mcp.remote.dispatch import submit_local_job
    kwargs = {
        "fits_path": fits_path, "nside": nside, "lmin": lmin, "lmax": lmax,
        "n_bandpowers": n_bandpowers, "apodisation_scale": apodisation_scale,
        "apodise": apodise, "mask_path": mask_path, "full_sky": full_sky,
        "spin": spin, "sigma_e": sigma_e, "e1_col": e1_col, "e2_col": e2_col,
        "z_col": z_col, "z_min": z_min, "z_max": z_max,
    }
    return submit_local_job("run_power_spectrum", heavy.ADHOC_JOB_ROOT, kwargs,
                             inject_session_dir=False)

compute_cls_from_session

compute_cls_from_session(session_dir: str, bin_index: int, save_maps: bool = False, deproject_templates: list[str] | None = None, workspace_cache_scope: str = 'session', jobspec: dict | None = None) -> dict

Compute the angular power spectrum for one tomographic bin using pipeline_config from session_context.json as the authoritative source for all NaMaster parameters.

Set save_maps=True to persist the HEALPix overdensity map + survey mask that this computation ALREADY builds (written to results/maps/delta_map_bin_{i}.fits / mask_bin_{i}.fits and registered). Use this instead of calling make_healpix_map again — it avoids generating the same map product twice.

Set deproject_templates=[name, ...] to also run NaMaster template deprojection against named manifest["systematics_templates"] (ingested via ingest_template_maps) — resolves them, no hand-built templates= argument needed. An unknown name returns needs_input. Adds cls_deprojected, delta_cls_deprojection, deprojected_templates (and cls_BB_deprojected for spin-2) to the output JSON ALONGSIDE the raw cls/cls_denoised — never replaces them.

Reads manifest.json to find the FITS path for bin_index, reads session_context.json for nside, lmax, lmin, bandwidth, apodise_mask, apodisation_scale, apodisation_type, and full_sky. Saves the result automatically to {session_dir}/results/spectra/cls_bin_{bin_index:02d}x.json.

This is the preferred entry point for AnalysisCoder — it requires no parameter arguments beyond session_dir and bin_index.

Parameters:

Name Type Description Default
session_dir str

Path to the session workspace directory (contains manifest.json and session_context.json).

required
bin_index int

Zero-based index into manifest["tomographic_bins"].

required
save_maps bool

Persist the HEALPix overdensity map + survey mask this computation already builds.

False
deproject_templates list[str] | None

Named manifest["systematics_templates"] to deproject via NaMaster; None skips deprojection.

None
workspace_cache_scope str

"session" (default) caches the mode-coupling matrix under this session only; "registry" shares it across every session with the same mask via the persistent dataset-registry store. A pure cache LOCATION choice — never affects results.

'session'
jobspec dict | None

Optional {"backend": "slurm", "site": "<site>"} to run this as a non-blocking remote job instead of a local one (async receipt; poll with check_job). The per-bin FITS closure is cache-aware — a bin already on the cluster (e.g. from a remote ingest) is not re-uploaded. This ALWAYS runs as a non-blocking job — omitting jobspec does not mean in-process; it means a local background job (still async, still poll with check_job). Pass jobspec only to pick Slurm instead of local, or to forward local resource options.

None

Returns:

Type Description
dict

An async receipt ``{job_id, status, mode: "async", run_dir,

dict

session_dir, next: "check_job", ...}`` — never the spectrum itself.

dict

Poll check_job(session_dir, job_id); once status == "done", its

dict

result carries the same dict as run_power_spectrum, plus

dict

session_dir (str), bin_index (int), pipeline_config

dict

(dict — the config actually used), output_path (str — where the

dict

result was saved).

Source code in cosmotron_mcp/server.py
@mcp.tool()
@sync_budget_guard
def compute_cls_from_session(
    session_dir: str,
    bin_index: int,
    save_maps: bool = False,
    deproject_templates: list[str] | None = None,
    workspace_cache_scope: str = "session",
    jobspec: dict | None = None,
) -> dict:
    """Compute the angular power spectrum for one tomographic bin using
    pipeline_config from session_context.json as the authoritative source
    for all NaMaster parameters.

    Set ``save_maps=True`` to persist the HEALPix overdensity map + survey mask
    that this computation ALREADY builds (written to
    results/maps/delta_map_bin_{i}.fits / mask_bin_{i}.fits and registered). Use this
    instead of calling make_healpix_map again — it avoids generating the same map
    product twice.

    Set ``deproject_templates=[name, ...]`` to also run NaMaster template
    deprojection against named ``manifest["systematics_templates"]`` (ingested
    via ``ingest_template_maps``) — resolves them, no hand-built `templates=`
    argument needed. An unknown name returns needs_input. Adds
    `cls_deprojected`, `delta_cls_deprojection`, `deprojected_templates` (and
    `cls_BB_deprojected` for spin-2) to the output JSON ALONGSIDE the raw
    `cls`/`cls_denoised` — never replaces them.

    Reads manifest.json to find the FITS path for bin_index, reads
    session_context.json for nside, lmax, lmin, bandwidth, apodise_mask,
    apodisation_scale, apodisation_type, and full_sky.  Saves the result
    automatically to {session_dir}/results/spectra/cls_bin_{bin_index:02d}_x_{bin_index:02d}.json.

    This is the preferred entry point for AnalysisCoder — it requires no
    parameter arguments beyond session_dir and bin_index.

    Args:
        session_dir: Path to the session workspace directory (contains
            manifest.json and session_context.json).
        bin_index: Zero-based index into ``manifest["tomographic_bins"]``.
        save_maps: Persist the HEALPix overdensity map + survey mask this
            computation already builds.
        deproject_templates: Named ``manifest["systematics_templates"]`` to
            deproject via NaMaster; `None` skips deprojection.
        workspace_cache_scope: ``"session"`` (default) caches the
            mode-coupling matrix under this session only; ``"registry"``
            shares it across every session with the same mask via the
            persistent dataset-registry store. A pure cache LOCATION choice
            — never affects results.
        jobspec: Optional ``{"backend": "slurm", "site": "<site>"}`` to run
            this as a non-blocking remote job instead of a local one (async
            receipt; poll with `check_job`). The per-bin FITS closure is
            cache-aware — a bin already on the cluster (e.g. from a remote
            ingest) is not re-uploaded. This ALWAYS runs as a non-blocking
            job — omitting `jobspec` does not mean in-process; it means a
            local background job (still async, still poll with `check_job`).
            Pass `jobspec` only to pick Slurm instead of local, or to forward
            local resource options.

    Returns:
        An async receipt ``{job_id, status, mode: "async", run_dir,
        session_dir, next: "check_job", ...}`` — never the spectrum itself.
        Poll `check_job(session_dir, job_id)`; once `status == "done"`, its
        `result` carries the same dict as `run_power_spectrum`, plus
        ``session_dir`` (str), ``bin_index`` (int), ``pipeline_config``
        (dict — the config actually used), ``output_path`` (str — where the
        result was saved).
    """
    _require_human_gates(session_dir)
    kwargs = {
        "bin_index": bin_index, "save_maps": save_maps,
        "deproject_templates": deproject_templates,
        "workspace_cache_scope": workspace_cache_scope,
    }
    from cosmotron_mcp.remote.dispatch import route_heavy_tool
    return route_heavy_tool(
        "compute_cls_from_session", session_dir,
        {"session_dir": session_dir, **kwargs}, jobspec,
    )

compute_all_cls_from_session

compute_all_cls_from_session(session_dir: str, save_maps: bool = True, deproject_templates: list[str] | None = None, workspace_cache_scope: str = 'session', jobspec: dict | None = None) -> dict

Compute all auto- and cross-power spectra for a tomographic session.

For N tomographic bins computes all N(N+1)/2 spectra in one call: - N auto-spectra saved as results/spectra/cls_bin_{i:02d}_x_{i:02d}.json - N(N-1)/2 cross-spectra saved as results/spectra/cls_bin_{i:02d}_x_{j:02d}.json (i < j)

Use this instead of calling compute_cls_from_session N times when you need cross-spectra for tomographic SACC assembly. The auto-spectra are identical to those produced by compute_cls_from_session(i, save_maps=True). Cross-spectra load the saved HEALPix maps (requires save_maps=True, the default) and run a NaMaster subprocess for each pair. Shot noise is zero for cross-spectra (independent bins), so cls_denoised = pixel-window-corrected cls.

deproject_templates=[name, ...] applies the same named manifest["systematics_templates"] deprojection to EVERY auto-spectrum bin (see compute_cls_from_session). Cross-spectra are never deprojected.

Prerequisites: ingest_to_session must have run (manifest needed).

workspace_cache_scope: "session" (default) or "registry" — see compute_cls_from_session; applied to both auto- and cross-spectra.

Parameters:

Name Type Description Default
session_dir str

Session directory (manifest.json must exist).

required
save_maps bool

Persist the HEALPix maps auto-spectra build (required for cross-spectra).

True
deproject_templates list[str] | None

Named templates to deproject from every auto-spectrum bin; cross-spectra are never deprojected.

None
workspace_cache_scope str

"session" (default) or "registry" — see compute_cls_from_session.

'session'
jobspec dict | None

Optional {"backend": "slurm", "site": "<site>"} to run all spectra as one non-blocking remote job instead of a local one (async receipt; poll with check_job). Cache-aware bin-FITS closure (no GB re-upload of cluster-cached bins). This ALWAYS runs as a non-blocking job — omitting jobspec does not mean in-process; it means a local background job (still async, still poll with check_job). Pass jobspec only to pick Slurm instead of local, or to forward local resource options.

None

Returns:

Type Description
dict

An async receipt ``{job_id, status, mode: "async", run_dir,

dict

session_dir, next: "check_job", ...}`` — never the spectra

dict

themselves. Poll check_job(session_dir, job_id); once

dict

status == "done", its result carries

dict

{session_dir, n_bins, auto_cls, cross_cls, all_pairs}.

Source code in cosmotron_mcp/server.py
@mcp.tool()
@sync_budget_guard
def compute_all_cls_from_session(
    session_dir: str,
    save_maps: bool = True,
    deproject_templates: list[str] | None = None,
    workspace_cache_scope: str = "session",
    jobspec: dict | None = None,
) -> dict:
    """Compute all auto- and cross-power spectra for a tomographic session.

    For N tomographic bins computes all N(N+1)/2 spectra in one call:
    - N auto-spectra saved as `results/spectra/cls_bin_{i:02d}_x_{i:02d}.json`
    - N(N-1)/2 cross-spectra saved as `results/spectra/cls_bin_{i:02d}_x_{j:02d}.json` (i < j)

    Use this instead of calling `compute_cls_from_session` N times when you need
    cross-spectra for tomographic SACC assembly. The auto-spectra are identical to
    those produced by `compute_cls_from_session(i, save_maps=True)`. Cross-spectra
    load the saved HEALPix maps (requires save_maps=True, the default) and run a
    NaMaster subprocess for each pair. Shot noise is zero for cross-spectra
    (independent bins), so `cls_denoised` = pixel-window-corrected `cls`.

    `deproject_templates=[name, ...]` applies the same named
    `manifest["systematics_templates"]` deprojection to EVERY auto-spectrum bin
    (see `compute_cls_from_session`). Cross-spectra are never deprojected.

    Prerequisites: `ingest_to_session` must have run (manifest needed).

    `workspace_cache_scope`: "session" (default) or "registry" — see
    `compute_cls_from_session`; applied to both auto- and cross-spectra.

    Args:
        session_dir: Session directory (manifest.json must exist).
        save_maps: Persist the HEALPix maps auto-spectra build (required for
            cross-spectra).
        deproject_templates: Named templates to deproject from every
            auto-spectrum bin; cross-spectra are never deprojected.
        workspace_cache_scope: ``"session"`` (default) or ``"registry"`` —
            see `compute_cls_from_session`.
        jobspec: Optional ``{"backend": "slurm", "site": "<site>"}`` to run
            all spectra as one non-blocking remote job instead of a local one
            (async receipt; poll with `check_job`). Cache-aware bin-FITS
            closure (no GB re-upload of cluster-cached bins). This ALWAYS
            runs as a non-blocking job — omitting `jobspec` does not mean
            in-process; it means a local background job (still async, still
            poll with `check_job`). Pass `jobspec` only to pick Slurm instead
            of local, or to forward local resource options.

    Returns:
        An async receipt ``{job_id, status, mode: "async", run_dir,
        session_dir, next: "check_job", ...}`` — never the spectra
        themselves. Poll `check_job(session_dir, job_id)`; once
        `status == "done"`, its `result` carries
        ``{session_dir, n_bins, auto_cls, cross_cls, all_pairs}``.
    """
    _require_human_gates(session_dir)
    kwargs = {
        "save_maps": save_maps, "deproject_templates": deproject_templates,
        "workspace_cache_scope": workspace_cache_scope,
    }
    from cosmotron_mcp.remote.dispatch import route_heavy_tool
    return route_heavy_tool(
        "compute_all_cls_from_session", session_dir,
        {"session_dir": session_dir, **kwargs}, jobspec,
    )

compute_systematics_cls_from_session

compute_systematics_cls_from_session(session_dir: str, bin_index: int, template_names: list[str] | None = None, jobspec: dict | None = None) -> dict

Cross-correlate a tomographic bin's field with ingested systematics templates (stellar density, seeing, dust, ...), using pipeline_config + manifest["systematics_templates"] as the sole parameter source.

Requires ingest_template_maps to have registered templates. If this bin's saved field map(s) + mask are not yet on disk, this auto-runs compute_cls_from_session(session_dir, bin_index, save_maps=True) first (reported via maps_built).

template_names=None uses every ingested template; an unknown name returns needs_input listing the available ones.

Writes results/systematics/systematics_cls_bin_{bin_index:02d}.json: per-template {ells, cls_cross, max_abs_cl, spin}, plus a sigma_cl per-bandpower reference (from the bin's covariance sqrt_diag if present, else a Knox estimate) for later contamination-threshold checks. Registers a "systematics_cls" artefact.

Parameters:

Name Type Description Default
session_dir str

Session directory.

required
bin_index int

Tomographic bin index.

required
template_names list[str] | None

Subset of ingested template names to cross-correlate against; None uses every ingested template.

None
jobspec dict | None

Optional {"backend": "slurm", "site": "<site>"} to run remotely instead of locally (async receipt; poll with check_job). This ALWAYS runs as a non-blocking job — omitting jobspec does not mean in-process; it means a local background job (still async, still poll with check_job). Pass jobspec only to pick Slurm instead of local.

None

Returns:

Type Description
dict

An async receipt ``{job_id, status, mode: "async", run_dir,

dict

session_dir, next: "check_job", ...}``. Poll

dict

check_job(session_dir, job_id); once status == "done", its

dict

result carries per-template {ells, cls_cross, max_abs_cl, spin}

dict

plus sigma_cl, or a needs_input payload listing available

dict

template names if an unknown name was requested.

Source code in cosmotron_mcp/server.py
@mcp.tool()
@sync_budget_guard
def compute_systematics_cls_from_session(
    session_dir: str,
    bin_index: int,
    template_names: list[str] | None = None,
    jobspec: dict | None = None,
) -> dict:
    """Cross-correlate a tomographic bin's field with ingested systematics
    templates (stellar density, seeing, dust, ...), using pipeline_config +
    manifest["systematics_templates"] as the sole parameter source.

    Requires ingest_template_maps to have registered templates. If this bin's
    saved field map(s) + mask are not yet on disk, this auto-runs
    compute_cls_from_session(session_dir, bin_index, save_maps=True) first
    (reported via `maps_built`).

    template_names=None uses every ingested template; an unknown name returns
    needs_input listing the available ones.

    Writes results/systematics/systematics_cls_bin_{bin_index:02d}.json: per-template
    {ells, cls_cross, max_abs_cl, spin}, plus a sigma_cl per-bandpower
    reference (from the bin's covariance sqrt_diag if present, else a Knox
    estimate) for later contamination-threshold checks. Registers a
    "systematics_cls" artefact.

    Args:
        session_dir: Session directory.
        bin_index: Tomographic bin index.
        template_names: Subset of ingested template names to cross-correlate
            against; `None` uses every ingested template.
        jobspec: Optional ``{"backend": "slurm", "site": "<site>"}`` to run
            remotely instead of locally (async receipt; poll with
            `check_job`). This ALWAYS runs as a non-blocking job — omitting
            `jobspec` does not mean in-process; it means a local background
            job (still async, still poll with `check_job`). Pass `jobspec`
            only to pick Slurm instead of local.

    Returns:
        An async receipt ``{job_id, status, mode: "async", run_dir,
        session_dir, next: "check_job", ...}``. Poll
        `check_job(session_dir, job_id)`; once `status == "done"`, its
        `result` carries per-template ``{ells, cls_cross, max_abs_cl, spin}``
        plus ``sigma_cl``, or a ``needs_input`` payload listing available
        template names if an unknown name was requested.
    """
    _require_human_gates(session_dir)
    from cosmotron_mcp.remote.dispatch import route_heavy_tool
    return route_heavy_tool(
        "compute_systematics_cls_from_session", session_dir,
        {"session_dir": session_dir, "bin_index": bin_index,
         "template_names": template_names}, jobspec,
    )

run_bmode_null_tests_from_session

run_bmode_null_tests_from_session(session_dir: str, significance: float = 0.01, jobspec: dict | None = None) -> dict

Run BB and EB null tests (H₀: Cℓ=0) for every spin-2 bin — a shear data check.

Uses cls_BB_denoised/cls_EB_denoised against the B-mode validation covariance (built on demand) and run_null_test; registers a null_test artefact per test.

Parameters:

Name Type Description Default
session_dir str

Session directory.

required
significance float

χ² significance threshold for the pass/fail verdict.

0.01
jobspec dict | None

Optional {"backend": "slurm", "site": "<site>"} to run remotely instead of locally (async receipt; poll with check_job). This ALWAYS runs as a non-blocking job — omitting jobspec does not mean in-process; it means a local background job (still async, still poll with check_job). Pass jobspec only to pick Slurm instead of local.

None

Returns:

Type Description
dict

An async receipt ``{job_id, status, mode: "async", run_dir,

dict

session_dir, next: "check_job", ...}``. Poll

dict

check_job(session_dir, job_id); once status == "done", its

dict

result carries ``{tests: [{bin, component, chi2, p_value,

dict

verdict}], n_tests, n_fail}. No spin-2 bins →``.

Source code in cosmotron_mcp/server.py
@mcp.tool()
@sync_budget_guard
def run_bmode_null_tests_from_session(
    session_dir: str, significance: float = 0.01, jobspec: dict | None = None,
) -> dict:
    """Run BB and EB null tests (H₀: Cℓ=0) for every spin-2 bin — a shear data check.

    Uses `cls_BB_denoised`/`cls_EB_denoised` against the B-mode validation
    covariance (built on demand) and `run_null_test`; registers a `null_test`
    artefact per test.

    Args:
        session_dir: Session directory.
        significance: χ² significance threshold for the pass/fail verdict.
        jobspec: Optional ``{"backend": "slurm", "site": "<site>"}`` to run
            remotely instead of locally (async receipt; poll with
            `check_job`). This ALWAYS runs as a non-blocking job — omitting
            `jobspec` does not mean in-process; it means a local background
            job (still async, still poll with `check_job`). Pass `jobspec`
            only to pick Slurm instead of local.

    Returns:
        An async receipt ``{job_id, status, mode: "async", run_dir,
        session_dir, next: "check_job", ...}``. Poll
        `check_job(session_dir, job_id)`; once `status == "done"`, its
        `result` carries ``{tests: [{bin, component, chi2, p_value,
        verdict}], n_tests, n_fail}``. No spin-2 bins → ``{skipped: ...}``.
    """
    _require_human_gates(session_dir)
    from cosmotron_mcp.remote.dispatch import route_heavy_tool
    return route_heavy_tool(
        "run_bmode_null_tests_from_session", session_dir,
        {"session_dir": session_dir, "significance": significance}, jobspec,
    )

compute_systematics_cls

compute_systematics_cls(delta_map: list, survey_mask: list, systematic_maps: dict[str, list], nside: int = 512, lmin: int = 10, lmax: int = 1000, n_bandpowers: int = 20, apodisation_scale: float = 0.5, spin: int = 0, field_maps_b: list | None = None) -> dict

Cross-correlate a HEALPix field with systematic template maps.

PREFER compute_systematics_cls_from_session — never hand-assemble maps/masks for this low-level primitive.

Each systematic template is mean-subtracted over the survey footprint before cross-correlation. Systematic templates are always spin-0 scalar maps; the data field spin is controlled by the spin parameter.

Parameters:

Name Type Description Default
delta_map list

HEALPix map consistent with nside. For spin-2 fields this is the Q (or E) component.

required
survey_mask list

HEALPix survey mask consistent with nside.

required
systematic_maps dict[str, list]

Mapping of {name: HEALPix map} for each systematic template. All maps must have the same NSIDE as delta_map.

required
nside int

HEALPix resolution parameter, as in compute_cls.

512
lmin int

Minimum multipole, as in compute_cls.

10
lmax int

Maximum multipole, as in compute_cls.

1000
n_bandpowers int

Number of bandpowers, as in compute_cls.

20
apodisation_scale float

Mask apodisation scale, as in compute_cls.

0.5
spin int

Spin weight of the data field (0 or 2).

0
field_maps_b list | None

Second spin component (U-map) of the data field when spin=2.

None

Returns:

Name Type Description
dict

This is a HEAVY_TOOLS entry — building NaMaster fields for the

dict

cross-correlation is compute-node work, so it ALWAYS runs as a

dict

non-blocking local background job, never inline (no jobspec

dict

param — session-less, local job routing only). Returns an async

dict

receipt ``{job_id, status, mode: "async", run_dir, session_dir,

next dict

"check_job", ...}` —session_dir` here is a fixed shared job

dict

root (this tool has no session of its own), NOT a pipeline session;

dict

use the session_dir the receipt itself returns when polling. Poll

dict

check_job(session_dir, job_id); once status == "done", its

dict

result carries a dict mapping systematic name to a dict with keys:

dict

ells (np.ndarray), cls_cross (np.ndarray — primary cross-Cl,

dict

E-mode coupling for spin-2), max_abs_cl (float — quick

dict

contamination diagnostic), spin (int).

Source code in cosmotron_mcp/server.py
@mcp.tool()
@sync_budget_guard
def compute_systematics_cls(
    delta_map: list,
    survey_mask: list,
    systematic_maps: dict[str, list],
    nside: int = 512,
    lmin: int = 10,
    lmax: int = 1000,
    n_bandpowers: int = 20,
    apodisation_scale: float = 0.5,
    spin: int = 0,
    field_maps_b: list | None = None,
) -> dict:
    """Cross-correlate a HEALPix field with systematic template maps.

    PREFER `compute_systematics_cls_from_session` — never hand-assemble
    maps/masks for this low-level primitive.

    Each systematic template is mean-subtracted over the survey footprint
    before cross-correlation.  Systematic templates are always spin-0 scalar
    maps; the data field spin is controlled by the ``spin`` parameter.

    Args:
        delta_map: HEALPix map consistent with `nside`. For spin-2 fields
            this is the Q (or E) component.
        survey_mask: HEALPix survey mask consistent with `nside`.
        systematic_maps: Mapping of ``{name: HEALPix map}`` for each
            systematic template. All maps must have the same NSIDE as
            `delta_map`.
        nside: HEALPix resolution parameter, as in `compute_cls`.
        lmin: Minimum multipole, as in `compute_cls`.
        lmax: Maximum multipole, as in `compute_cls`.
        n_bandpowers: Number of bandpowers, as in `compute_cls`.
        apodisation_scale: Mask apodisation scale, as in `compute_cls`.
        spin: Spin weight of the data field (0 or 2).
        field_maps_b: Second spin component (U-map) of the data field when
            `spin=2`.

    Returns:
        This is a `HEAVY_TOOLS` entry — building NaMaster fields for the
        cross-correlation is compute-node work, so it ALWAYS runs as a
        non-blocking local background job, never inline (no `jobspec`
        param — session-less, local job routing only). Returns an async
        receipt ``{job_id, status, mode: "async", run_dir, session_dir,
        next: "check_job", ...}`` — `session_dir` here is a fixed shared job
        root (this tool has no session of its own), NOT a pipeline session;
        use the `session_dir` the receipt itself returns when polling. Poll
        `check_job(session_dir, job_id)`; once `status == "done"`, its
        `result` carries a dict mapping systematic name to a dict with keys:
        ``ells`` (np.ndarray), ``cls_cross`` (np.ndarray — primary cross-Cl,
        E-mode coupling for spin-2), ``max_abs_cl`` (float — quick
        contamination diagnostic), ``spin`` (int).
    """
    from cosmotron_mcp import heavy
    from cosmotron_mcp.remote.dispatch import submit_local_job
    kwargs = {
        "delta_map": delta_map, "survey_mask": survey_mask,
        "systematic_maps": systematic_maps, "nside": nside, "lmin": lmin,
        "lmax": lmax, "n_bandpowers": n_bandpowers,
        "apodisation_scale": apodisation_scale, "spin": spin,
        "field_maps_b": field_maps_b,
    }
    return submit_local_job("compute_systematics_cls", heavy.ADHOC_JOB_ROOT,
                             kwargs, inject_session_dir=False)

apply_template_deprojection

apply_template_deprojection(delta_map: list, survey_mask: list, systematic_maps: dict[str, list], nside: int = 512, lmin: int = 10, lmax: int = 1000, n_bandpowers: int = 20, apodisation_scale: float = 0.5, spin: int = 0, field_maps_b: list | None = None, subtract_deprojection_bias: bool = False) -> dict

Remove systematic contamination via NaMaster template deprojection.

In a session, deprojection runs ONLY via compute_cls_from_session(session_dir, bin_index, deproject_templates=[...]) — this low-level primitive is for CLI/script use with no session, and is denied to every executing agent's tool map.

Constructs two NaMaster fields — one with contaminant templates (for deprojection) and one without (for comparison) — and returns the deprojected Cl estimates. Systematic templates are always spin-0 scalar maps; NaMaster supports cross-spin deprojection.

Parameters:

Name Type Description Default
delta_map list

HEALPix map consistent with nside. For spin-2 fields this is the Q (or E) component.

required
survey_mask list

HEALPix survey mask consistent with nside.

required
systematic_maps dict[str, list]

Mapping of {name: HEALPix template map} to deproject.

required
nside int

HEALPix resolution parameter, as in compute_cls.

512
lmin int

Minimum multipole, as in compute_cls.

10
lmax int

Maximum multipole, as in compute_cls.

1000
n_bandpowers int

Number of bandpowers, as in compute_cls.

20
apodisation_scale float

Mask apodisation scale, as in compute_cls.

0.5
spin int

Spin weight of the data field (0 or 2).

0
field_maps_b list | None

Second spin component (U-map) of the data field when spin=2.

None
subtract_deprojection_bias bool

When True, subtract NaMaster's analytic contaminant-deprojection bias (nmt.deprojection_bias) via decouple_cell(cl_bias=...) — the full MASTER estimate. cl_guess is data-driven (raw coupled pseudo-Cℓ / ⟨mask²⟩); no cosmology or fabricated theory. Off by default to match the Cosmotheka reference (~0.1–0.5% second-order term).

False

Returns:

Name Type Description
dict

This is a HEAVY_TOOLS entry — constructing two NaMaster fields

dict

(with/without templates) is compute-node work, so it ALWAYS runs as

dict

a non-blocking local background job, never inline (no jobspec

dict

param — session-less, local job routing only). Returns an async

dict

receipt ``{job_id, status, mode: "async", run_dir, session_dir,

next dict

"check_job", ...}` —session_dir` here is a fixed shared job

dict

root (this tool has no session of its own), NOT a pipeline session;

dict

use the session_dir the receipt itself returns when polling. Poll

dict

check_job(session_dir, job_id); once status == "done", its

dict

result carries: ells (np.ndarray), cls_deprojected

dict

(np.ndarray — primary deprojected Cl, EE for spin-2), cls_raw

dict

(np.ndarray — Cls before deprojection), delta_cls (np.ndarray —

dict

cls_raw - cls_deprojected), templates_removed (list[str]),

dict

n_templates (int), spin (int), cls_BB_deprojected

dict

(np.ndarray — BB deprojected, only when spin=2), cls_BB_raw

dict

(np.ndarray — BB raw, only when spin=2).

Source code in cosmotron_mcp/server.py
@mcp.tool()
@sync_budget_guard
def apply_template_deprojection(
    delta_map: list,
    survey_mask: list,
    systematic_maps: dict[str, list],
    nside: int = 512,
    lmin: int = 10,
    lmax: int = 1000,
    n_bandpowers: int = 20,
    apodisation_scale: float = 0.5,
    spin: int = 0,
    field_maps_b: list | None = None,
    subtract_deprojection_bias: bool = False,
) -> dict:
    """Remove systematic contamination via NaMaster template deprojection.

    In a session, deprojection runs ONLY via
    `compute_cls_from_session(session_dir, bin_index, deproject_templates=[...])`
    — this low-level primitive is for CLI/script use with no session, and is
    denied to every executing agent's tool map.

    Constructs two NaMaster fields — one with contaminant templates (for
    deprojection) and one without (for comparison) — and returns the
    deprojected Cl estimates.  Systematic templates are always spin-0 scalar
    maps; NaMaster supports cross-spin deprojection.

    Args:
        delta_map: HEALPix map consistent with `nside`. For spin-2 fields
            this is the Q (or E) component.
        survey_mask: HEALPix survey mask consistent with `nside`.
        systematic_maps: Mapping of ``{name: HEALPix template map}`` to
            deproject.
        nside: HEALPix resolution parameter, as in `compute_cls`.
        lmin: Minimum multipole, as in `compute_cls`.
        lmax: Maximum multipole, as in `compute_cls`.
        n_bandpowers: Number of bandpowers, as in `compute_cls`.
        apodisation_scale: Mask apodisation scale, as in `compute_cls`.
        spin: Spin weight of the data field (0 or 2).
        field_maps_b: Second spin component (U-map) of the data field when
            `spin=2`.
        subtract_deprojection_bias: When `True`, subtract NaMaster's
            analytic contaminant-deprojection bias
            (``nmt.deprojection_bias``) via ``decouple_cell(cl_bias=...)`` —
            the full MASTER estimate. ``cl_guess`` is data-driven (raw
            coupled pseudo-Cℓ / ⟨mask²⟩); no cosmology or fabricated theory.
            Off by default to match the Cosmotheka reference (~0.1–0.5%
            second-order term).

    Returns:
        This is a `HEAVY_TOOLS` entry — constructing two NaMaster fields
        (with/without templates) is compute-node work, so it ALWAYS runs as
        a non-blocking local background job, never inline (no `jobspec`
        param — session-less, local job routing only). Returns an async
        receipt ``{job_id, status, mode: "async", run_dir, session_dir,
        next: "check_job", ...}`` — `session_dir` here is a fixed shared job
        root (this tool has no session of its own), NOT a pipeline session;
        use the `session_dir` the receipt itself returns when polling. Poll
        `check_job(session_dir, job_id)`; once `status == "done"`, its
        `result` carries: ``ells`` (np.ndarray), ``cls_deprojected``
        (np.ndarray — primary deprojected Cl, EE for spin-2), ``cls_raw``
        (np.ndarray — Cls before deprojection), ``delta_cls`` (np.ndarray —
        ``cls_raw - cls_deprojected``), ``templates_removed`` (list[str]),
        ``n_templates`` (int), ``spin`` (int), ``cls_BB_deprojected``
        (np.ndarray — BB deprojected, only when spin=2), ``cls_BB_raw``
        (np.ndarray — BB raw, only when spin=2).
    """
    from cosmotron_mcp import heavy
    from cosmotron_mcp.remote.dispatch import submit_local_job
    kwargs = {
        "delta_map": delta_map, "survey_mask": survey_mask,
        "systematic_maps": systematic_maps, "nside": nside, "lmin": lmin,
        "lmax": lmax, "n_bandpowers": n_bandpowers,
        "apodisation_scale": apodisation_scale, "spin": spin,
        "field_maps_b": field_maps_b,
        "subtract_deprojection_bias": subtract_deprojection_bias,
    }
    return submit_local_job("apply_template_deprojection", heavy.ADHOC_JOB_ROOT,
                             kwargs, inject_session_dir=False)

run_null_test

run_null_test(cls_array: list, cov_matrix: list, test_name: str = 'null_test', significance: float = 0.01, output_path: str | None = None, generated_by: str | None = None, agent_name: str | None = None) -> dict

Run a chi-squared null test: H₀: C_ell = 0.

Uses the full covariance matrix if provided, otherwise falls back to the diagonal. A result is flagged as FAIL if the p-value falls below the significance threshold.

Parameters:

Name Type Description Default
cls_array list

1-D array of bandpower estimates to test against zero.

required
cov_matrix list

2-D covariance matrix of shape (n_bandpowers, n_bandpowers).

required
test_name str

Label for logging and the return dict.

'null_test'
significance float

p-value threshold below which the test is considered a failure.

0.01
output_path str | None

If provided, write the result JSON to this path. Omit to get the result dict only, without saving to disk.

None
generated_by str | None

Infrastructure kwarg — omit in normal usage; auto-populated from the executor environment.

None
agent_name str | None

Infrastructure kwarg — omit in normal usage; auto-populated from the executor environment.

None

Returns:

Type Description
dict

This is a HEAVY_TOOLS entry (included for interface uniformity

dict

with its siblings, though it's cheap arithmetic on small

dict

already-computed arrays) — ALWAYS runs as a non-blocking local

dict

background job, never inline (no jobspec param — session-less,

dict

local job routing only). Returns an async receipt ``{job_id,

dict

status, mode: "async", run_dir, session_dir, next: "check_job",

dict

...}` —session_dir` here is a fixed shared job root (this tool

dict

has no session of its own), NOT a pipeline session; use the

dict

session_dir the receipt itself returns when polling. Poll

dict

check_job(session_dir, job_id); once status == "done", its

dict

result carries: test_name (str), chi2 (float), ndof

dict

(int), p_value (float), passed (bool), significance

dict

(float), verdict (str — human-readable 'PASS'/'FAIL').

Source code in cosmotron_mcp/server.py
@mcp.tool()
@sync_budget_guard
def run_null_test(
    cls_array: list,
    cov_matrix: list,
    test_name: str = "null_test",
    significance: float = 0.01,
    output_path: str | None = None,
    generated_by: str | None = None,
    agent_name: str | None = None,
) -> dict:
    """Run a chi-squared null test: H₀: C_ell = 0.

    Uses the full covariance matrix if provided, otherwise falls back to
    the diagonal. A result is flagged as FAIL if the p-value falls below
    the significance threshold.

    Args:
        cls_array: 1-D array of bandpower estimates to test against zero.
        cov_matrix: 2-D covariance matrix of shape
            ``(n_bandpowers, n_bandpowers)``.
        test_name: Label for logging and the return dict.
        significance: p-value threshold below which the test is considered
            a failure.
        output_path: If provided, write the result JSON to this path. Omit
            to get the result dict only, without saving to disk.
        generated_by: Infrastructure kwarg — omit in normal usage;
            auto-populated from the executor environment.
        agent_name: Infrastructure kwarg — omit in normal usage;
            auto-populated from the executor environment.

    Returns:
        This is a `HEAVY_TOOLS` entry (included for interface uniformity
        with its siblings, though it's cheap arithmetic on small
        already-computed arrays) — ALWAYS runs as a non-blocking local
        background job, never inline (no `jobspec` param — session-less,
        local job routing only). Returns an async receipt ``{job_id,
        status, mode: "async", run_dir, session_dir, next: "check_job",
        ...}`` — `session_dir` here is a fixed shared job root (this tool
        has no session of its own), NOT a pipeline session; use the
        `session_dir` the receipt itself returns when polling. Poll
        `check_job(session_dir, job_id)`; once `status == "done"`, its
        `result` carries: ``test_name`` (str), ``chi2`` (float), ``ndof``
        (int), ``p_value`` (float), ``passed`` (bool), ``significance``
        (float), ``verdict`` (str — human-readable ``'PASS'``/``'FAIL'``).
    """
    from cosmotron_mcp import heavy
    from cosmotron_mcp.remote.dispatch import submit_local_job
    kwargs = {
        "cls_array": cls_array, "cov_matrix": cov_matrix,
        "test_name": test_name, "significance": significance,
        "output_path": output_path, "generated_by": generated_by,
        "agent_name": agent_name,
    }
    return submit_local_job("run_null_test", heavy.ADHOC_JOB_ROOT,
                             kwargs, inject_session_dir=False)

create_namaster_bins

create_namaster_bins(lmin: int, lmax: int, bandwidth: int) -> dict

Create NaMaster bandpower bins using uniform bandwidth.

Always use lmax_bins (not requested lmax) when constructing NmtField objects.

Parameters:

Name Type Description Default
lmin int

Minimum multipole.

required
lmax int

Maximum requested multipole.

required
bandwidth int

Multipoles per bandpower bin.

required

Returns:

Type Description
dict

dict with ells_ini, ells_end, effective_ells,

dict

n_bandpowers, lmax_bins.

Source code in cosmotron_mcp/server.py
@mcp.tool()
@sync_budget_guard
def create_namaster_bins(
    lmin: int,
    lmax: int,
    bandwidth: int,
) -> dict:
    """Create NaMaster bandpower bins using uniform bandwidth.

    Always use lmax_bins (not requested lmax) when constructing NmtField objects.

    Args:
        lmin: Minimum multipole.
        lmax: Maximum requested multipole.
        bandwidth: Multipoles per bandpower bin.

    Returns:
        ``dict`` with ``ells_ini``, ``ells_end``, ``effective_ells``,
        ``n_bandpowers``, ``lmax_bins``.
    """
    return _create_namaster_bins(lmin, lmax, bandwidth)