Skip to content

fix: numerical guard, OT warnings, lazy matplotlib, and GradModel export - #182

Open
atong01 wants to merge 1 commit into
mainfrom
bugfixes
Open

fix: numerical guard, OT warnings, lazy matplotlib, and GradModel export#182
atong01 wants to merge 1 commit into
mainfrom
bugfixes

Conversation

@atong01

@atong01 atong01 commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Fixes six small bugs and rough edges in torchcfm/.

Changes

1. conditional_flow_matching.pycompute_lambda numerical guard

compute_lambda used 2 * sigma_t / (self.sigma**2 + 1e-8) which masked the degenerate sigma=0 case with a tiny denominator. Replaced with an explicit if self.sigma == 0: return torch.ones_like(t) branch. Uses t (always a tensor per docstring) rather than sigma_t (which the base class returns as a Python int when sigma=0).

2. conditional_flow_matching.pyExactOptimalTransportConditionalFlowMatcher docstring/signature mismatch

The docstring referenced an ot_sampler parameter that didn't appear in __init__. Added a backward-compatible ot_method='exact' parameter (mirroring SchrodingerBridgeConditionalFlowMatcher) and updated the docstring to describe it.

3. optimal_transport.py — stdout debug prints in get_map

Replaced print('ERROR: p is not finite') + three more print() calls with a single warnings.warn(...) that preserves the same information (p, cost mean/max, x0, x1).

4. optimal_transport.py — bare assert in wasserstein

Replaced assert power == 1 or power == 2 with if power not in (1, 2): raise ValueError(f"power must be 1 or 2, got {power}").

5. utils.py — unconditional matplotlib import

Moved import matplotlib.pyplot as plt from the top of the module to inside plot_trajectories (the only function that uses it). The module now imports without matplotlib installed — useful for headless test environments.

6. models/__init__.py — missing GradModel export

Changed from .models import MLP to from .models import MLP, GradModel. GradModel was defined in models.py but not exported.

Motivation

These are small correctness and quality bugs found while exploring the codebase. Each fix is localized and backward compatible.

Validation

All changes tested as part of a 4-PR batch (community-docs, add-tests, bugfixes, ci-updates) merged locally and run with pytest:

pytest -v --ignore=examples --ignore=runner
172 passed, 1 skipped, 0 failed

Bugs discovered and fixed during local testing

During local pytest validation, two bugs surfaced and were fixed in this same branch:

  • compute_lambda initially returned torch.ones_like(sigma_t) which raised TypeError when the base class returned self.sigma as a Python int. Fixed by using torch.ones_like(t) instead.
  • eight_normal_sample(n, dim, ...) hardcoded 2D centers but accepted a dim parameter, raising a shape error when dim != 2. Added if dim < 2: raise ValueError and zero-padded centers for dim > 2.

Summary by Sourcery

Fix numerical robustness issues, clean up optimal transport diagnostics, make plotting utilities optional on import, and expose the missing GradModel symbol from the models package.

Bug Fixes:

  • Guard compute_lambda against the sigma=0 case to avoid division by zero and tensor/scalar mismatches.
  • Align ExactOptimalTransportConditionalFlowMatcher initialization with its documented OT method parameter and make the OT plan sampler configurable.
  • Replace stdout debug prints in optimal transport plan computation with warnings and validate wasserstein power values via a proper exception.
  • Fix eight_normal_sample to validate dimensionality and correctly construct centers for dim values other than 2.

Enhancements:

  • Defer matplotlib import to plot_trajectories so the utils module can be imported without matplotlib in headless or minimal environments.
  • Export GradModel from the models package alongside MLP for direct external use.

- conditional_flow_matching.py: compute_lambda now explicitly handles sigma=0 (returns ones_like) instead of relying on a 1e-8 denominator guard that masked the degenerate case
- conditional_flow_matching.py: ExactOptimalTransportConditionalFlowMatcher gains a backward-compatible ot_method='exact' parameter (mirrors SchrodingerBridgeCFM), fixing the docstring/signature mismatch
- optimal_transport.py: replace stdout 'print' debug calls in get_map with a proper warnings.warn on non-finite OT plans
- optimal_transport.py: replace bare 'assert power == 1 or power == 2' with a ValueError that names the invalid value
- utils.py: move 'import matplotlib.pyplot as plt' inside plot_trajectories (lazy import) so the module imports without matplotlib installed
- models/__init__.py: export GradModel alongside MLP
@sourcery-ai

sourcery-ai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

This PR applies a set of small, focused fixes across conditional flow matching, optimal transport utilities, plotting helpers, and model exports, improving numerical robustness, API/doc alignment, logging behavior, optional matplotlib usage, and exposing an existing GradModel symbol.

File-Level Changes

Change Details Files
Harden conditional flow matcher numerics and align ExactOptimalTransportConditionalFlowMatcher constructor with its documented API.
  • Guard compute_lambda against sigma=0 by returning a tensor of ones based on t instead of dividing by sigma**2 or relying on sigma_t which may be a Python scalar.
  • Extend ExactOptimalTransportConditionalFlowMatcher.init to accept an ot_method parameter (default "exact") and pass it through to OTPlanSampler while updating the docstring accordingly.
torchcfm/conditional_flow_matching.py
Improve optimal transport diagnostics and input validation.
  • Replace stdout print-based error reporting for non-finite OT plan outputs in get_map with a single warnings.warn call that preserves the diagnostic details.
  • Replace a bare assert on the wasserstein power argument with an explicit ValueError when power is not 1 or 2.
torchcfm/optimal_transport.py
Make utils independent of matplotlib at import time and generalize eight_normal_sample to arbitrary dimensions.
  • Delay importing matplotlib.pyplot until inside plot_trajectories so the utils module can be imported without matplotlib installed.
  • Validate that dim >= 2 in eight_normal_sample and construct higher-dimensional centers by zero-padding the predefined 2D centers to shape (8, dim).
