#!/usr/bin/python
#################################################################################
# combine a shiftx csv and shifty csv output
# and print out csv, bmrb star and tablar format
#
# author: Yifeng Liu
# date: June 4th, 2010
# last updated: Nov 21st, 2011 (by Yifeng Liu)
# last updated: Jan 06st, 2012 (by Beomsoo Han)
#################################################################################
import sys, re, os
from natsorted import *
from math import exp  ## add for new combine rule
# from numpy import *
tablerows = {} # a hash table for all rows in the table, mapping from residue number to table entry object

# mean and std for observed secondary chemical shifts
meanstd = {
# 'C'        : (-0.0931685, 1.9301960), # (-0.1306832, 1.9588225),
'C'        : (-0.1306832, 1.9588225),
'CA'       : (0.5985418,  2.2167534), # (0.5916252,  2.2232116),
'CB'       : (0.2110142,  1.7927954), # (0.1536476, 1.7955304),
'CD'       : (0.1053127,  0.8841948), # (0.1008803, 0.9190691),
'CD1'      : (0.1515656,  1.5273872), # (0.1365291, 1.5568043),
'CD2'      : (0.4881216,  1.6296155), # (0.5203491, 1.6645161),
'CE'       : (0.0531371,  0.6946732), # (0.0531371, 0.6946732),
'CE1'      : (0.1875406,  1.2819486), # (0.1875406, 1.2819486),
'CE2'      : (0.1714644,  0.9419704), # (0.1714644, 0.9419704),
'CG'       : (0.2000866,  0.9807942), # (0.1812809, 1.0101149),
'CG1'      : (0.5649324,  1.3285663), # (0.5565336, 1.3148962),
'CG2'      : (0.5023613,  1.2902987), # (0.5359641, 1.2798302),
'CZ'       : (0.1814107,  1.2871387), # new addition
'H'        : (0.0321042,  0.7182266), # (0.0233455, 0.7035170),
'HA'       : (0.0368358,  0.5022386), # (0.0224559, 0.4985098),
'HA2'      : (-0.0795705, 0.3988592), # (-0.0938101, 0.3916421),
'HA3'      : (0.1987512,  0.4206170), # (0.2118395, 0.4008206),
'HB'       : (-0.0540100, 0.2759903), # (-0.0580315, 0.2837668),
# 'HB2'      : (-0.0278069, 0.2930100), # (-0.0475658, 0.3579876),
'HB2'      : (-0.0475658, 0.3579876),
'HB3'      : (-0.0363121, 0.3033426), # (-0.0495894, 0.3610869),
'HD1'      : (-0.1612276, 0.2350308), # (-0.1957184, 0.2970093),
'HD2'      : (-0.1185549, 0.2924862), # (-0.1538220, 0.3611255),
'HD3'      : (-0.0664780, 0.2487720), # (-0.0668469, 0.2584818),
'HD21'     : (-0.1510726, 0.4949505), # old value
'HD22'     : (0.1680352,  0.4343576), # old value
'HE'       : (-0.4290152, 0.3816857), # (-0.5382103, 0.4660226),
'HE1'      : (-0.0629864, 0.4918308), # (-0.0724552, 0.4918618),
'HE2'      : (-0.1233462, 0.2108998), # (-0.1329407, 0.2267426),
'HE21'     : (-0.2534072, 0.4227775), # old value
'HE22'     : (0.1032250,  0.3800324),  # old value
'HE3'      : (-0.1053953, 0.2334108), # (-0.0996356, 0.2406151),
'HG'       : (-0.0844853, 0.3090926), # (-0.0979355, 0.3245224),
'HG1'      : (-0.0781499, 0.2173279), # (-0.0875957, 0.2271778),
'HG12'     : (-0.1420523, 0.3756947), # (-0.1676448, 0.3954893),
'HG13'     : (0.0175029,  0.3996602), # (0.0122330, 0.4125584),
'HG2'      : (-0.0572989, 0.2160706), # (-0.0671057, 0.2350133),
'HG3'      : (-0.0803935, 0.2294562), # (-0.0799687, 0.2517081),
'HH2'      : (-0.1767182, 0.2744137), # old value
'HZ'       : (-0.2343304, 0.2854936), # (-0.2592637, 0.3351579),
'HZ2'      : (-0.3291366, 0.3447289), # old value
'HZ3'      : (-0.3573125, 0.3370303), # old value
'N'        : (-0.5950627, 4.2815778), # (-0.6578595, 4.2769095)
}

