The Higher Education and Research forge

Home My Page Projects Code Snippets Project Openings EMULSION public releases
Summary Activity Surveys SCM Listes Sympa

SCM Repository

1 """Usage:
2     emulsion run [--plot] MODEL [options] [(-p KEY=VALUE)...]
3     emulsion show MODEL [options] [(-p KEY=VALUE)...]
4     emulsion describe MODEL PARAM...
5     emulsion diagrams MODEL [options]
6     emulsion plot MODEL [options]
7     emulsion check MODEL [options]
8     emulsion generate MODEL
9     emulsion (-h | --help | -V | --version | -L | --license)
11 Commands:
12   run MODEL                   Run simulations based on the specified MODEL
13                               (MODEL is the path to the YAML file describing the
14                               model to run).
15   show MODEL                  Print all MODEL parameter values and exit.
16   describe MODEL PARAM...     Describe the role of specified PARAMeters in the
17                               MODEL and exit.
18   diagrams MODEL              Produce model diagrams (as option --view-model
19                               when running/plotting) and open them
20   plot MODEL                  Plot outputs for MODEL (assumed already run) and
21                               exit.
22   check MODEL                 Check the syntactic correctness of the given MODEL
23                               (path to YAML file of the model to run), according
24                               to EMULSION meta-model. Option '--meta-tree'
25                               generates a figure representing EMULSION meta-
26                               model. If the model is correct, option
27                               '--model-tree' also generates a figure for the
28                               MODEL structure.
29   generate MODEL              Generate a skeleton to help writing specific
30                               pieces of code before the MODEL can be run, and
31                               exit.
33 Options:
34   -h --help                   Display this page and exit.
35   -V --version                Display version number and exit.
36   -L --license                Display license and exit.
37   --plot                      Plot outputs just after running the model.
38   -r RUNS --runs RUNS         Specify the number of repetitions of the same
39                               model [default: 10].
40   -t STEPS --time STEPS       Specify the number of time steps to run in each
41                               repetition. If the model defines a total_duration,
42                               it is used as time limit, unless the '-t' option
43                               is explicitly specified. Otherwise, the default
44                               value is 100 steps.
45   -p KEY=VAL --param KEY=VAL  Change parameter named KEY to the specified VALue.
46   --view-model                Produce diagrams to represent the state machines
47                               of the model (requires Graphviz). Figures are
48                               stored in figure-dir.
49   --silent                    Show only the progression of repetitions instead
50                               of the progression of each simulation.
51   --quiet                     Show no progression information at all.
52   --save FILE                 Save simulation state (all agents state and
53                               parameters) at the end of the simulation.
54   --load FILE                 Use a saved simulation to start the current one.
55   --output-dir OUTPUT         Specify a directory for simulation outputs
56                               [default: outputs].
57   --input-dir INPUT           Specify a directory for simulation inputs
58                               [default: data].
59   --figure-dir FIG            Specify a directory for graphic outputs (figures)
60                               [default: img].
61   --log-params                When producing CSV outputs, insert the name and
62                               value of each parameter explicitly changed by
63                               option -p/--param.
64   --format FORMAT             Specify an image format for diagram outputs
65                               [default: svg].
67 Advanced options:
68   --seed SEED                 Set a seed value for random numbers. When not
69                               specified, the seed is set according to system
70                               time and the process id.
71   --show-seed                 Print the seed used for random numbers.
72   --start-id ID               ID of the first repetition of the same model
73                               [default: 0].
74   --echo                      Just print command-line arguments parsed by Python
75                               docopt module and exit.
76   --deterministic             Run the simulation in deterministic mode if
77                               available.
78   --modifiable                Output the list of modifiable parameters and exit.
79   --level LEVEL               Specify the LEVEL (scale) for running the model.
80                               Valid values are those defined in the 'level'
81                               section of the MODEL. The corresponding agent
82                               class will be used to manage the simulation of
83                               lower-level entities. When no value is given, the
84                               highest level (if any) is used.
85   --model-tree                Output a figure or the syntactic tree that
86                               represents the model if it complies to EMULSION
87                               DSL syntax (requires Graphviz).
88   --meta-tree                 Output a figure of the meta-model associated with
89                               EMULSION DSL (requires Graphviz).
92 EMULSION (Epidemiological Multi-Level Simulation framework)
93 ===========================================================
95 Contributors and contact:
96 -------------------------
98     - Sébastien Picault (sebastien.picault@inra.fr)
99     - Yu-Lin Huang
100     - Vianney Sicard
101     - Sandie Arnoux
102     - Gaël Beaunée
103     - Pauline Ezanno (pauline.ezanno@inra.fr)
105     BIOEPAR, INRA, Oniris, Atlanpole La Chantrerie,
106     Nantes CS 44307 CEDEX, France
109 How to cite:
110 ------------
112     S. Picault, Y.-L. Huang, V. Sicard, P. Ezanno (2017). "Enhancing
113     Sustainability of Complex Epidemiological Models through a Generic
114     Multilevel Agent-based Approach", in: C. Sierra (ed.), 26th
115     International Joint Conference on Artificial Intelligence (IJCAI),
116     AAAI, p. 374-380. DOI: 10.24963/ijcai.2017/53
119 License:
120 --------
122    Copyright 2016 INRA and Univ. Lille
124    Inter Deposit Digital Number: IDDN.FR.001.280043.000.R.P.2018.000.10000
126    Agence pour la Protection des Programmes,
127    54 rue de Paradis, 75010 Paris, France
129    Licensed under the Apache License, Version 2.0 (the "License");
130    you may not use this file except in compliance with the License.
131    You may obtain a copy of the License at
133        http://www.apache.org/licenses/LICENSE-2.0
135    Unless required by applicable law or agreed to in writing, software
136    distributed under the License is distributed on an "AS IS" BASIS,
137    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
138    See the License for the specific language governing permissions and
139    limitations under the License.
140 """
142 ## TO BE IMPLEMENTED LATER:
144   #   emulsion sensitivity MODEL DESIGN [options]
145   #   emulsion change MODEL NEW_MODEL (-p KEY=VALUE)...
148   # sensitivity MODEL DESIGN    Run a sensitivity analysis for the specified MODEL
149   #                             using the provided experimental DESIGN.
150   #                             NOT IMPLEMENTED YET.
151   # change MODEL NEW_MODEL      Modify the initial MODEL into a NEW_MODEL using
152   #                             appropriate options, and exit. NOT IMPLEMENTED YET
154   # --init INIT_FILE            Speficy a file for initial conditions.
155   #                             NOT USED YET.
156   # --test                      Run the simulation in test mode. NOT USED YET.
157   # --metamodel META            Specify a metamodel for syntax checking
158   #                             [default: ../../scripts/emulsion.tx]
161 import sys
162 import os
163 import time
164 import subprocess
165 import datetime                     as dt
166 import webbrowser
168 from   pathlib                      import Path
170 import yaml
171 import numpy                        as np
173 from   docopt                       import docopt
174 from   textx                        import metamodel_from_file
175 from   textx.export                 import metamodel_export, model_export
176 import colorama
178 import emulsion
179 from   emulsion.model               import EmulsionModel
180 from   emulsion.tools.state         import StateVarDict
181 from   emulsion.tools.misc          import load_class
182 from   emulsion.tools.plot          import plot_outputs
183 from   emulsion.tools.simulation    import MultiSimulation
185 VERSION = '1.0.6'
186 DEFAULT_VERSION = "1.0"
187 LICENSE = 'Apache-2.0'
188 DEFAULT_LICENSE = "Apache-2.0"
190 def get_metamodel():
191     """Return the path to the metamodel used for checking syntax of
192     EMULSION YAML files.
194     """
195     # retrieve path to EMULSION repository
196     parts = Path(emulsion.__file__).parts[:-1] + ("resources", "emulsion.tx")
197     return Path(*parts)
199 def get_version():
200     """Retrieve the version number of current program.
202     """
203     # try:
204     #     proc = subprocess.Popen(["git", "describe",
205     #                              "--tags", "--dirty", "--always"],
206     #                             stdout=subprocess.PIPE)
207     # except EnvironmentError:
208     #     print("unable to run git")
209     #     return 'Unknown'
210     # stdout = proc.communicate()[0].strip().decode('utf-8')
211     # if proc.returncode != 0:
212     #     print("unable to run git")
213     #     return DEFAULT_VERSION
214     # return stdout
215     return DEFAULT_VERSION if VERSION.startswith('[') else VERSION
217 def get_license():
218     """Retrieve the license  of current program.
220     """
221     return DEFAULT_LICENSE if LICENSE.startswith('[') else LICENSE
223 def change_parameters(params, change_list):
224     """Change either the model or local parameters according to the list
225     of new values.
227     """
228     model_changes = {}
229     modifiable = params.model.get_modifiable_parameters()
230     for key, val in [p.split('=') for p in change_list]:
231         if key in params:
232             # if the new value is the name of another parameter,
233             # retrieve its current value
234             if val in params:
235                 val = params[val]
236             # retrieve the value with correct type
237             params[key] = type(params[key])(val)
238         elif key in modifiable:
239             model_changes[key] = val
240         else:
241             print(colorama.Fore.RED + colorama.Style.BRIGHT +\
242                   'Unknown parameter:{}'.format(key) + colorama.Style.RESET_ALL)
243             sys.exit(-1)
244     if model_changes:
245         params.model.change_parameter_values(model_changes,
246                                              log_params=params.log_params)
248 def describe_parameters(params):
249     """Display the role of all parameters specified in the PARAM argument
250     and exit.
252     """
253     model = params.model
254     print(colorama.Style.BRIGHT + '\n{!s: ^72}'.format(params.model))
255     print('{: ^72}'.format('ROLE OF PARAMETERS (AND CURRENT DEFINITION)') +\
256           colorama.Style.RESET_ALL)
257     print('-'*72)
258     for name in params.to_describe:
259         print(model.describe_name(name))
260     print('-'*72)
263 def show_parameters(params, short=False):
264     """Display all parameters involved in the current program and model
265     and exit.
267     """
268     modifiable = params.model.get_modifiable_parameters()
269     if short:
270         print(' '.join('{}={}'.format(key, params.model.get_value(key))
271                        for key in modifiable))
272         sys.exit()
273     print(colorama.Style.BRIGHT + '\n{!s: ^72}'.format(params.model))
274     print('{: ^72}'.format('AVAILABLE PARAMETERS (with their current value)'))
275     print('-'*72)
276     print('MODEL PARAMETERS')
277     print('-'*72 + colorama.Style.RESET_ALL)
278     for key, val in modifiable.items():
279         print('  {:.<34}{!s:.>34}'.format(key, val))
280     print('-'*72)
281     # print('PROGRAM PARAMETERS')
282     # print('-'*72)
283     # for key, val in params.items():
284     #     print('  {:.<34}{!s:.>34}'.format(key, val))
285     sys.exit()
288 def generate_model(params):
289     """Generate a skeleton for the pieces of specific code to write. If
290     needed, create subdirectories. If files already exist, add a timestamp
291     to the filename.
293     """
294     model = params.model
295     src_path = Path(__file__).parent.parent
296     paths = sorted(set([Path(level_desc['file'])
297                         for level_desc in model.levels.values()
298                         if 'file' in level_desc
299                         # if not level_desc['module'].startswith('emulsion.agent')
300     ]))
301     for mod_path in paths:
302         # mod_path = Path(src_path, *module.split('.')).with_suffix('.py')
303         module = '.'.join(mod_path.parent.parts + (mod_path.stem,))
304         if mod_path.exists():
305             print(colorama.Fore.YELLOW, 'WARNING, file {} already exists, '.format(mod_path))
306             mod_path = mod_path.with_suffix('.py.%s' %
307                                             (dt.datetime.now().timestamp()))
308             print('Writing in {} instead'.format(mod_path) + colorama.Style.RESET_ALL)
309         mod_path.parent.mkdir(parents=True, exist_ok=True)
310         print(colorama.Fore.GREEN + colorama.Style.BRIGHT +\
311               'GENERATING CODE SKELETON {}\nFOR MODULE {}'.format(mod_path, module) +\
312               colorama.Style.RESET_ALL)
313         with open(mod_path, 'w') as f:
314             print(model.generate_skeleton(module), file=f)
317 def run_model(params):
318     """Run the model with the specified local parameters.
320     Args:
321         params: a dictionary with all parameters required to carry out the
322           simulations
324     Returns:
325         The instance of MultiSimulation class which carried out the simulations
327     See also:
328         `emulsion.tools.simulation.MultiSimulation`_
329     """
330     count_path = Path(params.output_dir, 'counts.csv')
331     if count_path.exists():
332         count_path.unlink()
333     multi_simu = MultiSimulation(**params)
334     # multi_simu.write_dot()
335     start = time.perf_counter()
336     multi_simu.run()
337     end = time.perf_counter()
338     print(colorama.Style.BRIGHT + 'Simulation finished in {:.2f} s'.format(end-start))
339     print(colorama.Fore.GREEN + 'Outputs stored in {}'.format(count_path) + colorama.Style.RESET_ALL)
340     return multi_simu
343 def produce_diagrams(params, view=False):
344     """Use Graphviz to render state machines of the model. If *view* is
345     set to True, opens the diagrams with system viewer.
347     Args:
348         params: a dictionary with all parameters required to carry out the
349           simulations
350         view: a boolean indicating whether or not diagrams have to be opened
351           directly
353     """
354     model = params.model
355     model.write_dot(params.output_dir)
356     prefix = model.model_name
357     for name, _ in model.state_machines.items():
358         inpath = Path(params.output_dir, prefix + '_' + name + '.dot')
359         outpath = Path(params.figure_dir,
360                        prefix + '_' + name + '_machine.' + params.img_format)
361         os.system("dot -T%s %s > %s" % (params.img_format, inpath, outpath))
362         print(colorama.Fore.GREEN + 'Generated state machine diagram {}'.format(outpath) + colorama.Style.RESET_ALL)
363         if view:
364             webbrowser.open(outpath.absolute().as_uri())
366 def check_model(params, filemodel, view_meta=False, view_model=False):
367     """Check the syntax of the model according to the grammar specified in
368     EMULSION metamodel (``src/emulsion/resources/emulsion.tx``). If
369     *show_meta* is True, produce a figure to represent the metamodel
370     in *figure_dir* (requires GraphViz). If the syntax is correct and
371     *show_model* is True, also produce a figure for the model
372     structure.
374     """
375     source_path = Path(filemodel)
376     metapath = get_metamodel()
377     if not metapath.exists():
378         print(colorama.Fore.RED + colorama.Style.BRIGHT +\
379               'ERROR, metamodel file not found: {}'.format(metapath) +\
380               colorama.Style.RESET_ALL)
381         sys.exit(-1)
382     meta = metamodel_from_file(metapath)
383     if view_meta:
384         meta_output = Path(params.figure_dir,
385                            'meta_' + metapath.name).with_suffix('.dot')
386         metamodel_export(meta, meta_output)
387         figname = str(meta_output.with_suffix('.' + params.img_format))
388         os.system("dot -T%s %s > %s" % (params.img_format,
389                                         meta_output, figname))
390         print(colorama.Fore.GREEN + 'Produced Metamodel figure: {}'.format(figname) +\
391               colorama.Style.RESET_ALL)
393     with open(source_path) as f:
394         content = f.read()
396     normalized = yaml.dump(yaml.load(content), default_flow_style=False)
397     with open('tmp.yaml', 'w') as f:
398         print(normalized, file=f)
399     model_check = meta.model_from_str(normalized)
400     if view_model:
401         model_path = Path(params.figure_dir,
402                           'model_%s' % (source_path.name, )).with_suffix('.dot')
403         model_export(model_check, model_path)
404         figname = str(model_path.with_suffix('.' + params.img_format))
405         os.system("dot -T%s %s > %s" % (params.img_format, model_path, figname))
406         print(colorama.Fore.GREEN + 'Produced Model figure {}'.format(figname) + colorama.Style.RESET_ALL)
407     Path('tmp.yaml').unlink()
408     print(colorama.Fore.GREEN + colorama.Style.BRIGHT +\
409           'File {} complies with Emulsion syntax'.format(filemodel)+\
410           colorama.Style.RESET_ALL)
413 def not_implemented(_):
414     """Default behavior for unimplemented features.
416     """
417     print(colorama.Fore.RED + colorama.Style.BRIGHT +\
418           'Feature not implemented in this model.' + colorama.Style.RESET_ALL)
419     sys.exit(0)
421 def set_seed(params, seed=None, show=False):
422     """Initialize the numpy's Random Number Generator, either with the
423     specified seed, or with a seed calculated from current time and
424     process ID.
426     """
427     if seed is None:
428         params.seed = int(os.getpid() + time.time())
429     else:
430         params.seed = int(seed)
431     np.random.seed(params.seed)
432     if show:
433         print(colorama.Style.BRIGHT + 'RANDOM SEED: {}'.format(params.seed) + colorama.Style.RESET_ALL)
436 def init_main_level(params):
437     """Initialize the upper simulation level, in charge of making all
438     sub-levels work properly.
440     """
441     if params.level not in params.model.levels:
442         print(colorama.Fore.RED + colorama.Style.BRIGHT + \
443               'ERROR, level {} not found'.format(params.level) + colorama.Style.RESET_ALL)
444         sys.exit(-1)
446     module_name = params.model.levels[params.level]['module']
447     class_name = params.model.levels[params.level]['class_name']
448     try:
449         params.target_agent_class = load_class(module_name,
450                                                class_name=class_name)[0]
451     except AttributeError:
452         print(colorama.Fore.RED + colorama.Style.BRIGHT + \
453               'ERROR, agent class not found for level {}: {}.{}'.format(
454                   params.level, module_name, class_name) + colorama.Style.RESET_ALL)
455         sys.exit(-1)
456     except ModuleNotFoundError:
457         print(colorama.Fore.RED + colorama.Style.BRIGHT + \
458               'ERROR, module not found for level {}: {}'.format(params.level, module_name) +\
459               colorama.Style.RESET_ALL)
460         sys.exit(-1)
462 def main(args=None):
463     """Run EMuLSion's main program according to the command-line
464     arguments.
466     """
467     colorama.init()
468     if args is None:
469         args = docopt(__doc__, version=get_version())
471     if args['--license']:
472         print(colorama.Fore.CYAN + get_license() + colorama.Style.RESET_ALL)
473         sys.exit(0)
475     if not Path(args['MODEL']).exists():
476         print(colorama.Fore.RED + colorama.Style.BRIGHT +\
477               'ERROR: file {} not found'.format(args['MODEL']) + colorama.Style.RESET_ALL)
478         sys.exit(-1)
480     params = StateVarDict()
481     params.model = EmulsionModel(filename=args['MODEL'])
482     params.nb_simu = int(args['--runs'])
483     params.stochastic = not args['--deterministic']
484     params.to_describe = args['PARAM']
486     params.save_to_file = args['--save']
487     params.load_from_file = args['--load']
489     params.level = args['--level']
490     if params.level is None:
491         params.level = params.model.root_level
492     if not args['--modifiable']:
493         print(colorama.Style.DIM + 'Simulation level:{}'.format(params.level) + colorama.Style.NORMAL)
495     params.silent = args['--silent']
496     params.quiet = args['--quiet']
497     params.log_params = args['--log-params']
498     params.start_id = int(args['--start-id'])
500     params.output_dir = Path(args['--output-dir'])
501     params.input_dir = Path(args['--input-dir'])
502     params.figure_dir = Path(args['--figure-dir'])
503     params.img_format = args['--format']
504     if not params.output_dir.exists():
505         params.output_dir.mkdir(parents=True)
506     if not params.figure_dir.exists():
507         params.figure_dir.mkdir(parents=True)
508     params.output_dir = str(params.output_dir)
510     params.stock_agent = False
511     params.keep_history = False
513     if args['--param']:
514         change_parameters(params, args['--param'])
516     if args['--time']:
517         params.steps = int(args['--time'])
518     elif 'total_duration' in params.model.parameters:
519         params.steps = int(np.ceil(params.model.get_value('total_duration') \
520                                    / params.model.delta_t))
521     else:
522         params.steps = 100
524     if args['--modifiable']:
525         show_parameters(params, short=True)
527     set_seed(params, seed=args['--seed'], show=args['--show-seed'])
529     if args['--echo']:
530         print(args)
531         sys.exit(0)
533     params.view_machines = False
534     if args['--view-model']:
535         produce_diagrams(params)
536         params.view_machines = True
538     if args['check']:
539         check_model(params, args['MODEL'], args['--meta-tree'], args['--model-tree'])
540     elif args['diagrams']:
541         produce_diagrams(params, view=True)
542         sys.exit(0)
543     elif args['generate']:
544         generate_model(params)
545     elif args['run']:
546         init_main_level(params)
547         run_model(params)
548         if args['--plot']:
549             plot_outputs(params)
550     elif args['show']:
551         show_parameters(params)
552     elif args['describe']:
553         describe_parameters(params)
554     elif args['plot']:
555         plot_outputs(params)
556     elif args['change']:
557         not_implemented(params)
558     elif args['sensitivity']:
559         not_implemented(params)
560     else:
561         return params
564 ################################################################
565 #                  _
566 #                 (_)
567 #  _ __ ___   __ _ _ _ __
568 # | '_ ` _ \ / _` | | '_ \
569 # | | | | | | (_| | | | | |
570 # |_| |_| |_|\__,_|_|_| |_|
572 ################################################################
574 if __name__ == '__main__':
575     main()