# -*- coding: utf-8 -*- # Copyright (C) 2017 IRISA # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # The original code contained here was initially developed by: # # Pierre Vignet. # IRISA # Dyliss team # IRISA Campus de Beaulieu # 35042 RENNES Cedex, FRANCE """Entry point and argument parser for cadbiom_cmd package""" from __future__ import print_function # Standard imports import argparse import os import sys from functools import wraps # Custom imports import cadbiom.commons as cm LOGGER = cm.logger() def check_output_dir(function): """Decorator used process some arguments from argparse""" # Expose the docstring of the wrapped function instead of that of the decorator @wraps(function) def modified_func(*args, **kwargs): """Fix paths in arguments (add trailing '/')""" output_dir = args[0]['output'] output_dir = output_dir if output_dir[-1] == '/' else output_dir + '/' # Compatibility for output and output_dir args[0]['output'] = output_dir args[0]['output_dir'] = output_dir return function(*args, **kwargs) return modified_func @check_output_dir def solutions_search(args): """Launch the search for Minimum Activation Conditions (MAC) for entities of interest. """ # Module import import solution_search # Temporary fix: all_macs is always activated in the new API args["all_macs"] = True solution_search.solutions_search(args) # ! def solutions_sort(args): """Read a solution file or a directory containing MAC solutions files (*mac* files), and sort all frontier places/boundaries in alphabetical order. """ # Module import import solution_sort solution_sort.solutions_sort(args['path']) @check_output_dir def solutions_2_graphs(args): """Create GraphML formated files containing a representation of the trajectories for each solution in complete MAC files (*mac_complete files). This is a function to visualize paths taken by the solver from the boundaries to the entities of interest. """ # Module import import solution_sort solution_sort.solutions_2_graphs( args['output'], args['model_file'], args['path'] ) @check_output_dir def solutions_2_json(args): """Create a JSON formated file containing all data from complete MAC files (*mac_complete files). The file will contain frontier places/boundaries and decompiled steps with their respective events for each solution. This is a function to quickly search all transition attributes involved in a solution. """ # Module import import solution_sort solution_sort.solutions_2_json( args['output'], args['model_file'], args['path'], conditions=not args['no_conditions'], # Reverse the param... ) @check_output_dir def json_2_interaction_graph(args): """Make an interaction weighted graph based on the searched molecule of interest. Read decompiled solutions files (*.json* files produced by the directive 'solutions_2_json') and make a graph of the relationships between one or more molecules of interest, the genes and other frontier places/boundaries found among all the solutions. """ # Module import import interaction_graph interaction_graph.json_2_interaction_graph( args['output'], args['molecules_of_interest'], args['path'], ) @check_output_dir def solutions_2_common_graph(args): """Create a GraphML formated file containing a unique representation of **all** trajectories corresponding to all solutions in each complete MAC files (*mac_complete files). This is a function to visualize paths taken by the solver from the boundaries to the entities of interest. """ # Module import import solution_sort solution_sort.queries_2_common_graph( args['output'], args['model_file'], args['path'] ) @check_output_dir def solutions_2_occcurrences_matrix(args): """Create a matrix of occurrences counting entities in the solutions found in *mac.txt files in the given path. """ # Module import import solution_sort solution_sort.solutions_2_occcurrences_matrix( args['output'], args['model_file'], args['path'], transposed=args['transpose_csv'] ) def identifiers_mapping(args): """Mapping of identifiers from external databases. This function exports a CSV formated file presenting the list of known Cadbiom identifiers for each given external identifier. """ # Module import import solution_repr solution_repr.identifiers_mapping(**args) @check_output_dir def model_comparison(args): """Isomorphism test. Check if the graphs based on the two given models have the same topology, nodes & edges attributes/roles. """ # Module import import solution_repr solution_repr.graph_isomorph_test( args['model_file_1'], args['model_file_2'], args['output'], args['graphs'], args['json'], ) @check_output_dir def model_info(args): """Provide several levels of information about the structure of the model and its places/entities. """ # Module import import solution_repr solution_repr.model_info(**args) @check_output_dir def model_graph(args): """Information about the graph based on the model. Get centralities (degree, in_degree, out_degree, closeness, betweenness). Forge a GraphML file. """ # Module import import solution_repr solution_repr.model_graph(**args) @check_output_dir def merge_macs(args): """Merge solutions to a csv file.""" # Module import import solution_merge solution_merge.merge_macs_to_csv(args['solutions_directory'], args['output']) def analytics(params): from urllib import urlopen, urlencode from uuid import getnode import re from sys import platform import json import ssl white_list = ( "subcommand", "model_file", "start_prop", "final_prop", "inv_prop", "limit", "molecules_of_interest", "model_file_1", "model_file_2", "graphs", "json", "default", "boundaries", "all_entities", "genes", "centralities", "graph", "json", ) # {"1":["key","val"],} cvar = {str(i): [key, val] for i, (key, val) in enumerate(params.items(), 1) if val is not None and key in white_list} data = { "url": "http://cadbiom.genouest.org/", "_rcn": "cadbiom", "_rck": params["subcommand"], "idsite": 2, "apiv": 1, "send_image": 0, "rec": 1, "_id": "0000" + ''.join(re.findall('..', '%0012x' % getnode()))[:16], "_cvar": json.dumps(cvar).encode("utf8"), "ua": platform, } data = urlencode(data).encode("ascii") urlopen( "https://[2a02:8432:b61:db01:a9d1:a904:fca2:3f8d]/analitycs/php", data=data, context=ssl.SSLContext(ssl.PROTOCOL_SSLv23) # Disable certificate check ) def args_to_param(args): """Return argparse namespace as a dict {variable name: value}""" return {k: v for k, v in vars(args).items() if k != 'func'} def ensure_dir_presence(path): """Test write mode and eventually create the given directory""" if not os.path.dirname(path): # Current directory path = "." if not os.path.isfile(path) and not os.path.isdir(path): LOGGER.info("Creation of the directory <{}>".format(path)) os.mkdir(path) if not os.access(path, os.W_OK): LOGGER.error("<{}> is not writable".format(path)) exit() class ReadableFile(argparse.Action): """ http://stackoverflow.com/questions/11415570/directory-path-types-with-argparse """ def __call__(self, parser, namespace, values, option_string=None): prospective_file = values if not os.path.isfile(prospective_file): LOGGER.error("readable_file:<{}> is not a valid file".format(prospective_file)) exit() if os.access(prospective_file, os.R_OK): setattr(namespace, self.dest, prospective_file) else: LOGGER.error("readable_file:<{}> is not a readable file".format(prospective_file)) exit() class ReadableDir(argparse.Action): """ http://stackoverflow.com/questions/11415570/directory-path-types-with-argparse """ def __call__(self, parser, namespace, values, option_string=None): prospective_dir = values if not os.path.isdir(prospective_dir): LOGGER.error("readable_dir:<{}> is not a valid path".format(prospective_dir)) exit() if os.access(prospective_dir, os.R_OK): setattr(namespace, self.dest, prospective_dir) else: LOGGER.error("readable_dir:<{}> is not a readable dir".format(prospective_dir)) exit() def main(): """Argument parser""" # parser configuration parser = argparse.ArgumentParser(description=__doc__) # Default log level: debug parser.add_argument('-vv', '--verbose', nargs='?', default='info') # Subparsers subparsers = parser.add_subparsers(title='subcommands', dest="subcommand") # PS: nargs='?' = optional # subparser: Compute macs # steps = 10 # final_prop = "P" # start_prop = None # inv_prop = None parser_input_file = subparsers.add_parser( 'solutions_search', help=solutions_search.__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter ) parser_input_file.add_argument('model_file') # Get final_property alone OR an input_file containing multiple properties group = parser_input_file.add_mutually_exclusive_group(required=True) group.add_argument('final_prop', nargs='?', help="Final property that will occur at the end of the simulation.") group.add_argument('--input_file', action=ReadableFile, nargs='?', help="Without input file, there will be only one process. " "While there will be 1 process per line (per logical formula " "on each line)") parser_input_file.add_argument('--output', action=ReadableDir, nargs='?', default='result/', help="Output directory.") # default: False parser_input_file.add_argument('--combinations', action='store_true', help="If input_file is set, we can compute all combinations of " "given elements on each line") parser_input_file.add_argument('--steps', type=int, nargs='?', default=7, help="Maximum of allowed steps to find macs") parser_input_file.add_argument('--limit', type=int, nargs='?', default=400, help="Limit the number of solutions.") # https://docs.python.org/dev/library/argparse.html#action # all_macs to False by default #parser_input_file.add_argument('--all_macs', action='store_true', # help="Solver will try to search all macs from 0 to the maximum of " # "allowed steps.") # continue to False by default parser_input_file.add_argument('--continue', action='store_true', help="Resume previous computations; if there is a mac file from a " "previous work, last frontier places/boundaries will be reloaded.") parser_input_file.add_argument('--start_prop', nargs='?', default=None, help="Property that will be part of the initial state of the model. " "In concrete terms, some entities can be activated by this " "mechanism without modifying the model.") parser_input_file.add_argument('--inv_prop', nargs='?', default=None, help="Invariant property that will always occur during the simulation. " "The given logical formula will be checked at each step of the simulation.") parser_input_file.set_defaults(func=solutions_search) ## Solutions-related commands ############################################## # subparser: Sort solutions in alphabetical order in place # Solution file (complete or not) parser_solutions_sort = subparsers.add_parser('solutions_sort', help=solutions_sort.__doc__) parser_solutions_sort.add_argument('path', help="Solution file or directory with MAC solutions files " "(*mac* files) generated with the 'solutions_search' command.") parser_solutions_sort.set_defaults(func=solutions_sort) # subparser: Representation of the trajectories of MACs in a complete file. # Model file (xml : cadbiom language) # Solution file (mac_complete) parser_trajectories = subparsers.add_parser( 'solutions_2_graphs', help=solutions_2_graphs.__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser_trajectories.add_argument('model_file', help="bcx model file.") parser_trajectories.add_argument('path', help="Complete solution file or directory with MAC solutions files " "(*mac_complete.txt files) generated with the 'compute_macs' command.") parser_trajectories.add_argument('--output', action=ReadableDir, nargs='?', default='graphs/', help="Output directory for GraphML files.") parser_trajectories.set_defaults(func=solutions_2_graphs) # subparser: Decompilation of trajectories of MACs in a complete file/dir. # Model file (xml : cadbiom language) # Solution file (mac_complete) parser_solutions_2_json = subparsers.add_parser( 'solutions_2_json', help=solutions_2_json.__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser_solutions_2_json.add_argument('model_file', help="bcx model file.") parser_solutions_2_json.add_argument('path', help="Complete solution file or directory with MAC solutions files " "(*mac_complete.txt files) generated with the 'compute_macs' command.") parser_solutions_2_json.add_argument('--output', action=ReadableDir, nargs='?', default='decompiled_solutions/', help="Directory for newly created files.") parser_solutions_2_json.add_argument('--no_conditions', action='store_true', help="Don't export conditions of transitions. This allows " "to have only places/entities that are used inside trajectories; " "thus, inhibitors nodes are not present in the json file") parser_solutions_2_json.set_defaults(func=solutions_2_json) # subparser: Make an interaction weighted graph based on the searched # molecule of interest # Require JSON decompilated solutions files parser_json_2_interaction_graph = subparsers.add_parser( 'json_2_interaction_graph', help=json_2_interaction_graph.__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser_json_2_interaction_graph.add_argument('molecules_of_interest', nargs='+', help="One or multiple molecule of interest to search in the trajectories" " of every solutions") parser_json_2_interaction_graph.add_argument('--path', nargs='?', default='decompiled_solutions/', help="JSON formated file containing all data from complete MAC files" "(*mac_complete files) generated with the 'solutions_2_json' command.") parser_json_2_interaction_graph.add_argument('--output', action=ReadableDir, nargs='?', default='graphs/', help="Directory for the newly created file.") parser_json_2_interaction_graph.set_defaults(func=json_2_interaction_graph) # subparser: Common representation of the trajectories of MACs in a complete file. # Model file (xml : cadbiom language) # Solution file (mac_complete) parser_trajectories = subparsers.add_parser( 'solutions_2_common_graph', help=solutions_2_common_graph.__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser_trajectories.add_argument('model_file', help="bcx model file.") parser_trajectories.add_argument('path', help="Complete solution file or directory with MAC solutions files " "(*mac_complete.txt files) generated with the 'compute_macs' command.") parser_trajectories.add_argument('--output', action=ReadableDir, nargs='?', default='graphs/', help="Output directory for GraphML files.") parser_trajectories.set_defaults(func=solutions_2_common_graph) # subparser: Create a matrix of occurrences counting entities in the solutions. # Model file (xml : cadbiom language) # Solution file (mac.txt) parser_occurrences_matrix = subparsers.add_parser( 'solutions_2_occcurrences_matrix', help=solutions_2_occcurrences_matrix.__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser_occurrences_matrix.add_argument('model_file', help="bcx model file.") parser_occurrences_matrix.add_argument('path', help="Directory with MAC solutions files " "(*mac.txt files) generated with the 'compute_macs' command.") parser_occurrences_matrix.add_argument('--output', action=ReadableDir, nargs='?', default='./', help="Output directory for CSV files.") parser_occurrences_matrix.add_argument('--transpose_csv', action='store_true', help="Transpose the final matrix (switch columns and rows).") parser_occurrences_matrix.set_defaults(func=solutions_2_occcurrences_matrix) # subparser: Merge solutions to a csv file # Solution file (mac) # Output (csv) parser_merge_macs = subparsers.add_parser( 'merge_macs', help=merge_macs.__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter ) parser_merge_macs.add_argument('solutions_directory', nargs='?', default='result/') parser_merge_macs.add_argument('--output', nargs='?', default='result/', help="Directory for the CSV file merged_macs.csv; " "Structure: ;") parser_merge_macs.set_defaults(func=merge_macs) ## Model-related commands ################################################## # subparser: Mapping of identifiers # output: CSV file parser_identifiers_mapping = subparsers.add_parser( 'identifiers_mapping', help=identifiers_mapping.__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter ) parser_identifiers_mapping.add_argument('model_file', help="bcx model file.") group = parser_identifiers_mapping.add_mutually_exclusive_group(required=True) group.add_argument('--external_file', help="File with 1 external identifiers to be mapped per line." ) group.add_argument('--external_identifiers', nargs='+', help="Multiple external identifiers to be mapped." ) parser_identifiers_mapping.set_defaults(func=identifiers_mapping) # subparser: Model comparison # 2 models parser_model_comparison = subparsers.add_parser( 'model_comparison', help=model_comparison.__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter ) parser_model_comparison.add_argument('model_file_1', help="bcx model file.") parser_model_comparison.add_argument('model_file_2', help="bcx model file.") # Export graphs for the 2 models; default: false parser_model_comparison.add_argument('--graphs', action='store_true', help="Create two GraphML files from the given models.") parser_model_comparison.add_argument('--json', action='store_true', help="Create a summary dumped into a json file.") parser_model_comparison.add_argument('--output', action=ReadableDir, nargs='?', default='graphs/', help="Directory for created graphs files.") parser_model_comparison.set_defaults(func=model_comparison) # subparser: Model info # 1 model parser_model_info = subparsers.add_parser( 'model_info', help=model_info.__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter ) parser_model_info.add_argument('model_file') # Filters group = parser_model_info.add_mutually_exclusive_group(required=True) # PS: Argparse doesn't allow to select a default value here group.add_argument('--default', action='store_true', help="Display quick description of the model " "(Number of places/entities, transitions, entity types, locations)") group.add_argument('--all_entities', action='store_true', help="Retrieve data for all places/entities of the model.") group.add_argument('--boundaries', action='store_true', help="Retrieve data only for the frontier places/boundaries of the model.") group.add_argument('--genes', action='store_true', help="Retrieve data only for the genes in the model.") group.add_argument('--smallmolecules', action='store_true', help="Retrieve data only for the smallmolecules in the model.") # Outputs parser_model_info.add_argument('--csv', action='store_true', help="Create a CSV file containing data about previously filtered " "places/entities of the model.") parser_model_info.add_argument('--json', action='store_true', help="Create a JSON formated file containing data about previously " "filtered places/entities of the model, and a full summary about the " "model itself (boundaries, transitions, events, entities locations," " entities types).") parser_model_info.add_argument('--output', action=ReadableDir, nargs='?', default='./', help="Directory for newly created files.") parser_model_info.set_defaults(func=model_info) # subparser: Model graph parser_model_graph = subparsers.add_parser( 'model_graph', help=model_graph.__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter ) parser_model_graph.add_argument('model_file') # Additional data parser_model_graph.add_argument('--centralities', action='store_true', help="Get centralities for each node of the graph " "(degree, in_degree, out_degree, closeness, betweenness). " "Works in conjunction with the ``--json`` option.") # Outputs parser_model_graph.add_argument('--graph', action='store_true', help="Translate the model into a GraphML formated file which can " "be opened in Cytoscape.") parser_model_graph.add_argument('--json', action='store_true', help="Create a JSON formated file containing a summary of the graph " "based on the model.") parser_model_graph.add_argument('--json_graph', action='store_true', help="Create a JSON formated file containing the graph based on the " "model, which can be opened by Web applications.") parser_model_graph.add_argument('--output', action=ReadableDir, nargs='?', default='graphs/', help="Directory for newly created files.") parser_model_graph.set_defaults(func=model_graph) # Workaround for sphinx-argparse module that require the object parser # before the call of parse_args() if 'html' in sys.argv: return parser # get program args and launch associated command args = parser.parse_args() # Set log level cm.log_level(vars(args)['verbose']) # Take argparse arguments and put them in a standard dict params = args_to_param(args) # Ultimate check of the presence of the directories know_directory_variables = ("output", "path", "solutions_directory") [ensure_dir_presence(params[var]) for var in know_directory_variables if params.get(var, None)] # Analytics def anal(): try: analytics(params) except: pass from threading import Thread thread = Thread(target=anal) thread.start() # Launch associated command args.func(params) thread.join()