# pS, pT, pY: Phosphorylated Residues
aa_name_dict = {
    'A' : 'ALA',     'C' : 'CYS',    'D' : 'ASP',    'E' : 'GLU',
    'F' : 'PHE',     'G' : 'GLY',    'H' : 'HIS',    'I' : 'ILE',
    'K' : 'LYS',     'L' : 'LEU',    'M' : 'MET',    'N' : 'ASN',
    'P' : 'PRO',     'Q' : 'GLN',    'R' : 'ARG',    'S' : 'SER',
    'T' : 'THR',     'V' : 'VAL',    'W' : 'TRP',    'Y' : 'TYR',
    'B' : 'CYS',     'pS': 'SEP',    'pT': 'TPO',    'pY': 'PTR'
}

# hash table for shfity predictions
shifty_hash = {}

# hash table for shiftx predictions
shiftx_hash = {}

# hash table for the combined predictions
combine_hash = {}

# combined set of keys
keyset = set()

# sequence identity cutoff for shfity results
id = 0

# correlation threshold
CORR_THRESHOLD = 0.9

# Minimal std for assigning shiftx weights
MINSTD = 0.45 # updated from 0.5 --> 0.45

# Maximum std for assigning shiftx weights
MAXSTD = 1.5  # backbone: updated from 1.5 --> 1.4 --> 1.5 (V1.07)
MAXSTD_SIDECHAIN = 1.4  # new for sidechain


