cadbiom_cmd.py 15.7 KB
Newer Older
1
# -*- coding: utf-8 -*-
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 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 <http://www.gnu.org/licenses/>.
#
# The original code contained here was initially developed by:
#
#     Pierre Vignet.
#     IRISA
#     Dyliss team
#     IRISA Campus de Beaulieu
#     35042 RENNES Cedex, FRANCE
24
"""Entry point and argument parser for cadbiom_cmd package"""
25
26
27
from __future__ import unicode_literals
from __future__ import print_function

28
29
30
# Standard imports
import argparse
import os
31
import sys
32

33
# Custom imports
34
import cadbiom.commons as cm
35

36
37
LOGGER = cm.logger()

38
def launch_researchs(args):
39
40
    """Parse arguments and launch Cadbiom search of MACs
    (Minimal Activation Conditions).
41
42
    """

43
44
    # Module import
    import solution_search
45
    params = args_to_param(args)
46
    solution_search.launch_researchs(params) # !
47

48

49
def launch_sort(args):
VIGNET Pierre's avatar
VIGNET Pierre committed
50
    """Read a solution file or a directory containing MAC solutions files
51
    (*cam* files), and sort all frontier places/boundaries in alphabetical order.
52
53
    """

54
55
    # Module import
    import solution_sort
56
    params = args_to_param(args)
57
    solution_sort.sort_solutions(params['path'])
58

59

60
def solutions_2_graph(args):
61
62
63
    """Parse a complete solution file and make a representation of trajectories.
    """

64
65
66
    # Module import
    import solution_repr
    params = args_to_param(args)
67
    solution_repr.solutions_2_graph(
68
69
        params['output'],
        params['chart_file'],
70
        params['path']
71
    )
72

73
def solutions_2_json(args):
74
    """Create a JSON formated file containing all data from complete MAC files
75
76
    (*cam_complete files). The file will contain frontier places/boundaries
    and decompiled steps with their respective events for each solution.
77
78
79
80
81
82

    This is a function to quickly search all transition attributes involved
    in a solution.
    """

    # Module import
83
    import solution_sort
84
85
86
    params = args_to_param(args)
    params['output'] = params['output'] if params['output'][-1] == '/' \
                        else params['output'] + '/'
87
    solution_sort.solutions_2_json(
88
89
        params['output'],
        params['chart_file'],
90
        params['path'],
91
        conditions=not params['no_conditions'], # Reverse the param...
92
    )
93

94
95
def model_comparison(args):
    """Isomorphism test.
96

97
98
    Check if the graphs based on the two given models have  the same topology,
    nodes & edges attributes/roles.
99
100
101
102
103
    """

    # Module import
    import solution_repr
    params = args_to_param(args)
104
105
    params['output'] = params['output'] if params['output'][-1] == '/' \
                        else params['output'] + '/'
106
107
108
    solution_repr.graph_isomorph_test(
        params['model_file_1'],
        params['model_file_2'],
109
        params['output'],
110
111
        params['graphs'],
        params['json'],
112
113
114
    )


115
116
117
def model_info(args):
    """Provide several levels of information about the structure of the model
    and its places/entities.
118
119
120
121
122
    """

    # Module import
    import solution_repr
    params = args_to_param(args)
123
124

    params['output_dir'] = params['output'] if params['output'][-1] == '/' \
125
                        else params['output'] + '/'
126
    solution_repr.model_info(**params)
127
128
129


def model_graph(args):
130
    """Information about the graph based on the model.
131
132

    Get centralities (degree, in_degree, out_degree, closeness, betweenness).
133
    Forge a GraphML file.
134
135
136
137
138
139
140
141
    """

    # Module import
    import solution_repr
    params = args_to_param(args)
    params['output_dir'] = params['output'] if params['output'][-1] == '/' \
                        else params['output'] + '/'
    solution_repr.model_graph(**params)
142
143


144
def merge_cams(args):
145
    """Merge solutions to a csv file."""
146
147
148

    # Module import
    import solution_merge
149
    params = args_to_param(args)
150
151
    solution_merge.merge_cams_to_csv(params['solutions_directory'],
                                     params['output'])
152
153


154
155
156
157
158
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'}


159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
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):
            raise argparse.ArgumentTypeError(
                "readable_file:{0} is not a valid path".format(
                    prospective_file)
                )

        if os.access(prospective_file, os.R_OK):
            setattr(namespace, self.dest, prospective_file)
        else:
            raise argparse.ArgumentTypeError(
                "readable_file:{0} is not a readable file".format(
                    prospective_file)
                )


