Files
addie-uniprot-hits/grantham_matrix.py
T

591 lines
13 KiB
Python

import gurobipy as gp
from gurobipy import GRB
from prettify import red, green, warn, blue
GRANTHAM_MATRIX = { ('A', 'A'): '0',
('A', 'C'): '195',
('A', 'D'): '126',
('A', 'E'): '107',
('A', 'F'): '113',
('A', 'G'): '60',
('A', 'H'): '86',
('A', 'I'): '94',
('A', 'K'): '106',
('A', 'L'): '96',
('A', 'M'): '84',
('A', 'N'): '111',
('A', 'P'): '27',
('A', 'Q'): '91',
('A', 'R'): '112',
('A', 'S'): '99',
('A', 'T'): '58',
('A', 'V'): '64',
('A', 'W'): '148',
('A', 'Y'): '112',
('C', 'A'): '195',
('C', 'C'): '0',
('C', 'D'): '154',
('C', 'E'): '170',
('C', 'F'): '205',
('C', 'G'): '159',
('C', 'H'): '174',
('C', 'I'): '198',
('C', 'K'): '202',
('C', 'L'): '198',
('C', 'M'): '196',
('C', 'N'): '139',
('C', 'P'): '169',
('C', 'Q'): '154',
('C', 'R'): '180',
('C', 'S'): '112',
('C', 'T'): '149',
('C', 'V'): '192',
('C', 'W'): '215',
('C', 'Y'): '194',
('D', 'A'): '126',
('D', 'C'): '154',
('D', 'D'): '0',
('D', 'E'): '45',
('D', 'F'): '177',
('D', 'G'): '94',
('D', 'H'): '81',
('D', 'I'): '168',
('D', 'K'): '101',
('D', 'L'): '172',
('D', 'M'): '160',
('D', 'N'): '23',
('D', 'P'): '108',
('D', 'Q'): '61',
('D', 'R'): '96',
('D', 'S'): '65',
('D', 'T'): '85',
('D', 'V'): '152',
('D', 'W'): '181',
('D', 'Y'): '160',
('E', 'A'): '107',
('E', 'C'): '170',
('E', 'D'): '45',
('E', 'E'): '0',
('E', 'F'): '140',
('E', 'G'): '98',
('E', 'H'): '40',
('E', 'I'): '134',
('E', 'K'): '56',
('E', 'L'): '138',
('E', 'M'): '126',
('E', 'N'): '42',
('E', 'P'): '93',
('E', 'Q'): '29',
('E', 'R'): '54',
('E', 'S'): '80',
('E', 'T'): '65',
('E', 'V'): '121',
('E', 'W'): '152',
('E', 'Y'): '122',
('F', 'A'): '113',
('F', 'C'): '205',
('F', 'D'): '177',
('F', 'E'): '140',
('F', 'F'): '0',
('F', 'G'): '153',
('F', 'H'): '100',
('F', 'I'): '21',
('F', 'K'): '102',
('F', 'L'): '22',
('F', 'M'): '28',
('F', 'N'): '158',
('F', 'P'): '114',
('F', 'Q'): '116',
('F', 'R'): '97',
('F', 'S'): '155',
('F', 'T'): '103',
('F', 'V'): '50',
('F', 'W'): '40',
('F', 'Y'): '22',
('G', 'A'): '60',
('G', 'C'): '159',
('G', 'D'): '94',
('G', 'E'): '98',
('G', 'F'): '153',
('G', 'G'): '0',
('G', 'H'): '98',
('G', 'I'): '135',
('G', 'K'): '127',
('G', 'L'): '138',
('G', 'M'): '127',
('G', 'N'): '80',
('G', 'P'): '42',
('G', 'Q'): '87',
('G', 'R'): '125',
('G', 'S'): '56',
('G', 'T'): '59',
('G', 'V'): '109',
('G', 'W'): '184',
('G', 'Y'): '147',
('H', 'A'): '86',
('H', 'C'): '174',
('H', 'D'): '81',
('H', 'E'): '40',
('H', 'F'): '100',
('H', 'G'): '98',
('H', 'H'): '0',
('H', 'I'): '94',
('H', 'K'): '32',
('H', 'L'): '99',
('H', 'M'): '87',
('H', 'N'): '68',
('H', 'P'): '77',
('H', 'Q'): '24',
('H', 'R'): '29',
('H', 'S'): '89',
('H', 'T'): '47',
('H', 'V'): '84',
('H', 'W'): '115',
('H', 'Y'): '83',
('I', 'A'): '94',
('I', 'C'): '198',
('I', 'D'): '168',
('I', 'E'): '134',
('I', 'F'): '21',
('I', 'G'): '135',
('I', 'H'): '94',
('I', 'I'): '0',
('I', 'K'): '102',
('I', 'L'): '5',
('I', 'M'): '10',
('I', 'N'): '149',
('I', 'P'): '95',
('I', 'Q'): '109',
('I', 'R'): '97',
('I', 'S'): '142',
('I', 'T'): '89',
('I', 'V'): '29',
('I', 'W'): '61',
('I', 'Y'): '33',
('K', 'A'): '106',
('K', 'C'): '202',
('K', 'D'): '101',
('K', 'E'): '56',
('K', 'F'): '102',
('K', 'G'): '127',
('K', 'H'): '32',
('K', 'I'): '102',
('K', 'K'): '0',
('K', 'L'): '107',
('K', 'M'): '95',
('K', 'N'): '94',
('K', 'P'): '103',
('K', 'Q'): '53',
('K', 'R'): '26',
('K', 'S'): '121',
('K', 'T'): '78',
('K', 'V'): '97',
('K', 'W'): '110',
('K', 'Y'): '85',
('L', 'A'): '96',
('L', 'C'): '198',
('L', 'D'): '172',
('L', 'E'): '138',
('L', 'F'): '22',
('L', 'G'): '138',
('L', 'H'): '99',
('L', 'I'): '5',
('L', 'K'): '107',
('L', 'L'): '0',
('L', 'M'): '15',
('L', 'N'): '153',
('L', 'P'): '98',
('L', 'Q'): '113',
('L', 'R'): '102',
('L', 'S'): '145',
('L', 'T'): '92',
('L', 'V'): '32',
('L', 'W'): '61',
('L', 'Y'): '36',
('M', 'A'): '84',
('M', 'C'): '196',
('M', 'D'): '160',
('M', 'E'): '126',
('M', 'F'): '28',
('M', 'G'): '127',
('M', 'H'): '87',
('M', 'I'): '10',
('M', 'K'): '95',
('M', 'L'): '15',
('M', 'M'): '0',
('M', 'N'): '142',
('M', 'P'): '87',
('M', 'Q'): '101',
('M', 'R'): '91',
('M', 'S'): '135',
('M', 'T'): '81',
('M', 'V'): '21',
('M', 'W'): '67',
('M', 'Y'): '36',
('N', 'A'): '111',
('N', 'C'): '139',
('N', 'D'): '23',
('N', 'E'): '42',
('N', 'F'): '158',
('N', 'G'): '80',
('N', 'H'): '68',
('N', 'I'): '149',
('N', 'K'): '94',
('N', 'L'): '153',
('N', 'M'): '142',
('N', 'N'): '0',
('N', 'P'): '91',
('N', 'Q'): '46',
('N', 'R'): '86',
('N', 'S'): '46',
('N', 'T'): '65',
('N', 'V'): '133',
('N', 'W'): '174',
('N', 'Y'): '143',
('P', 'A'): '27',
('P', 'C'): '169',
('P', 'D'): '108',
('P', 'E'): '93',
('P', 'F'): '114',
('P', 'G'): '42',
('P', 'H'): '77',
('P', 'I'): '95',
('P', 'K'): '103',
('P', 'L'): '98',
('P', 'M'): '87',
('P', 'N'): '91',
('P', 'P'): '0',
('P', 'Q'): '76',
('P', 'R'): '103',
('P', 'S'): '74',
('P', 'T'): '38',
('P', 'V'): '68',
('P', 'W'): '147',
('P', 'Y'): '110',
('Q', 'A'): '91',
('Q', 'C'): '154',
('Q', 'D'): '61',
('Q', 'E'): '29',
('Q', 'F'): '116',
('Q', 'G'): '87',
('Q', 'H'): '24',
('Q', 'I'): '109',
('Q', 'K'): '53',
('Q', 'L'): '113',
('Q', 'M'): '101',
('Q', 'N'): '46',
('Q', 'P'): '76',
('Q', 'Q'): '0',
('Q', 'R'): '43',
('Q', 'S'): '68',
('Q', 'T'): '42',
('Q', 'V'): '96',
('Q', 'W'): '130',
('Q', 'Y'): '99',
('R', 'A'): '112',
('R', 'C'): '180',
('R', 'D'): '96',
('R', 'E'): '54',
('R', 'F'): '97',
('R', 'G'): '125',
('R', 'H'): '29',
('R', 'I'): '97',
('R', 'K'): '26',
('R', 'L'): '102',
('R', 'M'): '91',
('R', 'N'): '86',
('R', 'P'): '103',
('R', 'Q'): '43',
('R', 'R'): '0',
('R', 'S'): '110',
('R', 'T'): '71',
('R', 'V'): '96',
('R', 'W'): '101',
('R', 'Y'): '77',
('S', 'A'): '99',
('S', 'C'): '112',
('S', 'D'): '65',
('S', 'E'): '80',
('S', 'F'): '155',
('S', 'G'): '56',
('S', 'H'): '89',
('S', 'I'): '142',
('S', 'K'): '121',
('S', 'L'): '145',
('S', 'M'): '135',
('S', 'N'): '46',
('S', 'P'): '74',
('S', 'Q'): '68',
('S', 'R'): '110',
('S', 'S'): '0',
('S', 'T'): '58',
('S', 'V'): '124',
('S', 'W'): '177',
('S', 'Y'): '144',
('T', 'A'): '58',
('T', 'C'): '149',
('T', 'D'): '85',
('T', 'E'): '65',
('T', 'F'): '103',
('T', 'G'): '59',
('T', 'H'): '47',
('T', 'I'): '89',
('T', 'K'): '78',
('T', 'L'): '92',
('T', 'M'): '81',
('T', 'N'): '65',
('T', 'P'): '38',
('T', 'Q'): '42',
('T', 'R'): '71',
('T', 'S'): '58',
('T', 'T'): '0',
('T', 'V'): '69',
('T', 'W'): '128',
('T', 'Y'): '92',
('V', 'A'): '64',
('V', 'C'): '192',
('V', 'D'): '152',
('V', 'E'): '121',
('V', 'F'): '50',
('V', 'G'): '109',
('V', 'H'): '84',
('V', 'I'): '29',
('V', 'K'): '97',
('V', 'L'): '32',
('V', 'M'): '21',
('V', 'N'): '133',
('V', 'P'): '68',
('V', 'Q'): '96',
('V', 'R'): '96',
('V', 'S'): '124',
('V', 'T'): '69',
('V', 'V'): '0',
('V', 'W'): '88',
('V', 'Y'): '55',
('W', 'A'): '148',
('W', 'C'): '215',
('W', 'D'): '181',
('W', 'E'): '152',
('W', 'F'): '40',
('W', 'G'): '184',
('W', 'H'): '115',
('W', 'I'): '61',
('W', 'K'): '110',
('W', 'L'): '61',
('W', 'M'): '67',
('W', 'N'): '174',
('W', 'P'): '147',
('W', 'Q'): '130',
('W', 'R'): '101',
('W', 'S'): '177',
('W', 'T'): '128',
('W', 'V'): '88',
('W', 'W'): '0',
('W', 'Y'): '37',
('Y', 'A'): '112',
('Y', 'C'): '194',
('Y', 'D'): '160',
('Y', 'E'): '122',
('Y', 'F'): '22',
('Y', 'G'): '147',
('Y', 'H'): '83',
('Y', 'I'): '33',
('Y', 'K'): '85',
('Y', 'L'): '36',
('Y', 'M'): '36',
('Y', 'N'): '143',
('Y', 'P'): '110',
('Y', 'Q'): '99',
('Y', 'R'): '77',
('Y', 'S'): '144',
('Y', 'T'): '92',
('Y', 'V'): '55',
('Y', 'W'): '37',
('Y', 'Y'): '0',
('U', 'A'): '301',
('U', 'C'): '301',
('U', 'D'): '301',
('U', 'E'): '301',
('U', 'F'): '301',
('U', 'G'): '301',
('U', 'H'): '301',
('U', 'I'): '301',
('U', 'K'): '301',
('U', 'L'): '301',
('U', 'M'): '301',
('U', 'N'): '301',
('U', 'P'): '301',
('U', 'Q'): '301',
('U', 'R'): '301',
('U', 'S'): '301',
('U', 'T'): '301',
('U', 'V'): '301',
('U', 'W'): '301',
('U', 'Y'): '301',
('U', 'U'): '0',
('A', 'U'): '301',
('C', 'U'): '301',
('D', 'U'): '301',
('E', 'U'): '301',
('F', 'U'): '301',
('G', 'U'): '301',
('H', 'U'): '301',
('I', 'U'): '301',
('K', 'U'): '301',
('L', 'U'): '301',
('M', 'U'): '301',
('N', 'U'): '301',
('P', 'U'): '301',
('Q', 'U'): '301',
('R', 'U'): '301',
('S', 'U'): '301',
('T', 'U'): '301',
('V', 'U'): '301',
('W', 'U'): '301',
('Y', 'U'): '301',
('U', 'U'): '0'
}
# attach index id to each amino acid to differentiate them in the LP
def prepare(aa_set1, aa_set2):
count_dict = {}
new_set1 = []
new_set2 = []
for a in aa_set1:
if a not in count_dict:
count_dict[a] = 0
else:
count_dict[a] += 1
new_set1.append(f"{a}{count_dict[a]}")
for a in aa_set2:
if a not in count_dict:
count_dict[a] = 0
else:
count_dict[a] += 1
new_set2.append(f"{a}{count_dict[a]}")
return (new_set1, new_set2)
def min_distance_LP(aa_set1, aa_set2):
(aa_set1, aa_set2) = prepare(aa_set1, aa_set2)
lp = gp.Model("Min_Grantham", env=gp.Env(params={"OutputFlag":0}))
lp.setParam("OutputFlag", 0)
# OBJ: Min. SUM(x_i * distance(v_i, v_j)) + PENALTY*SUM(1-v_i)
# x_i: boolean, whether or not a pair is selected. Eg. 'A' is paired with 'C'
# v_i: boolean, whether or not amino acid v_i is paired
xi_dict = {}
vi_dict = {}
for a in aa_set1:
vi_dict[a] = lp.addVar(vtype=GRB.BINARY)
for b in aa_set2:
vi_dict[b] = lp.addVar(vtype=GRB.BINARY)
for a in aa_set1:
row_xi = []
for b in aa_set2:
xi_varhandle = lp.addVar(vtype=GRB.BINARY)
xi_dict[(a,b)] = xi_varhandle
row_xi.append(xi_varhandle)
# x_i <= v_a, x_i <= v_b
# if pair x_i is selected, v_a and v_b must be selected
lp.addConstr(xi_varhandle <= vi_dict[a])
lp.addConstr(xi_varhandle <= vi_dict[b])
# if v_i is chosen, there must be exactly one x_i which covers it
# v_i == sum(x_i with v_i in it)
for a in aa_set1:
covered = []
for (x,y) in list(xi_dict.keys()):
if a==x:
covered.append(xi_dict[(x,y)])
lp.addConstr(vi_dict[a] == sum(covered))
for b in aa_set2:
covered = []
for (x,y) in list(xi_dict.keys()):
if b==y:
covered.append(xi_dict[(x,y)])
lp.addConstr(vi_dict[b] == sum(covered))
# set objective: Min. SUM(x_i * distance(v_i, v_j)) + PENALTY*SUM(1-v_i)
# Penalty: pairing is ALWAYS cheaper than unpaired
PENALTY = 300
coefs = {}
for (a,b) in list(xi_dict.keys()):
aa1 = a[0]
aa2 = b[0]
distance = int(GRANTHAM_MATRIX[(aa1,aa2)])
coefs[(a,b)] = distance
#lp.setObjective(sum([coef*xi_dict[(a,b)] for ((a,b), coef) in list(coefs.items())]), GRB.MINIMIZE)
lp.setObjective(sum([coef*xi_dict[(a,b)] for ((a,b), coef) in list(coefs.items())]) \
+ PENALTY*sum([(1-vi) for vi in list(vi_dict.values())]), GRB.MINIMIZE)
lp.optimize()
if lp.status != GRB.OPTIMAL:
print(red("LP infeasible. Quitting."))
return (0, [], [])
selected_pairs = []
unpaired_aas = []
rev_xi_dict = {v : k for k,v in list(xi_dict.items())}
rev_vi_dict = {v : k for k,v in list(vi_dict.items())}
for v in lp.getVars():
if v in rev_xi_dict and v.X>0:
selected_pairs.append(rev_xi_dict[v])
if v in rev_vi_dict and v.X==0:
unpaired_aas.append(rev_vi_dict[v])
return (lp.objVal, selected_pairs, unpaired_aas)
# calculate the Grantham distance (approx) between two sets of amino acids
# Amino Acids sets should be in the form ['A', 'Y', 'W']
def min_distance_measure(aa_set1, aa_set2):
aa_set1 = sorted(aa_set1)
aa_set2 = sorted(aa_set2)
set1_matched = []
set2_matched = []
# filter out amino acids which match
for a in aa_set1:
match_b = False
for b in aa_set2:
if a==b:
set1_matched.append(a)
set2_matched.append(b)
match_b = b
break
if match_b: aa_set2.remove(match_b)
[aa_set1.remove(x) for x in set1_matched]
distance = 0
set1_matched = []
for a in aa_set1:
if aa_set2:
min_distance = int(GRANTHAM_MATRIX[(a,aa_set2[0])])
pair_aa = aa_set2[0]
else: break
for b in aa_set2:
curr_distance = int(GRANTHAM_MATRIX[(a, b)])
if curr_distance < min_distance:
min_distance = curr_distance
pair_aa = b
distance += min_distance
aa_set2.remove(pair_aa)
[aa_set1.remove(x) for x in set1_matched]
# penalty distance when number of amino acids does not match
PENALTY = 100
total_penalty = abs(len(aa_set1) - len(aa_set2)) * PENALTY
distance += total_penalty
return distance