Skip to content

fix(mass_transfer): deprecate silently-ignored m_slope arg in ntu_og#11

Open
defnalk wants to merge 6 commits intomainfrom
fix/ntu-og-deprecate-m-slope
Open

fix(mass_transfer): deprecate silently-ignored m_slope arg in ntu_og#11
defnalk wants to merge 6 commits intomainfrom
fix/ntu-og-deprecate-m-slope

Conversation

@defnalk
Copy link
Copy Markdown
Owner

@defnalk defnalk commented Apr 9, 2026

Fixes #5

Summary

  • `mass_transfer.ntu_og` accepted an `m_slope` parameter that the docstring claimed enabled an equilibrium-corrected calculation, but the function body never referenced it. Every call returned the dilute-limit `ln(Y_bottom / Y_top)` regardless of `m_slope`. Callers using the supposed rigorous mode were silently getting the optimistic dilute approximation on real plant data.
  • This PR is the semver-clean v0.1.x fix: non-zero `m_slope` now emits a `DeprecationWarning` pointing at ntu_og silently ignores m_slope parameter #5 and stating that the parameter will be removed in v0.2.0. The `m_slope=0.0` default path is unchanged and warning-free.
  • A proper Colburn-equation entry point taking the absorption factor explicitly is planned for v0.2.0 (tracked in ntu_og silently ignores m_slope parameter #5).

Why a deprecation, not an immediate fix?

The current signature has no way to compute Colburn's equation — it would also need the L/G molar flows. Implementing the rigorous form requires a breaking signature change, which belongs in v0.2.0 under semver. Until then, the honest move is to surface the bug loudly without breaking existing scripts that pass `m_slope=0`.

Before / after

```python

BEFORE — silent dilute-limit answer regardless of m_slope

ntu_og(0.14, 0.02, m_slope=0.85)
1.9676... # identical to ntu_og(0.14, 0.02), no warning
```

```python

AFTER — same value, but the user is told

ntu_og(0.14, 0.02, m_slope=0.85)
DeprecationWarning: ntu_og(m_slope=...) has never been implemented and is silently
ignored — the function always returns the dilute-limit ln(Y_bottom / Y_top). The
parameter will be removed in meapy v0.2.0; until then, calling with a non-zero
m_slope produces the same result as calling with m_slope=0. See
#5 for the planned Colburn-based replacement API.
1.9676...
```

The default `m_slope=0` call path is unchanged:
```python

ntu_og(0.14, 0.02) # no warning, same value as before
1.9676...
```

Testing notes

Two new tests pin the behaviour in `tests/unit/test_mass_transfer.py::TestNTUOG`:

  • `test_nonzero_m_slope_warns_and_returns_dilute_limit` — asserts the warning fires and the returned value is bit-identical to the `m_slope=0` case.
  • `test_zero_m_slope_does_not_warn` — runs both `ntu_og(0.14, 0.02)` and `ntu_og(0.14, 0.02, m_slope=0.0)` under `warnings.simplefilter("error")` to guarantee the default path stays clean.

Local run:
```
$ pytest tests/unit/test_mass_transfer.py -q
40 passed in 0.13s
```

Full unit suite (124 + 2 new + the four other commits already on main):
```
$ pytest tests/ -q
130 passed
```

Out of scope (follow-ups)

  • The Colburn replacement API itself — will land in v0.2.0 per ntu_og silently ignores m_slope parameter #5.
  • CHANGELOG entry — included in a separate `docs(changelog)` commit on main since several other audit commits also need entries.

🤖 Generated with Claude Code

defnalk and others added 5 commits April 9, 2026 20:30
…ross-section

PlantGeometry.COLUMN_CROSS_SECTION_M2 was computed with the literal 3.14159,
which truncates pi at the 6th significant figure. For the 0.1 m absorber
column the resulting area is off by ~8e-9 m² — small in absolute terms but
it propagates into every K_OGa and H_OG calculation that uses the default
geometry, and the discrepancy is visible against any independent re-derivation.

Use math.pi so the constant is exact to double precision.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PlantGeometry.SAMPLING_HEIGHTS_M, ExperimentLabels.LABELS, and
ExperimentLabels.COOLING_WATER_FLOWRATES_KG_H were defined as plain list/dict
attributes on a class with no instances. Because Python class attributes are
shared, any caller that mutated these (e.g. SAMPLING_HEIGHTS_M.append(...) or
COOLING_WATER_FLOWRATES_KG_H["A"] = 0) would silently corrupt the value for
every other consumer in the same process. The risk is real for notebook
workflows where the same kernel is reused across analyses.

Switch the lists to tuples and wrap the dict in types.MappingProxyType so
mutations raise TypeError immediately. The public read API is unchanged —
indexing, iteration, len(), and `in` all behave identically.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The efficiency() docstring already states that η > 1 indicates measurement
inconsistency (instrumentation drift, heat gain, or a swapped stream
assignment), but the function never surfaced this — it returned the value
silently and downstream U / ε / NTU calculations propagated the inconsistency.

Emit a logger.warning with the offending duties and concrete remediation
hints. Behaviour is unchanged for callers that don't configure logging
(the package-level NullHandler swallows it), so this is non-breaking for
existing scripts but immediately visible to anyone running with
logging.basicConfig(level=logging.WARNING) or structured-log handlers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous implementation called np.mean inside a Python for-loop, paying
NumPy's per-call overhead once per element. cProfile against a 100k-sample
input showed 1.2M function calls and 222 ms wall time — completely dominated
by 100k np.mean dispatches.

Replace the loop with the standard cumulative-sum trick: build a padded
cumsum once, then index it twice to get every trailing-window sum in a
single vectorised expression. The leading edge (where a full window is not
yet available) is handled by counting actual window length per index, which
preserves the previous behaviour exactly (verified by the existing 19 unit
tests, all passing).

Benchmark (Apple M-series, Python 3.13.5, NumPy 2.x, window=60, best-of-5):

    size         before        after        speedup
    -----------  ------------  -----------  --------
        1,000    ~2.2 ms          0.013 ms    ~170x
       10,000     27.2 ms         0.078 ms     349x
      100,000    222.5 ms         0.893 ms     249x
    1,000,000  2,223.3 ms        11.69  ms     190x

cProfile call count for size=100k drops from 1,200,006 to 11.

Also adds benchmarks/bench_rolling_mean.py — a reproducible script with the
exact baseline + cProfile dump so future regressions are easy to spot.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
)

