591 lines
13 KiB
Python
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
|
|
|
|
|