#################################################################################
# a python class representating a table entry
# useful for converting a shiftx2 csv format to a tabular format
# using object oriented python
# author: Yifeng Liu
# date: July 26th, 2010
#################################################################################
class TableEntry(object):
    ''' an object oriented modelling of a table row '''

    def __init__(self):
        ''' initialize all the table entries into empty entries '''
        ''' ('Num', 'RES', 'CA', 'CB', 'CO', 'N', 'H', 'HA') ''' # backbone atoms
        ''' ('Num', 'RES', 'CD',  'CD1', 'CD2', 'CE', 'CE1', 'CE2', 'CG', 'CG1', 'CG2', 'CZ')''' # sidechain C atoms
        ''' ('Num', 'RES', 'HB', 'HB2', 'HB3', 'HD1', 'HD2', 'HD21', 'HD22', 'HD3', 'HE', 'HE1', 'HE2', 'HE21', 'HE22', 'HE3', 'HG', 'HG1', 'HG12', 'HG13', 'HG2', 'HG3', 'HZ') ''' # sidechain H atoms

        # major table
        # String bbheader = "NUM  RES  HA     H      N        CA      CB      C\n";
        # String sideCs_header = "NUM  RES   CD     CD1    CD2   CE  CE1  CE2  CG     CG1   CG2\n";
        # String sideHs_header = "NUM  RES  HB   HB2  HB3  HD1  HD2  HD21 HD22 HD3  HE   HE1  HE2  HE21 HE22 HE3  HG   HG1  HG12 HG13 HG2  HG3  HZ\n";

        self.H = self.HA = self.CA = self.CB = self.CO = self.N = '****'
        self.CD = self.CD1 = self.CD2 = self.CE = self.CE1 = self.CE2 = self.CG = self.CG1 = self.CG2 = self.CZ = '****'
        self.HB = self.HB2 = self.HB3 = \
            self.HD1 = self.HD2 = self.HD21 = self.HD22 = self.HD3 = \
            self.HE = self.HE1 = self.HE2 = self.HE21 = self.HE22 = self.HE3 = \
            self.HG = self.HG1 = self.HG12 = self.HG13 = self.HG2 = self.HG3 = \
            self.HZ = '****'

        self.restype = ""
        self.resnum = None

        '''
        self.field_hash = {
            "restype" : self.restype,
            "resnum"  : self.resnum,
            "CA" : self.CA, "CB" : self.CB, "CO" : self.CO,
            "C"  : self.CO, "N"  : self.N,  "H"  : self.H,
            "HN" : self.H, "HA" : self.HA, "CD" : self.CD,
            "CD1": self.CD1, "CD2": self.CD2, "CG" : self.CG,
            "CG1": self.CG1, "CG2": self.CG2, "HB" : self.HB,
            "HB2": self.HB2, "HB3": self.HB3, "HD1": self.HD1,
            "HD2": self.HD2, "HD21": self.HD21, "HD22": self.HD22,
            "HD3" : self.HD3, "HE"  : self.HE, "HE1" : self.HE1,
            "HE2" : self.HE2, "HE21": self.HE21, "HE22": self.HE22,
            "HE3" : self.HE3, "HG"  : self.HG, "HG1" : self.HG1,
            "HG12": self.HG12, "HG13": self.HG13, "HG2" : self.HG2,
            "HG3" : self.HG3, "HZ" : self.HZ
            }
           '''

    def setfield(self, fname, value):
        ''' set the given field with the given value '''
        if fname == "restype":
            self.restype = value
        elif fname == "resnum":
            self.resnum = value
        elif fname == "CA":
            self.CA = value
        elif fname == "CB":
            self.CB = value
        elif fname == "CO" or fname == "C":
            self.CO = value
        elif fname == "N":
            self.N = value
        elif fname == "H" or fname == "HN":
            self.H = value
        elif fname == "HA":
            self.HA = value

        elif fname == "CD":
            self.CD = value
        elif fname == "CD1":
            self.CD1 = value
        elif fname == "CD2":
            self.CD2 = value
        elif fname == "CE":
            self.CE = value
        elif fname == "CE1":
            self.CE1 = value
        elif fname == "CE2":
            self.CE2 = value
        elif fname == "CG":
            self.CG = value
        elif fname == "CG1":
            self.CG1 = value
        elif fname == "CG2":
            self.CG2 = value
        elif fname == "CZ":
            self.CZ = value

        elif fname == "HB":
            self.HB = value
        elif fname == "HB2":
            self.HB2 = value
        elif fname == "HB3":
            self.HB3 = value
        elif fname == "HD1":
            self.HD1 = value
        elif fname == "HD2":
            self.HD2 = value
        elif fname == "HD21":
            self.HD21 = value
        elif fname == "HD22":
            self.HD22 = value
        elif fname == "HD3":
            self.HD3 = value
        elif fname == "HE":
            self.HE = value
        elif fname == "HE1":
            self.HE1 = value
        elif fname == "HE2":
            self.HE2 = value
        elif fname == "HE21":
            self.HE21 = value
        elif fname == "HE22":
            self.HE22 = value
        elif fname == "HE3":
            self.HE3 = value
        elif fname == "HG":
            self.HG = value
        elif fname == "HG1":
            self.HG1 = value
        elif fname == "HG12":
            self.HG12 = value
        elif fname == "HG13":
            self.HG13 = value
        elif fname == "HG2":
            self.HG2 = value
        elif fname == "HG3":
            self.HG3 = value
        elif fname == "HZ":
            self.HZ = value
        else:
            ''' print "the specified field", fname, "is not supported." '''

    def backbone_str(self):
        values = [self.CA, self.CB, self.CO, self.N, self.H, self.HA] #, self.HB2, self.HB3, self.HG2, self.HG3, self.HD2, self.HD3, self.HE2, self.HE3]
        header = [str(self.resnum), self.restype]
        return  "%3s %4s %8s %8s %8s %8s %8s %8s" % tuple(header + values)

    def sidec_str(self):
        values =  [self.CD, self.CD1, self.CD2, self.CE, self.CE1, self.CE2, self.CG, self.CG1, self.CG2, self.CZ]
        header = [str(self.resnum), self.restype]
        return  "%3s %4s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s" % tuple(header + values)

    def sideh_str(self):
        values = [self.HB, self.HB2, self.HB3, self.HD1, self.HD2, self.HD21, self.HD22, self.HD3, \
                     self.HE, self.HE1, self.HE2, self.HE21, self.HE22, self.HE3, self.HG, self.HG1, self.HG12, self.HG13, self.HG2, self.HG3, self.HZ]
        header = [str(self.resnum), self.restype]
        return  "%3s %4s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s" % tuple(header + values)