class ReadableDir(argparse.Action):
183
184
185
186
187
188
189
190
191
    """
    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):
            raise argparse.ArgumentTypeError(
192
193
194
                "readable_dir:{0} is not a valid path".format(
                    prospective_dir)
                )
195
196
197
198
199

        if os.access(prospective_dir, os.R_OK):
            setattr(namespace, self.dest, prospective_dir)
        else:
            raise argparse.ArgumentTypeError(
200
201
202
                "readable_dir:{0} is not a readable dir".format(
                    prospective_dir)
                )
203
204


205
def main():
206
    """Argument parser"""
207
208
209
210

    # parser configuration
    parser = argparse.ArgumentParser(description=__doc__)
    # Default log level: debug
VIGNET Pierre's avatar
VIGNET Pierre committed
211
    parser.add_argument('-vv', '--verbose', nargs='?', default='info')
212
213
214
    # Subparsers
    subparsers = parser.add_subparsers(title='subcommands')

215
    # PS: nargs='?' = optional
216
217
218
219
220
    # subparser: Compute macs
    #    steps      = 10
    #    final_prop = "P"
    #    start_prop = None
    #    inv_prop   = None
221
222
223
224
225
    parser_input_file = subparsers.add_parser(
        'compute_macs',
        help=launch_researchs.__doc__,
        formatter_class=argparse.ArgumentDefaultsHelpFormatter
    )
226
    parser_input_file.add_argument('chart_file')
VIGNET Pierre's avatar
VIGNET Pierre committed
227
    # Get final_property alone OR an input_file containing multiple properties
228
    group = parser_input_file.add_mutually_exclusive_group(required=True)
229
    group.add_argument('final_prop', nargs='?')
230
231
232
233
234
235
236
    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.")

237
    # default: False
238
239
240
241
242
    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=10,
        help="Maximum of allowed steps to find macs")
243
    # https://docs.python.org/dev/library/argparse.html#action
244
    # all_macs to False by default
245
246
247
    parser_input_file.add_argument('--all_macs', action='store_true',
        help="Solver will try to search all macs with 0 to the maximum of " + \
        "allowed steps.")
VIGNET Pierre's avatar
VIGNET Pierre committed
248
    # continue to False by default
249
250
    parser_input_file.add_argument('--continue', action='store_true',
        help="Resume previous computations; if there is a mac file from " + \
251
        "a previous work, last frontier places/boundaries will be reloaded.")
252
253
    parser_input_file.add_argument('--start_prop', nargs='?', default=None)
    parser_input_file.add_argument('--inv_prop', nargs='?', default=None)
254

255
    parser_input_file.set_defaults(func=launch_researchs)
256

257
    ## Solutions-related commands ##############################################
258
259
260
261
262

    # subparser: Sort solutions in alphabetical order in place
    # Solution file (complete or not)
    parser_solutions_sort = subparsers.add_parser('sort_solutions',
                                                  help=launch_sort.__doc__)
263
    parser_solutions_sort.add_argument('path',
264
265
        help="Solution file or directory with MAC solutions files "
        "(*cam* files) generated with the 'compute_macs' command.")
266
267
268
269
270
    parser_solutions_sort.set_defaults(func=launch_sort)


    # subparser: Representation of the trajectories of MACs in a complete file.
    # Model file (xml : cadbiom language)
271
    # Solution file (cam_complete)
272
    parser_trajectories = subparsers.add_parser(
273
274
        'solutions_2_graph',
        help=solutions_2_graph.__doc__,
275
276
277
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser_trajectories.add_argument('chart_file',
        help="bcx model file.")
278
    parser_trajectories.add_argument('path',
279
280
        help="Complete solution file or directory with MAC solutions files "
        "(*cam_complete.txt files) generated with the 'compute_macs' command.")
281
    parser_trajectories.add_argument('--output', action=ReadableDir,
282
        nargs='?', default='graphs/',
283
        help="Output directory for GraphML files.")
284
    parser_trajectories.set_defaults(func=solutions_2_graph)
285
286


287
    # subparser: Decompilation of trajectories of MACs in a complete file/dir.
288
289
    # Model file (xml : cadbiom language)
    # Solution file (cam_complete)
290
291
292
    parser_solutions_2_json = subparsers.add_parser(
        'solutions_2_json',
        help=solutions_2_json.__doc__,
293
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
294
    parser_solutions_2_json.add_argument('chart_file',
295
        help="bcx model file.")
296
297
    parser_solutions_2_json.add_argument('path',
        help="Complete solution file or directory with MAC solutions files "
298
        "(*cam_complete.txt files) generated with the 'compute_macs' command.")
299
    parser_solutions_2_json.add_argument('--output', action=ReadableDir,
300
        nargs='?', default='decompiled_solutions/',
301
        help="Directory for newly created files.")
302
303
304
    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; "
305
        "thus, inhibitors nodes are not present in the json file")
306
    parser_solutions_2_json.set_defaults(func=solutions_2_json)
307
308


309
310
311
    # subparser: Merge solutions to a csv file
    # Solution file (cam)
    # Output (csv)
312
313
314
315
316
    parser_merge_cams = subparsers.add_parser(
        'merge_cams',
        help=merge_cams.__doc__,
        formatter_class=argparse.ArgumentDefaultsHelpFormatter
    )
317
    parser_merge_cams.add_argument('solutions_directory', nargs='?',
318
        default='result/')
319
    parser_merge_cams.add_argument('--output', nargs='?',
320
321
        default='result/merged_cams.csv',
        help="CSV file: <Final property formula>;<cam>")
322
323
    parser_merge_cams.set_defaults(func=merge_cams)

324
    ## Model-related commands ##################################################
325

326
327
    # subparser: Model comparison
    # 2 models
328
    parser_model_comparison = subparsers.add_parser(
329
330
        'model_comparison',
        help=model_comparison.__doc__,
331
332
333
334
335
336
        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.")
337
    # Export graphs for the 2 models; default: false
338
    parser_model_comparison.add_argument('--graphs', action='store_true',
339
        help="Create two GraphML files from the given models.")
340
341
    parser_model_comparison.add_argument('--json', action='store_true',
        help="Create a summary dumped into a json file.")
342
    parser_model_comparison.add_argument('--output', action=ReadableDir,
343
344
        nargs='?', default='graphs/',
        help="Directory for created graphs files.")
345
    parser_model_comparison.set_defaults(func=model_comparison)
346
347


348
349
    # subparser: Model infos
    # 1 model
350
351
352
    parser_model_info = subparsers.add_parser(
        'model_info',
        help=model_info.__doc__,
353
354
        formatter_class=argparse.ArgumentDefaultsHelpFormatter
    )
355
    parser_model_info.add_argument('model_file')
356
    # Filters
357
358
    group = parser_model_info.add_mutually_exclusive_group(required=True)
    # PS: Argparse doesn't allow to select a default value here
359
360
    group.add_argument('--default', action='store_true',
        help="Display quick description of the model "
361
362
363
364
365
        "(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.")
366
367
368
    group.add_argument('--genes', action='store_true',
        help="Retrieve data only for the genes in the model.")
    # Outputs
369
370
371
372
373
374
375
376
377
    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,
378
379
        nargs='?', default='./',
        help="Directory for newly created files.")
380
    parser_model_info.set_defaults(func=model_info)
381

382
383
384
385
386
387
388
389
390

    # 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
391
    parser_model_graph.add_argument('--centralities', action='store_true',
VIGNET Pierre's avatar
VIGNET Pierre committed
392
393
394
        help="Get centralities for each node of the graph "
        "(degree, in_degree, out_degree, closeness, betweenness). "
        "Works in conjunction with the ``--json`` option.")
395
396
    # Outputs
    parser_model_graph.add_argument('--graph', action='store_true',
397
398
        help="Translate the model into a GraphML formated file which can "
        "be opened in Cytoscape.")
399
    parser_model_graph.add_argument('--json', action='store_true',
400
401
        help="Create a JSON formated file containing a summary of the graph "
        "based on the model.")
VIGNET Pierre's avatar
VIGNET Pierre committed
402
403
404
    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.")
405
406
    parser_model_graph.add_argument('--output', action=ReadableDir,
        nargs='?', default='graphs/',
VIGNET Pierre's avatar
VIGNET Pierre committed
407
        help="Directory for newly created files.")
408
409
410
    parser_model_graph.set_defaults(func=model_graph)


411
412
413
414
    # Workaround for sphinx-argparse module that require the object parser
    # before the call of parse_args()
    if 'html' in sys.argv:
        return parser
415

416
417
    # get program args and launch associated command
    args = parser.parse_args()
418

419
    # Set log level
420
421
    cm.log_level(vars(args)['verbose'])

422
423
    # launch associated command
    args.func(args)