Skip to content

Allow ceiling-only evaluation without a prediction - #245

Merged
LeonHafner merged 3 commits into
ArcInstitute:mainfrom
cachris1:ceiling-only-no-pred
Jul 27, 2026
Merged

Allow ceiling-only evaluation without a prediction#245
LeonHafner merged 3 commits into
ArcInstitute:mainfrom
cachris1:ceiling-only-no-pred

Conversation

@cachris1

Copy link
Copy Markdown
Contributor

cell-eval run previously required --adata-pred. The data ceiling is estimated from the real data alone (disjoint self-split), so a prediction is unnecessary when only the ceiling is wanted.

  • --adata-pred is now optional; omitting it (together with --ceiling) computes only the real-data ceiling. Omitting it without --ceiling errors.
  • MetricsEvaluator accepts adata_pred=None (ceiling-only mode): skips the main de_comparison, and compute() raises directing to compute_ceiling().
  • _build_anndata_pair mirrors real into pred when no prediction is given (the placeholder is never scored; the ceiling reads only .real).

`cell-eval run` previously required `--adata-pred`. The data ceiling is
estimated from the real data alone (disjoint self-split), so a prediction is
unnecessary when only the ceiling is wanted.

- `--adata-pred` is now optional; omitting it (together with `--ceiling`)
  computes only the real-data ceiling. Omitting it without `--ceiling` errors.
- `MetricsEvaluator` accepts `adata_pred=None` (ceiling-only mode): skips the
  main `de_comparison`, and `compute()` raises directing to `compute_ceiling()`.
- `_build_anndata_pair` mirrors real into pred when no prediction is given (the
  placeholder is never scored; the ceiling reads only `.real`).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a "ceiling-only" mode to the evaluation pipeline, allowing users to estimate the real-data ceiling without providing a predicted AnnData object (adata_pred). The CLI and MetricsEvaluator have been updated to handle adata_pred as optional, bypassing prediction-based computations and enforcing that --ceiling is specified when running in this mode. The review feedback highlights a misleading CLI help message and recommends raising explicit validation errors if precomputed DE results are provided in ceiling-only mode, as they are currently silently ignored.