########################################################################
# helper functions
########################################################################
def convert_table(lines, shifttype):
    ''' convert the given list of comma separated strings into a tabular output '''

    outstrs = []
    for line in lines:
        line = line.strip()
        if line != "":
            tmplist = line.split(',')
            resnum = int(tmplist[0])
            restype = tmplist[1]
            atomtype = tmplist[2]
            cs = "%.4f" % float(tmplist[3])

            if resnum not in tablerows:
                row = TableEntry()
                row.setfield("resnum", resnum)
                row.setfield("restype", restype)
                row.setfield(atomtype, cs)
                tablerows[resnum] = row
            else:
                row = tablerows[resnum]
                row.setfield("resnum", resnum)
                row.setfield("restype", restype)
                row.setfield(atomtype, cs)
                tablerows[resnum] = row

    resnums = tablerows.keys()
    resnums.sort()

    if "ALL" in shifttype or "BACKBONE" in shifttype: # all or backbone
    # print out backbone table
        outstr = "BACKBONE ATOMS\n" + "%3s %4s %8s %8s %8s %8s %8s %8s" % \
            ('Num', 'RES', 'CA', 'CB', 'CO', 'N', 'H', 'HA')
        outstrs.append(outstr)

        for row in resnums:
            outstrs.append(tablerows[row].backbone_str())
        outstrs.append("\n")

    if "ALL" in shifttype or "SIDECHAIN" in shifttype: # all or sidechain
    # print out sidechain C table
        outstr = "SIDECHAIN CARBON\n" + "%3s %4s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s" % \
            ('Num', 'RES', 'CD',  'CD1', 'CD2', 'CE', 'CE1', 'CE2', 'CG', 'CG1', 'CG2', 'CZ')
        outstrs.append(outstr)
        for row in resnums:
            outstrs.append(tablerows[row].sidec_str())
        outstrs.append("\n")

        # print out sidechain H table
        outstr = "SIDECHAIN PROTON\n" + "%3s %4s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s" % ('Num', 'RES', 'HB', 'HB2', 'HB3', 'HD1', 'HD2', 'HD21', 'HD22', 'HD3', 'HE', 'HE1', 'HE2', 'HE21', 'HE22', 'HE3', 'HG', 'HG1', 'HG12', 'HG13', 'HG2', 'HG3', 'HZ')
        outstrs.append(outstr)
        for row in resnums:
            outstrs.append(tablerows[row].sideh_str())
        outstrs.append("\n")

    outstr = "\n".join(outstrs)
    return outstr

def convert_bmrb(lines):
    ''' convert the given list of strings in csv format into bmrb NMR-star format '''
    outstrs = []
    lid = 1
    for line in lines:
        tmplist = line.split(',')
        outstr = "%4s %3s %3s %5s %8s . 1" % (str(lid), tmplist[0], aa_name_dict[tmplist[1]], tmplist[2], tmplist[3])
        outstrs.append(outstr)
        lid += 1
    outstr = "\n".join(outstrs)
    return outstr

def convert_nef(lines):
    ''' convert the given list of strings in csv format into NEF format '''
    outstrs = []
    lid = 1
    for line in lines:
        tmplist = line.split(',')
        outstr = "%6s %4s %5s   %-4s   %-7.3f   %-4s" % ("A", tmplist[0], aa_name_dict[tmplist[1]], tmplist[2], float(tmplist[3]), ".")
        outstrs.append(outstr)
        lid += 1
    outstr = "\n".join(outstrs)
    return outstr

## add 'outformat' for screening atoms like HA2/HA3
def filter_shifttype(lines, shifttype, outformat):
    ''' filter the given lines for the eligable shift types (backbone, sidechain H, sidechan C, all shifts) '''
    # print "filtering shift types", shifttype
    newlines = []

    if outformat == "table":
        backbone = set(['CA', 'CB', 'CO', 'C', 'N', 'H', 'HA'])
    else:
        backbone = set(['CA', 'CB', 'CO', 'C', 'N', 'H', 'HA','HA2','HA3'])
    sidechainh = set(['HB', 'HB2', 'HB3', 'HD1', 'HD2', 'HD21', 'HD22', 'HD3', 'HE', 'HE1', \
                          'HE2', 'HE21', 'HE22', 'HE3', 'HG', 'HG1', 'HG12', 'HG13', 'HG2', 'HG3', 'HZ'])
    sidechainc = set(['CD',  'CD1', 'CD2', 'CE', 'CE1', 'CE2', 'CG', 'CG1', 'CG2', 'CZ'])
    sidechain = sidechainh.union(sidechainc)
    allatoms = backbone.union(sidechain)

    select_atom = allatoms
    if "ALL" in shifttype:
        select_atom = allatoms
    elif "BACKBONE" in shifttype:
        select_atom = backbone
    elif "SIDECHAIN" in shifttype:
        select_atom = sidechain

    for line in lines:
        tmplist = line.split(',')
        atomtype = tmplist[2]
        if atomtype in select_atom:
            # print atomtype
            newlines.append(line)

    return newlines

