Skip to content

[bug] out of bounds #122

Description

@DamienFr

Hi,

Thank you the the time taken to develop this tool !

I ran into a bug :

/OpenRDP/bin/openrdp corpus.masked.aln -o ./clonalframe_analysis/clade1
Running in serial mode ./OpenRDP/openrdp/bootscan.py:282: RuntimeWarning: divide by zero encountered in log
  (log_n_fact - (log_i_fact + log_ni_fact)) + np.log(p ** n) + np.log((1 - p) ** (n - i)))
Traceback (most recent call last):
  File "./OpenRDP/bin/openrdp", line 46, in <module>
    results = scanner.run_scans(args.infile, args.ref)
  File "./OpenRDP/openrdp/__init__.py", line 341, in run_scans
    tmethod.execute(triplet, fail=self.fail)
    ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
  File "./OpenRDP/openrdp/maxchi.py", line 264, in execute
    right_peak = chi2_values[best[0]][right]
                 ~~~~~~~~~~~~~~~~~~~~^^^^^^^
IndexError: index 9596 is out of bounds for axis 0 with size 9596

I'm not good enough in python to be able to fix it myself so i used an AI to do so. AI generated text / code is highlighted as quote. I'm not saying this is a good way to go, nor that this modification is safe, but in my case it solved the bug and the modified script did produce sensible results.

Note that the bug i correct below is NOT the "divide by zero encountered in log" which is something else which doesn't seem to prevent the analysis to perform ok.

Hope i was able to provide some help !

Diagnosis
This isn't your alignment's fault — it's a genuine off-by-one bug in OpenRDP's current source (openrdp/maxchi.py). I pulled the actual code from PoonLab/OpenRDP to confirm it, and your traceback's line number (264) matches exactly.

What happens: in MaxChi's window-optimization step, after finding the strongest chi² peak, the code sets an initial window:
python

left, right = best[2] - initial_window // 2, best[2] + initial_window // 2

with no bounds check. It then tries to expand it further, guarded only by:
python

if left > 0 and right < len(seq1):
...
right += 1

That guard lets right grow to exactly len(seq1) (one past the last valid index), and then:
python

right_peak = chi2_values[best[0]][right] # line 264 — crashes

indexes an array of length len(seq1) at position len(seq1), exactly matching your index 9596 is out of bounds for axis 0 with size 9596. This happens whenever the strongest MaxChi peak sits close enough to the end of your alignment (or in the middle, if enough "expand" moves accumulate). Your corpus.masked.aln alignment is 9596 columns long, so a breakpoint signal near either edge triggered it.

The chimaera.py module reuses the exact same window-optimization logic (OpenRDP's own README says "Same as MaxChi for the window finding function"), and I confirmed it has the identical bug — so it can crash the same way on a different dataset even if it didn't this time.
Fix

The safest fix is to clamp left/right to valid array bounds right before they're used to index, rather than touching the optimization logic itself. Here's a patch script that applies this to both files (idempotent, keeps .bak backups):
Created a file, ran a command, read a file
What to do

Run this against your actual install:
bash

python3 patch_openrdp_maxchi_bug.py ./OpenRDP/openrdp

It'll edit maxchi.py and chimaera.py in place (backups saved as .bak), then just re-run your openrdp command as before.

patch_openrdp_maxchi_bug.py :


#!/usr/bin/env python3
"""
patch_openrdp_maxchi_bug.py

Fixes an off-by-one IndexError in OpenRDP's MaxChi and Chimaera modules.

Root cause: during the window-optimization step, `left`/`right` window
boundaries can reach exactly len(sequence) (one past the last valid
index) via either the initial window placement or an 'expand' move that
is only bounds-checked BEFORE incrementing, not after. They are then used
to index the chi2_values array directly, e.g.:

    right_peak = chi2_values[best[0]][right]   # maxchi.py
    right_peak = chi2_values[right]            # chimaera.py

...which raises:
    IndexError: index N is out of bounds for axis 0 with size N

This script inserts a clamp of left/right to the valid index range right
before they are used, in both openrdp/maxchi.py and openrdp/chimaera.py.
It is idempotent (safe to run twice) and writes a .bak of each file the
first time it patches it.

Usage:
    python3 patch_openrdp_maxchi_bug.py /path/to/OpenRDP/openrdp
"""
import shutil
import sys
from pathlib import Path

PATCHES = {
    "maxchi.py": {
        "old": (
            "            # 3, determine if left or right side of the new window is the best\n"
            "            left_peak = chi2_values[best[0]][left]\n"
            "            right_peak = chi2_values[best[0]][right]\n"
        ),
        "new": (
            "            # 3, determine if left or right side of the new window is the best\n"
            "            # --- patched: clamp to valid index range (fixes off-by-one IndexError) ---\n"
            "            left = max(left, 0)\n"
            "            right = min(right, len(chi2_values[best[0]]) - 1)\n"
            "            left_peak = chi2_values[best[0]][left]\n"
            "            right_peak = chi2_values[best[0]][right]\n"
        ),
    },
    "chimaera.py": {
        "old": (
            "                left_peak = chi2_values[left]\n"
            "                right_peak = chi2_values[right]\n"
        ),
        "new": (
            "                # --- patched: clamp to valid index range (fixes off-by-one IndexError) ---\n"
            "                left = max(left, 0)\n"
            "                right = min(right, len(chi2_values) - 1)\n"
            "                left_peak = chi2_values[left]\n"
            "                right_peak = chi2_values[right]\n"
        ),
    },
}


def patch_file(path: Path, old: str, new: str) -> str:
    if not path.exists():
        return f"SKIP (not found): {path}"
    text = path.read_text()
    if new in text:
        return f"ALREADY PATCHED: {path}"
    if old not in text:
        return (
            f"NOT PATCHED (expected code not found -- file may differ from the "
            f"version this patch targets, please check manually): {path}"
        )
    backup = path.with_suffix(path.suffix + ".bak")
    if not backup.exists():
        shutil.copy2(path, backup)
    path.write_text(text.replace(old, new))
    return f"PATCHED (backup saved to {backup}): {path}"


def main():
    if len(sys.argv) != 2:
        sys.exit(f"Usage: {sys.argv[0]} /path/to/OpenRDP/openrdp")
    pkg_dir = Path(sys.argv[1])
    for fname, spec in PATCHES.items():
        print(patch_file(pkg_dir / fname, spec["old"], spec["new"]))


if __name__ == "__main__":
    main()

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions