Skip to content

Add missing llr2bitProb function to optic.utils - #34

Closed
Hewei603 wants to merge 1 commit into
edsonportosilva:mainfrom
Hewei603:fix/missing-llr2bitProb
Closed

Add missing llr2bitProb function to optic.utils#34
Hewei603 wants to merge 1 commit into
edsonportosilva:mainfrom
Hewei603:fix/missing-llr2bitProb

Conversation

@Hewei603

@Hewei603 Hewei603 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Problem

The current main branch cannot be imported. optic/comm/modulation.py imports llr2bitProb from optic.utils, but that function is not defined anywhere in the package:

>>> from optic.comm.modulation import grayMapping
ImportError: cannot import name 'llr2bitProb' from 'optic.utils'

Since optic.comm.modulation is imported by optic.comm.metrics, optic.models.tx, optic.dsp.equalization and optic.dsp.carrierRecovery, this breaks essentially the whole library, including all the example notebooks.

The import was introduced in cee1ca6, but the function itself was never committed.

What was expected

llr2bitProb is referenced in two places, both as llr2bitProb(-llr):

  • softEstimator() in optic/comm/modulation.py
  • calcExtrLLR() in optic/comm/metrics.py

calcLLR() produces LLRs following the log(P(b=0)/P(b=1)) convention, so Pb1 = llr2bitProb(-llr) is consistent with the logistic function

llr2bitProb(x) = 1 / (1 + exp(-x))   =>   P(b=1) = 1 / (1 + exp(LLR))

Changes

  • Add llr2bitProb() to optic/utils.py, decorated with @njit(cache=True) so it can be called from the jitted softEstimator() and calcExtrLLR() routines, and list it in the module autosummary.
  • Add the missing llr2bitProb import in optic/comm/metrics.py, where the function was used but never imported (which would raise a NameError in calcExtrLLR() even after the import error above is fixed).

Verification

Tested on Python 3.14 with numpy 2.4.6 / scipy 1.18.0 / numba 0.66.0:

  • All modules import correctly again.
  • pytest tests/ runs (test_dsp.py and test_modulation.py pass).
  • End-to-end check with a single-channel PDM-16QAM coherent link (32 GBd, 400 km, manakovSSF + pdmCoherentReceiver + edc + mimoAdaptEqualizer + cpr) gives BER = 0, SNR ≈ 22.7 dB, GMI = 4.00 bits/symbol, as expected.

Note (unrelated to this PR)

tests/test_metrics.py::test_fastBERcalc still fails, but for an independent reason: it calls awgn(symbTx, snrdB) passing a float where a parameters object is expected. Because awgn() reads its configuration with getattr(param, "snr", 20), the SNR silently falls back to the 20 dB default and the measured BER no longer follows the swept EbN0 values. I can open a separate PR/issue for that if you'd like.

Summary by Sourcery

Restore importability and soft-metric computations by adding the missing LLR-to-bit-probability utility and wiring it into metrics.

New Features:

  • Introduce llr2bitProb utility to convert log-likelihood ratios to bit probabilities.

Bug Fixes:

  • Fix import errors across modulation and metrics modules by defining and importing the previously missing llr2bitProb function.

Enhancements:

  • Document LLR conventions and usage notes for llr2bitProb in the utilities module.

`optic.comm.modulation` imports `llr2bitProb` from `optic.utils`, but the
function is not defined anywhere in the package. As a result, importing
`optic.comm.modulation` (and therefore most of the library) fails on the
current main branch with:

    ImportError: cannot import name 'llr2bitProb' from 'optic.utils'

The helper is used by `softEstimator` (optic/comm/modulation.py) and by
`calcExtrLLR` (optic/comm/metrics.py), both of which call it as
`llr2bitProb(-llr)`. Since the LLRs produced by `calcLLR` follow the
log(P(b=0)/P(b=1)) convention, the expected mapping is the logistic
function, so that P(b=1) = 1/(1 + exp(LLR)).