def print_output(lines, outformat, shifttype, outfile):
    ''' print the output lines to the specified format '''
    ## add 'outformat' into passing parameters
    lines = filter_shifttype(lines, shifttype, outformat)

    outstr = ""
    if "bmrb" in outformat:
        outstr = convert_bmrb(lines)
    elif "nef" in outformat:
        outstr = convert_nef(lines)
    elif "table" in outformat:
        outstr = convert_table(lines, shifttype)
    else: # print csv
        # Add by B HAN
        outstr = "NUM,RES,ATOMNAME,SHIFT\n"
        # outstr = "\n".join(outstrs)
        outstr = outstr + "\n".join(lines)

    output = open(outfile, 'w')
    print >> output, outstr
    output.close()

#def calc_corr(dict1, dict2, atomtype):
#    ''' given two dictionaries of type: 'resnum-restype-atomname' : 'cs'
#    this function calculates the pearson correlation coefficients between the command set of predictions '''
#    keyset = set(dict1.keys()) & set(dict2.keys()) # sets of keys for common predictions
#    # coverage1 = "%.4f" % (len(keyset) * 1.00 / len(dict1.keys()))
#    # coverage2 = "%.4f" % (len(keyset) * 1.00 / len(dict2.keys()))
#    keylist = list(keyset)
#    keylist.sort()
#
#    rms_square = 0
#    N = 0
#
#    # convert the common predictions into two ordered list
#    list1 = []
#    list2 = []
#    for key in keylist:
#        tmplist = key.split('#')
#        # print tmplist
#        if atomtype=="ALL" or tmplist[2] == atomtype:
#            list1.append(dict1[key])
#            list2.append(dict2[key])
#            diff = float(dict1[key]) - float(dict2[key])
#            rms_square = rms_square + diff**2
#            N = N + 1
#        else:
#            continue
#
#    rms_square = rms_square / N
#    rmsd = math.sqrt(rms_square)
#
#    array1 = array(list1)
#    array2 = array(list2)
#    corr = corrcoef(array1, array2)[0, 1]
#
#    # print corr, coverage1, coverage2
#    return (corr, rmsd)

# depreciated function - don't use
#def calc_corr2(dict1, dict2):
#    ''' given two dictionaries of type: 'resnum-restype-atomname' : 'cs'
#    this function calculates the pearson correlation coefficients between the command set of predictions '''
#    keyset = set(dict1.keys()) & set(dict2.keys()) # sets of keys for common predictions
#    coverage1 = "%.4f" % (len(keyset) * 1.00 / len(dict1.keys()))
#    coverage2 = "%.4f" % (len(keyset) * 1.00 / len(dict2.keys()))
#    keylist = list(keyset)
#    keylist.sort()
#
#    # convert the common predictions into two ordered list
#    list1 = []
#    list2 = []
#    for key in keylist:
#        list1.append(dict1[key])
#        list2.append(dict2[key])
#
#    array1 = array(list1)
#    array2 = array(list2)
#    corr = corrcoef(array1, array2)[0, 1]
#
#    # print corr, coverage1, coverage2
#    return (corr, coverage1, coverage2)