Comment thread src/cell_eval/_cli/_run.py Outdated
Comment on lines +172 to +176
ceiling_only = args.adata_pred is None
if ceiling_only and not args.ceiling:
raise ValueError(
"--adata-pred is required unless --ceiling is passed (ceiling-only mode)."
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In ceiling-only mode, any precomputed DE results passed via --de-pred or --de-real will be silently ignored because the main evaluation is skipped and the ceiling estimation computes its own DE on the split halves. To prevent user confusion and ensure correctness, we should raise a ValueError if these arguments are provided in ceiling-only mode.

Suggested change
ceiling_only = args.adata_pred is None
if ceiling_only and not args.ceiling:
raise ValueError(
"--adata-pred is required unless --ceiling is passed (ceiling-only mode)."
)
ceiling_only = args.adata_pred is None
if ceiling_only:
if not args.ceiling:
raise ValueError(
"--adata-pred is required unless --ceiling is passed (ceiling-only mode)."
)
if args.de_pred is not None or args.de_real is not None:
raise ValueError(
"Precomputed DE results (--de-pred or --de-real) cannot be used in ceiling-only mode "
"as the ceiling is estimated by splitting the real data and re-computing DE on the halves."
)

# alone, so no prediction is required. When adata_pred is None we skip
# building the main de_comparison (compute() is unavailable) - only
# compute_ceiling() may be called.
self.ceiling_only = adata_pred is None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When MetricsEvaluator is instantiated programmatically in ceiling-only mode (adata_pred=None), any provided de_pred or de_real arguments are silently ignored. We should raise a ValueError to prevent programmatic API misuse and make this limitation explicit.

        self.ceiling_only = adata_pred is None
        if self.ceiling_only and (de_pred is not None or de_real is not None):
            raise ValueError(
                "de_pred and de_real cannot be provided in ceiling-only mode (adata_pred=None)."
            )

@LeonHafner LeonHafner left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Design is right — the ceiling reads only .real, so requiring a prediction was an artificial barrier. I merged this onto current main locally and it composes cleanly. Notes below.

Blockers

  • No tests for the new mode. The suite is 48 passed both with and without this PR, so nothing exercises ceiling-only. Worth covering: compute_ceiling() with adata_pred=None, compute() raising, and the CLI refusal when -ap and --ceiling are both omitted.
  • CI is stale — needs a rebase. Checks ran Jul 24 on b53dde6; #246 merged Jul 27. pull_request builds test the merge commit, so these green ticks validated a main without the r > 0 ceiling guard, and CI is not re-run when the base moves.

Should fix

  • CLI misuse raise ValueError gives a traceback and exit 1; parser.error() gives a usage message and exit 2. It is the first thing a user hits on a wrong invocation.
  • pred = real aliases the same object (pair.real is pair.pred is True). Harmless now since compute() is blocked and the halves are fresh copies, but a future write to .pred would silently corrupt .real — worth a copy or an explicit comment at the pair boundary.
  • -ap help text: "omit together with --ceiling" reads as "omit both", which is precisely the case that errors. "omit when passing --ceiling" is what is meant.

On the --de-real / --de-pred comment above

  • --de-pred in ceiling-only mode is incoherent (there is no prediction) — erroring is fair.
  • --de-real is a reasonable thing to try, but the ceiling must compute DE on its own halves, so it cannot be reused. That reads as a warning rather than a hard failure — erroring turns a harmless no-op into a broken command.

Verified on the merge with current main

  • ceiling-only run: exit 0, writes only the two ceiling CSVs.
  • --celltype-col path: exit 0, per-celltype prefixed outputs.
  • ceiling values byte-identical across all 25 columns to the with-prediction run — the property the PR rests on.
  • all four CI gates pass on the merged tree.

Minor

  • Ceiling-only writes no real_de.csv/pred_de.csv (correct — the self-split DE is in-memory), but that differs from the normal run. One line in the help text or PR body would save a question later.

…tyle usage error

Tests for the new mode, which the suite did not exercise at all:
- ceiling-only shape: main de_comparison skipped, pred side aliases real, compute()
  refuses instead of silently scoring real against itself
- the ceiling is unchanged by supplying a prediction (same seed, same values) - the
  property the mode rests on
- both precomputed-DE warnings fire
- the CLI usage error exits 2

Precomputed de_pred/de_real cannot be reused in ceiling-only mode: the main
comparison is skipped and the ceiling computes DE on its own disjoint halves. Both
are now warned about rather than silently dropped, in MetricsEvaluator so the CLI and
the programmatic API are covered by one check. A warning rather than an error, so a
stray argument does not break an otherwise valid run.

Omitting --adata-pred without --ceiling is a usage error, so exit 2 with a message on
stderr the way argparse does, instead of surfacing a ValueError traceback.

Record the pred-placeholder invariant: `pair.real is pair.pred` in ceiling-only mode -
an alias, not a copy, since copying a matrix that is never scored would double peak
memory. Safe only because compute() is blocked and compute_ceiling() derives fresh
copies of both halves from .real, so an in-place write to .pred would corrupt .real.

Help text: "omit when passing --ceiling" (omitting both is the case that errors), and
note that ceiling-only writes no results.csv and no DE tables.
@LeonHafner

Copy link
Copy Markdown
Collaborator

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a 'ceiling-only' mode to the evaluation pipeline, allowing users to compute the real-data ceiling without providing a predicted AnnData object (adata_pred). The CLI has been updated to make --adata-pred optional when --ceiling is specified, and it now exits gracefully with a usage error if a prediction is omitted without enabling the ceiling. In the MetricsEvaluator, calling compute() is disabled in ceiling-only mode, and warnings are issued if precomputed differential expression tables are supplied. To support this mode efficiently, the evaluator aliases the real data as a placeholder for the prediction to avoid unnecessary memory overhead. Comprehensive unit tests have been added to verify these behaviors, CLI constraints, and output consistency. There are no review comments, so no feedback is provided.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates cell-eval run and MetricsEvaluator to support a ceiling-only workflow where the data ceiling is computed from the real AnnData alone, without requiring a prediction. This aligns the CLI and programmatic API with the fact that compute_ceiling() self-splits the real data and does not use the prediction.

Changes:

  • Make --adata-pred optional for cell-eval run when --ceiling is provided; otherwise treat it as a usage error.
  • Add a ceiling_only mode to MetricsEvaluator (adata_pred=None), skipping the main DE comparison and blocking compute() with a clear error directing users to compute_ceiling().
  • Add tests covering ceiling-only evaluator invariants, CLI behavior, and warnings for unused precomputed DE inputs.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/cell_eval/_evaluator.py Adds ceiling-only mode (adata_pred=None), blocks compute(), and mirrors real→pred in _build_anndata_pair for placeholder pairing.
src/cell_eval/_cli/_run.py Makes --adata-pred optional and adds ceiling-only CLI flow control (exit 2 when missing pred without --ceiling).
tests/test_ceiling.py Adds ceiling-only mode tests, including CLI rejection and warning behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/cell_eval/_cli/_run.py
@LeonHafner
LeonHafner merged commit 6928cf8 into ArcInstitute:main Jul 27, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants