From 1cf6e97fa155f6eb48ea14aea30ee805c4962801 Mon Sep 17 00:00:00 2001 From: fabrice <fabrice.allain@pasteur.fr> Date: Wed, 2 Mar 2016 14:31:22 +0100 Subject: [PATCH] Bug fixe: wrong atom list when pair_list set to all (add terminal atoms) --- ariaec/econverter.py | 264 ++++++++++++++++++++---------------------- ariaec/econverter.pyc | Bin 28647 -> 27429 bytes ariaec/maplot.py | 2 +- ariaec/maplot.pyc | Bin 5130 -> 5130 bytes ariaec/protmap.pyc | Bin 45110 -> 45110 bytes ariaec/setup.py | 15 +-- ariaec/setup.pyc | Bin 7791 -> 7818 bytes 7 files changed, 137 insertions(+), 144 deletions(-) diff --git a/ariaec/econverter.py b/ariaec/econverter.py index 0f5ac00..cfbfd10 100644 --- a/ariaec/econverter.py +++ b/ariaec/econverter.py @@ -15,12 +15,15 @@ import json import re import pkg_resources as pkgr import aria.legacy.AminoAcid as AminoAcid -import aria.ConversionTable as ConversionTable -import aria.conversion -from .base import (Capturing, get_filename) +from .base import get_filename, Capturing from .protein import Protein from .reader import ProtFileListReader from .protmap import (ResAtmMap, ResMap) +from aria.Molecule import Molecule +from aria.tools import string_to_segid +from aria.AriaXML import AriaXMLPickler +from aria.conversion import Converter, SequenceList, MoleculeSettings + logger = logging.getLogger(__name__) @@ -90,10 +93,52 @@ class AriaEcBbConverter(object): return math.sqrt(n) / float(l) -class AriaXMLConverter(object): - def __init__(self): - # TODO: stock here pickle file path for cns donor/ acceptor dict - pass +class AriaXMLConverter(Converter, object): + def __init__(self, settings): + Converter.__init__(self) + self._mol_set = MoleculeSettings() + self._pickler = AriaXMLPickler() + self.outprefix = "" + self.settings = settings + self.molecule = None + + def load_molecule(self, seqpath): + self._mol_set['format'] = 'seq' + self._mol_set['input'] = seqpath + self._mol_set['output'] = os.path.join(self.settings.infra["xml"], + self.outprefix + ".xml") + self._mol_set['type'] = 'PROTEIN' + self._mol_set['segid'] = ' ' + self._mol_set['first_residue_number'] = 1 + self._mol_set['naming_convention'] = '' + self._mol_set['name'] = self.outprefix + + segids = self._mol_set['segid'] + segids = segids.split('/') + + segids = [string_to_segid(segid) for segid in segids] + # Recup molecule + chain_types = {} + for s in segids: + chain_types[s] = self._mol_set['type'] + + sequence = SequenceList(chain_types, self._mol_set['first_residue_number']) + + with Capturing() as output: + sequence.parse(self._mol_set['input'], self._mol_set['format'], + self._mol_set['naming_convention']) + + logger.info("\n" + "".join(output)) + + factory = self.create_factory() + + factory.reset() + factory.unfreeze() + + chains = sequence.create_chains(factory) + + self.molecule = Molecule(self._mol_set['name']) + [self.molecule.add_chain(chains[seg]) for seg in segids] @staticmethod def deff(distance_list, dpow=6): @@ -350,11 +395,35 @@ assign (resid {res1} and name n) (resid {res1} and name ca) (resid {res1} and na class AriaEcXMLConverter(AriaXMLConverter): - def __init__(self, settings): + def __init__(self, *args, **kwargs): self.restraint_list = [] - self.settings = settings - self.protname = "" - super(AriaEcXMLConverter, self).__init__() + super(AriaEcXMLConverter, self).__init__(*args, **kwargs) + + def atm_product(self, idx1, res1, idx2, res2, prod_type="min"): + + def resname(res): + return AminoAcid.AminoAcid(res)[0] + + def min_atms(aa1, aa2, atms): + # Function to minimize atom pair list between aa1 & aa2 + return [ + atmpair for atmpair in atms if atmpair in ( + ('CA', 'CA'), + ('CB', 'CB'), + self.settings.scsc_min[resname(aa1)][resname(aa2)])] + atms1 = self.molecule.get_chains()[0].getResidues()[idx1].atoms.keys() + atms2 = self.molecule.get_chains()[0].getResidues()[idx2].atoms.keys() + if prod_type == "min": + return min_atms(res1, res2, list(itertools.product(atms1, atms2))) + elif prod_type == "heavy": + atms1 = filter(ResAtmMap.heavy_reg.match, atms1) + atms2 = filter(ResAtmMap.heavy_reg.match, atms2) + return list(itertools.product(atms1, atms2)) + elif prod_type == "all": + return list(itertools.product(atms1, atms2)) + else: + logger.error("Wrong pair_list option. Pair_list set to min") + return min_atms(res1, res2, list(itertools.product(atms1, atms2))) def targetdistmap(self, distype, sequence, distfile=None, groupby=None): # TODO: valeur par defaut de distfile au fichier contenant les infos @@ -451,79 +520,14 @@ class AriaEcXMLConverter(AriaXMLConverter): pair_flag = self.settings.setup.config["pair_list"] target_dist = self.settings.setup.config["restraint_distance"] - conv_table = ConversionTable.ConversionTable().table['AMINO_ACID'][ - 'iupac'] - - # TODO: atm_pair method - if pair_flag == "min": - def min_atms(aa1, aa2, atms): - # Function to minimize atom pair list between aa1 & aa2 - return [ - atmpair for atmpair in atms if atmpair in ( - ('CA', 'CA'), - ('CB', 'CB'), - self.settings.scsc_min[aa1][aa2])] - aa_atm = dict((AminoAcid.AminoAcid(aa)[0], - filter(contactmap.heavy_reg.match, atms.keys())) - for aa, atms in conv_table.items()) - aa_atm_pair = \ - { - (aa1, aa2): min_atms(aa1, aa2, list(itertools.product(atms1, - atms2))) - for aa1, atms1 in aa_atm.items() - for aa2, atms2 in aa_atm.items() - } - elif pair_flag == "heavy": - # Dict giving all atms foreach aa - aa_heav_atm = dict((AminoAcid.AminoAcid(aa)[0], - filter(contactmap.heavy_reg.match, atms.keys())) - for aa, atms in conv_table.items()) - - # Dict giving atoms product for each res-res pair - aa_atm_pair = {(aa1, aa2): list(itertools.product(atms1, atms2)) - for aa1, atms1 in aa_heav_atm.items() - for aa2, atms2 in aa_heav_atm.items() - } - elif pair_flag == "all": - # Dict giving all atms foreach aa - aa_atm = dict((AminoAcid.AminoAcid(aa)[0], - filter(contactmap.all_reg.match, atms.keys())) - for aa, atms in conv_table.items()) - - # Dict giving atoms product for each res-res pair - aa_atm_pair = {(aa1, aa2): list(itertools.product(atms1, atms2)) - for aa1, atms1 in aa_atm.items() - for aa2, atms2 in aa_atm.items() - } - else: - logger.error("Wrong pair_list option. Pair_list set to min") - - def min_atms(aa1, aa2, atms): - # Function to minimize atom pair list between aa1 & aa2 - return [ - atmpair for atmpair in atms if atmpair in ( - ('CA', 'CA'), - ('CB', 'CB'), - self.settings.scsc_min[aa1][aa2])] - aa_atm = dict((AminoAcid.AminoAcid(aa)[0], - filter(contactmap.heavy_reg.match, atms.keys())) - for aa, atms in conv_table.items()) - aa_atm_pair = \ - { - (aa1, aa2): min_atms(aa1, aa2, list(itertools.product(atms1, - atms2))) - for aa1, atms1 in aa_atm.items() - for aa2, atms2 in aa_atm.items() - } - def min_ind(ind): return ind if ind >= 0 else 0 def max_ind(ind, max_idx): return ind if ind <= max_idx else max_idx - def resname(residx): - return AminoAcid.AminoAcid(contactmap.index.values[residx][-3:])[0] + def resname_3l(residx): + return AminoAcid.AminoAcid(contactmap.index.values[residx][-3:])[1] max_seqidx = len(contactmap.sequence) restraint_dict = collections.OrderedDict() @@ -531,18 +535,19 @@ class AriaEcXMLConverter(AriaXMLConverter): contrib_id = 0 for contactidx, contact in enumerate(pair_list): - # /!\ humanidx in contact must start at 1 !n_factor - # Add neighbors if neigh_flag logger.debug("Contact %s" % str(contact)) + + # Add neighbors if neigh_flag resx_idx = range(min_ind(contact[0] - 1), max_ind(contact[0] + 2, max_seqidx)) if \ neigh_flag else [contact[0]] resy_idx = range(min_ind(contact[1] - 1), max_ind(contact[1] + 2, max_seqidx)) if \ neigh_flag else [contact[1]] - contactweight = weight_list[contactidx] + contactweight = weight_list[contactidx] dist_list = [] + if adr_flag: rest_id += 1 # TODO: Autre dist_list if target != ResMap @@ -576,22 +581,27 @@ class AriaEcXMLConverter(AriaXMLConverter): target_dist = self.settings.setup.confn_factorig[ "restraint_distance"] - for resx in resx_idx: - for resy in resy_idx: - resxn = targetdist.index.levels[0][resx] - resyn = targetdist.index.levels[0][resy] - for atm_pair in aa_atm_pair[(resname(resx), - resname(resy))]: + for idx_x in resx_idx: + for idx_y in resy_idx: + mapidx_x = targetdist.index.levels[0][idx_x] + mapidx_y = targetdist.index.levels[0][idx_y] + res_x = resname_3l(idx_x) + res_y = resname_3l(idx_y) + + atm_pairs = self.atm_product(idx_x, res_x, idx_y, res_y, + pair_flag) + + for atm_pair in atm_pairs: if adr_flag: contrib_id += 1 else: if len(targetdist.index.levels) == 2: - target_dist = targetdist.loc[resxn, - atm_pair[0]][resyn, + target_dist = targetdist.loc[mapidx_x, + atm_pair[0]][mapidx_y, atm_pair[1]] else: - target_dist = "%.2f" % targetdist.iat[resx, - resy] + target_dist = "%.2f" % targetdist.iat[idx_x, + idx_y] if target_dist is None: # In case missing distance values @@ -600,7 +610,7 @@ class AriaEcXMLConverter(AriaXMLConverter): logger.warning( "Target distance is missing for restraint " "%s-%s (%s). Using default distance (%s)" - % (resx + 1, resy + 1, atm_pair, target_dist)) + % (idx_x + 1, idx_y + 1, atm_pair, target_dist)) rest_id += 1 contrib_id = 1 @@ -636,12 +646,12 @@ class AriaEcXMLConverter(AriaXMLConverter): "weight": 1.0 }, "spin_pair": { - resx + 1: atm_pair[0], - resy + 1: atm_pair[1] + idx_x + 1: atm_pair[0], + idx_y + 1: atm_pair[1] } } xml_file = self.settings.infra["xml"] + "/" + "_".join(( - self.protname, listname)) + ".xml" + self.outprefix, listname)) + ".xml" self.write_dist_xml(restraint_dict, xml_file) return xml_file, pair_list @@ -667,11 +677,11 @@ class AriaEcXMLConverter(AriaXMLConverter): :param hbmap: Extra hbond map (eg: metapsicov hbonds) :return: """ - dihed_file = os.path.join(self.settings.infra["tbl"], self.protname + + dihed_file = os.path.join(self.settings.infra["tbl"], self.outprefix + "_dihed.tbl") - hb_file = os.path.join(self.settings.infra["tbl"], self.protname + + hb_file = os.path.join(self.settings.infra["tbl"], self.outprefix + "_hbond.tbl") - ssdist_file = os.path.join(self.settings.infra["tbl"], self.protname + + ssdist_file = os.path.join(self.settings.infra["tbl"], self.outprefix + "_ssdist.tbl") self.write_dihedral_tbl(protein.sec_struct.ss_matrix, dihed_file) self.write_hb_tbl(protein, hb_file, @@ -684,39 +694,21 @@ class AriaEcXMLConverter(AriaXMLConverter): ssdist_file) return {'hbond': hb_file, 'dihed': dihed_file, 'ssdist': ssdist_file} - def write_xmlseq(self, seqpath): - xml_file = os.path.join(self.settings.infra["xml"], self.protname + - ".xml") - - m = aria.conversion.MoleculeSettings() - m['format'] = 'seq' - m['input'] = seqpath - m['output'] = xml_file - m['type'] = 'PROTEIN' - m['segid'] = ' ' - m['first_residue_number'] = 1 - m['naming_convention'] = '' - m['name'] = self.protname - - c = aria.conversion.ConverterSettings() - c.reset() - c['molecule'] = m - c['project_name'] = self.protname - - converter = aria.conversion.Converter() - converter.setSettings(c) - - # TODO: generate xml in order to use convert method ?? - # converter.convert() - with Capturing() as output: - converter._convert_sequence() + def write_xmlseq(self): - logger.info("\n" + "".join(output)) + try: + + self._pickler.dump(self.molecule, self._mol_set[ + 'output']) + + except Exception, msg: + + logger.error("Error writing xml seq file : %s" % msg) - return xml_file + return self._mol_set['output'] - def write_project(self, aria_template, seqfile, dist_files, tbl_files, - desc=""): + def write_ariaproject(self, aria_template, seqfile, dist_files, tbl_files, + desc=""): if aria_template: template = os.path.abspath(aria_template) @@ -755,21 +747,21 @@ class AriaEcXMLConverter(AriaXMLConverter): for direct in (work_dir, temp_root): - if not os.path.exists(os.path.join(direct, self.protname)): - os.makedirs(os.path.join(direct, self.protname)) + if not os.path.exists(os.path.join(direct, self.outprefix)): + os.makedirs(os.path.join(direct, self.outprefix)) - if not os.path.exists(os.path.join(direct, self.protname, desc)): - os.makedirs(os.path.join(direct, self.protname, desc)) + if not os.path.exists(os.path.join(direct, self.outprefix, desc)): + os.makedirs(os.path.join(direct, self.outprefix, desc)) - work_dir = os.path.join(work_dir, self.protname, desc) - temp_root = os.path.join(temp_root, self.protname, desc) + work_dir = os.path.join(work_dir, self.outprefix, desc) + temp_root = os.path.join(temp_root, self.outprefix, desc) aria_project_dict['working_directory'] = work_dir aria_project_dict['temp_root'] = temp_root - project = {'project_name': "_".join((self.protname, desc)), + project = {'project_name': "_".join((self.outprefix, desc)), 'date': datetime.date.today().isoformat(), - 'file_root': "_".join((self.protname, desc))} + 'file_root': "_".join((self.outprefix, desc))} aria_project_dict.update(project) data_molecule = {'molecule_file': seqfile} diff --git a/ariaec/econverter.pyc b/ariaec/econverter.pyc index b625787498e3efd62d67472e4b42a83b28e25c1c..12fd3dfe9c8a55270612d758122c6bc87f40bac5 100644 GIT binary patch delta 7895 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 Lol`x(l<ofyc()S$ delta 8467 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 G?*9QiJ9BCP diff --git a/ariaec/maplot.py b/ariaec/maplot.py index e5b834e..ba351a5 100644 --- a/ariaec/maplot.py +++ b/ariaec/maplot.py @@ -80,7 +80,7 @@ class AriaEcContactMap(object): # Use only position filter # self.filter(fo.mapdict, fo.filetype, fo.contactlist, # self.protein, clashlist=fo.clashlist, - # protname=self.protname, + # outprefix=self.outprefix, # outdir=self.settings.outdir, mapfilters="pos") self.allresmap[(fo.filename, fo.filetype)] = fo.mapdict diff --git a/ariaec/maplot.pyc b/ariaec/maplot.pyc index a461771fb53bdf1b0630d498d1820a065b5c73ae..a21fef1bed62dd942c979bb3f41cec9c39e17d56 100644 GIT binary patch delta 16 XcmeCu=+a<k{>;nOx8~|bc4iR(E?Naw delta 16 XcmeCu=+a<k{>;m@@AA=&?93tnFhm8O diff --git a/ariaec/protmap.pyc b/ariaec/protmap.pyc index 3c211cc7e6fb5eb7499d1a522e96e82ce70facdb..53032a74ad2c6c3e5e53856f5b9c34f2eb8551d1 100644 GIT binary patch delta 16 Ycmdn?fN9$UCRXOpyj(^bSq(P;06E(R3;+NC delta 16 Ycmdn?fN9$UCRXOpyj;c`Sq(P;06E|W4FCWD diff --git a/ariaec/setup.py b/ariaec/setup.py index fe2f1db..1aebaa8 100644 --- a/ariaec/setup.py +++ b/ariaec/setup.py @@ -54,7 +54,7 @@ class AriaEcSetup: # -------------------------------------------------------------------- # self.outprefix = get_filename(self.settings.setup.args.get("seq", None)) - self.converter.protname = self.outprefix + self.converter.outprefix = self.outprefix # ------------------------- Load sequence ---------------------------- # self.protein.set_aa_sequence(self.settings.setup.args.get("seq", None)) # -------------- Load secondary structure prediction ----------------- # @@ -69,7 +69,7 @@ class AriaEcSetup: # ---------------------------- Processing ---------------------------- # # -------------------------------------------------------------------- # # TODO: write submatrix in a file - # TODO: change read method in reader to __call__ ? + # TODO: change read method in reader to __call__ # -------------------------- contact maps ---------------------------- # self.reader.read(self.settings.setup.args.get("infiles"), filetypelist=self.settings.setup.args.get("contact_types"), @@ -142,7 +142,8 @@ class AriaEcSetup: # ----------------------------- SEQ file ----------------------------- # self.protein.write_seq(os.path.join(self.settings.infra.get("others", ''), self.outprefix + ".seq")) - + # Load aria molecule object from generated seq file + self.converter.load_molecule(self.protein.seqfile_path) # --------------------------- TBL restraints ------------------------- # # Setting contact number limit for hbmap n_hb = int(len(self.protein.aa_sequence.sequence) * @@ -159,16 +160,16 @@ class AriaEcSetup: self.allresmap, self.targetmap) # --------------------------- XML SEQ file --------------------------- # - seq_file = self.converter.write_xmlseq(self.protein.seqfile_path) + xmlseq_file = self.converter.write_xmlseq() # ---------------------- ARIA XML project file ----------------------- # aria_template = self.settings.main.config["ariaproject_template"] if \ self.settings.main.config["ariaproject_template"] and \ os.path.exists(self.settings.main.config["ariaproject_template"])\ else None - self.converter.write_project(aria_template, - seq_file, dist_files, tbl_files, - desc="_".join(sorted(self.allresmap.keys()))) + self.converter.write_ariaproject(aria_template, + xmlseq_file, dist_files, tbl_files, + desc="_".join(sorted(self.allresmap.keys()))) # ------------------------------ others ------------------------------ # self.write_optional_files() diff --git a/ariaec/setup.pyc b/ariaec/setup.pyc index fc95c7c9edf6e7ecf1230d01559c4f2a62b55c5b..4f16a2b361a1c02638a25850235bf1943cb5f8fb 100644 GIT binary patch delta 1017 zcmZ`%&1(};5TCc3>}I#wWYc`KX_B@!X{**+8*RN*sA8*#q68H$B?R9}vrUt_S#1j~ zegv_2QG9wVh&Sz}lCuX-f)oS~f}qD9#DfR@1DyBrQAC7gnD?8R-^~1Ge@;C*p~}Au z{rB6?Z`7|I$*-_o&r{tGYXEit>>#`6&5W90D?k;9JKsdW19lrK%RmQVowO=K?2|Ve z>V%qvA0eD440eP?l?f7HV_NT>DAb6DtZa!2jK$#e+$O9`;@t&n64>3s#)a)^GSi@V zCV-j(@dFJB2d7Dbtpn`@8Ww#$GHJ44_f(T>ibHW6HUbMWIL`@<i1HLHDE!I8y<qn> zKxU}tLj6Fya9G&eVqQ%*wg@z~;OiM+?_(ifPZ;$Y06PsdDmeIRY*+0wGEklsNGmhl z&cqdw0XOgpi4BOOXb9yqtm2DxIN#;AfE8b}bz-lzP>*&)oGXS^gKWn)W(x702cCB6 zM_(*JQJ@si=4JUI=Kjw!1m(<MlW}>I;5L|FdE8;RPoNAU2bb9o?g6k5f<4lzO0w5l z?=YVLmp;_W8x?B0hru0dL0mERMH@DcG!fb_oFQiF5o28VctRw~uyH+dDhoA$byXnk z|8M|*&3E4B3`hTo;R!L!Id}zoqL1n$;7&BS>+khNseaP9NCpf5C{+qHzgQ_**Kd_9 z*N;fmx_P5mvhqs>cb+{CT#G9RS}Vn>TXjR|E;~in$`_nsff>P?+JVr@Vu?Kq-X$+t z)Z8$om>eZZqLGBEk#0Fy-!eZCa+clh+%|KVo#TCTye@Z!O@^}_=ec`<n;BLOFO%*1 zkFcVor#W&JjjN%#yt-t~I8MdMO>+D=H&^PBo+=?bY$LHrrrA((l{{oGl8jucA4_G4 M=JiXa6edCP2L=Akw*UYD delta 1022 zcmZ`%&ubG=5T3W$>}Jz!vfCtWe%Cfh>yNh4NcB<=0WB(MX{~s%l){U&TbtC)LR)C4 zS`_gh=)+?{@T4Lt>rL>c2NeYMsK*{W_23_%^WG*YJqV9s-ZwMf%zW?NKeaoq`u^y_ z_uoIfQGd$PUq9QC9|gm(0boVIGT1GDVZ;PW0jfaK{v`r3SRJV516qV8X;*Z!Z~n3w zg)IpWA)F@$R-DC^F%n@VEw|YNTSO*nFGK~#dg1i!HZ&!1Pr!x*R#MoMu;~^vjfiUo zs3DLLP*XTKO%|*m&~Bh!U)e`mEZ9BQ;u_~r3Wtrsk`L_1gvLa94we*t<>5ZC4tOBb z)$?XQ&|VxC_O@71^WGL=ZwtPj4%PrO1F0zLl?ST;G%h&!YHUaC(~D4>5J)>S-^s*9 zkpVaG3yF0|qG$-s6<Iyd(`Enmbp)&jTCLN2t%Z6t0ZFbHRt>T*fs!Fau<yv7(yxJ@ zFhzl~KtrOTc!=5m^9(_)ux~OhZw4HX`I+n-gxdsa;E{oK2%N)U9RX{&U6p0ewVdgU zfHT@o;@YrXZD_O9La3+p5YT?u@S)IBQx+lA>8Nn<?NFW~|4dgUX!@b4v=fdA2Zb#2 z7iy~F+WD9R_*dWhTSFb+A2TtCB^c-Ayo7BBM}ouPjETgY`y;q4=}rIuR_wY{n_sTF z6@8L0H+*F<glMCB<J#i&>fFk_v*0KQRM!?8PQ%gBU9}gT>Kw~Qwq#DgUPf+`Cv4QX z7nb8BLo||BHIncp+>gd5Le8;=-LH*Hm^jUgr@Od0%ci2`t_$3~$jt<M5M3p&TqUL` z`3graqj5AeGwUnWGqzo~r%!PFBsVi|Db*n4GkcoeCKXo6tdqO!U51e<cQRWhnjDf0 IDM}*bAL$^?=l}o! -- GitLab