Project: s41467-025-60048-1 | Function: gen_base_pair_motif (python)

system: You are an expert Python programmer implementing algorithms from scientific papers.
Complete the masked function based on the algorithm description.
Follow Python best practices and maintain consistent code style.
Return **only** the complete Python function code.

user: Complete this Python function:

**DESCRIPTION:** Generates comprehensive base pair motifs for RNA secondary structure prediction by enumerating all possible neighbor configurations around canonical base pairs. Creates outer motifs, inner chain-break motifs, and inner hairpin motifs using combinatorial generation of RNA base sequences.

**KEY TERMS:** {
  "pair": "canonical base pair (GC, AU, etc.)",
  "radius": "neighbor distance parameter r",
  "outer_motif": "extending outward sequences",
  "inner_chain_break": "separated chain segments",
  "inner_hairpin": "loop structures"
}

**CONTEXT CODE:** import os
from itertools import product

import numpy as np

from .RNA_kit import mut_seq


SRC_DIR = os.path.dirname(os.path.realpath(__file__))
BPM_path = os.path.join(os.path.dirname(SRC_DIR), 'paras', 'key.energy')

def gen_all_BPMs(canonical_pairs=None, radius:int=3):
    '''
    Generate all outer and inner base pair motifs of six kinds of canonical pairs.
    inner hairpin base pair motif: BPM_iH,
    inner chain-break  base pair motif: BPM_iCB,
    outer chain-break base pair motif: BPM_oCB,

    Parameters
    ----------
    canonical_pairs: list
    list of canonical_pairs, default=['GC', 'CG', 'AU', 'UA', 'GU', 'UG']

    radius: int=3

    Returns
    -------
    yield: bpm
    '''
    assert radius>=2
    if canonical_pairs is None:
        canonical_pairs = ['GC', 'CG', 'AU', 'UA', 'GU', 'UG']
    for pair in canonical_pairs:
        yield from gen_base_pair_motif(pair, radius)


def gen_base_pair_motif(pair:str, radius:int=3):
    # MASKED: Generates comprehensive base pair motifs for RNA secondary structure prediction by enumerating all possible neighbor configurations around canonical base pairs implementation
    raise NotImplementedError("This function is masked for testing purposes.")


def get_BPM_type(BPM_seq:str, radius:int=3)->str:
    if '-' in BPM_seq:
        if BPM_seq.endswith(f'{2*radius+1}-{radius}'):
            return 'BPM_iCB'
        else:
            return 'BPM_oCB'
    else:
        return 'BPM_iH'


def get_BPM_seqs(seq:str, i:int, j:int, r:int=3)->(str, str):
    '''
    Generate r-neighbor base pair motif for base pair (i, j) of seq
        ------i-----
                   |
        ------j-----

    Parameters
    ----------
    seq: str
        RNA sequence
    i: int
        index of base pair
    j: int
        index of base pair
    r: int
        r-neighbor

    Returns
    -------
    (bpm1, bpm2): (str, str)
        base pair motifs
    '''
    if i>j:
        i, j = j, i
    L = len(seq)
    # base pair motif 1: outer base pair motif
    left = max(i-r, 0)
    right = min(j+r, L-1)
    bpm_seq1 = seq[j: right+1] + seq[left: i+1]
    bpm1 = bpm_seq1 + f'_0_{len(bpm_seq1)-1}-{right-j}'

    # base pair motif 2: inner base pair motif
    bpm2 = None
    if j-i<=2*r: # hairpin loop
        bpm2 = seq[i:j+1] + f'_0_{j-i}'
    else:
        bpm2 = seq[i: i+r+1] + seq[j-r:j+1] + f'_0_{2*r+1}-{r}'
    return bpm1, bpm2


class BPM_energy:
    def __init__(self, path=BPM_path, radius=3):
        '''
            inner hairpin base pair motif: BPM_iH,
            inner chain-break  base pair motif: BPM_iCB,
            outer chain-break base pair motif: BPM_oCB,
        '''
        self.radiu = radius
        self.energy_table = {}
        self.norm_energy_table = {}
        # minE_H_CB_L_dic   ## min energy is minus
        minE_H_CB_L_dic = {}
        with open(path) as fp:
            for line in fp.readlines():
                line = line.strip('\n ')
                if line and not line.startswith('#'):
                    BPM, energy = line.split()
                    self.energy_table[BPM] = float(energy)
                    H_CB_L = BPM[BPM.find('_'):]
                    if H_CB_L in minE_H_CB_L_dic:
                        ## min energy is minus
                        minE_H_CB_L_dic[H_CB_L] = min(minE_H_CB_L_dic[H_CB_L], self.energy_table[BPM])
                    else:
                        minE_H_CB_L_dic[H_CB_L] = self.energy_table[BPM]
        for BPM in self.energy_table:
            H_CB_L = BPM[BPM.find('_'):]
            self.norm_energy_table[BPM] = self.energy_table[BPM]/minE_H_CB_L_dic[H_CB_L]
        #                       total negative
        # self.energy_table      76086 51184
        # self.norm_energy_table 76086 24902

    def get_energy_by_type(self, energy_table, BPM_seq:str, BPM_type:str='all'):
        cur_type = get_BPM_type(BPM_seq)
        if BPM_type == 'all' or cur_type==BPM_type:
            return energy_table[BPM_seq]
        else:
            return 0

    def get_energy(self, seq:str, radius:int=3, normalize_energy:bool=True, dispart_outer_inner=True, return_BPM:bool=False, BPM_type:str='all'):
        '''
        Generate energy map in shape of LxL according to input seq (L) and energy table

                norm  True          False
         dispart   


Complete the function implementation replacing "raise NotImplementedError".