Project: s41467-024-49957-9 | Function: WAlignScore (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:** Primer self-complementarity and dimerization scoring algorithm implementing weighted alignment with position-dependent penalties, GC/AT differential scoring, and indel penalties to quantify primer-primer interaction strength for multiplexed PCR optimization

**KEY TERMS:** {
  "GC_score": "2 points for G-C base pair matches (stronger binding)",
  "AT_score": "1 point for A-T base pair matches (weaker binding)",
  "indel_penalty": "3 point penalty for insertion/deletion gaps",
  "replace_penalty": "2 point penalty for base mismatches",
  "score": "dynamic programming matrix for optimal local alignment",
  "max_score": "maximum alignment score indicating dimerization potential"
}

**CONTEXT CODE:** #!/usr/bin/env python3
# -*- coding: utf-8 -*-


'''
Functions for primer design
Architecture:
design.py
    primer_generator(object)
        WAlignScore()
    PrimerSetBadnessFast()
'''


__author__ = 'Michael X. Wang'


import os
CURRENT_DIR = os.path.dirname(__file__)


import random
import json
from collections import Counter

import numpy as np
import pandas as pd
from Bio import SeqIO

import basic
from basic import seq2num
from basic import num2seq
from basic import revcomp

import logging
logger = logging.getLogger('main')

from msa_tools import average_scores, expand_degenerate_sequence


GC_LETTER = ['C', 'G', 'c', 'g']


def WAlignScore(seq1, seq2=None):
    '''
    Calculate primer self badness
    '''
    raise NotImplementedError("Function has been masked for testing purposes")


class primer_generator(object):
    '''
    Usage:
        g = primer_generator(temperature, salinity)
        retult_1 = g.get(seq1, dG_max=-11.8, min_GC=0.2, max_GC=0.75, min_complexity=0.4, max_len=36, check_SNP=True, prefix='')
        retult_2 = g.get(seq2, dG_max=-11.8, min_GC=0.1, max_GC=0.9, min_complexity=0.3, max_len=36, check_SNP=True)
    '''
    def __init__(self, temperature=60, salinity=0.18, end_pool_path=None):
        # Parameter = np.array([[11, -7.6, -21.3],
        #                     [12, -7.2, -20.4],
        #                     [13, -8.4, -22.4],
        #                     [14, -7.8, -21.0],
        #                     [21, -7.2, -21.3],
        #                     [22, -7.6, -21.3],
        #                     [23, -8.2, -22.2],
        #                     [24, -8.5, -22.7],
        #                     [31, -8.5, -22.7],
        #                     [32, -7.8, -21.0],
        #                     [33, -8.0, -19.9],
        #                     [34, -10.6, -27.2],
        #                     [41, -8.2, -22.2],
        #                     [42, -8.4, -22.4],
        #                     [43, -9.8, -24.4],
        #                     [44, -8.0, -19.9],])
        paraH = np.array([[-7.6, -7.2, -8.4, -7.8],
                          [-7.2, -7.6, -8.2, -8.5],
                          [-8.5, -7.8, -8.0, -10.6],
                          [-8.2, -8.4, -9.8, -8.0]])
        paraS = np.array([[-21.3, -20.4, -22.4, -21.0],
                          [-21.3, -21.3, -22.2, -22.7],
                          [-22.7, -21.0, -19.9, -27.2],
                          [-22.2, -22.4, -24.4, -19.9]])
        self.paraS = paraS + 0.368 * np.log(salinity)
        self.paraG = paraH - (temperature + 273.15) * self.paraS / 1000
        self.dG_init = 0.2 - (temperature + 273.15) * (-5.7)/1000

        # load pre-generated pool of primer end sequences
        if not end_pool_path is None:
            with open(end_pool_path) as f:
                end_pool = json.load(f)
            self.check_end = True
            self.l_end = len(end_pool[0])
            self.end_pool = [s.lower() for s in end_pool]
        else:
            self.check_end = False
            self.l_end = None
            self.end_pool = None
        
    def StacksDG(self, seq):
        rep = basic.seq2arr(seq)
        dG_stacks = 0
        for i, _ in enumerate(rep[:-1]):
            dG_stacks += self.paraG[rep[i]][rep[i+1]]
        return dG_stacks
    
    def get(self, seq, prefix='', dG_max=-11.8, min_GC=0.2, max_GC=0.75, 
            min_complexity=0.4, min_len=15, max_len=36, check_SNP=True, check_BLAST=False):
        '''
        Input:
            Sequence to generate primers from; restrictions (dG, GC, etc.)
        Output:
            DataFrame of primers; dictionary of failure cases
        '''
        INTR_SCORE = 2

        # input should be lowercase
        if seq == seq.upper(): # all uppercase
            # logger.info('input sequence should be in lowercase, with SNPs in uppercase. ')
            seq = seq.lower()
            if check_SNP:
                # logger.info('ignore check_SNP')
                check_SNP = False

        primer_seq = [] # primer sequence
        primer_arr = [] # array format
        primer_len = [] # primer length without prefix
        primer_dist = [] # distance to mother sequence end
        badness = []
        primer_dG = []
        BLAST_hits = []
        end_fail = 0 # number of primers failed end check
        SNP_fail = 0 # number of primers failed SNP check
        GC_fail = 0 # number of primers failed GC ratio check
        complexity_fail = 0 # number of primers failed complexity check
        for end_pos in range(len(seq)-1, 8, -1): # end_pos is 0-based
            # check end
            if self.check_end:
                if not seq[end_pos-self.l_end+1:end_pos+1] in self.end_pool:
                    end_fail += 1
                    continue
            # check SNP in last 5 bases
            if check_SNP:
                if seq[end_pos-4:end_pos+1] != seq[end_pos-4:end_pos+1].lower():


Complete the function implementation replacing "raise NotImplementedError".