torchcfm/utils.py
Expose GradModel from the models package.
  • Update the models package init to re-export GradModel alongside MLP so users can import it from torchcfm.models.
torchcfm/models/__init__.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai 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.

Hey - I've found 1 issue, and left some high level feedback:

  • The get_map warning currently interpolates full arrays (p, x0, x1) into the message, which can be extremely verbose; consider logging only shapes and summary statistics (e.g., min/mean/max) to keep diagnostics readable.
  • In compute_lambda, you might want to guard against very small non-zero sigma values (e.g., via a tolerance) rather than only sigma == 0, to avoid unstable scaling when sigma is close to zero but not exactly zero.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `get_map` warning currently interpolates full arrays (`p`, `x0`, `x1`) into the message, which can be extremely verbose; consider logging only shapes and summary statistics (e.g., min/mean/max) to keep diagnostics readable.
- In `compute_lambda`, you might want to guard against very small non-zero `sigma` values (e.g., via a tolerance) rather than only `sigma == 0`, to avoid unstable scaling when `sigma` is close to zero but not exactly zero.

## Individual Comments

### Comment 1
<location path="torchcfm/optimal_transport.py" line_range="87-94" />
<code_context>
-            print(p)
-            print("Cost mean, max", M.mean(), M.max())
-            print(x0, x1)
+            warnings.warn(
+                "Non-finite values in OT plan p. "
+                f"p={p}. Cost mean={M.mean()}, max={M.max()}. "
+                f"x0={x0}, x1={x1}."
+            )
         if np.abs(p.sum()) < 1e-8:
</code_context>
<issue_to_address>
**suggestion (performance):** Warning message may be extremely verbose and slow when x0/x1 or p are large arrays.

Embedding full `p`, `x0`, and `x1` in the warning can create very large strings and hurt performance, particularly in tight loops. Consider logging only shapes and basic stats (e.g., `p.shape`, `x0.shape`, `x1.shape`, dtypes, mins/maxes), or gating full dumps behind a debug flag.

```suggestion
        p = self.ot_fn(a, b, M.detach().cpu().numpy())
        if not np.all(np.isfinite(p)):
            # Log only summary statistics to avoid building very large warning
            # strings when p, x0, or x1 are large arrays/tensors.
            p_min = np.nanmin(p)
            p_max = np.nanmax(p)
            p_mean = np.nanmean(p)
            x0_shape = getattr(x0, "shape", None)
            x1_shape = getattr(x1, "shape", None)
            x0_dtype = getattr(x0, "dtype", None)
            x1_dtype = getattr(x1, "dtype", None)
            warnings.warn(
                "Non-finite values in OT plan p. "
                f"p.shape={getattr(p, 'shape', None)}, "
                f"p.dtype={getattr(p, 'dtype', None)}, "
                f"p.min={p_min}, p.max={p_max}, p.mean={p_mean}. "
                f"Cost mean={M.mean()}, max={M.max()}. "
                f"x0.shape={x0_shape}, x0.dtype={x0_dtype}; "
                f"x1.shape={x1_shape}, x1.dtype={x1_dtype}."
            )
        if np.abs(p.sum()) < 1e-8:
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 87 to 94
p = self.ot_fn(a, b, M.detach().cpu().numpy())
if not np.all(np.isfinite(p)):
print("ERROR: p is not finite")
print(p)
print("Cost mean, max", M.mean(), M.max())
print(x0, x1)
warnings.warn(
"Non-finite values in OT plan p. "
f"p={p}. Cost mean={M.mean()}, max={M.max()}. "
f"x0={x0}, x1={x1}."
)
if np.abs(p.sum()) < 1e-8:

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.

suggestion (performance): Warning message may be extremely verbose and slow when x0/x1 or p are large arrays.

Embedding full p, x0, and x1 in the warning can create very large strings and hurt performance, particularly in tight loops. Consider logging only shapes and basic stats (e.g., p.shape, x0.shape, x1.shape, dtypes, mins/maxes), or gating full dumps behind a debug flag.

Suggested change
p = self.ot_fn(a, b, M.detach().cpu().numpy())
if not np.all(np.isfinite(p)):
print("ERROR: p is not finite")
print(p)
print("Cost mean, max", M.mean(), M.max())
print(x0, x1)
warnings.warn(
"Non-finite values in OT plan p. "
f"p={p}. Cost mean={M.mean()}, max={M.max()}. "
f"x0={x0}, x1={x1}."
)
if np.abs(p.sum()) < 1e-8:
p = self.ot_fn(a, b, M.detach().cpu().numpy())
if not np.all(np.isfinite(p)):
# Log only summary statistics to avoid building very large warning
# strings when p, x0, or x1 are large arrays/tensors.
p_min = np.nanmin(p)
p_max = np.nanmax(p)
p_mean = np.nanmean(p)
x0_shape = getattr(x0, "shape", None)
x1_shape = getattr(x1, "shape", None)
x0_dtype = getattr(x0, "dtype", None)
x1_dtype = getattr(x1, "dtype", None)
warnings.warn(
"Non-finite values in OT plan p. "
f"p.shape={getattr(p, 'shape', None)}, "
f"p.dtype={getattr(p, 'dtype', None)}, "
f"p.min={p_min}, p.max={p_max}, p.mean={p_mean}. "
f"Cost mean={M.mean()}, max={M.max()}. "
f"x0.shape={x0_shape}, x0.dtype={x0_dtype}; "
f"x1.shape={x1_shape}, x1.dtype={x1_dtype}."
)
if np.abs(p.sum()) < 1e-8:

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.

1 participant