ntu_og(y_bottom, y_top, m_slope=...) accepted m_slope and the docstring
promised "a rigorous calculation including the equilibrium back-pressure"
when a non-zero value was passed. The function body never references
m_slope at any point — every call returns the dilute-limit ln(Y_b / Y_t)
regardless. Callers running the supposed "rigorous mode" were silently
getting the dilute approximation, an optimistic NTU on real plant data.

This is the v0.1.x semver-clean fix:
* Non-zero m_slope now emits DeprecationWarning pointing at issue #5 and
  documenting that the parameter will be removed in v0.2.0.
* The default m_slope=0.0 path stays warning-free, so existing scripts
  and the existing test suite are unaffected.
* Two new tests pin the behaviour:
  - non-zero m_slope warns and returns the same value as m_slope=0
  - default-call path is warning-free under warnings.simplefilter("error")

A proper Colburn-equation entry point taking the absorption factor
explicitly will land in v0.2.0 (tracked in #5).

Fixes #5

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
defnalk added a commit that referenced this pull request Apr 9, 2026
Records the four direct commits landed on main this session and the
ntu_og(m_slope) deprecation arriving via PR #11 / issue #5. Each entry
follows Keep a Changelog conventions and is filed under the appropriate
heading (Fixed / Changed / Performance / Added / Deprecated).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CI was failing on this branch with:
  UP035 Import from `collections.abc` instead: `Mapping`
   --> src/meapy/constants.py:20

Move the Mapping import. Behaviour unchanged — Mapping is the same
ABC in either location since Python 3.9.
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.

ntu_og silently ignores m_slope parameter

1 participant