diff --git a/rptools/rpcompletion/Args.py b/rptools/rpcompletion/Args.py index d9fafba..cb8d25a 100644 --- a/rptools/rpcompletion/Args.py +++ b/rptools/rpcompletion/Args.py @@ -71,6 +71,11 @@ def add_arguments(parser: ArgumentParser) -> ArgumentParser: help='Name of the file containing the list of cofactors to ignore (default: None)', default=None ) + parser.add_argument( + '--forward', dest='forward', + help='Consider reactions in the forward direction', + required=False, action='store_true' + ) # parser.add_argument('--pathway_id', type=str, default='rp_pathway') # parser.add_argument('--compartment_id', type=str, default='MNXC3') # parser.add_argument('--species_group_id', type=str, default='rp_trunk_species') diff --git a/rptools/rpcompletion/__main__.py b/rptools/rpcompletion/__main__.py index c7a3ffc..d91518f 100644 --- a/rptools/rpcompletion/__main__.py +++ b/rptools/rpcompletion/__main__.py @@ -66,6 +66,7 @@ def _cli(): lower_flux_bound=int(args.lower_flux_bound), maxsubpaths=args.maxsubpaths, cofile=args.cofactors, + forward=args.forward, logger=logger ) @@ -73,8 +74,9 @@ def _cli(): if not os_path.exists(args.outdir): os_mkdir(args.outdir) # Write out selected pathways + local_cache = {} for pathway in pathways: - pathway.to_rpSBML().write_to_file( + pathway.to_rpSBML(cache=cache, local_cache=local_cache).write_to_file( os_path.join( args.outdir, pathway.get_id() diff --git a/rptools/rpcompletion/rpcompletion.py b/rptools/rpcompletion/rpcompletion.py index b2fa7c4..ef92781 100644 --- a/rptools/rpcompletion/rpcompletion.py +++ b/rptools/rpcompletion/rpcompletion.py @@ -47,6 +47,7 @@ def rp_completion( lower_flux_bound: float = default_lower_flux_bound, maxsubpaths: int = default_maxsubpaths, cofile: str = default_cofactors, + forward: bool = False, logger: Logger = getLogger(__name__) ) -> List[rpPathway]: """Process to the completion of metabolic pathways @@ -140,6 +141,7 @@ def rp_completion( ec_numbers=ec_numbers, cache=cache, cofactors=cofactors, + forward=forward, logger=logger ) @@ -159,8 +161,7 @@ def rp_completion( pathways=pathway_combinatorics, transfos=full_transfos, sink_molecules=sink_molecules, - rr_reactions=cache.get('rr_reactions'), - compounds_cache=cache.get('cid_strc'), + cache=cache, maxsubpaths=maxsubpaths, lower_flux_bound=lower_flux_bound, upper_flux_bound=upper_flux_bound, @@ -175,6 +176,7 @@ def __complete_transformations( ec_numbers: Dict, cache: rrCache, cofactors: List[str] = [], + forward: bool = False, logger: Logger = getLogger(__name__) ) -> Dict: """From template reactions, put back chemical species @@ -206,15 +208,8 @@ def __complete_transformations( # For each transformation for transfo_id, transfo in transfos.items(): logger.debug(f'transfo_id: {transfo_id}, transfo: {transfo}') - full_transfos[transfo_id] = {} full_transfos[transfo_id]['ec'] = ec_numbers[transfo_id]['ec'] - # Convert transformation into SMILES - transfo_smi = '{left}>>{right}'.format( - left=__build_smiles(transfo['left']), - right=__build_smiles(transfo['right']) - ) - logger.debug(f'transfo_smi: {transfo_smi}') # Add compounds of the current transformation full_transfos[transfo_id]['left'] = dict(transfos[transfo_id]['left']) @@ -226,25 +221,39 @@ def __complete_transformations( # MULTIPLE RR FOR ONE TRANSFO for rule_id in transfo['rule_ids']: + logger.debug(f'rule_id: {rule_id}') # MULTIPLE TEMPLATE REACTIONS FOR ONE RR # If 'tmpl_rxn_id' is not given, # the transformation will be completed # for each template reaction from reaction rule was built from try: - full_transfos[transfo_id]['complement'][rule_id] = rebuild_rxn( + # Convert transformation into SMILES + left_smiles = __build_smiles(transfo['left']) + right_smiles = __build_smiles(transfo['right']) + logger.debug(f'transfo_smi: {left_smiles}>>{right_smiles}') + if forward: + transfo_smi = f'{left_smiles}>>{right_smiles}' + else: + transfo_smi = f'{right_smiles}>>{left_smiles}' + full_transfo = rebuild_rxn( cache=cache, rxn_rule_id=rule_id, transfo=transfo_smi, - direction='forward', cmpds_to_ignore=cofactors, - cspace=cache.get('cspace'), - # tmpl_rxn_id=tmpl_rxn_id, + cspace=cache.get_cspace(), + cspace_type=cache.get_type(), logger=logger ) + if not forward: + for rxn_id, _transfo in full_transfo.items(): + _transfo['full_transfo']['right'], _transfo['full_transfo']['left'] = _transfo['full_transfo']['left'], _transfo['full_transfo']['right'] + _transfo['added_cmpds']['right'], _transfo['added_cmpds']['left'] = _transfo['added_cmpds']['left'], _transfo['added_cmpds']['right'] + full_transfos[transfo_id]['complement'][rule_id] = deepcopy(full_transfo) logger.debug(f'full_transfos[{transfo_id}]["complement"][{rule_id}]: {full_transfos[transfo_id]["complement"][rule_id]}') except KeyError as e: - logger.error(f'Could not find reaction rule {rule_id} in the cache. Are you in the right data type space?') + # logger.error(f'Could not find reaction rule {rule_id} in the cache. Are you in the right chemical space (mnx3.1, mnx4.4, rr2026...)?') + logger.error(e) exit(1) return full_transfos @@ -292,14 +301,24 @@ def __build_smiles( Parameters ---------- side: Dict - Stoichiometric chemical reaction side + Stoichiometric chemical reaction side with species IDs as keys + and stoichiometric coefficients as values logger: Logger, optional Returns ------- SMILES string """ - return '.'.join([Cache.get(spe_id).get_smiles() for spe_id in side.keys()]) + logger.debug(f'side: {side}') + smiles_list = [] + for spe_id, stoichio_coeff in side.items(): + logger.debug(f'spe_id: {spe_id}, sto_coeff: {stoichio_coeff}') + spe_smiles = Cache.get(spe_id).get_smiles() + # Repeat SMILES based on stoichiometric coefficient + smiles_list.extend([spe_smiles] * int(stoichio_coeff)) + + result = '.'.join(smiles_list) + return result def __build_reader( @@ -426,7 +445,8 @@ def __get_compound_from_cache( resConv = cache._convert_depiction( idepic=smiles, itype='smiles', - otype={'inchi'} + otype={'inchi'}, + logger=logger ) inchi = resConv['inchi'] except NotImplementedError as e: @@ -440,7 +460,8 @@ def __get_compound_from_cache( resConv = cache._convert_depiction( idepic=smiles, itype='smiles', - otype={'inchikey'} + otype={'inchikey'}, + logger=logger ) inchikey = resConv['inchikey'] except NotImplementedError as e: @@ -581,6 +602,8 @@ def __read_pathways( for compound in compounds: sto, spe = compound.split('.') transfos[transfo_id][side][spe] = int(sto) + # if sto != '1': + # print(transfos[transfo_id]) return pathways, transfos @@ -655,7 +678,7 @@ def __build_pathway_combinatorics( 'left': dict(full_transfos[transfo_id]['left']) } except KeyError as e: - logger.error(f'Could not find transformation {transfo_id} in the cache. Are you in the right data type space?') + logger.error(f'Could not find transformation {transfo_id} in the cache. Are you in the right chemical space (mnx3.1, mnx4.4, rr2026...)?') exit(1) # Build list of transformations # where each transfo can correspond to multiple reactions @@ -696,8 +719,7 @@ def __build_all_pathways( pathways: Dict, transfos: Dict, sink_molecules: List, - rr_reactions: Dict, - compounds_cache: Dict, + cache: rrCache, maxsubpaths: int, lower_flux_bound: float, upper_flux_bound: float, @@ -719,10 +741,8 @@ def __build_all_pathways( Full chemical transformations sink_molecules: List Sink chemical species IDs - rr_reactions: Dict - Reaction rules cache - compounds_cache: Dict - Compounds cache + cache: rrCache + Cache that contains reaction rules, reactions and compounds data maxsubpaths: int Number of pathways (best) kept per master pathway, after completion lower_flux_bound: float @@ -736,6 +756,17 @@ def __build_all_pathways( Set of ranked rpPathway objects """ + logger.debug(f'pathways: {pathways}') + logger.debug(f'transfos: {transfos}') + logger.debug(f'sink_molecules: {sink_molecules}') + logger.debug(f'cache: {cache}') + logger.debug(f'maxsubpaths: {maxsubpaths}') + logger.debug(f'lower_flux_bound: {lower_flux_bound}') + logger.debug(f'upper_flux_bound: {upper_flux_bound}') + + rr_reactions=cache.get('rr_reactions') + compounds_cache=cache.get('cid_strc') + res_pathways = {} nb_pathways = 0 @@ -771,8 +802,10 @@ def __build_all_pathways( ## COMPOUNDS # Template reaction compounds added_cmpds = transfo['complement'][rule_ids][tmpl_rxn_id]['added_cmpds'] + # Add missing compounds to the cache for side in added_cmpds.keys(): + # Compounds with no structure are in '_nostruct' for spe_id in added_cmpds[side].keys(): logger.debug(f'Add missing compound {spe_id}') if spe_id not in Cache.get_objects(): @@ -937,17 +970,17 @@ def __add_compounds( return _compounds for side in ['right', 'left']: # added compounds with struct - for cmpd_id, cmpd in compounds_to_add[side].items(): - if cmpd_id in _compounds[side]: - _compounds[side][cmpd_id] += cmpd['stoichio'] - else: - _compounds[side][cmpd_id] = cmpd['stoichio'] - # added compounds with no struct - for cmpd_id, cmpd in compounds_to_add[side+'_nostruct'].items(): + for cmpd_id, cmpd_sto in compounds_to_add[side].items(): if cmpd_id in _compounds[side]: - _compounds[side][cmpd_id] += cmpd['stoichio'] + _compounds[side][cmpd_id] += cmpd_sto else: - _compounds[side][cmpd_id] = cmpd['stoichio'] + _compounds[side][cmpd_id] = cmpd_sto + # # added compounds with no struct + # for cmpd_id, cmpd in compounds_to_add[side+'_nostruct'].items(): + # if cmpd_id in _compounds[side]: + # _compounds[side][cmpd_id] += cmpd['stoichio'] + # else: + # _compounds[side][cmpd_id] = cmpd['stoichio'] return _compounds diff --git a/rptools/rpextractsink/rpextractsink.py b/rptools/rpextractsink/rpextractsink.py index ebce4eb..3a72e8a 100644 --- a/rptools/rpextractsink/rpextractsink.py +++ b/rptools/rpextractsink/rpextractsink.py @@ -155,8 +155,12 @@ def get_inchi_from_crossid( logger.debug('Server is still too busy after multiple attempts. Aborting retrieval.') return '' logger.debug(f'Final page content after retries: {page.text}') - url_crossid = re_search(r'/chem_info/\w+', page.text).group() - return get_inchi_from_url(f'{url_mnx}{url_crossid}', logger) + try: + url_crossid = re_search(r'/chem_info/\w+', page.text).group() + return get_inchi_from_url(f'{url_mnx}{url_crossid}', logger) + except Exception as e: + logger.debug(f'Error retrieving InChI from URL: {e}') + return '' def get_inchi_from_url( @@ -187,6 +191,86 @@ def get_inchi_from_url( return '' +def bigg_to_mnxid(bigg_id: str, cache: rrCache, logger: Logger = getLogger(__name__)): + ''' + Convert a BiGG ID to a MetaNetX ID using the cache. + + :param bigg_id: BiGG ID of the metabolite + :type bigg_id: str + :param cache: cache object + :type cache: rrCache + :param logger: logger object, by default getLogger(__name__) + :type logger: Logger, optional + :return: InChI string or None if not found + :rtype: str or None + ''' + logger.debug(f'Converting BiGG ID {bigg_id} to MetaNetX ID...') + + # Cleanup BiGG ID if it has compartment suffix + # Detect if compartment suffix is present, i.e. '_' is the penultimate character, + # then remove it + if len(bigg_id) > 2 and bigg_id[-2] == '_': + bigg_id = bigg_id[:-2] + logger.debug(f'Removed compartment suffix from BiGG ID, new ID: {bigg_id}') + bigg_ids = [bigg_id] + # Detect if M_ prefix is present, then add to the list without M_ + if bigg_id.startswith('M_'): + bigg_ids.append(bigg_id[2:]) + logger.debug(f'Also considering BiGG ID without M_ prefix: {bigg_id[2:]}') + + mnx_id = '' + for bigg_id in bigg_ids: + if mnx_id != '': + break + logger.debug(f'Trying BiGG ID: {bigg_id}') + mnx_id = cache.get('cid_xref').get('biggM', {}).get(bigg_id, '') + if mnx_id == '': + mnx_id = cache.get('cid_xref').get('bigg.metabolite', {}).get(bigg_id, '') + logger.debug(f'MetaNetX ID from BiGG ID {bigg_id}: {mnx_id}') + + if mnx_id == '': + logger.warning(f'Could not find MetaNetX ID for BiGG ID {bigg_id}') + + return mnx_id + + +def get_inchi_from_mnxid( + mnx_id: str, + cache: rrCache, + standalone: bool = False, + logger: Logger = getLogger(__name__) +) -> str: + ''' + Get the InChI from a given MetaNetX ID using the cache. + + :param mnx_id: MetaNetX ID + :type mnx_id: str + :param cache: cache object + :type cache: rrCache + :param standalone: do not use MetaNetX, by default False + :type standalone: bool, optional + :param logger: logger object, by default getLogger(__name__) + :type logger: Logger, optional + :return: InChI + :rtype: str + ''' + logger.debug(f'Retrieving InChI from MetaNetX ID {mnx_id} using cache...') + inchi = '' + # Get InChI from cache + if mnx_id in cache.get('cid_strc'): + inchi = cache.get('cid_strc')[mnx_id]['inchi'] + else: + logger.debug(f'Could not retrieve InChI for {mnx_id} from cache') + if not standalone: + # Get InChI from MetaNetX + inchi = get_inchi_from_url( + f'https://www.metanetx.org/chem_info/{mnx_id}', + logger + ) + logger.debug(f'InChI from MetaNetX: {inchi}') + return inchi + + def genSink( cache: rrCache, input_sbml: str, @@ -236,7 +320,10 @@ def genSink( for i in rpsbml.getModel().getListOfSpecies(): if (i.getCompartment() == compartment_id and i.id not in dead_ends): + logger.debug(f'Adding species: {i.getId()} (Compartment: {i.getCompartment()})') species.append(i) + else: + logger.warning(f'Skipping species: {i.getId()} (Compartment: {i.getCompartment()})') if not species: logger.warning(f'Could not retrieve any species in the compartment: {compartment_id}') @@ -250,18 +337,15 @@ def genSink( # Find MNX ID from MIRIAM mnx_id = find_mnx_id(miriam) + # print(f"spe = {spe.getId()}, mnx_id = {mnx_id}") + # print(get_inchi_from_url( + # f'https://www.metanetx.org/chem_info/{spe.getId()}', + # logger + # )) + # exit(0) + if mnx_id: - # Get InChI from cache - if mnx_id in cache.get('cid_strc'): - inchi = cache.get('cid_strc')[mnx_id]['inchi'] - else: - logger.debug(f'Could not retrieve InChI for {mnx_id} from cache') - if not standalone: - # Get InChI from MetaNetX - inchi = get_inchi_from_url( - f'https://www.metanetx.org/chem_info/{mnx_id}', - logger - ) + inchi = get_inchi_from_mnxid(mnx_id, cache, standalone, logger) elif not standalone: # Search on MetaNetX with MIRIAM cross-references i = 0 @@ -270,14 +354,21 @@ def genSink( id = url.split('/')[-1] inchi = get_inchi_from_crossid(id, logger) i += 1 + if not inchi: + mnx_id = bigg_to_mnxid(spe.getId(), cache, logger) + if mnx_id: + inchi = get_inchi_from_mnxid(mnx_id, cache, standalone, logger) - if not inchi: - logger.warning(f'Could not retrieve any InChI for {spe.getId()}') - else: + if inchi: logger.debug(f'InChI: {inchi}') if spe.getId() in sink: logger.warning(f'MetaNetX ID {spe.getId()} already in sink') + # if mnx_id: + # sink[mnx_id] = inchi + # else: sink[spe.getId()] = inchi + else: + logger.warning(f'Could not retrieve any InChI for {spe.getId()}') return sink diff --git a/rptools/rplibs/rpPathway.py b/rptools/rplibs/rpPathway.py index 3bafd63..670557b 100644 --- a/rptools/rplibs/rpPathway.py +++ b/rptools/rplibs/rpPathway.py @@ -41,6 +41,7 @@ Pathway, Compound ) +from rr_cache import rrCache from numpy import isin from .rpSBML import rpSBML from .rpReaction import ( @@ -72,7 +73,6 @@ def __init__( self, infile: str = None, id: str = '', - cache: Cache = None, logger: Logger = getLogger(__name__) ): """Create a rpPathway object with default settings. @@ -83,8 +83,6 @@ def __init__( ID of the reaction infile: str Path to the input file (SBML) - cache: Cache, optional - Cache to store compounds once over reactions logger : Logger, optional """ self.__rpsbml = rpSBML(inFile=infile, logger=logger) @@ -92,7 +90,7 @@ def __init__( Pathway.__init__( self, id=id, - cache=cache, + # cache=cache, logger=logger ) rpObject.__init__(self, logger) @@ -730,10 +728,17 @@ def get_rpsbml(self) -> rpSBML: """Get the rpSBML object.""" return self.__rpsbml - def to_rpSBML(self) -> rpSBML: + def to_rpSBML(self, cache: rrCache = None, local_cache: dict = {}) -> rpSBML: """Convert the current rpPathway object into a rpSBML object. + Parameters + ---------- + cache: rrCache, optional + Cache to use for the conversion + local_cache: dict, optional + Local cache to use for the conversion + Returns ------- rpSBML object. @@ -751,34 +756,75 @@ def to_rpSBML(self) -> rpSBML: # lower_flux_bound ) - ## Create the groups (pathway, species, sink species) - rpsbml.create_enriched_group( - group_id='rp_pathway', - members=self.get_reactions_ids(), - infos={ - **rpObject._to_dict(self), - 'global_score': self.get_global_score() - } - ) - for group_id, group_members in self.get_species_groups().items(): - rpsbml.create_enriched_group( - group_id=f'rp_{group_id}_species', - members=group_members - ) - ## Add species to the model for specie in self.get_species(): if not isinstance(specie, rpCompound): specie = rpCompound.from_compound(specie) - rpsbml.createSpecies( - species_id=specie.get_id(), - species_name=specie.get_name(), - inchi=specie.get_inchi(), - inchikey=specie.get_inchikey(), - smiles=specie.get_smiles(), - compartment=specie.get_compartment(), - infos=self.get_specie(specie.get_id())._to_dict(full=False) - ) + # Convert into MetaNetX ID if possible + old_species_id = species_id = specie.get_id() + if not specie.get_id().startswith('MNX') and cache is not None: + # If present in the cache, use the cached value + if local_cache.get(old_species_id, '') != '': + self.get_logger().debug( + f'Species {old_species_id} already converted into {species_id} for MetaNetX annotation, using cached value.' + ) + species_id = local_cache[old_species_id] + # BiGG + elif species_id.startswith('M_'): + # Remove 'M_' prefix and compartment suffix if exist + species_id = species_id[2:] + if species_id[-2] == '_': + species_id = species_id[:-2] + # collect all keys strating with 'bigg' + bigg_keys = [k for k in cache.get('cid_xref').keys() if k.startswith('bigg')] + for bigg_key in bigg_keys: + # print(cache.get('cid_xref')[bigg_key].keys()) + if species_id in cache.get('cid_xref')[bigg_key]: + species_id = cache.get('cid_xref')[bigg_key][species_id] + break + # ChEBI + elif species_id.startswith('CHEBI:'): + # Remove 'CHEBI:' prefix + species_id = species_id[6:] + # collect all keys strating with 'chebi' + chebi_keys = [k for k in cache.get('cid_xref').keys() if k.startswith('chebi')] + for chebi_key in chebi_keys: + if species_id in cache.get('cid_xref')[chebi_key]: + species_id = cache.get('cid_xref')[chebi_key][species_id] + break + # # KEGG + # elif species_id.startswith('C'): + # # Remove 'C' prefix + # species_id = species_id[1:] + # # collect all keys strating with 'kegg' + # kegg_keys = [k for k in cache.get('cid_xref').keys() if k.startswith('kegg')] + # for kegg_key in kegg_keys: + # if species_id in cache.get('cid_xref')[kegg_key]: + # species_id = cache.get('cid_xref')[kegg_key][species_id] + # break + if old_species_id != species_id: + local_cache[old_species_id] = species_id + self.get_logger().debug( + f'Species {old_species_id} converted into {species_id} for MetaNetX annotation.' + ) + + # To not add twice the same compound under different notations, + # e.g. the transfo to complete has O=O species under M_h2o_c notation and + # has been completed by another O=O under CHEBI:15379 + if species_id not in rpsbml.getListOfSpeciesIds(): + rpsbml.createSpecies( + species_id=species_id, + species_name=specie.get_name(), + inchi=specie.get_inchi(), + inchikey=specie.get_inchikey(), + smiles=specie.get_smiles(), + compartment=specie.get_compartment(), + infos=self.get_specie(specie.get_id())._to_dict(full=False) + ) + else: + self.get_logger().debug( + f'Species {species_id} already exist in the rpSBML model, nothing added.' + ) ## Add reactions to the model for rxn in self.get_list_of_reactions(): @@ -787,11 +833,26 @@ def to_rpSBML(self) -> rpSBML: 'miriam': rxn.get_miriam() } xref = [f'http://identifiers.org/ec-code/{ec}' for ec in xref['ec-code'] if ec != ''] + # Convert reactants and products compounds IDs into cache format for MetaNetX annotation + self.get_logger().debug(f'rxn {rxn.get_id()} before conversion: reactants {rxn.get_reactants()}, products {rxn.get_products()}') + reactants = dict(rxn.get_reactants()) + products = dict(rxn.get_products()) + # Apply local cache transformation to both reactants and products + for species_dict in (reactants, products): + for id in list(species_dict.keys()): + if id in local_cache: + cached_id = local_cache[id] + # If the cached ID is not already in the species dict, replace the ID by the cached ID + if cached_id not in species_dict: + species_dict[cached_id] = species_dict.pop(id) + else: # If the cached ID is already in the species dict, sum the stoichiometry of the two IDs and replace the ID by the cached ID + species_dict[cached_id] += species_dict.pop(id) + self.get_logger().debug(f'rxn {rxn.get_id()} after conversion: reactants {reactants}, products {products}') # Add the reaction in the model rpsbml.createReaction( id=rxn.get_id(), - reactants=rxn.get_reactants(), - products=rxn.get_products(), + reactants=reactants, + products=products, smiles=rxn.get_smiles(), fbc_upper=rxn.get_fbc_upper(), fbc_lower=rxn.get_fbc_lower(), @@ -801,6 +862,22 @@ def to_rpSBML(self) -> rpSBML: infos=rxn._to_dict(full=False) ) + ## Create the groups (pathway, species, sink species) + rpsbml.create_enriched_group( + group_id='rp_pathway', + members=self.get_reactions_ids(), + infos={ + **rpObject._to_dict(self), + 'global_score': self.get_global_score() + } + ) + for group_id, group_members in self.get_species_groups().items(): + _members = [local_cache.get(spe_id, spe_id) for spe_id in group_members] + rpsbml.create_enriched_group( + group_id=f'rp_{group_id}_species', + members=_members + ) + return rpsbml @staticmethod diff --git a/rptools/rplibs/rpSBML.py b/rptools/rplibs/rpSBML.py index a4fc1c7..85773de 100644 --- a/rptools/rplibs/rpSBML.py +++ b/rptools/rplibs/rpSBML.py @@ -1209,20 +1209,24 @@ def copySpecies( source_member, f'Retrieving the source species: {source_spe_id}' ) - rpSBML.checklibSBML( - target_member.getAnnotation( - ).getChild( - 'RDF' - ).addChild( - source_member.getAnnotation( + if target_member.getAnnotation() is not None: + self.logger.debug(f'Found annotation for target species: {corr_species[source_spe_id]}') + rpSBML.checklibSBML( + target_member.getAnnotation( ).getChild( 'RDF' - ).getChild( - 'BRSynth' - ) - ), - 'Replacing the annotations' - ) + ).addChild( + source_member.getAnnotation( + ).getChild( + 'RDF' + ).getChild( + 'BRSynth' + ) + ), + 'Replacing the annotations' + ) + else: + self.logger.debug(f'No annotation found for target species: {corr_species[source_spe_id]}') source_member.setId(corr_species[source_spe_id]) # print('-------------------------------------') # print(target_member.toXMLNode().toXMLString()) @@ -1758,6 +1762,10 @@ def getListOfGroups(self) -> List[libsbml.Group]: return list(self.getPlugin('groups').getListOfGroups()) + def getListOfSpeciesIds(self) -> List[str]: + + return [spe.getId() for spe in self.getModel().getListOfSpecies()] + def getGroup( self, group_id: str @@ -4080,10 +4088,40 @@ def getReactionConstraints( bounds: Tuple[float, float] Tuple of lower and upper flux bounds """ + self.logger.debug(f'Getting reaction constraints for reaction: {rxn_id}') reac_fbc = self.getModel().getReaction(rxn_id).getPlugin('fbc') - old_lower_value = self.getModel().getParameter(reac_fbc.getLowerFluxBound()).value - old_upper_value = self.getModel().getParameter(reac_fbc.getUpperFluxBound()).value - return old_lower_value, old_upper_value + self.logger.debug(f'Getting reaction plugin: {reac_fbc}') + self.logger.debug(f'Getting lower flux bound parameter: {reac_fbc.getLowerFluxBound()}') + self.logger.debug(f'Getting upper flux bound parameter: {reac_fbc.getUpperFluxBound()}') + + lb_id = reac_fbc.getLowerFluxBound() + ub_id = reac_fbc.getUpperFluxBound() + + # Default COBRA-style bounds + DEFAULT_LB = 0.0 + DEFAULT_UB = 1000.0 + + # Lower bound + if lb_id is None: + old_lower_value = DEFAULT_LB + else: + lb_param = self.getModel().getParameter(lb_id) + if lb_param is None: + old_lower_value = DEFAULT_LB + else: + old_lower_value = lb_param.value + + # Upper bound + if ub_id is None: + old_upper_value = DEFAULT_UB + else: + ub_param = self.getModel().getParameter(ub_id) + if ub_param is None: + old_upper_value = DEFAULT_UB + else: + old_upper_value = ub_param.value + + return old_upper_value, old_lower_value def setReactionConstraints( self, @@ -4472,6 +4510,10 @@ def createReaction( meta_id=meta_id ) for key, value in infos.items(): + # Rewriting Reaction Rules w/o ':' since it used in XML tags + # If value is a list of string, replace ':' by '_' in each string + if isinstance(value, list): + value = [v.replace(':', '_') for v in value] self.updateBRSynth( sbase_obj=reac, annot_header=key, @@ -4545,6 +4587,17 @@ def createSpecies( :rtype: None :return: None """ + self.logger.debug(f'Species ID: {species_id}') + self.logger.debug(f'Species Name: {species_name}') + self.logger.debug(f'Chemical Xref: {chemXref}') + self.logger.debug(f'InChI: {inchi}') + self.logger.debug(f'InChIKey: {inchikey}') + self.logger.debug(f'SMILES: {smiles}') + self.logger.debug(f'Compartment: {compartment}') + self.logger.debug(f'Meta ID: {meta_id}') + self.logger.debug(f'Additional Infos: {infos}') + self.logger.debug(f'Is Boundary: {is_boundary}') + spe = self.getModel().createSpecies() rpSBML.checklibSBML(spe, 'create species') @@ -4782,9 +4835,12 @@ def genericModel( # compartments for comp_id, comp in compartments.items(): comp_lst = [] - for db, comps in comp['annot'].items(): - for comp_name in comps: - comp_lst += [f"http://identifiers.org/{self.miriam_header['compartment'][db]}{comp_name}"] + if type(comp['annot']) == dict: + for db, comps in comp['annot'].items(): + for comp_name in comps: + comp_lst += [f"http://identifiers.org/{self.miriam_header['compartment'][db]}{comp_name}"] + else: + comp_lst = comp['annot'] self.createCompartment(1, comp_id, comp['name'], comp_lst) # self.createCompartment(1, comp_id, comp['name'], comp['annot']) diff --git a/rptools/rpscore/rpscore.py b/rptools/rpscore/rpscore.py index c0afae3..52a475d 100644 --- a/rptools/rpscore/rpscore.py +++ b/rptools/rpscore/rpscore.py @@ -203,18 +203,24 @@ def features_encoding (df, flag): sub_smiles = rxn_smiles_list[0] sub_m= Chem.MolFromSmiles(sub_smiles) #print(m) - sub_fp = AllChem.GetMorganFingerprintAsBitVect(sub_m, 2, nBits = 2048) - sub_arr = np.array([]) - DataStructs.ConvertToNumpyArray(sub_fp, sub_arr) - sub_fp= sub_arr.reshape(1,-1) + if sub_m is not None: + sub_fp = AllChem.GetMorganFingerprintAsBitVect(sub_m, 2, nBits = 2048) + sub_arr = np.array([]) + DataStructs.ConvertToNumpyArray(sub_fp, sub_arr) + sub_fp= sub_arr.reshape(1,-1) + else: + sub_fp = np.zeros((1, 2048)) pro_smiles = rxn_smiles_list[1] pro_m= Chem.MolFromSmiles(pro_smiles) #print(m) - pro_fp = AllChem.GetMorganFingerprintAsBitVect(pro_m, 2, nBits = 2048) - pro_arr = np.zeros((1,)) - DataStructs.ConvertToNumpyArray(pro_fp, pro_arr) - pro_fp= pro_arr.reshape(1,-1) + if pro_m is not None: + pro_fp = AllChem.GetMorganFingerprintAsBitVect(pro_m, 2, nBits = 2048) + pro_arr = np.zeros((1,)) + DataStructs.ConvertToNumpyArray(pro_fp, pro_arr) + pro_fp= pro_arr.reshape(1,-1) + else: + pro_fp = np.zeros((1, 2048)) rxn_fp = np.concatenate([sub_fp , pro_fp]).reshape(1, -1) elif len(rxn_smiles_list) < 2: diff --git a/rptools/rpthermo/rpthermo.py b/rptools/rpthermo/rpthermo.py index ecdd88b..a0f79f4 100644 --- a/rptools/rpthermo/rpthermo.py +++ b/rptools/rpthermo/rpthermo.py @@ -137,6 +137,8 @@ def runThermo( ) # Else, take search values from rpCompound else: + # print(spe.get_name(), spe.get_id(), spe.get_inchikey(), spe.get_inchi(), spe.get_smiles()) + # exit() cc_species[spe.get_id()] = search_equilibrator_compound( cc=cc, id=spe.get_id(), @@ -203,15 +205,24 @@ def runThermo( logger=logger ) - ## THERMO + ## PATHWAY print_title( txt='Computing thermodynamics (eQuilibrator)...', logger=logger, waiting=True ) + # Compute thermo for the net reaction of the pathway + # Remove reactions from the net reaction with no thermo info + # to avoid errors in eQuilibrator + _reactions = [] + for rxn in reactions: + if results['reactions'][rxn.get_id()]['dG0_prime']['value'] == 'NaN': + logger.warning(f"Reaction {rxn.get_id()} has been removed from the net reaction as it has no thermo info") + else: + _reactions.append(rxn) results['net_reaction'] = eQuilibrator( - species_stoichio=Reaction.sum_stoichio(reactions), + species_stoichio=Reaction.sum_stoichio(_reactions), species_ids=species_cc_ids, cc=cc, logger=logger @@ -282,6 +293,8 @@ def copy_data( compound = cc.get_compound(val) # If compound is found in eQuilibrator, then... if compound is not None: + # print(f'Compound {val} found in eQuilibrator with {key} as key') + # exit() # ...copy initial data into result compound _compound = copy_data(compound, data) return _compound diff --git a/rptools/rpviz/Viewer.py b/rptools/rpviz/Viewer.py index fc461a2..512cff7 100644 --- a/rptools/rpviz/Viewer.py +++ b/rptools/rpviz/Viewer.py @@ -8,7 +8,7 @@ import pandas -import distutils.dir_util +from shutil import copytree from pathlib import Path from bs4 import BeautifulSoup @@ -33,11 +33,11 @@ def copy_templates(self): :return: None """ - distutils.dir_util.copy_tree( - str(self.template_folder), - str(self.out_folder) - ) - + copytree( + self.template_folder, + self.out_folder, + dirs_exist_ok=True + ) def write_json_deprecated(self, dict_paths, scores): """ Write the html file, according the html template. diff --git a/rptools/rpviz/__main__.py b/rptools/rpviz/__main__.py index 44b1a69..1ea62e1 100644 --- a/rptools/rpviz/__main__.py +++ b/rptools/rpviz/__main__.py @@ -77,6 +77,14 @@ def __build_arg_parser(prog='python -m rpviz.cli'): " containing all dependencies." ) ) + parser.add_argument( + "--hide-panels", + action="store_true", + help=( + "If set, the panels will be hidden by default in the visualiser. " + "The user can then choose to show them or not." + ) + ) return parser @@ -167,7 +175,7 @@ def __run(args): # Write single HTML if requested if args.autonomous_html is not None: - str_html = get_autonomous_html(args.output_folder) + str_html = get_autonomous_html(args.output_folder, hide_side_panels=args.hide_panels) with open(args.autonomous_html, 'wb') as ofh: ofh.write(str_html) diff --git a/rptools/rpviz/templates/css/viewer.css b/rptools/rpviz/templates/css/viewer.css index 9523254..92865c1 100644 --- a/rptools/rpviz/templates/css/viewer.css +++ b/rptools/rpviz/templates/css/viewer.css @@ -5,8 +5,11 @@ __license__ = 'MIT' /* Basic layout ***************************************************************/ -body { +html, body { margin: 0px; + padding: 0px; + height: 100%; + width: 100%; } #base { @@ -16,7 +19,7 @@ body { } #viewer { - height: 80%; + height: 100%; box-shadow: 0 0 5px 0px black; } diff --git a/rptools/rpviz/templates/js/viewer.js b/rptools/rpviz/templates/js/viewer.js index 48a8ed7..8af5414 100644 --- a/rptools/rpviz/templates/js/viewer.js +++ b/rptools/rpviz/templates/js/viewer.js @@ -831,7 +831,7 @@ $(function(){ // Playing with zoom to get the best fit cy.minZoom(1e-50); cy.on('layoutstop', function(e){ - cy.minZoom(cy.zoom()*0.9); // 0.9 to enable the user dezoom a little + cy.minZoom(1e-50); // Allow full zoom-out range }); // Layout let layout = element_collection.layout({ diff --git a/rptools/rpviz/utils.py b/rptools/rpviz/utils.py index fed0e5e..d2f9489 100644 --- a/rptools/rpviz/utils.py +++ b/rptools/rpviz/utils.py @@ -786,10 +786,11 @@ def annotate_chemical_svg(network: Dict) -> Dict: return network -def get_autonomous_html(ifolder): +def get_autonomous_html(ifolder, hide_side_panels=False): """Merge all needed file into a single HTML :param ifolder: folder containing the files to be merged + :param hide_side_panels: bool, whether to hide side panels by default :return html_str: string, the HTML """ # find and open the index file @@ -820,6 +821,12 @@ def get_autonomous_html(ifolder): ori = b'' rep = b'' html_string = html_string.replace(ori, rep) + + # Add CSS to hide side panels if requested + if hide_side_panels: + hide_panels_js = b'' + html_string = html_string.replace(b'', hide_panels_js + b'') + # replace the network net_string = open(ifolder + '/network.json', 'rb').read() ori = b'src="' + 'network.json'.encode() + b'">' diff --git a/tests/rpcompletion/test_rpcompletion.py b/tests/rpcompletion/test_rpcompletion.py index 839f659..bacc22b 100644 --- a/tests/rpcompletion/test_rpcompletion.py +++ b/tests/rpcompletion/test_rpcompletion.py @@ -99,6 +99,11 @@ def test_rp_completion(self): f'rp_{pathway_id}.xml' ) ref_pathway = rpPathway(ref_file) + print() + print(pathways[f'rp_{pathway_id}']) + print() + print(ref_pathway) + print() self.assertEqual(pathways[f'rp_{pathway_id}'], ref_pathway) def test_rp_completion_wo_cofactors(self): diff --git a/tests/rpextractsink/data/output_sink_woDE.csv b/tests/rpextractsink/data/output_sink_woDE.csv index d9a0ce4..bf9fa5d 100644 --- a/tests/rpextractsink/data/output_sink_woDE.csv +++ b/tests/rpextractsink/data/output_sink_woDE.csv @@ -61,7 +61,7 @@ "MNXM360__64__MNXC3","InChI=1S/C9H16N3O13P3/c10-7-1-2-12(9(14)11-7)8-3-5(13)6(23-8)4-22-27(18,19)25-28(20,21)24-26(15,16)17/h1-2,5-6,8,13H,3-4H2,(H,18,19)(H,20,21)(H2,10,11,14)(H2,15,16,17)" "MNXM411__64__MNXC3","InChI=1S/C9H15N3O10P2/c10-7-1-2-12(9(14)11-7)8-3-5(13)6(21-8)4-20-24(18,19)22-23(15,16)17/h1-2,5-6,8,13H,3-4H2,(H,18,19)(H2,10,11,14)(H2,15,16,17)" "MNXM529__64__MNXC3","InChI=1S/C7H14N2O4/c8-4(6(10)11)2-1-3-5(9)7(12)13/h4-5H,1-3,8-9H2,(H,10,11)(H,12,13)" -"MNXM1017__64__MNXC3","InChI=1S/C4H8O/c1-2-3-4-5/h4H,2-3H2,1H3" +"MNXM458__64__MNXC3","InChI=1S/C4H8O2/c1-2-3-4(5)6/h2-3H2,1H3,(H,5,6)" "MNXM199__64__MNXC3","InChI=1S/C5H11NO2/c1-3(2)4(6)5(7)8/h3-4H,6H2,1-2H3,(H,7,8)" "MNXM20__64__MNXC3","InChI=1S/C5H6O5/c6-3(5(9)10)1-2-4(7)8/h1-2H2,(H,7,8)(H,9,10)" "MNXM238__64__MNXC3","InChI=1S/C5H8O3/c1-3(2)4(6)5(7)8/h3H,1-2H3,(H,7,8)" @@ -239,7 +239,6 @@ "MNXM63489__64__MNXC3","InChI=1S/C34H54N6O19/c1-13(29(49)39-18(8-9-23(45)46)31(51)40-19(33(54)55)7-5-6-17(35)32(52)53)36-30(50)14(2)57-27-20(37-15(3)43)12-56-22(11-42)28(27)59-34-24(38-16(4)44)26(48)25(47)21(10-41)58-34/h12-14,17-19,21-22,24-28,34,41-42,47-48H,5-11,35H2,1-4H3,(H,36,50)(H,37,43)(H,38,44)(H,39,49)(H,40,51)(H,45,46)(H,52,53)(H,54,55)" "MNXM143__64__MNXC3","InChI=1S/C8H15NO6/c1-3(11)9-5-7(13)6(12)4(2-10)15-8(5)14/h4-8,10,12-14H,2H2,1H3,(H,9,11)" "MNXM32389__64__MNXC3","InChI=1S/C26H43N5O14/c1-11(28-23(38)12(2)45-21-17(29-13(3)33)10-44-18(9-32)20(21)36)22(37)30-15(7-8-19(34)35)24(39)31-16(26(42)43)6-4-5-14(27)25(40)41/h11-12,14-18,20-21,32,36H,4-10,27H2,1-3H3,(H,28,38)(H,29,33)(H,30,37)(H,31,39)(H,34,35)(H,40,41)(H,42,43)" -"MNXM276__64__MNXC3","InChI=1S/C5H8O3S/c1-9-3-2-4(6)5(7)8/h2-3H2,1H3,(H,7,8)" "MNXM1219__64__MNXC3","InChI=1S/C7H13O10P/c8-3(1-4(9)7(12)13)6(11)5(10)2-17-18(14,15)16/h3,5-6,8,10-11H,1-2H2,(H,12,13)(H2,14,15,16)" "MNXM140__64__MNXC3","InChI=1S/C6H13NO2/c1-4(2)3-5(7)6(8)9/h4-5H,3,7H2,1-2H3,(H,8,9)" "MNXM507__64__MNXC3","InChI=1S/C9H14N3O9P/c10-7-4(9(15)16)11-2-12(7)8-6(14)5(13)3(21-8)1-20-22(17,18)19/h2-3,5-6,8,13-14H,1,10H2,(H,15,16)(H2,17,18,19)" @@ -260,7 +259,7 @@ "MNXM51279__64__MNXC3","InChI=1S/C39H77O8P/c1-3-5-7-9-11-13-15-17-19-21-23-25-27-29-31-33-38(40)45-35-37(36-46-48(42,43)44)47-39(41)34-32-30-28-26-24-22-20-18-16-14-12-10-8-6-4-2/h37H,3-36H2,1-2H3,(H2,42,43,44)" "MNXM63__64__MNXC3","InChI=1S/C9H16N3O14P3/c10-5-1-2-12(9(15)11-5)8-7(14)6(13)4(24-8)3-23-28(19,20)26-29(21,22)25-27(16,17)18/h1-2,4,6-8,13-14H,3H2,(H,19,20)(H,21,22)(H2,10,11,15)(H2,16,17,18)" "MNXM45856__64__MNXC3","InChI=1S/C48H89N3O15P2/c1-3-5-7-9-11-13-15-17-19-21-23-25-27-29-31-33-43(52)61-37-40(64-44(53)34-32-30-28-26-24-22-20-18-16-14-12-10-8-6-4-2)38-62-67(57,58)66-68(59,60)63-39-41-45(54)46(55)47(65-41)51-36-35-42(49)50-48(51)56/h35-36,40-41,45-47,54-55H,3-34,37-39H2,1-2H3,(H,57,58)(H,59,60)(H2,49,50,56)" -"MNXM722742__64__MNXC3","InChI=1S/C6H12O4S/c1-11-2-3-4(7)5(8)6(9)10-3/h3-9H,2H2,1H3" +"MNXM407__64__MNXC3","InChI=1S/C6H13O7PS/c1-15-2-3-4(7)5(8)6(12-3)13-14(9,10)11/h3-8H,2H2,1H3,(H2,9,10,11)" "MNXM465__64__MNXC3","InChI=1S/C5H8N2O5/c6-5(12)7-2(4(10)11)1-3(8)9/h2H,1H2,(H,8,9)(H,10,11)(H3,6,7,12)" "MNXM252__64__MNXC3","InChI=1S/C5H6N2O4/c8-3-1-2(4(9)10)6-5(11)7-3/h2H,1H2,(H,9,10)(H2,6,7,8,11)" "MNXM1446__64__MNXC3","InChI=1S/C35H55N7O26P2/c1-13(28(50)40-18(33(56)57)7-8-21(45)39-17(32(54)55)6-4-5-16(36)31(52)53)37-29(51)14(2)64-27-23(38-15(3)44)34(66-19(11-43)25(27)48)67-70(61,62)68-69(59,60)63-12-20-24(47)26(49)30(65-20)42-10-9-22(46)41-35(42)58/h9-10,13-14,16-20,23-27,30,34,43,47-49H,4-8,11-12,36H2,1-3H3,(H,37,51)(H,38,44)(H,39,45)(H,40,50)(H,52,53)(H,54,55)(H,56,57)(H,59,60)(H,61,62)(H,41,46,58)" @@ -319,6 +318,7 @@ "MNXM288__64__MNXC3","InChI=1S/C9H12N2O6/c12-3-4-6(14)7(15)8(17-4)11-2-1-5(13)10-9(11)16/h1-2,4,6-8,12,14-15H,3H2,(H,10,13,16)" "MNXM114070__64__MNXC3","InChI=1S/C4H9NO2S/c5-3(1-2-8)4(6)7/h3,8H,1-2,5H2,(H,6,7)" "MNXM318__64__MNXC3","InChI=1S/C20H25N7O6/c1-27-12(9-23-16-15(27)18(31)26-20(21)25-16)8-22-11-4-2-10(3-5-11)17(30)24-13(19(32)33)6-7-14(28)29/h2-5,12-13,22H,6-9H2,1H3,(H,24,30)(H,28,29)(H,32,33)(H4,21,23,25,26,31)" +"MNXM522__64__MNXC3","InChI=1S/C6H13O7PS/c1-15-3-5(8)6(9)4(7)2-13-14(10,11)12/h5-6,8-9H,2-3H2,1H3,(H2,10,11,12)" "MNXM162358__64__MNXC3","InChI=1S/C6H11O6PS/c1-14-3-2-5(7)6(8)4-12-13(9,10)11/h2-4H2,1H3,(H2,9,10,11)" "MNXM1324__64__MNXC3","InChI=1S/C6H14O12P2/c7-4-3(1-16-19(10,11)12)18-6(9,5(4)8)2-17-20(13,14)15/h3-5,7-9H,1-2H2,(H2,10,11,12)(H2,13,14,15)" "MNXM454__64__MNXC3","InChI=1S/C8H12NO6P/c1-5-8(11)7(3-10)6(2-9-5)4-15-16(12,13)14/h2,10-11H,3-4H2,1H3,(H2,12,13,14)" @@ -408,6 +408,7 @@ "MNXM1051__64__MNXC3","InChI=1S/C11H19N3O7S/c12-6(11(20)21)1-2-8(16)14-7(4-22-5-15)10(19)13-3-9(17)18/h6-7,15H,1-5,12H2,(H,13,19)(H,14,16)(H,17,18)(H,20,21)" "MNXM401__64__MNXC3","InChI=1S/C10H13N5O5/c11-10-13-7-4(8(19)14-10)12-2-15(7)9-6(18)5(17)3(1-16)20-9/h2-3,5-6,9,16-18H,1H2,(H3,11,13,14,19)" "MNXM606__64__MNXC3","InChI=1S/C5H12O8P2/c1-5(4-6)2-3-12-15(10,11)13-14(7,8)9/h2,6H,3-4H2,1H3,(H,10,11)(H2,7,8,9)" +"MNXM2212__64__MNXC3","InChI=1S/C2H5NO/c3-1-2-4/h2H,1,3H2" "MNXM452__64__MNXC3","InChI=1S/C9H15N2O14P3/c12-5-3-8(11-2-1-7(13)10-9(11)14)23-6(5)4-22-27(18,19)25-28(20,21)24-26(15,16)17/h1-2,5-6,8,12H,3-4H2,(H,18,19)(H,20,21)(H,10,13,14)(H2,15,16,17)" "MNXM363__64__MNXC3","InChI=1S/C33H58N7O17P3S/c1-4-5-6-7-8-9-10-11-12-13-24(42)61-17-16-35-23(41)14-15-36-31(45)28(44)33(2,3)19-54-60(51,52)57-59(49,50)53-18-22-27(56-58(46,47)48)26(43)32(55-22)40-21-39-25-29(34)37-20-38-30(25)40/h20-22,26-28,32,43-44H,4-19H2,1-3H3,(H,35,41)(H,36,45)(H,49,50)(H,51,52)(H2,34,37,38)(H2,46,47,48)" "MNXM642__64__MNXC3","InChI=1S/C33H56N7O17P3S/c1-4-5-6-7-8-9-10-11-12-13-24(42)61-17-16-35-23(41)14-15-36-31(45)28(44)33(2,3)19-54-60(51,52)57-59(49,50)53-18-22-27(56-58(46,47)48)26(43)32(55-22)40-21-39-25-29(34)37-20-38-30(25)40/h12-13,20-22,26-28,32,43-44H,4-11,14-19H2,1-3H3,(H,35,41)(H,36,45)(H,49,50)(H,51,52)(H2,34,37,38)(H2,46,47,48)" @@ -416,6 +417,8 @@ "MNXM4708__64__MNXC3","InChI=1S/C8H16NO9P/c1-4(11)9-5(2-10)7(13)8(14)6(12)3-18-19(15,16)17/h2,5-8,12-14H,3H2,1H3,(H,9,11)(H2,15,16,17)" "MNXM1860__64__MNXC3","InChI=1S/C10H15N4O15P3/c15-5-3(1-26-31(22,23)29-32(24,25)28-30(19,20)21)27-9(6(5)16)14-2-11-4-7(14)12-10(18)13-8(4)17/h2-3,5-6,9,15-16H,1H2,(H,22,23)(H,24,25)(H2,19,20,21)(H2,12,13,17,18)" "MNXM4133__64__MNXC3","InChI=1S/C10H14N4O12P2/c15-5-3(1-24-28(22,23)26-27(19,20)21)25-9(6(5)16)14-2-11-4-7(14)12-10(18)13-8(4)17/h2-3,5-6,9,15-16H,1H2,(H,22,23)(H2,19,20,21)(H2,12,13,17,18)" +"MNXM54130__64__MNXC3","InChI=1S/C83H137NO27P2/c1-51(2)26-16-27-52(3)28-17-29-53(4)30-18-31-54(5)32-19-33-55(6)34-20-35-56(7)36-21-37-57(8)38-22-39-58(9)40-23-41-59(10)42-24-43-60(11)44-25-45-61(12)46-47-102-112(97,98)111-113(99,100)110-80-68(84-63(14)87)77(71(92)66(49-86)105-80)108-83-79(104-64(15)88)78(69(90)62(13)103-83)109-82-74(95)72(93)70(91)67(106-82)50-101-81-75(96)73(94)76(107-81)65(89)48-85/h26,28,30,32,34,36,38,40,42,44,46,62,65-83,85-86,89-96H,16-25,27,29,31,33,35,37,39,41,43,45,47-50H2,1-15H3,(H,84,87)(H,97,98)(H,99,100)" +"MNXM65211__64__MNXC3","InChI=1S/C89H147NO32P2/c1-52(2)26-16-27-53(3)28-17-29-54(4)30-18-31-55(5)32-19-33-56(6)34-20-35-57(7)36-21-37-58(8)38-22-39-59(9)40-23-41-60(10)42-24-43-61(11)44-25-45-62(12)46-47-112-123(106,107)122-124(108,109)121-85-70(90-64(14)93)82(74(99)69(116-85)51-110-86-78(103)75(100)72(97)67(49-92)115-86)119-89-84(114-65(15)94)83(71(96)63(13)113-89)120-88-79(104)76(101)73(98)68(117-88)50-111-87-80(105)77(102)81(118-87)66(95)48-91/h26,28,30,32,34,36,38,40,42,44,46,63,66-89,91-92,95-105H,16-25,27,29,31,33,35,37,39,41,43,45,47-51H2,1-15H3,(H,90,93)(H,106,107)(H,108,109)" "MNXM361__64__MNXC3","InChI=1S/C4H7NO3/c5-3(1-2-6)4(7)8/h2-3H,1,5H2,(H,7,8)" "MNXM353__64__MNXC3","InChI=1S/C4H9NO3/c5-3(1-2-6)4(7)8/h3,6H,1-2,5H2,(H,7,8)" "MNXM351__64__MNXC3","InChI=1S/C34H40N4O4/c1-7-21-17(3)25-13-26-19(5)23(9-11-33(39)40)31(37-26)16-32-24(10-12-34(41)42)20(6)28(38-32)15-30-22(8-2)18(4)27(36-30)14-29(21)35-25/h7-8,35-38H,1-2,9-16H2,3-6H3,(H,39,40)(H,41,42)" @@ -424,6 +427,7 @@ "MNXM612__64__MNXC3","InChI=1S/C4H6N4O3/c5-3(10)6-1-2(9)8-4(11)7-1/h1H,(H3,5,6,10)(H2,7,8,9,11)" "MNXM647__64__MNXC3","InChI=1S/C10H13N5O4/c11-10-13-8-7(9(18)14-10)12-3-15(8)6-1-4(17)5(2-16)19-6/h3-6,16-17H,1-2H2,(H3,11,13,14,18)" "MNXM572__64__MNXC3","InChI=1S/C9H14N2O11P2/c12-5-3-8(11-2-1-7(13)10-9(11)14)21-6(5)4-20-24(18,19)22-23(15,16)17/h1-2,5-6,8,12H,3-4H2,(H,18,19)(H,10,13,14)(H2,15,16,17)" +"MNXM856__64__MNXC3","InChI=1S/C48H74N11O11P/c1-23(70-71(67,68)69)22-55-38(66)16-17-45(6)29(18-35(52)63)43-48(9)47(8,21-37(54)65)28(12-15-34(51)62)40(59-48)25(3)42-46(7,20-36(53)64)26(10-13-32(49)60)30(56-42)19-31-44(4,5)27(11-14-33(50)61)39(57-31)24(2)41(45)58-43/h19,23,26-29,43,59H,10-18,20-22H2,1-9H3,(H2,49,60)(H2,50,61)(H2,51,62)(H2,52,63)(H2,53,64)(H2,54,65)(H,55,66)(H2,67,68,69)" "MNXM1105__64__MNXC3","InChI=1S/C58H86N16O18P2/c1-25(91-94(87,88)92-93(85,86)89-23-33-45(82)46(83)52(90-33)74-24-67-44-50(74)71-53(65)72-51(44)84)22-66-41(81)16-17-55(6)31(18-38(62)78)49-58(9)57(8,21-40(64)80)30(12-15-37(61)77)43(73-58)27(3)48-56(7,20-39(63)79)28(10-13-35(59)75)32(68-48)19-34-54(4,5)29(11-14-36(60)76)42(69-34)26(2)47(55)70-49/h19,24-25,28-31,33,45-46,49,52,82-83H,10-18,20-23H2,1-9H3,(H19,59,60,61,62,63,64,65,66,68,69,70,71,72,73,75,76,77,78,79,80,81,84,85,86,87,88)/p+1" "MNXM114097__64__MNXC3","InChI=1S/C5H10O4/c1-5(2,9)3(6)4(7)8/h3,6,9H,1-2H3,(H,7,8)" "MNXM392__64__MNXC3","InChI=1S/C3H4O4/c4-1-2(5)3(6)7/h4H,1H2,(H,6,7)" @@ -457,14 +461,12 @@ "MNXM553__64__MNXC3","InChI=1S/C27H46N7O17P3S/c1-4-5-6-7-18(36)55-11-10-29-17(35)8-9-30-25(39)22(38)27(2,3)13-48-54(45,46)51-53(43,44)47-12-16-21(50-52(40,41)42)20(37)26(49-16)34-15-33-19-23(28)31-14-32-24(19)34/h14-16,20-22,26,37-38H,4-13H2,1-3H3,(H,29,35)(H,30,39)(H,43,44)(H,45,46)(H2,28,31,32)(H2,40,41,42)" "MNXM1281__64__MNXC3","InChI=1S/C6H11N3O/c7-5(3-10)1-6-2-8-4-9-6/h2,4-5,10H,1,3,7H2,(H,8,9)" "MNXM1458__64__MNXC3","InChI=1S/C10H11NO5/c1-5(9(12)13)16-8-4-6(10(14)15)2-3-7(8)11/h2-4,7-8H,1,11H2,(H,12,13)(H,14,15)" -"MNXM65126__64__MNXC3","InChI=1S/C71H117NO17P2/c1-49(2)26-16-27-50(3)28-17-29-51(4)30-18-31-52(5)32-19-33-53(6)34-20-35-54(7)36-21-37-55(8)38-22-39-56(9)40-23-41-57(10)42-24-43-58(11)44-25-45-59(12)46-47-83-90(79,80)89-91(81,82)88-70-64(72-61(14)74)68(66(77)63(48-73)86-70)87-71-69(85-62(15)75)67(78)65(76)60(13)84-71/h26,28,30,32,34,36,38,40,42,44,46,60,63-71,73,76-78H,16-25,27,29,31,33,35,37,39,41,43,45,47-48H2,1-15H3,(H,72,74)(H,79,80)(H,81,82)" "MNXM32950__64__MNXC3","InChI=1S/C21H43O7P/c1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-21(23)27-18-20(22)19-28-29(24,25)26/h20,22H,2-19H2,1H3,(H2,24,25,26)" "MNXM2264__64__MNXC3","InChI=1S/C87H143N7O23P2/c1-56(2)29-18-30-57(3)31-19-32-58(4)33-20-34-59(5)35-21-36-60(6)37-22-38-61(7)39-23-40-62(8)41-24-42-63(9)43-25-44-64(10)45-26-46-65(11)47-27-48-66(12)53-54-113-118(109,110)117-119(111,112)116-87-77(92-71(17)96)79(78(98)75(55-95)115-87)114-70(16)82(101)89-68(14)81(100)94-74(86(107)108)51-52-76(97)93-73(50-28-49-72(88)85(105)106)83(102)90-67(13)80(99)91-69(15)84(103)104/h29,31,33,35,37,39,41,43,45,47,53,67-70,72-75,77-79,87,95,98H,18-28,30,32,34,36,38,40,42,44,46,48-52,54-55,88H2,1-17H3,(H,89,101)(H,90,102)(H,91,99)(H,92,96)(H,93,97)(H,94,100)(H,103,104)(H,105,106)(H,107,108)(H,109,110)(H,111,112)" "MNXM47__64__MNXC3","InChI=1S/C17H27N3O17P2/c1-6(22)18-10-13(26)11(24)7(4-21)35-16(10)36-39(31,32)37-38(29,30)33-5-8-12(25)14(27)15(34-8)20-3-2-9(23)19-17(20)28/h2-3,7-8,10-16,21,24-27H,4-5H2,1H3,(H,18,22)(H,29,30)(H,31,32)(H,19,23,28)" "MNXM722781__64__MNXC3","InChI=1S/C95H156N8O28P2/c1-58(2)30-19-31-59(3)32-20-33-60(4)34-21-35-61(5)36-22-37-62(6)38-23-39-63(7)40-24-41-64(8)42-25-43-65(9)44-26-45-66(10)46-27-47-67(11)48-28-49-68(12)54-55-125-132(121,122)131-133(123,124)130-95-82(101-74(18)107)86(85(79(57-105)128-95)129-94-81(100-73(17)106)84(110)83(109)78(56-104)127-94)126-72(16)89(113)97-70(14)88(112)103-77(93(119)120)52-53-80(108)102-76(51-29-50-75(96)92(117)118)90(114)98-69(13)87(111)99-71(15)91(115)116/h30,32,34,36,38,40,42,44,46,48,54,69-72,75-79,81-86,94-95,104-105,109-110H,19-29,31,33,35,37,39,41,43,45,47,49-53,55-57,96H2,1-18H3,(H,97,113)(H,98,114)(H,99,111)(H,100,106)(H,101,107)(H,102,108)(H,103,112)(H,115,116)(H,117,118)(H,119,120)(H,121,122)(H,123,124)" "MNXM31746__64__MNXC3","InChI=1S/C39H70N7O18P3S/c1-4-5-6-7-8-9-10-11-12-13-14-15-16-17-27(47)22-30(49)68-21-20-41-29(48)18-19-42-37(52)34(51)39(2,3)24-61-67(58,59)64-66(56,57)60-23-28-33(63-65(53,54)55)32(50)38(62-28)46-26-45-31-35(40)43-25-44-36(31)46/h25-28,32-34,38,47,50-51H,4-24H2,1-3H3,(H,41,48)(H,42,52)(H,56,57)(H,58,59)(H2,40,43,44)(H2,53,54,55)" "MNXM513__64__MNXC3","InChI=1S/C39H68N7O18P3S/c1-4-5-6-7-8-9-10-11-12-13-14-15-16-17-27(47)22-30(49)68-21-20-41-29(48)18-19-42-37(52)34(51)39(2,3)24-61-67(58,59)64-66(56,57)60-23-28-33(63-65(53,54)55)32(50)38(62-28)46-26-45-31-35(40)43-25-44-36(31)46/h25-26,28,32-34,38,50-51H,4-24H2,1-3H3,(H,41,48)(H,42,52)(H,56,57)(H,58,59)(H2,40,43,44)(H2,53,54,55)" -"MNXM1191__64__MNXC3","InChI=1S/CHNO/c2-1-3/h3H" "MNXM759__64__MNXC3","InChI=1S/C4H10NO7P/c5-3(4(7)8)2(6)1-12-13(9,10)11/h2-3,6H,1,5H2,(H,7,8)(H2,9,10,11)" "MNXM825__64__MNXC3","InChI=1S/C37H66N7O18P3S/c1-4-5-6-7-8-9-10-11-12-13-14-15-25(45)20-28(47)66-19-18-39-27(46)16-17-40-35(50)32(49)37(2,3)22-59-65(56,57)62-64(54,55)58-21-26-31(61-63(51,52)53)30(48)36(60-26)44-24-43-29-33(38)41-23-42-34(29)44/h23-26,30-32,36,45,48-49H,4-22H2,1-3H3,(H,39,46)(H,40,50)(H,54,55)(H,56,57)(H2,38,41,42)(H2,51,52,53)" "MNXM738__64__MNXC3","InChI=1S/C37H64N7O18P3S/c1-4-5-6-7-8-9-10-11-12-13-14-15-25(45)20-28(47)66-19-18-39-27(46)16-17-40-35(50)32(49)37(2,3)22-59-65(56,57)62-64(54,55)58-21-26-31(61-63(51,52)53)30(48)36(60-26)44-24-43-29-33(38)41-23-42-34(29)44/h23-24,26,30-32,36,48-49H,4-22H2,1-3H3,(H,39,46)(H,40,50)(H,54,55)(H,56,57)(H2,38,41,42)(H2,51,52,53)" @@ -501,6 +503,7 @@ "MNXM1199__64__MNXC3","InChI=1S/C43H77N3O20P2/c1-3-5-7-9-11-13-15-17-19-21-29(48)25-34(51)44-36-40(64-35(52)26-30(49)22-20-18-16-14-12-10-8-6-4-2)38(54)31(27-47)63-42(36)65-68(59,60)66-67(57,58)61-28-32-37(53)39(55)41(62-32)46-24-23-33(50)45-43(46)56/h23-24,29-32,36-42,47-49,53-55H,3-22,25-28H2,1-2H3,(H,44,51)(H,57,58)(H,59,60)(H,45,50,56)" "MNXM1839__64__MNXC3","InChI=1S/C34H66NO12P/c1-3-5-7-9-11-13-15-17-19-21-26(37)23-29(39)35-31-33(32(41)28(25-36)45-34(31)47-48(42,43)44)46-30(40)24-27(38)22-20-18-16-14-12-10-8-6-4-2/h26-28,31-34,36-38,41H,3-25H2,1-2H3,(H,35,39)(H2,42,43,44)" "MNXM1569__64__MNXC3","InChI=1S/C68H129N2O20P/c1-5-9-13-17-21-25-29-33-37-41-51(72)45-57(76)69-61-65(88-59(78)47-53(74)43-39-35-31-27-23-19-15-11-7-3)63(80)55(49-71)86-67(61)85-50-56-64(81)66(89-60(79)48-54(75)44-40-36-32-28-24-20-16-12-8-4)62(68(87-56)90-91(82,83)84)70-58(77)46-52(73)42-38-34-30-26-22-18-14-10-6-2/h51-56,61-68,71-75,80-81H,5-50H2,1-4H3,(H,69,76)(H,70,77)(H2,82,83,84)" +"MNXM5748__64__MNXC3","InChI=1S/C30H29N3O16/c34-10-16(31-25(41)13-4-1-7-19(35)22(13)38)29(46)49-12-18(33-27(43)15-6-3-9-21(37)24(15)40)30(47)48-11-17(28(44)45)32-26(42)14-5-2-8-20(36)23(14)39/h1-9,16-18,34-40H,10-12H2,(H,31,41)(H,32,42)(H,33,43)(H,44,45)" "MNXM390__64__MNXC3","InChI=1S/C6H12O6/c7-1-2-3(8)4(9)5(10)6(11)12-2/h2-11H,1H2" "MNXM6016__64__MNXC3","InChI=1S/C114H208N2O39P2/c1-7-13-19-25-31-37-39-40-42-48-54-59-65-71-95(126)145-85(69-63-57-51-45-35-29-23-17-11-5)75-94(125)116-100-108(150-98(129)76-86(70-64-58-52-46-36-30-24-18-12-6)146-96(127)72-66-60-53-47-41-38-32-26-20-14-8-2)106(154-156(137,138)139)92(82-144-113(111(133)134)78-90(102(131)105(152-113)89(123)80-118)151-114(112(135)136)77-87(121)101(130)104(153-114)88(122)79-117)148-109(100)143-81-91-103(132)107(149-97(128)74-84(120)68-62-56-50-44-34-28-22-16-10-4)99(110(147-91)155-157(140,141)142)115-93(124)73-83(119)67-61-55-49-43-33-27-21-15-9-3/h37,39,83-92,99-110,117-123,130-132H,7-36,38,40-82H2,1-6H3,(H,115,124)(H,116,125)(H,133,134)(H,135,136)(H2,137,138,139)(H2,140,141,142)" "MNXM386__64__MNXC3","InChI=1S/C5H6N2O2/c1-3-2-6-5(9)7-4(3)8/h2H,1H3,(H2,6,7,8,9)" @@ -568,13 +571,16 @@ "MNXM2582__64__MNXC3","InChI=1S/C124H226N2O51P2/c1-7-13-19-25-31-37-38-44-50-56-62-68-96(141)164-84(66-60-54-48-42-35-29-23-17-11-5)72-98(143)168-115-100(126-94(139)71-83(65-59-53-47-41-34-28-22-16-10-4)163-95(140)67-61-55-49-43-36-30-24-18-12-6)117(161-79-91-102(145)114(167-97(142)70-82(132)64-58-52-46-40-33-27-21-15-9-3)99(118(165-91)177-179(158,159)160)125-93(138)69-81(131)63-57-51-45-39-32-26-20-14-8-2)166-92(113(115)176-178(155,156)157)80-162-123(121(151)152)74-90(173-124(122(153)154)73-85(133)101(144)110(174-124)88(136)77-129)112(111(175-123)89(137)78-130)171-120-107(150)116(106(149)109(170-120)87(135)76-128)172-119-105(148)103(146)104(147)108(169-119)86(134)75-127/h81-92,99-120,127-137,144-150H,7-80H2,1-6H3,(H,125,138)(H,126,139)(H,151,152)(H,153,154)(H2,155,156,157)(H2,158,159,160)" "MNXM611__64__MNXC3","InChI=1S/C7H8O5/c8-4-1-3(7(11)12)2-5(9)6(4)10/h1,5-6,9-10H,2H2,(H,11,12)" "MNXM1532__64__MNXC3","InChI=1S/C9H15N4O9P/c10-3-6(12-9(17)13-7(3)16)11-8-5(15)4(14)2(22-8)1-21-23(18,19)20/h2,4-5,8,14-15H,1,10H2,(H2,18,19,20)(H3,11,12,13,16,17)" +"MNXM3470__64__MNXC3","InChI=1S/C9H17O12P/c10-1-3(8(14)15)20-9-7(13)6(12)5(11)4(21-9)2-19-22(16,17)18/h3-7,9-13H,1-2H2,(H,14,15)(H2,16,17,18)" "MNXM943__64__MNXC3","InChI=1S/C5H14N2/c6-4-2-1-3-5-7/h1-7H2" "MNXM3426__64__MNXC3","InChI=1S/C17H35O7P/c1-2-3-4-5-6-7-8-9-10-11-12-13-17(19)23-14-16(18)15-24-25(20,21)22/h16,18H,2-15H2,1H3,(H2,20,21,22)" "MNXM863__64__MNXC3","InChI=1S/C42H46N4O16/c1-41(17-39(59)60)23(5-9-35(51)52)29-14-27-21(11-37(55)56)19(3-7-33(47)48)25(43-27)13-26-20(4-8-34(49)50)22(12-38(57)58)28(44-26)15-31-42(2,18-40(61)62)24(6-10-36(53)54)30(46-31)16-32(41)45-29/h13-16,23-24,44-45H,3-12,17-18H2,1-2H3,(H,47,48)(H,49,50)(H,51,52)(H,53,54)(H,55,56)(H,57,58)(H,59,60)(H,61,62)" "MNXM2449__64__MNXC3","InChI=1S/C5H8O4/c1-3(7)5(9)4(8)2-6/h4,6,8H,2H2,1H3" "MNXM6359__64__MNXC3","InChI=1S/C5H6O3/c1-3-5(7)4(6)2-8-3/h7H,2H2,1H3" +"MNXM722756__64__MNXC3","InChI=1S/C3H8N2O2/c4-1-2(5)3(6)7/h2H,1,4-5H2,(H,6,7)" "MNXM423__64__MNXC3","InChI=1S/C10H15N4O14P3/c15-6-4(1-25-30(21,22)28-31(23,24)27-29(18,19)20)26-10(7(6)16)14-3-13-5-8(14)11-2-12-9(5)17/h2-4,6-7,10,15-16H,1H2,(H,21,22)(H,23,24)(H,11,12,17)(H2,18,19,20)" "MNXM817__64__MNXC3","InChI=1S/C7H14N2O3/c1-5(10)9-6(7(11)12)3-2-4-8/h6H,2-4,8H2,1H3,(H,9,10)(H,11,12)" +"MNXM367__64__MNXC3","InChI=1S/C8H20NO6P/c1-9(2,3)4-5-14-16(12,13)15-7-8(11)6-10/h8,10-11H,4-7H2,1-3H3/p+1" "MNXM1030__64__MNXC3","InChI=1S/C7H10O7/c1-3(5(10)11)7(14,6(12)13)2-4(8)9/h3,14H,2H2,1H3,(H,8,9)(H,10,11)(H,12,13)" "MNXM2769__64__MNXC3","InChI=1S/C26H46N7O26P5S/c1-26(2,20(38)23(39)29-4-3-14(34)28-5-6-65)9-53-63(47,48)58-61(43,44)52-8-13-17(36)19(24(54-13)33-11-32-15-21(27)30-10-31-22(15)33)56-25-18(37)16(35)12(55-25)7-51-62(45,46)59-64(49,50)57-60(40,41)42/h10-13,16-20,24-25,35-38,65H,3-9H2,1-2H3,(H,28,34)(H,29,39)(H,43,44)(H,45,46)(H,47,48)(H,49,50)(H2,27,30,31)(H2,40,41,42)" "MNXM412__64__MNXC3","InChI=1S/C8H14N2O5S/c9-4(7(12)13)1-2-6(11)10-5(3-16)8(14)15/h4-5,16H,1-3,9H2,(H,10,11)(H,12,13)(H,14,15)" @@ -591,6 +597,7 @@ "MNXM287__64__MNXC3","InChI=1S/C10H14N5O10PS/c11-8-5-9(13-2-12-8)15(3-14-5)10-7(17)6(16)4(24-10)1-23-26(18,19)25-27(20,21)22/h2-4,6-7,10,16-17H,1H2,(H,18,19)(H2,11,12,13)(H,20,21,22)" "MNXM1089__64__MNXC3","InChI=1S/C20H28N10O19P4/c21-15-9-17(25-3-23-15)29(5-27-9)19-13(33)11(31)7(45-19)1-43-50(35,36)47-52(39,40)49-53(41,42)48-51(37,38)44-2-8-12(32)14(34)20(46-8)30-6-28-10-16(22)24-4-26-18(10)30/h3-8,11-14,19-20,31-34H,1-2H2,(H,35,36)(H,37,38)(H,39,40)(H,41,42)(H2,21,23,25)(H2,22,24,26)" "MNXM672__64__MNXC3","InChI=1S/C42H48N4O16/c1-41(17-39(59)60)23(5-9-35(51)52)29-14-27-21(11-37(55)56)19(3-7-33(47)48)25(43-27)13-26-20(4-8-34(49)50)22(12-38(57)58)28(44-26)15-31-42(2,18-40(61)62)24(6-10-36(53)54)30(46-31)16-32(41)45-29/h14-16,23-24,43-45H,3-13,17-18H2,1-2H3,(H,47,48)(H,49,50)(H,51,52)(H,53,54)(H,55,56)(H,57,58)(H,59,60)(H,61,62)" +"MNXM227__64__MNXC3","InChI=1S/C11H19NO9/c1-4(14)12-7-5(15)2-11(20,10(18)19)21-9(7)8(17)6(16)3-13/h5-9,13,15-17,20H,2-3H2,1H3,(H,12,14)(H,18,19)" "MNXM1278__64__MNXC3","InChI=1S/C49H60N4O5/c1-10-35-31(6)40-26-45-49(46(54)19-13-18-30(5)17-12-16-29(4)15-11-14-28(2)3)34(9)41(53-45)24-38-32(7)36(20-22-47(55)56)43(51-38)27-44-37(21-23-48(57)58)33(8)39(52-44)25-42(35)50-40/h10,14,16,18,24-27,46,51,53-54H,1,11-13,15,17,19-23H2,2-9H3,(H,55,56)(H,57,58)" "MNXM730__64__MNXC3","InChI=1S/C7H11NO5/c1-4(9)8-5(7(12)13)2-3-6(10)11/h5H,2-3H2,1H3,(H,8,9)(H,10,11)(H,12,13)" "MNXM1954__64__MNXC3","InChI=1S/C41H65N9O28P2/c1-15(32(58)45-17(3)37(62)63)44-35(61)21(8-6-7-20(42)38(64)65)47-25(53)10-9-22(39(66)67)48-33(59)16(2)43-34(60)18(4)74-31-27(46-19(5)52)40(76-23(13-51)29(31)56)77-80(71,72)78-79(69,70)73-14-24-28(55)30(57)36(75-24)50-12-11-26(54)49-41(50)68/h11-12,15-18,20-24,27-31,36,40,51,55-57H,6-10,13-14,42H2,1-5H3,(H,43,60)(H,44,61)(H,45,58)(H,46,52)(H,47,53)(H,48,59)(H,62,63)(H,64,65)(H,66,67)(H,69,70)(H,71,72)(H,49,54,68)" diff --git a/tests/rpextractsink/test_rpextractsink.py b/tests/rpextractsink/test_rpextractsink.py index e811623..594ac89 100644 --- a/tests/rpextractsink/test_rpextractsink.py +++ b/tests/rpextractsink/test_rpextractsink.py @@ -141,4 +141,7 @@ def _test_genSink( # for key, value in test_sink.items(): # print(f'"{key}","{value}"') # exit() + print('"Name","InChI"') + for cid, inchi in test_sink.items(): + print(f'"{cid}","{inchi}"') self.assertDictEqual(test_sink, ref_sink)