This adds the function to `optic.utils` as a Numba-compiled routine, so it
can be called from the jitted functions above, and adds the missing import
in `optic.comm.metrics`, where `llr2bitProb` was used without being
imported.
@sourcery-ai

sourcery-ai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

This PR restores importability and soft-metric functionality by adding the missing llr2bitProb helper to optic.utils (with Numba support) and wiring its usage/import into the metrics module.

File-Level Changes

Change Details Files
Implement the missing llr2bitProb helper and expose it from the utils module for use in jitted soft-estimation routines.
  • Add llr2bitProb(llr) function computing 1 / (1 + exp(-llr)) using NumPy.
  • Decorate llr2bitProb with @njit(cache=True) to allow use from Numba-jitted callers.
  • Document llr2bitProb in the utils module docstring/autosummary including LLR convention and sign-flip note.
optic/utils.py
Fix metrics module imports so llr2bitProb is correctly available where it is used.
  • Update import statement to bring llr2bitProb into the metrics namespace alongside dB2lin.
  • Ensure calcExtrLLR can call llr2bitProb without raising NameError once imports succeed.
optic/comm/metrics.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 left some high level feedback:

  • The docstring for llr2bitProb defines LLRs as log(P(b=1)/P(b=0)) while noting the rest of the library uses log(P(b=0)/P(b=1)); consider aligning conventions (or renaming the function) to avoid confusion and reduce the need for sign flips at call sites.
  • llr2bitProb uses the naive logistic formulation 1/(1+exp(-llr)); for very large |llr| values this can cause numerical under/overflow, so consider adding clipping or a more numerically stable implementation to handle extreme LLRs.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The docstring for llr2bitProb defines LLRs as log(P(b=1)/P(b=0)) while noting the rest of the library uses log(P(b=0)/P(b=1)); consider aligning conventions (or renaming the function) to avoid confusion and reduce the need for sign flips at call sites.
- llr2bitProb uses the naive logistic formulation 1/(1+exp(-llr)); for very large |llr| values this can cause numerical under/overflow, so consider adding clipping or a more numerically stable implementation to handle extreme LLRs.

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.

@edsonportosilva

Copy link
Copy Markdown
Owner

Hello @Hewei603! Thanks for your warning!

In fact, this issue was a result of a few changes and additions I have been doing in the repository this last week. The changes with llr2bitProb were already on the way, but not commited yet. I'm using a formulation that is more stable numerically than what you wrote.

I have updated the repository now, so please check if all is fine. So, thanks, but the pull request won't be needed this time. However, if you want to update/check the tests, please feel free to do it. It would be really valuable. Unfortunatelly, I don't have time to write a better and more complete set of tests for the repository, although the examples folder kind of indireclty assumed this task So, any help with this would be very welcome.

@Hewei603

Copy link
Copy Markdown
Contributor Author

Thanks for the quick turnaround, and for explaining the context — I confirmed that main imports cleanly again, and your branchless-overflow version of the sigmoid is clearly the better formulation. Closing this one.

I would be glad to take up the tests. I opened #35 with a first pass: the failing test_fastBERcalc is fixed (it was passing a float to awgn(), so the SNR silently fell back to the 20 dB default), everything is ported to pytest, coverage is extended to optic.utils, optic.dsp.core, optic.comm.modulation, optic.comm.metrics and optic.models.channels, and there is a small GitHub Actions workflow so that an unusable main gets caught automatically.

A few things surfaced while writing the tests — a one-sample offset at the output of edc(), a pnorm() docstring that does not match its behaviour, calcEVM() returning the squared EVM, and two deprecated imports that SciPy and NumPy will eventually remove. They are all described at the end of the PR, and I left the code untouched. Happy to split any of them into their own issue or PR if you prefer.

@Hewei603 Hewei603 closed this Jul 31, 2026
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.

2 participants