########################################################################
# main program
########################################################################
if __name__ == '__main__':
    if len(sys.argv)<4:
        print "Usage: python combine_cs.py [shiftx.out] [shifty.out] [output.txt] [outformat: 1=csv, 2=table, 3=bmrb, 4=nef] [shifttype: 1=all, 2=backbone, 3=sidechan] [optional: identity=0]"
        sys.exit(-1)

    shiftx_infile = sys.argv[1]
    shifty_infile = sys.argv[2]
    outfile = sys.argv[3]
    outformat = "csv"
    shifttype = "ALL"

    if len(sys.argv)>=5:
        if sys.argv[4] == "2":
            outformat = "table"
        if sys.argv[4] == "3":
            outformat = "bmrb"
        if sys.argv[4] == "4":
            outformat = "nef"

    if len(sys.argv)>=6:
        if sys.argv[5] == "2":
            shifttype = "BACKBONE"
        if sys.argv[5] == "3":
            shifttype = "SIDECHAIN"

    if len(sys.argv)>=7:
        id = float(sys.argv[6])
    outstrs = []

    # build a dictionary of shifty shifts
    # RESNUM, RESTYPE, ATOMTYPE, CS, IDENTITY
    # 1,M,CA,55.60,89.07

    if (os.path.exists(shifty_infile)):
        shiftyin = open(shifty_infile, 'r')
        for line in shiftyin:
            if line.strip() == "":
                continue
            tmplist = line.split(',')
            if tmplist[1].strip() == "B":
                tmplist[1] = "C" # convert B to C
            key = tmplist[0] + '#' + tmplist[1] + '#' + tmplist[2]
            value = tmplist[3]
            if ('****' in value):
                continue
            seqid = float(tmplist[4]) # sequence identity for the prediction
            if seqid >= id:
                keyset.add(key)
                shifty_hash[key] = value
        shiftyin.close()

    # build a dictionary for shiftx shifts
    # NUM RES IUPAC ATOM  PRED_CS
    # 0,HIS,H,H,8.3405

    ## add if condition and else part to check file existance (by B HAN)
    if (os.path.exists(shiftx_infile)):
        shiftxin = open(shiftx_infile, 'r')
        for line in shiftxin:
            if line.strip()=="" or line.startswith("NUM"):
                continue # ignore blank line and first line
            line = line.strip()

            tmplist = line.split(',')
            key = tmplist[0] + '#' + tmplist[2] + '#' + tmplist[3]
            value = "%.4f" % float(tmplist[4])
            # value = tmplist[4]
            keyset.add(key)
            shiftx_hash[key] = value
        shiftxin.close()
    else:
        print "\nError (combine.py): Cannot find a file \n\t("+ shiftx_infile +")\n"
        sys.exit(-1)

    # sort the keyset in numerical order
    # data = sorted(keyset, key=lambda item: (int(item.partition('#')[0])
    #                                 if item[0].isdigit() else float('inf'), item))
    data = natsorted(list(keyset))

    # combine shiftx and shifty predictions:
    # policy: use shifty prediction if it's available
    # otherwise use shiftx predictions

    if len(shifty_hash) == 0:
        for key in data:
            newvalue = shiftx_hash[key]
            if ('*' not in newvalue) and (float(newvalue)>0):
                tmplist = key.split('#')
                tmplist.append(newvalue)
                outstr = ','.join(tmplist)
                outstrs.append(outstr)
    elif len(shiftx_hash) == 0:
        for key in data:
            newvalue = shifty_hash[key]
            if ('*' not in newvalue) and (float(newvalue)>0):
                tmplist = key.split('#')
                tmplist.append(newvalue)
                outstr = ','.join(tmplist)
                outstrs.append(outstr)
    else:
        # both shiftx and shifty has prediction
        for key in data:
            if (key in shifty_hash):
                if (key in shiftx_hash):
                    tmplist = key.split('#')
                    # print tmplist
                    atomtype = tmplist[2]
                    if atomtype in meanstd:
                        (csmean, csstd) = meanstd[atomtype]
                        sx = float(shiftx_hash[key])
                        sy = float(shifty_hash[key])
                        diff = abs(sx - sy)
                        stds = diff / csstd
                        weight = 0
                        if stds > MINSTD:
                            ## 2012.01.05 modified by B Han
                            if atomtype in ("C","CA","CB","H","HA","N"):
                                if stds > MAXSTD:
                                    stds = MAXSTD
                                weight = stds**2 * 1.0 / (MAXSTD**2)  # backbone
                            else:
                                if stds > MAXSTD_SIDECHAIN:
                                    stds = MAXSTD_SIDECHAIN
                                weight = round(1.9 /((1.9+exp(6-4.4*stds))**(1.0 / 1.36)), 3) # sidechain
                            # print weight
                        newvalue = weight * sx + (1-weight) * sy
                    else:
                        sx = float(shiftx_hash[key])
                        newvalue = sx
                else: # only shifty has prediction
                    # v104: this prediction is ignored since SHIFTX+ should
                    # have predicted all atoms, this case represents senario
                    # that shifty makes prediction for unexpected atom types.
                    # newvalue = shifty_hash[key]

                    ## print ">>> key is not in shiftx:" + key
                    ## add 'continue' to skip when '-a SIDECHAIN' option (by B HAN)
                    continue
            else: # if no shifty prediction then we use shiftx prediciton
                newvalue = shiftx_hash[key]

            # if ('*' not in newvalue) and (float(newvalue)>0):
            if (float(newvalue)>0):
                tmplist = key.split('#')
                tmplist.append("%.4f" % float(newvalue))
                outstr = ','.join(tmplist)
                outstrs.append(outstr)

    # print the output
    print_output(outstrs, outformat, shifttype, outfile)
