diff --git a/.flake8 b/.flake8 index 3b0ea1dab..9a77d11b5 100644 --- a/.flake8 +++ b/.flake8 @@ -22,6 +22,9 @@ per-file-ignores = libensemble/__init__.py:F401 libensemble/libensemble/__init__.py:F401 + # worker uses regex with chars that resemble escape sequences + libensemble/libE_worker.py:W605 + # Need to turn of matching probes (before other imports) on some # systems/versions of MPI: libensemble/tests/standalone_tests/mpi_launch_test/create_mpi_jobs.py:E402 diff --git a/.travis.yml b/.travis.yml index 404cb2820..18f0bd5a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,15 +36,30 @@ jobs: env: MPI=mpich PY=3.7 COMMS_TYPE=t # tcp language: generic python: 3.7 - -# Below will update the OSX octave used on Travis. This was so slow that we -# were hitting the 50 minute limit on Travis. We no longer update octave in -# homebrew, and can no longer run regression # tests using octave on OSX. -# addons: -# homebrew: -# packages: -# - octave -# update: true + - os: osx + osx_image: xcode11.3 + env: MPI=mpich PY=3.8 COMMS_TYPE=m # mpi + language: generic + python: 3.8 + - os: osx + osx_image: xcode11.3 + env: MPI=mpich PY=3.8 COMMS_TYPE=l # local + language: generic + python: 3.8 + - os: osx + osx_image: xcode11.3 + env: MPI=mpich PY=3.8 COMMS_TYPE=t # tcp + language: generic + python: 3.8 + fast_finish: true + allow_failures: + - python: 3.8 + - os: osx + env: MPI=mpich PY=3.8 COMMS_TYPE=m # mpi + - os: osx + env: MPI=mpich PY=3.8 COMMS_TYPE=l # mpi + - os: osx + env: MPI=mpich PY=3.8 COMMS_TYPE=t # mpi services: - postgresql @@ -66,7 +81,7 @@ before_install: - conda info -a # For debugging conda issues - conda config --add channels conda-forge - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - conda create --yes --name condaenv python=3.7; + conda create --yes --name condaenv python=$PY; else conda create --yes --name condaenv python=$TRAVIS_PYTHON_VERSION; fi @@ -90,7 +105,7 @@ install: - if [[ "$TRAVIS_PYTHON_VERSION" == "3.8" ]]; then conda install nlopt mpi4py scipy mpich; export PETSC_CONFIGURE_OPTIONS='--with-batch'; - pip install petsc petsc4py; + conda install petsc4py; else conda install nlopt petsc4py petsc $MUMPS mpi4py scipy $MPI; fi @@ -98,6 +113,8 @@ install: # Begin: Dependencies only for regression tests - pip install DFO-LS - pip install deap + - conda install psutil + - pip install mpmath - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then pip install scikit-build packaging Tasmanian --user; fi @@ -125,7 +142,7 @@ before_script: # Run test (-z show output) script: - - ./libensemble/tests/run-tests.sh -z -$COMMS_TYPE + - ./libensemble/tests/run-tests.sh -A "-W error" -z -$COMMS_TYPE # Track code coverage after_success: diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 620a97051..a02ca6342 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,54 @@ Release Notes Below are the notes from all libEnsemble releases. +Release 0.7.1 +------------- + +:Date: Oct 15, 2020 + +Dependencies: + +* ``psutils`` is now a required dependency. (#478 #491) + +API additions: + +* Executor updates: + + * Addition of a zero-resource worker option for persistent gens (does not allocate nodes to gen). (#500) + * Multiple applications can be registered to the Executor (and submitted) by name. (#498) + * Wait function added to Tasks. (#499) + +* Gen directories can now be created with options analogous to those for sim dirs. (#349 / #489) + +Other changes: + +* Improve comms efficiency (Repack fields when NumPy version 1.15+). (#511) +* Fix multiprocessing error on macOS/Python3.8 (Use 'fork' instead of 'spawn'). (#502 / #503) + +Updates to example functions: + +* Allow APOSMM to trigger ensemble exit when condition reached. (#507) +* Improvement in how persistent APOSMM shuts down subprocesses (preventing PETSc MPI-abort). (#478) + +Documentation: + +* APOSMM Tutorial added. (#468) +* Writing guide for user functions added to docs (e.g., creating sim_f, gen_f, alloc_f). (#510) +* Addition of posters and presentations section to docs (inc. Jupyter notebooks/binder links). (#492 #497) + +:Note: + +* Tested platforms include Linux, MacOS, Theta (Cray XC40/Cobalt), Summit (IBM Power9/LSF), Bebop (Cray CS400/Slurm), and Bridges (HPE system at PSC). +* Cori (Cray XC40/Slurm) was not tested with release code due to system issues. +* Tested Python versions: (Cpython) 3.5, 3.6, 3.7, 3.8. + +:Known issues: + +* We currently recommended running in Central mode on Bridges as distributed runs are experiencing hangs. +* OpenMPI does not work with direct MPI job launches in mpi4py comms mode, since it does not support nested MPI launches + (Either use local mode or Balsam job controller). +* See known issues section in the documentation for more issues. + Release 0.7.0 ------------- @@ -20,34 +68,34 @@ Breaking API changes: API additions: -* Added gen_funcs.rc configuration framework with option to select APOSMM Optimizers for import (#444) -* Provide ``alloc_specs`` defaults via `alloc_funcs.defaults` module (#325) -* Added ``extra_args`` option to the Executor submit function to allow addition of arbitrary MPI runner options (#445) -* Added ``custom_info`` argument to MPI Executor to allow overriding of detected settings (#448) -* Added ``libE_specs`` option to disable log files (#368) +* Added gen_funcs.rc configuration framework with option to select APOSMM Optimizers for import. (#444) +* Provide ``alloc_specs`` defaults via `alloc_funcs.defaults` module. (#325) +* Added ``extra_args`` option to the Executor submit function to allow addition of arbitrary MPI runner options. (#445) +* Added ``custom_info`` argument to MPI Executor to allow overriding of detected settings. (#448) +* Added ``libE_specs`` option to disable log files. (#368) Other changes: * Added libEnsemble Conda package, hosted on conda-forge. -* Bugfix: Intermittent failures with repeated libE calls under `mpi4py` comms +* Bugfix: Intermittent failures with repeated libE calls under `mpi4py` comms. Every libE call now uses its own duplicate of provided communicator and closes out. (#373/#387) * More accurate timing in `libE_stats.txt`. (#318) * Addition of new post-processing scripts. Updates to example functions: -* Persistent APOSMM is now the recommended APOSMM (`aposmm.py` renamed to `old_aposmm.py`) (#435) +* Persistent APOSMM is now the recommended APOSMM (`aposmm.py` renamed to `old_aposmm.py`). (#435) * New alloc/gen func: Finite difference parameters with noise estimation. (#350) * New example gen func: Tasmanian UQ generator. (#351) * New example gen func: Deap/NSGA2 generator. (#407) * New example gen func to interface with VTMOP. -* New example sim func: Borehole (#367) -* New example use-case: WarpX/APOSMM (#425) +* New example sim func: Borehole. (#367) +* New example use-case: WarpX/APOSMM. (#425) :Note: * Tested platforms include Linux, MacOS, Theta (Cray XC40/Cobalt), Summit (IBM Power9/LSF), Bebop (Cray CS400/Slurm), Cori (Cray XC40/Slurm), and Bridges (HPE system at PSC). -* Tested Python versions: (Cpython) 3.5, 3.6, 3.7, 3.8 +* Tested Python versions: (Cpython) 3.5, 3.6, 3.7, 3.8. :Known issues: @@ -61,21 +109,21 @@ Release 0.6.0 API changes: -* sim/gen/alloc_specs options that do not directly involve these routines are moved to libE_specs (see docs) (#266, #269) -* sim/gen/alloc_specs now require user-defined attributes to be added under the ``'user'`` field (see docs and examples) (#266, #269). -* Addition of a utils module to help users create calling scripts. Includes an argument parser and utility functions (#308). -* check_inputs() function is moved to the utils module (#308). -* The libE_specs option ``nprocesses`` has been changed to ``nworkers`` (#235) +* sim/gen/alloc_specs options that do not directly involve these routines are moved to libE_specs (see docs). (#266, #269) +* sim/gen/alloc_specs now require user-defined attributes to be added under the ``'user'`` field (see docs and examples). (#266, #269) +* Addition of a utils module to help users create calling scripts. Includes an argument parser and utility functions. (#308) +* check_inputs() function is moved to the utils module. (#308) +* The libE_specs option ``nprocesses`` has been changed to ``nworkers``. (#235) New example functions: -* Addition of a persistent APOSMM generator function (#217). +* Addition of a persistent APOSMM generator function. (#217) Other changes: -* Overhaul of documentation, including HPC platform guides and a new pdf structure (inc. #232, #282) -* Addition of OpenMP threading and GPU support to forces test (#250). -* Balsam job_controller now tested on Travis (#47) +* Overhaul of documentation, including HPC platform guides and a new pdf structure. (inc. #232, #282) +* Addition of OpenMP threading and GPU support to forces test. (#250) +* Balsam job_controller now tested on Travis. (#47) :Note: @@ -97,7 +145,7 @@ Release 0.5.2 * All output from libEnsemble goes via logger. MANAGER_WARNING level added. This level and above are echoed to stderr by default. API option to change echo level. * Simulation directories are created only during sim_f calls are suffixed by _worker. #146 * New user function libE.check_inputs() can be used to check valid configuration of inputs. Can be called in serial or under MPI (see libE API). #65 -* Installation option has been added to install dependencies used in tests ``pip install libensemble[extras]`` +* Installation option has been added to install dependencies used in tests ``pip install libensemble[extras]``. * A profiling option has been added to sim_specs. #170 * Results comparison scripts have been included for convenience. @@ -116,14 +164,14 @@ Release 0.5.1 :Date: July 11, 2019 -* Fixed LSF resource detection for large jobs on LSF systems (e.g., Summit) #184 -* Added support for macOS #182 -* Improved the documentation (including addition of beginner's tutorial and FAQ) +* Fixed LSF resource detection for large jobs on LSF systems (e.g., Summit). #184 +* Added support for macOS. #182 +* Improved the documentation (including addition of beginner's tutorial and FAQ). :Note: * Tested platforms include Local Linux, Theta (Cray XC40/Cobalt), Summit (IBM Power9/LSF), and Bebop (Cray CS400/Slurm). -* Tested Python versions: (Cpython) 3.4, 3.5, 3.6, 3.7 +* Tested Python versions: (Cpython) 3.4, 3.5, 3.6, 3.7. :Known issues: @@ -134,19 +182,19 @@ Release 0.5.0 :Date: May 22, 2019 -* Added local (multiprocessing) and TCP options for manager/worker communications, in addition to mpi4py (#42). +* Added local (multiprocessing) and TCP options for manager/worker communications, in addition to mpi4py. (#42). * Example: libEnsemble can be run on MOM/launch nodes (e.g., those of ALCF/Theta & OLCF/Summit) and can remotely detect compute resources. * Example: libEnsemble can be run on a system without MPI. * Example: libEnsemble can be run with a local manager and remote TCP workers. * Added support for Summit/LSF scheduler in job controller. -* MPI job controller detects and retries launches on failure; adding resilience (#143). -* Job controller supports option to extract/print job times in libE_stats.txt (#136). -* Default logging level changed to INFO (#164). -* Logging interface added, which allows user to change logging level and file (#110). +* MPI job controller detects and retries launches on failure; adding resilience. (#143) +* Job controller supports option to extract/print job times in libE_stats.txt. (#136) +* Default logging level changed to INFO. (#164) +* Logging interface added, which allows user to change logging level and file. (#110) * All worker logging and calculation stats are routed through manager. -* libEnsemble can be run without a gen_func, for example, when using a previously computed random sample (#122). +* libEnsemble can be run without a gen_func, for example, when using a previously computed random sample. (#122) * Aborts dump persis_info with the history. :Note: @@ -167,22 +215,22 @@ Release 0.4.1 :Date: February 20, 2019 -* Logging no longer uses root logger (also added option to change libEnsemble log level) (#105) -* Added wait_on_run option for job controller launch to block until jobs have started (#111) -* persis_info can be passed to sim as well as gen functions (#112) -* Postprocessing scripts added to create performance/utilization graphs (#102) -* New scaling test added (not part of current CI test suite) (#114) +* Logging no longer uses root logger (also added option to change libEnsemble log level). (#105) +* Added wait_on_run option for job controller launch to block until jobs have started. (#111) +* persis_info can be passed to sim as well as gen functions. (#112) +* Postprocessing scripts added to create performance/utilization graphs. (#102) +* New scaling test added (not part of current CI test suite). (#114) Release 0.4.0 ------------- :Date: November 7, 2018 -* Separated job controller classes into different modules including a base class (API change) -* Added central_mode run option to distributed type (MPI) job_controllers (API addition) (#93) -* Made poll and kill job methods (API change) -* In job_controller, set_kill_mode is removed and replaced by a wait argument for a hard kill (API change) -* Removed register module - incorporated into job_controller (API change) +* Separated job controller classes into different modules including a base class (API change). +* Added central_mode run option to distributed type (MPI) job_controllers (API addition). (#93) +* Made poll and kill job methods (API change). +* In job_controller, set_kill_mode is removed and replaced by a wait argument for a hard kill (API change). +* Removed register module - incorporated into job_controller (API change). * APOSMM has improved asynchronicity when batch mode is false (with new example). (#96) * Manager errors (instead of hangs) when alloc_f or gen_f don't return work when all workers are idle. (#95) @@ -195,17 +243,17 @@ Release 0.3.0 :Date: September 7, 2018 -* Issues with killing jobs have been fixed (#21) -* Fixed job_controller manager_poll to work with multiple jobs (#62) +* Issues with killing jobs have been fixed. (#21) +* Fixed job_controller manager_poll to work with multiple jobs. (#62) * API change: persis_info now included as an argument to libE and is returned from libE instead of gen_info * Gen funcs: aposmm_logic module renamed to aposmm. * New example gen and allocation functions. -* Updated Balsam launch script (with new Balsam workflow) -* History is dumped to file on manager or worker exception and MPI aborted (with exit code 1) (#46) -* Default logging level changed to DEBUG and redirected to file ensemble.log -* Added directory of standalone tests (comms, job kills, and nested MPI launches) -* Improved and speeded up unit tests (#68) -* Considerable documentation enhancements +* Updated Balsam launch script (with new Balsam workflow). +* History is dumped to file on manager or worker exception and MPI aborted (with exit code 1). (#46) +* Default logging level changed to DEBUG and redirected to file ensemble.log. +* Added directory of standalone tests (comms, job kills, and nested MPI launches). +* Improved and speeded up unit tests. (#68) +* Considerable documentation enhancements. :Known issues: diff --git a/MANIFEST.in b/MANIFEST.in index a163b642a..762b9bc8f 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,8 +4,10 @@ include *.rst include LICENSE include CONTRIBUTING include install/* +include .flake8 recursive-include libensemble * +recursive-include postproc_scripts * recursive-include examples * recursive-exclude * __pycache__ recursive-exclude * *.py[co] @@ -14,4 +16,4 @@ recursive-exclude * *.o recursive-exclude * *.npy recursive-exclude * *.gitignore -recursive-include docs *.pdf *.rst conf.py Makefile make.bat +recursive-include docs * diff --git a/README.rst b/README.rst index 00122a14c..e5f6f8ca3 100644 --- a/README.rst +++ b/README.rst @@ -64,6 +64,7 @@ Required dependencies: * Python_ 3.5 or above * NumPy_ +* psutil_ For libEnsemble running with the mpi4py parallelism: @@ -213,7 +214,7 @@ Resources David Bindel and John-Luke Navarro}, title = {{libEnsemble} Users Manual}, institution = {Argonne National Laboratory}, - number = {Revision 0.7.0}, + number = {Revision 0.7.1}, year = {2020}, url = {https://buildmedia.readthedocs.org/media/pdf/libensemble/latest/libensemble.pdf} } @@ -221,8 +222,8 @@ Resources .. after_resources_rst_tag .. _Balsam: https://www.alcf.anl.gov/support-center/theta/balsam -.. _Coveralls: https://coveralls.io/github/Libensemble/libensemble?branch=master .. _Conda: https://docs.conda.io/en/latest/ +.. _Coveralls: https://coveralls.io/github/Libensemble/libensemble?branch=master .. _DFO-LS: https://github.com/numericalalgorithmsgroup/dfols .. _GitHub: https://github.com/Libensemble/libensemble .. _libEnsemble mailing list: https://lists.mcs.anl.gov/mailman/listinfo/libensemble @@ -235,7 +236,8 @@ Resources .. _NumPy: http://www.numpy.org .. _petsc4py: https://bitbucket.org/petsc/petsc4py .. _PETSc: http://www.mcs.anl.gov/petsc -.. _poster: https://figshare.com/articles/LibEnsemble_PETSc_TAO-_Sustaining_a_library_for_dynamic_ensemble-based_computations/7765454 +.. _poster: https://figshare.com/articles/libEnsemble_A_Python_Library_for_Dynamic_Ensemble-Based_Computations/12559520 +.. _psutil: https://pypi.org/project/psutil/ .. _PyPI: https://pypi.org .. _pytest-cov: https://pypi.org/project/pytest-cov/ .. _pytest-timeout: https://pypi.org/project/pytest-timeout/ diff --git a/binder/environment.yml b/binder/environment.yml new file mode 100644 index 000000000..8e531460f --- /dev/null +++ b/binder/environment.yml @@ -0,0 +1,11 @@ +name: libensemble-python + +channels: + - conda-forge + +dependencies: + - numpy + - openmpi + - openmpi-mpicc + - mpi4py + - libensemble diff --git a/docs/FAQ.rst b/docs/FAQ.rst index d7e9f5bf8..88e5f0d62 100644 --- a/docs/FAQ.rst +++ b/docs/FAQ.rst @@ -25,15 +25,15 @@ This may also occur with two processes if you are using a persistent generator. The generator will occupy the one worker, leaving none to run simulation functions. **I keep getting: "Not enough processors per worker to honor arguments." when -using the executor. Can I submit tasks to allocated processors anyway?** +using the Executor. Can I submit tasks to allocated processors anyway?** Automatic partitioning of resources can be disabled if you want to oversubscribe -(often if testing on a local machine) by configuring the executor with +(often if testing on a local machine) by configuring the Executor with ``auto_resources=False``. For example:: exctr = MPIExecutor(auto_resources=False) -Note that the executor ``submit()`` method has a parameter ``hyperthreads`` +Note that the Executor ``submit()`` method has a parameter ``hyperthreads`` which will attempt to use all hyperthreads/SMT threads available if set to ``True``. **FileExistsError: [Errno 17] File exists: './ensemble'** @@ -127,7 +127,7 @@ to ``pdb``. How well this works varies by system. :: **Can I use the MPI Executor when running libEnsemble with multiprocessing?** -Yes. The executor type determines only how libEnsemble workers +Yes. The Executor type determines only how libEnsemble workers execute and interact with user applications and is independent of ``comms`` chosen for manager/worker communications. diff --git a/docs/conf.py b/docs/conf.py index 5bb555319..8a588cbc8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -30,7 +30,7 @@ class Mock(MagicMock): def __getattr__(cls, name): return MagicMock() -MOCK_MODULES = ['argparse', 'numpy', 'mpi4py' , 'dfols', 'scipy', 'numpy.lib', 'numpy.lib.recfunctions', 'math', 'petsc4py', 'PETSc', 'nlopt', 'scipy.spatial', 'scipy.spatial.distance', 'scipy.io', 'deap', 'Tasmanian', 'numpy.linalg'] +MOCK_MODULES = ['argparse', 'numpy', 'mpi4py' , 'dfols', 'scipy', 'numpy.lib', 'numpy.lib.recfunctions', 'math', 'petsc4py', 'PETSc', 'nlopt', 'scipy.spatial', 'scipy.spatial.distance', 'scipy.io', 'deap', 'Tasmanian', 'numpy.linalg', 'mpmath', 'psutil'] sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES) #from libensemble import * @@ -66,7 +66,8 @@ def __getattr__(cls, name): 'sphinx.ext.napoleon', # 'sphinx.ext.autosectionlabel', # 'sphinx.ext.intersphinx', - 'sphinx.ext.imgconverter'] + 'sphinx.ext.imgconverter', + 'sphinx.ext.mathjax'] # autosectionlabel_prefix_document = True # extensions = ['sphinx.ext.autodoc', 'sphinx.ext.napoleon', 'sphinx.ext.imgconverter'] #breathe_projects = { "libEnsemble": "../code/src/xml/" } @@ -109,9 +110,9 @@ def __getattr__(cls, name): # built documents. # # The short X.Y version. -version = '0.7.0' +version = '0.7.1' # The full version, including alpha/beta/rc tags. -release = '0.7.0' +release = '0.7.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -123,7 +124,7 @@ def __getattr__(cls, name): # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '**.ipynb_checkpoints'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' diff --git a/docs/data_structures/calc_status.rst b/docs/data_structures/calc_status.rst index 32c6d77e2..859476cb6 100644 --- a/docs/data_structures/calc_status.rst +++ b/docs/data_structures/calc_status.rst @@ -14,7 +14,7 @@ status of the calculation, since it could include multiple application runs. It can be added as a third return variable in sim_f or gen_f functions. The calc_status codes are in the ``libensemble.message_numbers`` module. -Example of ``calc_status`` used along with :ref:`executor` in sim_f: +Example of ``calc_status`` used along with :ref:`Executor` in sim_f: .. code-block:: python :linenos: diff --git a/docs/data_structures/libE_specs.rst b/docs/data_structures/libE_specs.rst index e451ecfe9..b8180cf6f 100644 --- a/docs/data_structures/libE_specs.rst +++ b/docs/data_structures/libE_specs.rst @@ -25,17 +25,26 @@ Specifications for libEnsemble:: This will create a directory for each simulation, even if no sim_input_dir is specified. If False, all workers operate within the ensemble directory described below. Default: True + 'gen_dirs_make' [boolean] : + Whether to make generator-instance specific calculation directories for each gen call. + This will create a directory for each generator call, even if no gen_input_dir is specified. + If False, all workers operate within the ensemble directory. + Default: True 'ensemble_dir_path' [string] : - Path to main ensemble directory containing calculation (sim) directories. + Path to main ensemble directory containing calculation directories. Can serve as single working directory for all workers, or contain calculation directories. Default: './ensemble' 'use_worker_dirs' [boolean] : - Whether to organize calculation (sim) directories under worker-specific directories. + Whether to organize calculation directories under worker-specific directories. Default: False 'sim_dir_copy_files' [list] : List of paths to files or directories to copy into each sim dir, or ensemble dir. 'sim_dir_symlink_files' [list] : List of paths to files or directories to symlink into each sim dir. + 'gen_dir_copy_files' [list] : + List of paths to files or directories to copy into each gen dir, or ensemble dir. + 'gen_dir_symlink_files' [list] : + List of paths to files or directories to symlink into each gen dir. 'ensemble_copy_back' [boolean] : Whether to copy back directories within ensemble_dir_path back to launch location. Useful if ensemble_dir placed on node-local storage. @@ -43,6 +52,9 @@ Specifications for libEnsemble:: 'sim_input_dir' [string] : Copy this directory and it's contents for each simulation-specific directory. If not using calculation directories, contents are copied to the ensemble directory. + 'gen_input_dir' [string] : + Copy this directory and it's contents for each generator-instance specific directory. + If not using calculation directories, contents are copied to the ensemble directory. 'profile_worker' [boolean] : Profile using cProfile. Default: False 'disable_log_files' [boolean] : @@ -67,7 +79,7 @@ Specifications for libEnsemble:: libE_specs = {'comm': MPI.COMM_WORLD, 'comms': 'mpi', 'save_every_k_gens': 1000, - 'make_sim_dirs: True, + 'sim_dirs_make: True, 'ensemble_dir_path': '/scratch/ensemble' 'profile_worker': False} diff --git a/docs/dev_guide/release_management/release_platforms/rel_pypi.rst b/docs/dev_guide/release_management/release_platforms/rel_pypi.rst index 8c2d7ba54..879d3914f 100644 --- a/docs/dev_guide/release_management/release_platforms/rel_pypi.rst +++ b/docs/dev_guide/release_management/release_platforms/rel_pypi.rst @@ -7,8 +7,10 @@ libEnsemble is released on the Python Package Index (commonly known as PyPI). This enables users to ``pip install`` the package. The package is stored on PyPI in the form of a source distribution (commonly -known as a tarball). The tarball could be obtained from GitHub, although -historically this has been created with a checkout of libEnsemble from git. +known as a tarball). The tarball should be created as detailed below (which +creates the distribution package using the MANIFEST.in file in the git root +directory. Do not use the tarball on GitHub, which does not follow MANIFEST.in +and does not contain the required PKG-INFO file. You will need logon credentials for the libEnsemble PyPI. You will also need twine (which can be pip or Conda installed). diff --git a/docs/dev_guide/release_management/release_process.rst b/docs/dev_guide/release_management/release_process.rst index 38e850e3b..40cb1af3c 100644 --- a/docs/dev_guide/release_management/release_process.rst +++ b/docs/dev_guide/release_management/release_process.rst @@ -16,18 +16,23 @@ Before release - Release notes for this version are added to the documentation with release date, including a list of supported (tested) platforms. -- Version number is updated wherever it appears +- Version number is updated wherever it appears (and ``+dev`` suffix is removed) (in ``setup.py``, ``libensemble/__init__.py``, ``README.rst`` and twice in ``docs/conf.py``) - Year in ``README.rst`` under *Citing libEnsemble* and in ``docs/conf.py`` is checked for correctness. - ``setup.py`` and ``libensemble/__init__.py`` are checked to ensure all information is up to date. +- ``MANIFEST.in`` is checked. Locally, try out ``python setup.py sdist`` and check created tarball. + contains correct files and directories for PyPI package. + - Tests are run with source to be released (this may iterate): - On-line CI (currently Travis) tests must pass. - Scaling tests must be run on HPC platforms listed as supported in release notes. + Test variants by platform, launch mechanism, scale, and other factors can + be configured and exported by the libE-Templater_. - Coverage must not have decreased unless there is a justifiable reason. @@ -57,6 +62,8 @@ An administrator will take the following steps. - If the merge was made from a release branch (instead of develop), merge this branch into develop. +- Create a new commit on develop that appends ``+dev`` to the version number (wherever is appears). + After release ------------- @@ -64,3 +71,5 @@ After release on the kanban project board (inc. the release checklist). - Email libEnsemble mailing list, and notify the `everyone` channel in the libEnsemble Slack workspace. + +.. _libE-Templater: https://github.com/Libensemble/libE-templater diff --git a/docs/examples/alloc_funcs.rst b/docs/examples/alloc_funcs.rst index ec80d0b32..2ea4cca6d 100644 --- a/docs/examples/alloc_funcs.rst +++ b/docs/examples/alloc_funcs.rst @@ -8,6 +8,8 @@ Below are example allocation functions available in libEnsemble. .. note:: The default alloc_func is give_sim_work_first. +.. _gswf_label: + give_sim_work_first ------------------- .. automodule:: give_sim_work_first diff --git a/docs/examples/aposmm.rst b/docs/examples/aposmm.rst index 094efbe22..e676cfa23 100644 --- a/docs/examples/aposmm.rst +++ b/docs/examples/aposmm.rst @@ -2,7 +2,7 @@ APOSMM ----------------- Asynchronously Parallel Optimization Solver for finding Multiple Minima -(APOSMM) coordinates concurrent local optimization runs in order to identifying +(APOSMM) coordinates concurrent local optimization runs in order to identify many local minima. Configuring APOSMM @@ -21,6 +21,10 @@ Where ``optimizers`` is a string (or list of strings) from the available options To see the optimization algorithms supported, see `LocalOptInterfacer`_. +.. seealso:: + + :doc:`Persistent APOSMM Tutorial<../tutorials/aposmm_tutorial>` + Persistent APOSMM ^^^^^^^^^^^^^^^^^ diff --git a/docs/examples/calling_scripts.rst b/docs/examples/calling_scripts.rst index 891c37642..d7d5a9f1c 100644 --- a/docs/examples/calling_scripts.rst +++ b/docs/examples/calling_scripts.rst @@ -27,7 +27,7 @@ Electrostatic Forces with Executor This example is from a test for evaluating the scaling capabilities of libEnsemble by calculating particle electrostatic forces through a user application. This -application is registered with either the MPI or Balsam executor, then submitted +application is registered with either the MPI or Balsam Executor, then submitted for execution in the ``sim_f``. Note the use of the ``parse_args()`` and ``save_libE_output()`` convenience functions from the :doc:`tools<../utilities>` module. diff --git a/docs/executor/executor.rst b/docs/executor/executor.rst index 4965cc246..ae606ae7c 100644 --- a/docs/executor/executor.rst +++ b/docs/executor/executor.rst @@ -6,7 +6,7 @@ Executor Module See this :doc:`example` for usage. -See the executor APIs for optional arguments. +See the Executor APIs for optional arguments. .. toctree:: :maxdepth: 1 @@ -20,11 +20,11 @@ See the executor APIs for optional arguments. Task Class ---------- -Tasks are created and returned through the executor ``submit()`` function. Tasks +Tasks are created and returned through the Executor ``submit()`` function. Tasks can be polled and killed with the respective poll and kill functions. Task information can be queried through the task attributes below and the query functions. Note that the task attributes are updated only when they are -polled/killed (or through other task or executor functions). +polled/killed (or through other task or Executor functions). .. autoclass:: Task :members: @@ -39,7 +39,7 @@ Task Attributes Following is a list of task status and configuration attributes that can be retrieved from a task. -.. note:: These should not be set directly. Tasks are launched by the executor, +.. note:: These should not be set directly. Tasks are launched by the Executor, and task information can be queried through the task attributes below and the query functions. @@ -65,4 +65,4 @@ Run configuration attributes - some will be autogenerated: :task.stdout: (string) Name of file where the standard output of the task is written (in task.workdir) :task.stderr: (string) Name of file where the standard error of the task is written (in task.workdir) -A list of executor and task functions can be found under the ``executor`` module. +A list of Executor and task functions can be found under the ``executor`` module. diff --git a/docs/executor/mpi_executor.rst b/docs/executor/mpi_executor.rst index 44d5e3d43..019de4ec3 100644 --- a/docs/executor/mpi_executor.rst +++ b/docs/executor/mpi_executor.rst @@ -17,7 +17,7 @@ Class-specific Attributes ------------------------- Class-specific attributes can be set directly to alter the behavior of the MPI -executor. However, they should be used with caution, because they may not +Executor. However, they should be used with caution, because they may not be implemented in other executors. :max_submit_attempts: (int) Maximum number of launch attempts for a given diff --git a/docs/executor/overview.rst b/docs/executor/overview.rst index 871ac3c36..a33c1b3f9 100644 --- a/docs/executor/overview.rst +++ b/docs/executor/overview.rst @@ -4,7 +4,7 @@ Executor Overview A typical libEnsemble workflow will include launching tasks from a :ref:`sim_f` (or :ref:`gen_f`) running on a worker. We use "task" to represent an application submission by libEnsemble to the system, -may be a supercomputer, cluster, or other compute resource. +may be the compute nodes of a supercomputer, cluster, or other compute resource. The task could be launched via a subprocess call to ``mpirun`` or an alternative launcher such as ``aprun`` or ``jsrun``. The ``sim_f`` may then monitor this task, @@ -13,9 +13,9 @@ check output, and possibly kill the task. An **Executor** interface is provided by libEnsemble to remove the burden of system interaction from the user and ease the writing of portable user scripts that launch applications. The Executor provides the key functions: ``submit()``, -``poll()``, and ``kill()``. Task attributes can be queried to determine the status -following each of these commands. Functions are also provided to access and -interrogate files in the task's working directory. +``poll()``, ``wait()``, and ``kill()``. Task attributes can be queried to determine +the status following each of these commands. Functions are also provided to access +and interrogate files in the task's working directory. The main Executor class is an abstract class and is inherited by the MPIExecutor, for direct running of MPI applications. Another Executor is the BalsamMPIExecutor, @@ -45,11 +45,17 @@ In calling function:: exctr.register_calc(full_path=sim_app, calc_type='sim') +.. note:: + The *Executor* set up in the calling script is stored as a class attribute and + does **not** have to be passed to *libE*. It is extracted via *Executor.executor* + in the sim function (regardless of type). + In user sim func:: import time + from libensemble.executors.executor import Executor - # Will return executor (whether MPI or inherited such as Balsam). + # Will return Executor (whether MPI or inherited such as Balsam). exctr = Executor.executor task = exctr.submit(calc_type='sim', num_procs=8, app_args='input.txt', @@ -83,7 +89,7 @@ In user sim func:: See the :doc:`executor` interface for API. For a more realistic example see -the :doc:`Electrostatic Forces example <../examples/calling_scripts>`, +the :doc:`Electrostatic Forces example <../tutorials/executor_forces_tutorial>`, which launches the ``forces.x`` application as an MPI task. .. note:: @@ -91,9 +97,26 @@ which launches the ``forces.x`` application as an MPI task. **"jobs"** within Balsam, including within Balsam's database and when describing the state of a completed submission. +Note that applications can also be registered to the Executor using a name. The +equivalent lines in the above example would be: + +Calling script:: + + exctr.register_calc(full_path='/path/to/my/exe', app_name='forces_app') + +User sim func:: + + task = exctr.submit(app_name='forces_app', num_procs=8, app_args='input.txt', + stdout='out.txt', stderr='err.txt') + +The ``app_name`` can be any identfier, while ``full_path`` is the application to +be run. This approach allows multiple applications to be registered. + The MPIExecutor autodetects system criteria such as the appropriate MPI launcher and mechanisms to poll and kill tasks. It will also partition resources amongst -workers, ensuring that runs utilise different resources (e.g. nodes). +workers, ensuring that runs utilise different resources (e.g. nodes). The +``zero_resource_workers`` list option specifies workers that will not need +resources (e.g. a persistent generator might run on worker 1). Furthermore, the MPIExecutor offers resilience via the feature of re-launching tasks that fail because of system factors. @@ -103,4 +126,8 @@ Balsam_. Currently, these Executors launch at the application level within an existing resource pool. However, submissions to a batch scheduler may be supported in future Executors. +See :doc:`Running on HPC Systems<../platforms/platforms_index>` to see, with +diagrams, how common Executor options such as ``central_mode`` affect the +run configuration on clusters and supercomputers. + .. _Balsam: https://balsam.readthedocs.io/en/latest/ diff --git a/docs/function_guides/allocator.rst b/docs/function_guides/allocator.rst new file mode 100644 index 000000000..ca29c1b88 --- /dev/null +++ b/docs/function_guides/allocator.rst @@ -0,0 +1,126 @@ +Allocation Functions +==================== + +Although the included allocation functions, or ``alloc_f``'s are sufficient for +most users, those who want to fine-tune how data is passed to their ``gen_f`` +and ``sim_f`` can write their own. The ``alloc_f`` is unique since it is called +by the libEnsemble's manager instead of a worker. + +Most ``alloc_f`` function definitions written by users resemble:: + + def my_allocator(W, H, sim_specs, gen_specs, alloc_specs, persis_info): + +Where :doc:`W<../data_structures/worker_array>` is an array containing information +about each worker's state, and ``H`` is the *trimmed* History array, +containing rows initialized by the generator. + +Inside an ``alloc_f``, a :doc:`Work dictionary<../data_structures/work_dict>` is +instantiated:: + + Work = {} + +then populated with integer keys ``i`` for each worker and dictionary values to +give to those workers. An example Work dictionary from a run of +the ``test_1d_sampling.py`` regression test resembles:: + + { + 1: { + 'H_fields': ['x'], + 'persis_info': {'rand_stream': RandomState(...) at ..., 'worker_num': 1}, + 'tag': 1, + 'libE_info': {'H_rows': array([368])} + }, + + 2: { + 'H_fields': ['x'], + 'persis_info': {'rand_stream': RandomState(...) at ..., 'worker_num': 2}, + 'tag': 1, + 'libE_info': {'H_rows': array([369])} + }, + + 3: { + 'H_fields': ['x'], + 'persis_info': {'rand_stream': RandomState(...) at ..., 'worker_num': 3}, + 'tag': 1, + 'libE_info': {'H_rows': array([370])} + }, + + 4: { + 'H_fields': ['x'], + 'persis_info': {'rand_stream': RandomState(...) at ..., 'worker_num': 4}, + 'tag': 1, + 'libE_info': {'H_rows': array([371])} + } + } + +Based on information from the API reference above, this Work dictionary +describes instructions for each of the four workers to call the ``sim_f`` +with data from the ``'x'`` field and a given ``'H_row'`` from the +History array, and also pass ``persis_info``. + +Constructing these arrays and determining which workers are available +for receiving data is simplified by several functions available within the +``libensemble.tools.alloc_support`` module: + +.. currentmodule:: libensemble.tools.alloc_support +.. autofunction:: avail_worker_ids + +Many ``alloc_f`` routines loop over the available workers returned by the above +function to construct their Work dictionaries with the help of the following two +functions. + +.. currentmodule:: libensemble.tools.alloc_support +.. autofunction:: sim_work + +.. currentmodule:: libensemble.tools.alloc_support +.. autofunction:: gen_work + +Note that these two functions *append* an entry in-place to the Work dictionary +and additional parameters are appended to ``libE_info``. + +In practice, the structure of many allocation functions resemble:: + + Work = {} + ... + for ID in avail_worker_ids(W): + ... + if some_condition: + sim_work(Work, ID, chosen_H_fields, chosen_H_rows, persis_info) + ... + + if another_condition: + gen_work(Work, ID, chosen_H_fields, chosen_H_rows, persis_info) + ... + + return Work, persis_info + +The Work dictionary is returned to the manager with ``persis_info``. If ``1`` +is returned as third value, this instructs the run to stop. + +.. note:: An error occurs when the ``alloc_f`` returns nothing while + all workers are idle + +The final three functions available in the ``alloc_support`` module +are primarily for evaluating running generators: + +.. currentmodule:: libensemble.tools.alloc_support +.. autofunction:: test_any_gen + +.. currentmodule:: libensemble.tools.alloc_support +.. autofunction:: count_gens + +.. currentmodule:: libensemble.tools.alloc_support +.. autofunction:: count_persis_gens + +Descriptions of included allocation functions can be found :doc:`here<../examples/alloc_funcs>`. +The default allocation function used by libEnsemble if one isn't specified is +``give_sim_work_first``. During its worker ID loop, it checks if there's unallocated +work and assigns simulations for that work if so. Otherwise, it initializes +generators for up to ``'num_active_gens'`` instances. Other settings like +``batch_mode`` and blocking of non-active workers is also supported. See +:ref:`here` for more information about ``give_sim_work_first``. + +For a shorter, simpler example, here is the ``fast_alloc`` allocation function: + +.. literalinclude:: ../../libensemble/alloc_funcs/fast_alloc.py + :caption: /libensemble/alloc_funcs/fast_alloc.py diff --git a/docs/function_guides/function_guide_index.rst b/docs/function_guides/function_guide_index.rst new file mode 100644 index 000000000..31248910a --- /dev/null +++ b/docs/function_guides/function_guide_index.rst @@ -0,0 +1,20 @@ +====================== +Writing User Functions +====================== + +libEnsemble coordinates ensembles of calculations performed by three main +functions: a :ref:`Generator Function`, a :ref:`Simulator Function`, +and an :ref:`Allocation Function`, or ``gen_f``, ``sim_f``, and +``alloc_f`` respectively. These are all referred to as User Functions. Although +libEnsemble includes several ready-to-use User Functions like +:doc:`APOSMM<../examples/aposmm>`, it's expected many users will write their own or +adjust included functions for their own use-cases. +These guides describe common development patterns and optional components for +each kind of User Function. + +.. toctree:: + :maxdepth: 2 + + generator + simulator + allocator diff --git a/docs/function_guides/generator.rst b/docs/function_guides/generator.rst new file mode 100644 index 000000000..0ca4d69d3 --- /dev/null +++ b/docs/function_guides/generator.rst @@ -0,0 +1,112 @@ +Generator Functions +=================== + +As described in the :ref:`API`, the ``gen_f`` is called by a +libEnsemble worker via the following:: + + out = gen_f(H[gen_specs['in']][sim_ids_from_allocf], persis_info, gen_specs, libE_info) + +In practice, most ``gen_f`` function definitions written by users resemble:: + + def my_generator(H, persis_info, gen_specs, libE_info): + +Where :doc:`H<../data_structures/history_array>` is a selection of the +:doc:`History array<../history_output>`, determined by sim IDs from the +``alloc_f``, :doc:`persis_info<../data_structures/persis_info>` is a dictionary +containing state information, :doc:`gen_specs<../data_structures/gen_specs>` is a +dictionary containing pre-defined parameters for the ``gen_f``, and ``libE_info`` +is a dictionary containing libEnsemble-specific entries. See the API above for +more detailed descriptions of the parameters. + +.. note:: + + If the ``gen_f`` is a persistent generator, then ``gen_specs['in']`` will often be + empty if the ``alloc_f`` determines what fields to send to the generator. + +Typically users start by extracting their custom parameters initially defined +within ``gen_specs['user']`` in the calling script and defining a *local* History +array based on the datatype in ``gen_specs['out']``, to be returned. For example:: + + batch_size = gen_specs['user']['batch_size'] + local_H_out = np.zeros(batch_size, dtype=gen_specs['out']) + +This array should be populated by whatever values are generated within +the function. Finally, this array should be returned to libEnsemble +alongside ``persis_info``:: + + return local_H_out, persis_info + +.. note:: + + State ``gen_f`` information like checkpointing should be + appended to ``persis_info``. + +Persistent Generators +--------------------- + +While normal generators return after completing their calculation, persistent +generators receive Work units, perform computations, and communicate results +directly to the manager in a loop, not returning until explicitly instructed by +the manager. The calling worker becomes a dedicated :ref:`persistent worker`. +A ``gen_f`` is initiated as persistent by the ``alloc_f``, which also determines +which structures are sent to the ``gen_f``. In such cases, ``gen_specs`` is often +empty. + +Many users prefer persistent generators since they do not need to be +re-initialized every time their past work is completed and evaluated by a +simulation, and an can evaluate returned simulation results over the course of +an entire libEnsemble routine as a single function instance. + +Functions for a persistent generator to communicate directly with the manager +are available in the :ref:`libensemble.tools.gen_support` module. +Additional necessary resources are the status tags ``STOP_TAG``, ``PERSIS_STOP``, and +``FINISHED_PERSISTENT_GEN_TAG`` from ``libensemble.message_numbers``, with return +values from the ``gen_support`` functions compared to these tags to determine when +the generator should break its loop and return. + +Implementing the above functions is relatively simple: + +.. currentmodule:: libensemble.tools.gen_support +.. autofunction:: send_mgr_worker_msg + +This function call typically resembles:: + + send_mgr_worker_msg(libE_info['comm'], local_H_out[selected_IDs]) + +Note that ``send_mgr_worker_msg()`` has no return. + +.. currentmodule:: libensemble.tools.gen_support +.. autofunction:: get_mgr_worker_msg + +This function call typically resembles:: + + tag, Work, calc_in = get_mgr_worker_msg(libE_info['comm']) + + if tag in [STOP_TAG, PERSIS_STOP]: + cleanup() + break + +The logic following the function call is typically used to break the persistent +generator's main loop and return. + +.. currentmodule:: libensemble.tools.gen_support +.. autofunction:: sendrecv_mgr_worker_msg + +This function performs both of the previous functions in a single statement. Its +usage typically resembles:: + + tag, Work, calc_in = sendrecv_mgr_worker_msg(libE_info['comm'], local_H_out[selected_IDs]) + if tag in [STOP_TAG, PERSIS_STOP]: + cleanup() + break + +Once the persistent generator's loop has been broken because of +the tag from the manager, it should return with an additional tag:: + + return local_H_out, persis_info, FINISHED_PERSISTENT_GEN_TAG + +See :doc:`calc_status<../data_structures/calc_status>` for more information about +the message tags. + +Examples of normal and persistent generator functions +can be found :doc:`here<../examples/gen_funcs>`. diff --git a/docs/function_guides/simulator.rst b/docs/function_guides/simulator.rst new file mode 100644 index 000000000..4b602b61d --- /dev/null +++ b/docs/function_guides/simulator.rst @@ -0,0 +1,45 @@ +Simulator Functions +=================== + +As described in the :ref:`API`, the ``sim_f`` is called by a +libEnsemble worker via a similar interface to the ``gen_f``:: + + out = sim_f(H[sim_specs['in']][sim_ids_from_allocf], persis_info, sim_specs, libE_info) + +In practice, most ``sim_f`` function definitions written by users resemble:: + + def my_simulator(H, persis_info, sim_specs, libE_info): + +Where :doc:`sim_specs<../data_structures/sim_specs>` is a +dictionary containing pre-defined parameters for the ``sim_f``, and the other +parameters serve similar purposes to those in the ``gen_f``. + +The pattern of setting up a local ``H``, parsing out parameters from +``sim_specs``, performing calculations, and returning the local ``H`` +with ``persis_info`` should be familiar:: + + batch_size = sim_specs['user']['batch_size'] + local_H_out = np.zeros(batch_size, dtype=sim_specs['out']) + + ... # Perform simulation calculations + + return local_H_out, persis_info + +Simulator functions can also return a :doc:`calc_status<../data_structures/calc_status>` +integer attribute from the ``libensemble.message_numbers`` module to be logged. + +Descriptions of included simulator functions can be found :doc:`here<../examples/sim_funcs>`. + +The :doc:`Simple Sine tutorial<../tutorials/local_sine_tutorial>` is an +excellent introduction for writing simple user functions and using them +with libEnsemble. + +Executor +-------- + +libEnsemble's Executor is commonly used within simulator functions to launch +and monitor applications. An excellent overview is already available +:doc:`here<../executor/overview>`. + +See the :doc:`Executor with Electrostatic Forces tutorial<../tutorials/executor_forces_tutorial>` +for an additional example to try out. diff --git a/docs/history_output.rst b/docs/history_output.rst index 52e32e66a..766f83e18 100644 --- a/docs/history_output.rst +++ b/docs/history_output.rst @@ -61,34 +61,44 @@ Output Working Directory Structure libEnsemble features configurable output and working directory structuring for storing results at every step of a calculation, or directing workers to perform calculations on separate filesystems or in other directories. This is helpful -for users performing simulations who want to take advantage of high-speed -scratch spaces or disks, or organize their I/O by application run. - -With these features enabled, each time a worker initiates a simulation routine -it automatically enters a configurable directory, either a new directory specific -to that worker and simulation instance or a shared directory for all workers. -Where these directories are created or what files they contain is configurable -through settings in :ref:`libE_specs`. Defining any -compatible settings initiates this system with default settings for unspecified -options. Each setting will be described in detail here: +for users performing simulations or using high-resource generator functions who +want to take advantage of high-speed scratch spaces or disks, or organize their +I/O by application run. + +With these features enabled, each time a worker initiates a user function routine +(``gen_f`` or ``sim_f``) it automatically enters a configurable directory, +either a new directory specific to that worker and function instance or a shared +directory for all workers. Where these directories are created or what files +they contain is configurable through settings in :ref:`libE_specs`. +Defining any compatible settings initiates this system with default settings for +unspecified options. Each setting will be described in detail here: * ``'sim_dirs_make'``: Boolean. Enables per-simulation directories with default - settings. Directories are labeled in the form ``'sim0-worker1'`` and without - further configuration, placed in the ensemble directory ``./ensemble``, - relative to where libEnsemble was launched. Default: ``True``. If ``False``, - all workers will operate within the ensemble directory without producing - per-simulation directories. + settings. Directories are labeled in the form ``'sim0-worker1'``, by sim ID + and initiating worker. Without further configuration, directories are placed + in the ensemble directory ``./ensemble``, relative to where libEnsemble was + launched. Default: ``True`` with other sim_dir options enabled. If + ``False``, all workers will operate within the ensemble directory without + producing per-simulation directories. + +* ``'gen_dirs_make'``: Boolean. Enabled per-generator instance directories with + default settings. Directories are labeled in the form ``'gen1-worker1'``. by + initiating worker and how many times that worker has initiated the generator. + These behave similarly to simulation directories. Default: ``True`` with + other gen_dir options enabled. * ``'ensemble_dir_path'``: This location, typically referred to as the ensemble - directory, is where each worker places its simulation directories. If not - specified, simulation directories are placed in ``./ensemble``, relative to - where libEnsemble was launched. If ``'sim_dirs_make'`` is ``False``, all workers - will run within this directory. On supported systems, writing to local-node - storage is possible and recommended for increased performance.:: + directory, is where each worker places its calculation directories. If not + specified, calculation directories are placed in ``./ensemble``, relative to + where libEnsemble was launched. If ``'sim_dirs_make'`` is ``False``, workers + initiating simulation instances will run within this directory. This behavior + is similar when ``'gen_dirs_make'`` is ``False``. On supported systems, + writing to local-node storage is possible and recommended for increased + performance.:: libE_specs['ensemble_dir_path'] = "/scratch/my_ensemble" -* ``'use_worker_dirs'``: Boolean. Sorts simulation directories into +* ``'use_worker_dirs'``: Boolean. Sorts calculation directories into per-worker directories at runtime. Particularly useful for organization when running with multiple workers on global scratch spaces or the same node, and may produce performance benefits. Default: ``False``. @@ -97,6 +107,7 @@ options. Each setting will be described in detail here: - /ensemble_dir - /sim0-worker1 + - /gen1-worker1 - /sim1-worker2 ... @@ -105,6 +116,7 @@ options. Each setting will be described in detail here: - /ensemble_dir - /worker1 - /sim0 + - /gen1 - /sim4 ... - /worker2 @@ -116,12 +128,19 @@ options. Each setting will be described in detail here: application, this may be helpful for copying over configuration files for each launch. +* ``'gen_dir_copy_files'``: A list of paths for files to copy into generator + directories. If ``'gen_dirs_make'`` is False, these files are copied to the + ensemble directory. + * ``'sim_dir_symlink_files'``: A list of paths for files to symlink into simulation directories. +* ``'gen_dir_symlink_files'``: A list of paths for files to symlink into + generator directories. + * ``'ensemble_copy_back'``: Boolean. Instructs the manager to create an empty - directory where libEnsemble was launched where workers copy back their simulation - directories on a run's conclusion or an exception. Especially useful when + directory where libEnsemble was launched where workers copy back their calculation + directories when a run concludes or an exception occurs. Especially useful when ``'ensemble_dir_path'`` has been set to some scratch space or another temporary location. Default: ``False``. @@ -130,6 +149,11 @@ options. Each setting will be described in detail here: of new simulation directories. If ``'sim_dirs_make'`` is False, this directory's contents are copied into the ensemble directory. +* ``'sim_input_dir'``: A path to a directory to copy for generator + directories. This directory and it's contents are copied to form the base + of new generator directories. If ``'gen_dirs_make'`` is False, this directory's + contents are copied into the ensemble directory. + See the regression tests ``test_sim_dirs_per_calc.py`` and ``test_use_worker_dirs.py`` for examples of many of these settings. See ``test_sim_input_dir_option.py`` for examples of using these settings diff --git a/docs/images/ANL_CMYK.png b/docs/images/ANL_CMYK.png new file mode 100644 index 000000000..a3142fdef Binary files /dev/null and b/docs/images/ANL_CMYK.png differ diff --git a/docs/images/ECP_logo.png b/docs/images/ECP_logo.png new file mode 100755 index 000000000..9d9462464 Binary files /dev/null and b/docs/images/ECP_logo.png differ diff --git a/docs/images/basic_6hc.png b/docs/images/basic_6hc.png new file mode 100644 index 000000000..2506a6133 Binary files /dev/null and b/docs/images/basic_6hc.png differ diff --git a/docs/images/central_balsam.png b/docs/images/central_balsam.png new file mode 100644 index 000000000..d18508297 Binary files /dev/null and b/docs/images/central_balsam.png differ diff --git a/docs/images/centralized_Balsam_ThS.png b/docs/images/centralized_Balsam_ThS.png deleted file mode 100644 index 8ab646c8a..000000000 Binary files a/docs/images/centralized_Balsam_ThS.png and /dev/null differ diff --git a/docs/images/centralized_Balsam_ThS.xml b/docs/images/centralized_Balsam_ThS.xml deleted file mode 100644 index c5256c2f8..000000000 --- a/docs/images/centralized_Balsam_ThS.xml +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/images/centralized_Bb.png b/docs/images/centralized_Bb.png deleted file mode 100644 index fcb0d1ff9..000000000 Binary files a/docs/images/centralized_Bb.png and /dev/null differ diff --git a/docs/images/centralized_Bb.xml b/docs/images/centralized_Bb.xml deleted file mode 100644 index b4dbc89e1..000000000 --- a/docs/images/centralized_Bb.xml +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/images/centralized_MOM_ThS.png b/docs/images/centralized_MOM_ThS.png deleted file mode 100644 index a3489071e..000000000 Binary files a/docs/images/centralized_MOM_ThS.png and /dev/null differ diff --git a/docs/images/centralized_MOM_ThS.xml b/docs/images/centralized_MOM_ThS.xml deleted file mode 100644 index 297c1b0f8..000000000 --- a/docs/images/centralized_MOM_ThS.xml +++ /dev/null @@ -1,109 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/images/centralized_new.png b/docs/images/centralized_new.png new file mode 100644 index 000000000..66dce35b2 Binary files /dev/null and b/docs/images/centralized_new.png differ diff --git a/docs/images/combined_ThS.png b/docs/images/combined_ThS.png deleted file mode 100644 index 651fc4d7e..000000000 Binary files a/docs/images/combined_ThS.png and /dev/null differ diff --git a/docs/images/combined_ThS.xml b/docs/images/combined_ThS.xml deleted file mode 100644 index edf1aec9a..000000000 --- a/docs/images/combined_ThS.xml +++ /dev/null @@ -1,240 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/images/diagram_xml/centralized_new.xml b/docs/images/diagram_xml/centralized_new.xml new file mode 100644 index 000000000..e117931c0 --- /dev/null +++ b/docs/images/diagram_xml/centralized_new.xml @@ -0,0 +1,2 @@ + +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 \ No newline at end of file diff --git a/docs/images/diagram_xml/distributed_new.xml b/docs/images/diagram_xml/distributed_new.xml new file mode 100644 index 000000000..39509b0eb --- /dev/null +++ b/docs/images/diagram_xml/distributed_new.xml @@ -0,0 +1,2 @@ + +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 \ No newline at end of file diff --git a/docs/images/diagram_xml/persis_add_worker.xml b/docs/images/diagram_xml/persis_add_worker.xml new file mode 100644 index 000000000..d056d38d0 --- /dev/null +++ b/docs/images/diagram_xml/persis_add_worker.xml @@ -0,0 +1,2 @@ + +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 \ No newline at end of file diff --git a/docs/images/diagram_xml/persis_wasted_node.xml b/docs/images/diagram_xml/persis_wasted_node.xml new file mode 100644 index 000000000..74a9c154c --- /dev/null +++ b/docs/images/diagram_xml/persis_wasted_node.xml @@ -0,0 +1,2 @@ + +7L3XluNIsiX6Nf04s6DFI0EQAAlJEIR6OQuS0Fp//bgzMjOYXTXdfeb0mb6zbketqiAdyt3NbNve5o6ov+DnehOHoMvUNk6qv2BIvP0F5/+CYShCs+AXbNm/WiiS+Gp4DXn846Tvhkd+JD+v/NE653Ey/nbi1LbVlHe/N0Zt0yTR9FtbMAzt+vtpaVv9/tQueCV/aHhEQfXHViePp+yrlSGR73YpyV/ZzyejyI8jdfDz5B8NYxbE7frRhF/+gp+Htp2+PtXbOang5P2cl6/rhP/N0V8dG5Jm+kcuqJnlNWnE//gP5z8sypT2/zih0f+gyK/bLEE1/xjxua27eUpAowasCX7hP/o/7T8nZWjnJk7gfdG/4Nya5VPy6IIIHl2BG4C2bKqrH4d/jRt+eVXBCC2CgM9RW+fRj89VECYVF0Tl633vc1u1AzjUtA24KZfmVfWz6S8YnpLwH9jeNtMPl8GQH9+FoM4r6GzKHOVxADp+bpuxhV3ngip/NeBQBKYsATfjlmSYcmDs048DUws7P05DW/4yN/Gr5aML1PsHHOmSIa8TcDc4AXnz+jGeFn6bYDfInx37uPrrB7T/0YY/zAo7lmwfTT9sKiYteNqwg1N+HKWZH/71I8AI/Mf39dtdUfZHW/bpqj89NfgRIq9f9/72IvDhhyP9Z5yK+PtOhf3bqf4fciocJf/VToUzf9+p0H871f9DToUh/3Kkwv4Bp0L+7VT/H3YqgvrdqVDmj06FkX/iVDjz3+ZU7B+c6r/oQn8wyF95REwmTEz8cd7/YDcGC/G33X7zyn+GGRjy99jG/sQMyJ+ZAfsnmGGzUtzSVqR8kCJwRjY2L87/QIk/zHoSA27/42s7TFn7apuguny3ct92ebvxr3OUFobGe7aKZJr2H2EXzFP7u62SLZ9cePn/xMgfX72PQ/z249bvL/vPLw0Y8NdVOPbzu/d58Pu697ffLjR+xuCPxt9smzTxCcqgbzQBLUIOp5JH/uBbf4oqf+VrQcKk0Z+CQsQkYfrLn+Bk/+ZNf+JgYzsPUfK3sv4P8TEFwyuZ/saJJPXn/jkkVTDly+89+ef7Gvmv9bX/I1cj/zOe9v9Lp8LJf6lTUf9ap6L/j7yK/rdb/V23+r+FVX+zm7/RE6qafkwnLJIFP8ZJ9TOsTnF/4JK/joBPL/hbDZrgBTLQjxuBjn3d6+vof43+/OeN+g9x7r/2nb/Nwb+OtkOcDH915I/s7J/Ap0jsr/gU8ycCHPsTPkX+t9Ep+l+JRj8/e7+lu78HRr8h0Xe++5dgUcpESfSnbhsyJPGm6/9MLCL/Ud70X01x70vBVAX7xwldmzfT+HFnAzZ8lAKIvypaUn9Vu/475+M08lcO/dWDb/f+NZT/AlD+sTbutEMJUe6/Wr783+m3f1Cw/d+HtL9XWvhnQB7K/E8U/90tiD+pEOF/gnrEf1vVkf6TZPlXlgem6eDHvH6vJXHv36ex+1qOgnYIfn5J8w26yK/CiwJNYLRjPuUtLMCE7TS19V/+8crMXznShYL//LHi8qv9Rx/5OJiCv+Cnr6+Y0DWvv2Dn3OZ0c0Vk8dWewI/2eGaX5+t0Orcr+HrzzicP/OYeLhr08ISzy10dVwUfiAJ8na+rQHQWi4HPr9Olutxtk2j0I7YwIrTpYSV8TSQL3/UkZCilVrCHF9evl7MWPvaTqsiDGDg+uflcBiDwmZ/u6hj2l9eQK/u5qvxTsLp/wbhIu0mzNwQp9eifmGkzbnlaw/hoMXl7CNLItmBACziR1V0FZc6nzx/VQHmCmT6bVuV0M1+XzybRKk7yb5dJ5/Pz/tFwPXMXYv3t3vxtewm/3YV/cbL62+NPe+Rxn/fhXsT6eH3e5bSeKvH0ec7J19VT+1uXo+vN/Gx4XS+gy9ffutyIp+dng6efLm9D/s0uF9zvA+dP56fHfTZxJRj4b13m1lf1OX/n08sEXf48588GfnoUv/Xmz7qsXn4fuPpvW313+d+2+ltd/u+yFa4yiXflZpnqdoWwVu36WEuanF3PuxttdB+ysMB8R7p8PAug0S1Il+Ygifl6ii+ZWd2jLDNwZErnq0qP16u/SxeVcAi2rrbt41LlBbrtti/eFNhzfrmIhnnbGp3ie7HdxcuF1aTPUa3AUFxm22KQXCXQm2aICSEDiJiH9+zjTO6VrODGVvDq1b6J0q4561Il1uBM1vJbAvR4qgr+Ywox5LmeB642bVecUr2p7dAwUD+yISivosrMRkJ89PvEgnvN4sZVedrmXSEbw8JHTo6E6n3NWME2SRI7ffuHB1BdPfUrV5cCuNIur4RbS8OSSJubYdhVOj7N4OSexzmZn5eucV9zWgoPp/30bO11tqcrP8lFMToFovb4jfuIKK5c1/N0nsuVlgAFyjVpiApb/YgePvDOT/Z+meW+WOVreExI6rK+JSaNm76MTy9RVxitkyx/eYTJgBmZ6Sdy9Zxoaxp8XpXys/O+8ywN5EpbUkW7N6wWjI95VnkQEx6v5n3L8GeQz5YHteR7Hw0MT64fIxQtZCTGW6mh2/bSkmzjdWKKc4OfIefBoAhn2n65dWfxAgW5idXn4EJKclz8Fu/gmGRVO5saN+9UB1bXzswhnTGxHROFSTauBzdDLUPqn0sjX1pAiYSIR+/fc3UFHIMbu2tylZkIQZ7ghIzjMSnrFbNdNtrQcuM644sZn2EkWMARiUM6fcSwDiIN8Tg1r3t1DIL0uDNYmwqJ5hEoGDtoq5VtTPzM5FE5bD77H96IW7vypHZC2JF55ETIWCZ4jLYpWZuQOS/Nn1F/GwFyudcOqQDl4R6Vq0idZNzA5zCbmE+zqi8AcdernsuAOp9VJpDOFa5ww0Nf6oJrU+/AX9SHv4CYAhGVKl6An5/zdYBz0uKSBz0GYMNQT2+jcCS4Y7dEyoc3tmcAnisHxlAOFgrP4y9EkOqsvi2/IR1m8ie5vKSxZ5i16lMxOFdYvYZ3CTfTxM/4PkHMlkpmcOunXYUmTj+9gyKCQX5KH3cU7+COo1BN1gaUvu0e4OHUXn7HCgBj86yelhP7W+YAD/hThLXCAagewL8E7Ckdn/gvZlcY3447DfPiNloIeVoEJ/63yNYAnTt3nIQ/6+gg5j6UXDkQRNsKbs/fn7ifAOi/it3PyKekFIyQvb5mGC2zDyQ6+cDT+vuG13y77Thr5iUVOL/lHUySQcydVGHW/BLjEceGz1N0mig3qtMDHJ9ZUX07+Sn6dRUguffipJQRK70e51iwE7o0bnUuLzF/loG/6NcRAulvsR/euJewy/HN4+rmdaMqIZU1wXbIapeIkL+iwke/KI7n5AtMH9bd0HAz67QWFZ/7RuH1yU6fxUDBsPl1BczmwFb1Y3/3dr6/Xqm4EZPZa27g40y72Xbi3zOhRfUIRdxPczI4jYkAF2Pzdk40aX3xEhRPCtbu7XhfFyE3jCULljyXXyx/vkSdrqLc7xnU2BEyWtxjT8CNbNsseehOkc/jdYxOuHQOAjcGN6VJd2mpnedMVibxHCLQKOvG9n2n84m3tEw4Q9kmvul8mYrtgVyvAxsiuoZf+sAf5hz58C9OgxlHOMP5eqBwBoRc0DjcCLExusM0Ra5U9JkFytsFYsekX6iB6MD5KGV0iaIGRwnO3Pazdlv0+KJCeAD5EYOjKcglXSjZu99fLDawkgPanJI2YCTiwXcE8nf8woKcv23iXQieglW5L3j9/ZUZScOxxD5QUqwJzNZp5HIho9srkxbCuxe54lJLBQbwHTzquYDczqnMp++tAo6njDjGXgyBd0LyBLjsPTmxYjEg6/1IBQ8ijlWFCuW70jcDPJ+yDVCn4TFyLEh7BMBm5c6TrEC3DECKrjcCOAk4ChQk3j7vRYlE397Fv653gCniciec7KncBi7gb1BW5z4M5ao4rCi5tUQykKk5HmGk/Or/hTfOF49De7NAghfK3uqyD64bJu37GhvERUhf9eHJH+PFv87P231AnsJBh4QELCgIlyvhXLfG3mjaLwMr8aOcMHKCbqcP9OMJcQSAemttO6w7NnHNmrGJeF63ECOQOkohOCtd0eGO1Vai5X3kVXCEWwDBPIuC4JANo5jxcw0X7zXsFPY6MBWpwXC5y0UgUdYwf+PpsBAF/v262n76KNmmmclhzpFb4rUF6TQ4IoI9pAwN7zA/T+gEaPuv8DnhMIUUed5HOXxGdPMzYs5QfaRvXrHTzFLUKjmoBGp5OD7tcwbm2n18PZaLvjPy5STB+dP7R7sXrxsr1ZpKd1cKnIUbZqBM4gWvLUS9MnELAT8WugUtp46JYv3EfA+ID0d/ZU53OQJ5pNxV886y9hmjBrOBrCopHn5DYQ7CjHpzGj/gZEH4LUpbQcMM84hLNDRhDBcd5bMI8RmC2muFEJaNl8vuhlUzkdSFSs/dmE4R2qELGdWHTjZ8m6TFHh4ra+/B6/qt4NWTAxUDr2pNgam0V6/+vckdKjm5UaI0QVECHsoxA0QdClJSrRvUGOAI4wYGYIWcgRFZ8vrWMu0lASgPKKt87dkDcRr38SQCKzc1SEsHP7TALxD0wqVtwJ0Pp6QWe5y6gt8K3Wi3FTQiELUQvOa2Wj/9Gix3siDn9kLusbROcQ6P5X4MBLyfwBgqYCsZC7mWiwDyQxaB1AxbM+OOKHofE4YIOsxV3DsKJhlvsaoUHcPUBQdBEh30hzRU5FEoSADuzMW6uH0yDTkCHsvHY5Js1G2tA3ZbovGRT+gVdJrrw8wL9urbQLz3bIGM06lWh/i2YbXu4dhrx9cyM8RpJVQtGhNwuXujpItMBNLp+pml15sHIENBcNKVRs6l+TtJjXanH2hLwSBURZxdSuODJyAiHB8/Y4bgxWqXvEiVDJJZH26hxay+ZXbwMoDLVImt3x6vv7b1kZ1i9q6Zgw60SLS8SdIWNDlLUGF6Zq3nzftwUQp0TEYkOOHSqDnoDb9grjeKHdzxzAmUFX6ok6kDaVs+0cNpWKoJA8nUsNkB9ZQg1sJS/VDZ5zWDKKmhpoaeS4f1UCZ1z8T6zUhWkFDMl0QNTEVFi0LTt/Zb8WonFGIADzAAAemdsAt3b1JD/GAF0lu7nDRgQekY6i55KsO59AFq3+nvs774a2sAXeYFUy9NMcuOkMLEQonn99UYWUL+oFPs1QQj5BthNnyfYDMZnNslKPhvAgNvilNRUkwm/XUB1Oreeo4vxvDcK3I6BxaQr8EyrVIcstvk78EnOxivT+h5bDzjW4Bghac8JjnQi906xF5l+3SZm0OOvtHwwr1zBuQxuEywSXDrVKIh7vei0VPn2t3r7XBj4dtGrzPy1spZgRl3OhmryTCu3kPh8hQNAFJJGMhu3BMiZWfW+A4c+mNWQ1lMjeR1wtH4ST4JcGHKPvAU4gZWgP/KLMvMXGFIxULCeaQpZmTQBRI8Sb8jIBrYdXiSYgnObeygqCZJ7PXbohHKMGp8Ix6QJdQd8cG+eRaywakizSodO0wDCI8lrbgOTn5xwlk7sI1iKzXkGOiSp5oorDuOJDCuIe0AefZRHi/pIS7DkQ1C+zAuw4eHh9DDw6uTyzXLI8q0UQINDECQdHAsXZO38w2mMu1WgT6fzyITSoWJt0TNECqRugHa8AQhA1lCllGW6Zp6sFqwPj4Yn/p2MvFGYXecHq1c8jrewBz/gABJo5eB8ubeuNAuaShYcIhJbStUGkX+axLvtwlIAM4/Hn6K7NaDYfNSgjjtvhmRRCZZdtbFYl2UYlkx7Wx/WCsro/W8WMES1ZnrZbzC+kyC2zlkWDEOvRZxbbN5JHCADUEiyzZoUhZkvFAaiiVoYkijV8gJWQlarELPujDvRgO1O8sGkPpKjvHBkAG51TwO0AXzcaUh2SOxGaevZDEj9DCmiYbRZVI2GF0g5uMeLhMCdCvCWBpeGOtIbJYZ4lUz5x8hd72ZraHL/bQZY0y8TpMIk6QQXjAvkLa+pqlkMXq/7a4KpsGqguAkChUDtiTBkNRlF5AsFgJz3CGfeuZVn56ULEPl+pCkNswdct/SrUDt0nTZKTf4KUUQPOb6x4K8BrrYy5cRzw4V6JC7ldqtWTrc3VP34nlslDSPD05wRkzgVnF5Vkzx+krYZpcDLS2WG6oCjWUuE+HYZt04atC1FKA8zj6tySpKstfOOoIyRcNMVaej2/P5nRv5e+TdTE98z4ZyiknOWmVmmveFCcNgVumtBoAkPFhNzDZKow23ntP2ZT0tADcfUxpDFBPy4fREIC3yfdTKCnZSwzDpC27rAd6kW7ecmVi/PdM8lFovjI3Lh4KKYT20RwZYOcnLh8Fl/Q0fXF01bmbkdJSkhV1YHJQcOY4PGbQ4aCQpKQEgdFydE53wyUNlwEORWw80YWrdNNEdFnAzZ4QBF1NBOMeDlxeM1hC2nZbARxAWg+ofpPCuVeFKBscQsw6Jhr+g0+mTK05Aj7/6wxKYgtMukNlRmhpK81AcNAkLPtCTaaiy6UM/BxrbxHdA5kY3cFw28DpJx6c0Gc6yUdES5FXJwpDnDy9KN1E9VeUTcGj3OolQhWY6+wilBk2xhR8TNb2jYsg4UHZLLvOoNMnawsIxk2LbcsWYm1PiHtq88WfGaWGf4p19rt8VjfYCp+dUmPYz6do6PwCnvQWsFUh1cpokQ/JeT5R+9mmFDTRMBISOu4h+NdcpKwwtI7kiD4+WoOZ2YK9PxKWlF/I9R8qpflWgxe2ReKEpK8soLJa6KpA4Vh0krnMayl0wZIrnI56mJw652+KBflpmC3GrsswjehQQEUnkOeC+0FxvdJUH8rl8fnMPKX82NAmRI5HlE/SDx30SC1ZzBSFGXNk8sRYhtACjEAG1fZZwiYryzFAQbXAu1uyURMkPZSFmibqWiZkbwjTc+Do4VOawVIoFQYyuPg7B8ZYNKTeyb9ZN6yK5flenV86D1WpYPAVqkw4s4UUQQuYyQpthibR0MRVDzgUJVIBrKynS+9xHM6tjWo7ayTackdSSlx7n74x1I5QZ9xlRB3yZOGBH657eUHSvBmaxo6lXqI/KxksnwLOVND5HzzSuzR5/xHajSHfpfke20MqgGk8DnKTZmkeWBlboJlbdZlo5L4u9xjzIz9QdPEYokTMFgdk/v74zzV2IrmYrwcN2aDT4vRQUFMxV4c9V8hgQ3fKk+9YAY5KNET8Sqb0/k7qgveBtPSrtZjtulBSXIFNF1Fv3USs7KQB7iDPTCTwqu+QUGTDtb1Z+p6ibG8GKd2/waRySOB5JrZvUZNScmquGlwzlgtPvrRV3LCVhKuTvGT7c1O8sL3MKJxunhuh7QyNXWWnRMsliKuyhnlh0tl43kUaWDKozUbwRX0Ws3Nc2DC+gagJKoQkfsEySS5bZY/lHde4VrY9i1B25LzYV2fQ0TDg5vbQmPpeYGdxw7Z6JCohKzCVpHwVzF9VFdtjubMVoSQVaM5gbc4oBEcdwoGUMZ5jwbwzg7iV2ek6V+WYoqTDhqBQft01mcSNscPZ8RsIZYvaNSIqMa5WSwnP6q6JjGAlnkgbpNq1O43SlLcnYvr7rZ+0VRD/NVcjLuuoSvpH+APPf05HNC8NCuCCNp+82WmDExyspYgwEQYoK5zJZoowlQJZKC5jrBeeBNdhds7/7zV/v1kl2KKB/s3eNhCEm4ITsY1/O4xrTd/b0EiUMF1WWgsl8YI4JBP0B2SFPqbjqAxATY78DxEtIasunwuzJfsxLfMtyQfF55yBZxvdszKFoi/bxml8eYldeIJTU3tZkcL8d516ftluGRU8fiVsj79qUjjbmB1/n7yLwQlEBMViQYgYLMc67Xj41MBrKNofpACV3BEunFyt4ct2yQzZoaHo5sl6uP602Fi5L9EA4wlWE81mIhrx0gKJsFZCxQoGBBLbOUJS+GrfGjwoxbZiwtO4LXCq6encjoIsb1LtC01KZDLXAR71fewW39jiv8qNr+6jL6ylsNJO4tHBYB6VRed/DJ+wKs6PnJUFFW8eanFJHFBd2tbmDLL5+K25Pf13a4/QKgqENPL2r7mzVuZEI4m7OTFji6qW6XO9BMbGeV0z0BUNr1d+jCf9QVSdxVz0OTOIVtcwH+3DEJA9OvmBHHkorkFYaGsRGuDQCFE6Qzs/L4ohXEAIfOLOKp5vZS82zuQPmIzRKTFit81Lq16P48uqOotOF3R4616cUw4bLrQ6FK5aEiZ98r0twJx3EJSJq8Pk21gNddEpBJrizrHXcBqqQV5AVYf2glfDRf9szTLQjpVMBLsLV3gryH3i6+D3A83hEzKylplEPJoHW11WgorHJD3HENKNAw4k3YNloevjaPKHKmqAbIEyoCzSTAvOZljto+sHkTkdoHDvkygD7ymEaVTNkLxdNHJ28cZ2D5u5Q+fBP4JHazF5UOaxqIs966CWCYJOopmyfaw7hc/Uyk+j64ipGORvqs+pdTcrvhsyx6LRdANsZvBUm2IIJmWQiz3R6aD3OTdQ1+dWtK59c2u3M0w/mVVBAFrIgFqOmeU3DRMcVS0foj5px+1glNL1mG6c7VWaFyGeMnlk/f7KxsMqE1gX3lNkGn3HhlYHiGZcQKl3n3m04bcNo1Uab9aJJ4j7qWEJbidenLlPDrl+tlQY2K5mtSUwU2k58gvhGvTFGFWkwlhy6fwdXK4WLQvhrkT3XEWcsalo/Vw00ai0zgurlQu6wemLH0TlmMnbKy6JPO0OycP3nFPdPe0yXLuWpOIHbhKqn72/lPLPN+YOtGgWXCXxf92oP69RgPqWWti3zSghA4LyXklabKFSbiKfjMWlSDBVGXkafy+V8pwGlHpejfOzbIyfTe2ZV3RsVpJnlLlc4Pc9uxWbAuogRxlLN7FKaXXmauvY8ClccL581qiuQtXybc07ayE9eIR/BhYH8XUgkv+3rHK8vPSk6TUCd2Y6oiZlxUiy1RdtEJ0y/f2t6ipM4WTKB7+OJT5CCglyvOmTd6lbgl8oQyf15DlStu9R0AphUWJJLTrUMnajop2dChgoU++wo2VZi8smulUNPV39EHuHFhtXvraVpORxC4KcblMMzHvuwOGRAam0SUCP2uvmEq59LvBVsOrHHkM5bYYDECBrNaeys1GN3cxvSdc1qvVUYuCLhYj5OnHyPpjdAmxc5y1MD8fHdiG7uOf3WLvuFOM5ZZkKqZT2EoKgKJMxqyBBPW1Ph9VJF3aVAVZceNT5zDfVJsEbc1An/EHJogiGV2ZcGwRoofp2JtWFNXDzttgww1IALfSPBt72ENBIR7ykJMmljYE2W/tJhylnjZH09PepaV5moF7DS4c2h5YPg0LXnqKtO15/FWKUlyHRshNqDG6x0Q17ZDOLEUiOYkbrfDxLydcPcRo6lUkTF6l4pOILy1Ltq3lgkusFibLLwt4q0fllKb61ClYPJfCh+xXI4fm0TmJ6dDNj4NQEi4+ZQmhNwdbvY+sCSaDbrRiSITcQQuRVp5qA9zHehtw6gTuck4UpS7v1nvuGIDpqV8BRYM4c5xr+Sgkcc2LD5OWJCbHNlIONaLAG4Am+A4PUNayDRsEIP8xUa8wfTVwi8HvfDWCKXQKvIgpzR0FRkzugc2sDapwWo2oKzPJfyDINaB+GFd0tUpqbtJN0Sf/MVfr97nOXcTnZRbcDuT7aLZsEfmvHuMbjpcxSPDlEXCzMOKfIl2EzAAemEDXNAWGsFLqO6LVI7MXv0jy52l87fVwl5vFaRlx6KPOzGBS3cOmqTilFNCPpKXxw7/Wt3C0eI6qUdLhd5i4jD7F5pBAkpaY1SbPsDNj/LzSjK1EoCSTwoY5j6YErx9EKlJt7WC0HCSSdvXygBE5EgjjTco84VL9CT4p7sjb7oxykh7JAJXTS8bx6Cd98JSSF5gENTsF+cNIy8YyVz5BYOh6CO9aJGNTE0jEu/kCBfuyAEFgC0fpISlAkQJH1FMFW5FvEEj4LLhoLI6/MLRuCLM65pfpeGKo5/VXRe+ntJIJpdZSfMSX4IjmG5EL3rRRt1oF/xJR2CotESKJjoFuD1nH9x8xCq3NC/1Q1yAB8A4qLCDLTnoddNvW7htzhNICO/QtDjDqjbjoi8EXdeG/Pl7qnXO1BRQqcnb2uWZx+H+505n5o2FIuZYXqIcDqBJMuQZLbHpbPvqX7aiCm9v+ByZH/C6Q1mKs5rVpwNN0id9b3nx0iEGzRgxRL8269qyNK6dNJ6UzcB1YmbzCtWesfr3fAy4U62iNgCVhUHCJ0yFa3fObiyExcPf2OKw1OcSOtoUYQ8GMv7IYKDsY6omDqbv8vFBtdzYZEBGvqlT9glMja+O2CiEZ6r0OKJgD79JDtrTWO2k8uALH29wvrgxAlNt5wV1sBOFJto0BH4E1bjecouK45e/LLE2cY3YESauKeflw7IummTFVqJHrl6B4GNv8ZkHkuSHLamHiN4rm+fSy41ZgUuw6Cb2eijVeXNjJ/KLFW7VdeX7vWkh+CxUTISFBmLEL8qwVd+vly3s1/aNl0/VNPRvWlKH4KqWBxC9vQV6OIna5FgUgL7WhXkKYEsbrCI+Xa8yy6mfyMrI0Vzw5gjEDGd+HLEAcIleByOp9P3nlToFyjIlDcZobPdSnCmOgeKO8MUUGhyuWUJWbDzLspIxCZ7hJE8XM2KT5YFNcEMQ57ZjyFPWECYQEq+EMi5MfWn7zQGoK8hiVXQKb9rBKGCvMSrzag9Oy8eKowUk4bHuRvvUwKtOMF+Ft6Z6InQSnwpCFHmidZ1cfWexYMWR8QwZf6EBL5L74QUDY+0O63f7MbZmauZi8ahSG1GAzxN7i2yWs95J89nEchFBfKmi0TeUiZ871MSePES3QxyGm5Cuo0lL+0v4fRK/aN4htRSsbwCFZTj6CYs1FrB2pzM3uyai0EY7uKGFVAXLvW9c8ewkmMjz4Cwlfa+B6fBdDq9zuc4oFwqudPYjjYSgYcZCtfdIb47QXBZKgZWPQcSrjIdEtY9qKcDt1JxgCuCpAQ0VgIOPtDTGea6g/Nt0bqppDZpJZtAvEGZ92aZx9Pk7EsY8kCNLHkqBRjhLh45sEO8PYqfM8Wf3Eeg3kt1u6s5Mg+u7fDWTbNx5HBaErfLqaNgDEPZ9q7wTtteG3WgSFPPWkBtpqVWVS3TMPpFv6ahrpPIIj4CnUbziK9R7kk+xZrHfQQNykb4yTIUfuJk434XANM6MNl86mJ0psUYFtfGatKCHsWtSgVZqyH7hCMWhLaeyzR0TFgvpr/m4p6e/TCfYL1w7oJblNUFkkSdeYq97vBdBPLLEmXi9L5C7eBet8ZgYT3+S8m5+LZcnjeigvPOrml6QdmQFdrvnVeIVDlIqWmw6qLr25TNM15VGQKtk5nnRLOyS8Hjubxk4nOnC5aKPEY5MlYt+GrWHW16auYQ55Zbyy9XvZQb68J0VwUdbtfu5cvpxr6Qe8Ym7HzRuSZX2RtV7+58CYmSo9QK7nWpA7wLLtD3bDxLLxFbKRdWONIz3yyzgazmT0ZxPg2OdZa1x91rQ1wM6HjOAyV7HZZ5fyeLXsaRbKBXIhyoO7s3l8UI1em9wKaqRKygehWaKYJD+y1P5i4gShhF7gAr/ikdesUBF0pQyLy5wlGYZN0XD5oMGZULkqSdYa6//MoublmszuWym++hch3ePRpWT+xehYshaZj2sMbcBYXcxGPnnLBJ3TyNf8S9DzdyPOtzoElF4iFLC187E+rRge0uFca9ditT/9b2MVmcySt+1cUGgbxd9emgql6eo+hIV+BNrF28xKTKPRDRLU1yKt50tWRCIx6e3Pqtt7USWfXm1IUgV7oro9OC0rLnWiiTr5WDgMToHthKNAYBlkTEknhA33mM0tnp7yBecGeh433oAVKId6SGg7Z5282DBVYpB2lWmpt3CJ5op+89CMuZ2W+FxvVFH2I1Y7onDd7vvcfHoKH2xoXk5lUY9BRpT+0gqcfwRt5/1ZVBu1pB671E+elokbftDdI+4YaOTq11LfC3LC+mKsoyiXHttCaBOrMEmZtHVlVHSOCcWpe5ut8gZwy5wALSG/X7cXWQXQsdIytoUkNmdbHhOokPAwnJaza1trrbK49DXhO6cr6zLOEhs9VEUs/qUQW/NB3vPVrFfD3qninyqYK5Ik28ISu3B0yQfdLukzbxl9SaXNxk+HOYX2x+xc4IndS75dHpTHA6EBdoEN/GJuRZIKpm0SJlePmk0XKAHife727uYj0N1iGiaYHK6MbYHfU4vnd5h2eXoy785fGsBDvy76zkwkUuAQ4JSMxO4vGkWl6TFdwoHOgQ7RDu2OU0il1/qYIFQJL9igEH2eZpe6ZGWBOPtWYVKcJoO+CVNLCyjp1AlnJvFBa6dIXTNXvoFyt/M9z5UFnBfF0kWSgxxiHQengvJMBXcLnykBw6vHW/WfZSrlijde9lyAgmHHq5qPKhQVwLRYFLhOpJUqfTbNkqG7q5Vx+mP6k4YAtYK+JXCBdzlC8EEVCNK48ezBbe4ERkm+kOfmnEAe0SY8eDx/V1PxEkPmrxgI3nqO0eEbXXKRbm2m96TGWrUtGYw9oiVMs7o5Oevg2IrBE71/YWjebIYaiBLz03vOK6JnMKK8awLumwn4Qy7Q5nsOINYQWbhcrHUs2WvRwtV1sH0KCFpaWQVOyLm504mGp+qrGXjBSijnUgKFuEPfXvRSvBDshqnh5B4zyIbWDmbTAOfEIPrL5SPi8Hh5E+Ol2uiypUggesM7KUvy56KVPnqPNPSas80ftBSZ1g+NIjNgIDexLVx+sEGN+fEZOiLsSINW7vY9SMSA3TQgFWru/qkRMTyzacFreDy1IDY1QpiujUrYc7eQWxrilDKgB3n9Bk0s8j1HTQViRi23HjCQZ7UnRPMa29eblKFC0Hqwavn5tDzlhRnxH/aKOcJSndM28t1vT2ZNCMe4EVn0rvnZoavWgOAhgQ3qtzDwab9Be+tbQhZlQhUEu77IFAckwgzT5gwM8afd1Q8v6qtcPmjCidkpn5VVMNOZwLrswToAVCwC3pdgxIS5I2lsMc0BspcVBiBluROmAKsUfC0D30lKOH0XC4vX7txb74LphJVBV6PcELKAm5twy7jEvpRxp/qE46xCeD83SXSF2Hbvs5IdzgzSh//KyaZxaGryBEtjVP+qFpVvIwtUe8zRFLsha/pYXjx+cp9zMzGC3+8t6NYFP8w9fDc+UEPTe3LB+oy3meWpixtjsAaGCLGBUfdCNemYTFX8mF2V1YbD/phQd3YoRdQdPfdZ+kVFcsg9XTED0ilo4BYS1uxSOZk7pc2ULRerRUhBuiSZizIPUGazZBvYQL5ahw6IfTiJGCW1hdpZChV5RevrLER5dH3o8zlj4/6lVJSazYCz4tsAmZpRO9p2XXmrLQcmanKX/U8jsUpVNqriZD4UpAjas2ayoAkV95dl9didZFYZceby2GlUgdJS3zdMToWEZmHyDooLcXXliEcaGgQvvow3upGGafO4m7PGRn9QUQhktgKBlQm37EY0CSkc8quPlkCLuUYizgiqCzF+QRGmNMypF71huCwWXvlFSqTQgN4+CFQ1i40L/NnMcBb0XLquPxcpFM6/qr8qPdYawT0/nKoqCnuijtgMFlLi2lTgs5HM6ec4m5lAqheD4hwmQslwc1FjnVMz2fkaoxTEJA7eRw8VV2z5i52NOdIFwdkDQNVrW5AuYIC1fG609U5OsrzIdxNG6DXkwinJWhIrX47mFKGa1zX60qkrT68DKk790CTC6eoeAk+1kOafS2UBCc6iujE+uAAB/F4Or3GKHUm/O832MoAUeZOumF2EdBqT1K05T5Xq97GUvjazoVdqx6cWEt8HHcqXpc4Zsli7hRdcyqT1hg2w5E9p6wAsBgy7X0Fu69XE+SdeTzc4BiIE0O0a5CKtBK8b5AKe6DmYuPRqRduIE15X5y8ds59hSLR4ZBu1Xqcic7h4VryIS8INvd0WUTj4KhhIlMRlMCa0OQ61QD3w9xZEijt9sJAoMEPZ8tVvX8YyeLD//rIlAb3K7WuswCuUkuYGkwzBb3mb337zHjnUPidxXviWzHg7GqWfXRyF+nfESIM+dFA4LXNg4p7JJFyIZq2OUDo6GvJsyLHpwSEUQD8q5HYEF5p8ukfO2VQ72kbRy7Fyz1TJc1vW1TSfKBIcRuqgXoNUexYomXcKc6R0Qd1LJQRkG7IkhjeT7SiLg106cqi57kOlFGgdcza2PRqQKA6h2oyzqMDBn63EHz+MGmr6LPwIEKcBnVmSPpRQD+qDNNUwNs1GGKB2kOwbH3foSo4X9C8HBTby2LO77UPQi0QFDgTTklOeGKGAy1DQF5PQa4EibslpUFNeq8VQVZCOuBC1S0G7BMBFSKFBl3wvIlRqKMW3pOxC4wboYKqD2WyCbct4VF3i0voPEq3ir0i2GP+xc6ELRF4Vk8HBHCvp2o2tbULDKVphrn9ozUlNUl6nsP3mm+V6K+94LRuMVcMHxP7hzmgDhg6vUYtiFK7G1FXcSB8bqJsxPpy2scIBMWPLjziqgySnztBsKmLnKFC2RrmS0q8swngnokC5RuGMwicHQpG4h1HmrIrXajmDWKbcgxdqoyjaetyVCNrQEzxTRJwBsMs1LCjos/y3vnE56Jalwl1qjvZRdUt2yvxCEYj9HeAp9tS38Z7yjiN4bsoA9bE2sGdhqn5fDeEpEapa4LWbeDYGg0smiJzFbg2xuUczvBunT8SDL0RyUKLs8Y0EB9lM3NuF4NqBpGlK0dWbdny7MUxvW+3/bzzuPNHOaBXgaspoMa9OEWI5iO2/7F+cpx+CW+F0WAy1WDWO+GxCRDSoeUKn4xoZWzGmVe42wpCIX1CzHdxGpFdNW61VUsnA7sx1sZakO0RPho92FYXJoZFo+KocAq4qifXbjHof3FC4JLu2IVCofFKOykP1HKQ2qdsmGJaJtsW7fNdXO71EbhPJAJQgzTwiROeHruoWRscMYAC5Q61lWOa0Q1p9Bm4al30jgl8O8WtFKyFw6q0nTS6Iluvhdz2hJPZhZHZc/ZfzmbMEmq1sVxZMh2HtW+n12mWHhpl6dkBZ6zQAQhNM+ju8CatYO0Ecman5lR4528dOFyz0KzM0ir9wg1Crlqq3V1Lbo4TG8M3Lg35xnJDKfjGXfdRi3ZpN2GCnAPFjlnjcJ1l1+Le+oJuxAbhtIqBYRnXSvSLXQ2SktvIL06NYpoYRmioBc6gt6/9nphJqqRPTbOCXZbyu2807KcjsBWMhvA1UXeTq2ZCk825XSUBTE04iCowfrZloB5flddOV1xyndt262d9ztYdFX3r196B+HnM+KIpDvpbGDKS/g4qAerz27hEabOugjMARrclR5yeT5E4/303uVlvutuu7WKecSEUu7rM79c40vHx1rxMgpk6Qflxq4Gj5Y+fMEuhxf4+w4UXqadFywTb4WIUn0uzxp9hJFvtOYK943V+S9uJ4R0Eo8LSBVjsHaZi8aCRpSZ8SIrIcVduMP6TfHLdGrXXkTDhgeqzDC6EjCSCqGkvW4oCFOjmhG9EUJsqPuABBiPprbuhNwUUdYy0iKmEHzxsNwKJrTJgCstxKWiDreLwXmNJkNJykwkjfZpd9rcjUIcBdaHHM37ULFxHqjPMalRBk6S0/WjjacU8cT53aXY8hxoy7wQMxgWMrlI42EI3G+CIIf1fo9trio1O1YGcAuj4Ca99OgMnZTA4tKhlhW8CaJprouBMnpqhIhooPT2sKesVswxOSc/OyLy4fkZSL6NZK5vhE66IKtSAGUhIEiA3HgBKB3vjUOTvaua8gy89glJp5MBNKcpPhPuF3Q0/Uf89LUhtqwXuSv3u9QvEKuiDodrXEdgifAi+/SrPjwaz0vLGPtNdczQGR8ihua4+1CvUOVA3qRRSngfVbg+4Lg9IAcWUMtkPVExB8HvlC/AmguE7nqzbHQWOt1yUbp9rwfTL5gCjdvrqq6tLCHNHOcvLTWz7We9+HwazOjSkqjDwpw68Ky/wrTlhSnUXVE4JFiVXLtWxPZXUw7okUrJe89Kb8DkCN8tka79dh/C9vk8S75cLzeqmsUI7+1rHcXdCGa/e9TFKkVR3BxQL4oGC3f3KcuE7r9WR/npct0wmwXamULEFug4ukg8NKvZ81kCssmqfFu0KbLA4tu9piZ38+KRf8FdjQMJ0QgWHEYGQUJOOT2bFKg5PGwhdeDJFXI4raZiBw98C+1i92FRUdLUB6RWrTI6BNvYdUGdphORDsH1194ueDnkLPMWxLHf5PsYU5dHKnWwtD2GiD7n/rQNaUG3QLc/hwwplzlC1gc7zzE9Urhlhmw9+VuC9+QhPfGa28sSe84GgRiCbpyY1McqA3YDLrukEfughQxESzM9cSeMp1aFG8Sgyko7BCeAwjeeE/R2QaPdPM3TbWVOsSRQof0mJ4sea1eBVnB8SvUEcjAf8jtRkpMUrj9B1ozWL2iG23BHRXthQjwoYKB7POmb95/ZnF9jQjHptjX3ZA58PA0iEWdsyu3aoszHcMkz9eYywdzuMHQ3nAO6MaBRVI20W35OuhPMx/jNcLKvgiwqyq7NwHI8TD9PM+lg8od08hCe+QuoaoTqCpBGjhUGCbJsFsfQmbLXMiDEE/1E5NIpYfkuO1h3UYG34MwwveogGjYvBZB+4v7y471oDqISjHdEgizZw7vJMHUDeb1rq0ia5jW9LKblwR16sCKSsNWvXas8oTNCa1LuzSoi1KNbQk8IWJQoYUAOzeTaumtpwbqm3aXuBKq56hhCQ32CdAyf+RF+zFcKEgjsvbDIBjeozRYrdcnnxFXFCmIWEnPhwIjQVWSqxrdq9TQHmoG0CyHFGtRVeoD+PKssOVHD/6kCCPa60ILnOjcFN7DNMvtwtEXrW0kIV+m48y26ASlP6uodTkD+DL//YsPKp8xKuN109q0LBCW5H3fytMwjOQ2F3lFVLp1tiD7IwxQvtDztQHsWtvQ0Iy1MaFms+VrCS3a3rsLSWPw2ZMUZlpvu7EihOGDe50RB6YzPTLjXc2Pfy03YQzcTlipnmYEDvh/cNttGaR0BwiTcZ/+k6Ar42fSwTsGLNI7pAudsKjJ/kgt5XHfTEVAXBjkMZsAcBAW/6f5sc3ylXTD1oSAO9HT0VGyIxfChmWlkM/u3cY3hmo817U1r56qPi5BrEQlU9VxMIixjYbvMbtWOitchYbGIgDtivaPoVUcuAGgWKeYSaYzVPPbNJ186YxVS6HnFrrSJj74OXR2jZjFt16mrswP10XS+4Zed7IeyxdzQlJ4FAC1SGV2QTmEeVjm3l+ft9iTvc28Ewbua4jZxwNRxH8AtbdbAuuiO6nhKx5NwwZQQWfBoXEULEG8Fzj5EUn50fnGAk8Y0QOe/cVm6UtS1HzaKwgtsHqHQ7NXR2xYiusdFTGQmEfei4ylV2zxlJ0Pda3AlHwYPlcnR6cITH8KJTW1SBTNiHTIjTFt5bg0yH21CGI08PpylAOiOk6ecNVTUyly2KbIplrPM4BHTxQC2ohWzpPl0ZTXRo3dqgjtquNcvBkycoBvHnUgpAXGyTO3cHE8GSQuWJuAe7WbbMZdiWF8/TwOmbfsNH/p5fhOqh8/VyTTVUuB3dYnAEgSPpHBdU/LR3EnfRV3XOgdCDzHoMfDtnXR3qrnBBReEsgpWin1ygDwQnnvDdhZxHu9yEn279YGcliD7QmjOD8pJb4fXcV0n2D5VoSSqcZq9nRZ2O9Lk+91WYVEy+C4hp6sdUUGi+ciXxg08paXjlBauNno1A03KcEawXXJSxTfPyilKDCBo6uVrRcoIvgHSWGSa4yNIUnv98DzS6zYccI9RefZDrS8iApnqIxPDL9hjj5RyTmOSw/pUUxAg3QE8gPP+hiOIUNerxPqIXlwgx3SfWzNjV/N7P1ZiHXxwgvuvzvuEa6ujHc9iClQxgufr8i0JIBdJPB3FhiTxr2007nU/vrc6UxfmsHiI3tqA4sOTCIZrg+kGmsoS7iA21NwUr3M5fK8Vwm7nJyyuU4+n1CvIR2yJ3rtShm7Y+XSmuvkAPrGMST/Dv1siWKaVUOy7IJDHUtARgUvCnXk2y2HUcEHqk0OWdGqC3MleESIP9eFEJTgSWFnFgmlEQ3o++BKH+NRRbstUEXmZmk7ikdsF5HnQf8UsDjddUKrkSZ6B7zUZHsr1ZfNdz1NxluYGDIbKBrOYfO29jSdpkoqiZYfB22EBsjbDIMPOhfzphvgPjAeKn3BeewFH0kgrblAzCnuaKkHipiGRQJMKI50kKdpITa3z2Huo7kLFulhukTEN9SNQu1U6kpcNJI+WdvGTvJpUUkKGLUe4QkuPQqLcpT1tcVKHY+oZKfXlIwqIH/x7BySGXFcs1zbNRc3KYfOrpmwjQVtXOLC8U+ZcfkEfgzPMSVY+MDavPnt+0phRufQzdSsToVXhnsHNhVKKNbt8D4uRTdZMV6kDQvZgBlYJN2vgVx7BgF73FWqMkikUib1+KkAmo1/uSTZ3pIYO5JOX2e2SRvrxNzXwA2Dnr07fpuzm4gZ8j180bgxlcP6GZu8HBFV63aKvPcB3u4NLfzPBBUgzAFEQh/TprUF45/kosVDpg3SICpmc6AvoiZ2S2m0/kk44R0fQ0AsE2Met8Jd226zYSjPHUw3rY2Olg9xXrNQBq9jXlpVaHFOgvOZuPQIQMh7uHpQp1x6pfvCeu5Kgt5WyrXhWq/feqZVILlwDp1g4fPz67FrcjgYD0KHGpiCgCfcNDc0Q9QMOxXGQrpzNS4odrnkLme0iEqJeI1vZFTz5ekgHtZcTq947JgvEgbGvGer3Tt73m50HZl87ERLC5Q6INyNvARsCT2/gLpAth0zLUgUrjDlCq/gFQsrc5n2KVjstAV5lOeQAFWWPbJZ3YXAJqKtbYsmMpW3wDWDpQLpaCCFuxMDgQb8PJEjhYVlf3nsIrtIDcUrCIHqrhKt76ZwOioEdi2XCrYUIVmM4mu8f79MxZ/0MBGAI1Xxgdz6y1qoVovC7kZfuAuvY1V4x+LPZAhJ9QfYFxGIyesMUMu7ktZsXpSJL5+HovYYrrW84mMl0Ld0gqsPp9nT49mWZ9aRT9vtdUoUvNE0g1ugVVzAiuNFIs0O8Qqdm53MZHxanYoA2/+ojpGE0F6gMYOn6IQgrxicgXV3JIZ71d8rypDwQukJgLdwwvRhulAo5Fb4vo0fr3RxY2bPzdBCdKaHbeYA1neWUteY2sHA12Z/fdU3zdpugNNBIWGmnI/9lYwPDz4Kf1SfcVx6nTVPuJ1tztDoYg1FWLidih8u0atgaJxnrjxeWz6/lquCxtBlxnZrYNbzuRu7iBFsu6ZLMhGuVoimay5FLmv5iFduJZIJHCEQyTQZjSBp2G/J4Dgk80RpH4jqctNNvP3xyr0/IyeyJ6sZfw23IB2GAb28zaNZPqYLxTWKfToXXfFF2LLDQuI5IXUdGjkWCLMjP7eMyitRlxim841Eb+oegpPy22DEMN4DlgYASgpC0CLbA9wKB73MqLbTicX2Oct6FLH8pcvTC9ZViIb93UHrZr1i8PHiuRbPuNsUXw/EDuH1DKBSm51sqWFExo4PXQJy7WqyzRNwe1LvQux+KVCzKCjR9d4GLH1g3WKqo+Krm3Do2RLSpi23M7kVlnZNcboXCsqKDJlcxL4yweezdXhvTeIXkzEM0oa+NFPHDpPzt77ydVG44X9c60LzAcpIJcWvHB8rSSQPU11zTx9D3/j3JETNEumSWNqlAqSRwi4NxvJgukSCeG/Ft1l/1/n5dIu4Cvrc9KjMn9ozaiIF1V+UUGLZJFQvcT+0KFSXbi5nBkeaQsQpc0yTuyzAkvBsPNKExn6XaaytFmkExEbvff59Z5WTBmT3svsjQakC1pG9ptiVjyXAGfEzL6OUHsOMx1bz/HkYV4CIgipfu3F9F9kkFb5eY/AC7P9ZmnITzPUPmN3F3ThWdtFqxVnRfC19/jOncbn6IPGdh84FaQARB06EzqNKBojI7+Hd5q2zLxqONZgYkrfDX7dT/1V+aE8/Nl2A9Y+hVF28XVUl9PxOnO/Q6Hr8Hfbhw9qMCzEyY5MHFj4vemrchzx8RJmZGJynztsECDt7BPGR1tiu+MOI+67l/XuZBvco5zS2hkNuzPdkqf70LRCAxtrkBItRXwaZ1AmELr7/u2HXN56yFO0pfBzQQTG5avz+xAihbKcmvcCkMkMbrq7J7KctsjfFCbdHiZTrHGpSw6iveywmz0qOpm2pzgWM6ZvBIcgLxaeSxWlU0MmjPw4X+ksDtUkdfYVoHhh7lnN31X7yycIML+57ZQ38U8etxc02zmXZIqF7szTrJJ2mBaMmcO7dIDNZiXHrFm/BYeUNE7VcD308SdGYR175dgQLAbqkeYg8hfEmNg6u38EDcQUlrfn67LVR8kINP/Jdp7Ast3uHnJw+ROJ/TOJ4TmTjw+kKHxvZYU8BGjAfjX8/X/0XTdSxLiizLr3l7tFiitSz0Dk2hNRRff8nT88ymzcZO9ymKzAwP91ApItG2jiDwqT9VteOYjj7VK54Vc7+ff2kSebMBHcX2Tns4UCuxQ4AtlTRJR0j1Ab9rH1GllljFHygmgyIQFJ/RSgLBshJ8JfKxqXSo1FjpymDczgtvbYHpdKaAMXQvkQh51qg9Is5+JLiy0a/kLr7Scqo0fwUYwjg7jXx6KRMUrDq5Rt9U1lgNBJ862/4+OCcqAGhnL4v8SmZGHPGIwR23L3P7WeBLsm2xgkssBtpMOS2Gan/GjXYbceEJWHS/OL7tIV+nDG612Az3Mcl9ybxMhArVRRg9ZsGEJ4/UOeVUtX+BlJzHfjmzgNrHYpW5w4YQWsURvxJMpPlgBwhqUnbZ0+mfS2pPL6cbfqqYddOmmB6Uqr3hI44F8rRuQEqMo3Lzh3eNv6QnZ4ffa4iEzyqv/d/8XlJX+az6ZgratuN4vbb1/vCH10pQuf9FjmIK5WjiMc4wIu1T92hivKLPYgGyU5dhyOM5dSMRA76bNJ/HwNFNxMyjzCPe+/3K0AramM8QppS92GpwzBtTeLUhKCbXOzxBhIGtvhvkNdKm0QLhO5IQGiMTSHOmlU0sftGF/HPjJsHRa9D/Su+noAguWmES0Nppfl32kBDtV8UBl/rBFM6NLwMJEGaldFXIX4MOzdE2k0rAy6UE7RVyh0urMKAzk/KhybHEHBpUeJ2ceqoi/pd0nYpXhbB56IqxoFxMHWVTjHweqldffXXsRvXDdOn+lfuB67pYD5POJn+DH2JqRA8y0WxgK5jWlQ2HyHDDxPcj1LlK6UsGWi6CvJMGp6IivJ2bKeX+EOxLesyrjv4PDFAgXhOYgHV8s2qiwkqpEJVGUe5V+zBOt/sYFI8BrgdlveD+9ZeaMf2WGQyluBx2tVGLWgadzzkVyUvjlnuZGejsaxfH/ozruzRQX9veJyfgOAuk4Yeg3vPZzBLbWNjarW3iM6b7hLsjI4/K1sw+QwHeMLwW//lSt08ls6YIzHGUr8U+FP7LxTz/WOT8AVDxfPbjChBkXTrnwYts39pWcS6JOXbhxoKg5iiLlK3xG6wkQ6e/zDoVUW+TiRF4gfsKmnQfTuS1KsgHul0F6LcP/OinvHlTVF9GtNwt9dXH5zaiUGmXJllvDUeXg6l/CnkcmyUWmWVSUbmST6YwtagS6HppFAjTejYM05DMG71tz3ZTnn2Qs21T/f5oqHAFjzp4ysyLXvqJLt+nx9UEeGAJLeMz7rVkmqQl929Mx91sVkeQFY/TEP276eNCLBgSsZ4Ooog/IOeAFx+uRE4TuHnZKbkZG4UxWEb6/dRdLWr2tCES8BZw52asXwXZlNpQV1Fi2+Dz2oCEo456JZvVkaOHCXYJIleKvKAqtFTA0kei/r462/rOBrFfCDKbZ5MyVvzDGKeQX8Fn3cnRorTB8qJSaX9lrCf3siStKjtK+CCCSEprzzxwXiwZt9VRHuUn0BIZ/pw2+xru0Ni4UEywBwxKzgavHR57/9iFeIJYentslCnm6awOG2efEBwQKugUbsXWX2/xvoDQF+Nup8QA2qVt29QRnb7kxExu97pgMd0oD0E2M9Mydri+htKmQ+/AJtsryIIBEoIPUeS/NhxK+9UzclWX3qA2AccH++tmSEjW+q+xNSSVgTyE8fpzhWFVVxD9XNhku82sNTvNUy8vil+v5OV2urfqV2azqkn3PwmdI6Irpdd7r5lEeJjHff/E5akAXOQOJo7FvnGGHLBdx7v+SnYWptWanMh1pX8iJna/ka7zDTHW5DEQivmSTUYXz2lpQBoHV5lPLeDZBCO3mwQa6AOkF7exNq39+jvlV/l+hznPfO2/J9fekdJTViqAfvToSjT1SJ+yCYyhkMk74q0RRoV6tXAfhzxuESMqTlkuxr5NadJf4M1hFKJmcnGi1/0HJiT+dtEUTSS1fcrH0SgEKkmUk9ajmpyZpl76yhWEXjjnfSl28r5kuONoH6vP2WugalOsyC4O6JMagoMSQ3gG360hqyi+No/pIMxXItxbI5VaJG5p7eWQj2XCo+OnVwZ7CU4zmNYOpSkTt6GpJAbOWGzFu3EX4XYF6rAwuBDEC+akndrTE8n10O7In95hRLRNK9Zq4zA1glWv8dZAsRRpx3UnNNpgQL73EAkVHGDPMkr3zP4VtZW7+8QsRFpGuvD0UA5CWy9/WUJ1TJf97QMURv8ILdYJpVCY8O2WORGLCNf4dUa9HikwPO3Q5fNiXnpgVWKo/Fgay9r39CRwKIfhCMlk5550TA3xefz2sC4faxs1Hv5ye+t0XLhL4/U4F3I6rsQgULnUOYOJLiNz9SZzATHpst3hkDFUxlTXzCFWHpuP0UfkEYsSXfP51KM0B0WxW1TbBzYIMoy6pssfwFnoaoJWIiP7lDsFm4hb8VUBisTGql2qoKTSJcFtcezszMCjtJuRZmGrwbBEhvVHSl9iawgV1j+GWVmBuKj1yOrJZC9qdVHIv5jQ+ynL7DOR/ZyBGLPOnbtxzL9eoJSJhvxeMg3Pd8+csIQ416paaoW71JKO/aSmaE7137/+qVNntN6B9Alg4i/k2V/lpBXLV7Bvo7imp8sxkhVQ6bjF8Az3S9mC0NLXAWG3jk5e1F+0gPMi/bN1Vb+cn77XMoJDAs45KIwk1OXXvoLZuB/+vmSHP5N+eo3CO5bkVn+GTPeT95ByBZL5IKFalvHrWRj012Bdm5Y/mBRtTroa/8XwZ2tqEQeslSBJ6YXsQk15n7KdThTIsoaBdpnVHgmL/S5Rr8nSFUR4rg2sFmrlDcsNfKAcs32lVN9atKEmwU41OxSKy1KtfPW8KP6qaOMzA6/5IbYz/EkaQ9rah8Rvn0YQCDADahTQJHVLiMCE6dM/s+o7PT3xMk3umsULvE+b2AULq+Ux3tl3lAJY/ET31osmOqvc52LSnTc0FamfZVPAKIj4xjvJMGMXZrozUt2DNo5/aBCmxURy96Bd5jZEgHZ/2dIFp0K8IhtqqgoXxz/M6MztaNE+Ki+v2/1B5Yu0KlcvoFy0p1qwvjgsH3SHKVGu5Zd7cDgpoYZA6AgHFMlnf25bsl7W1xuC20j6/EpJac2/gx5ILWmHKd+sbej8KN9ggvfkF4Ya14vp8YX8HbE4dFYMZfzL76VFnaYNNr+xPkgv30VGkSnMlXJOPNoGXHMtzxr5KhWBH67HnYqDc0wTSJ5+ZOIwsEJbJKBrU5XKhw5qSlA/eZQ4fwkL57si7w80YZxHPTiu4/PSedd5uhE5KHpje0BRkKfLr/leGNkXXdA8KPaC+4jeOdPSY4Psfpw/qxendx5HzEqf1q4u05PqV86AJwfkEF8eWJUHcYCNHmzu8Eu9cFPhvQBw2yRCMnwdyMI1gK5pEMRkne9iPXD35fyVHsJPtu8zRp/DX136X6ca+vKyzZo2lvC5SBtdY++QolEt+0NDklqR8MqtbXJiM4VQICtTb06hhWjLxlnlFcgC5C/7omacCTbQDOyK6OH2N5jzR1Kohh0CU7MWQeiy4zmuQbLP8WFLSUa0ra0V5uzlApknPQrcehxepuXJTkElzNPfay67xmV2Fe9Jl59eRBmx0O35fxPGYBK2GKo4j6+T37URZAWCwQd/CKX8Kmrr1GoC+hpCfvn3JRvCT1UzfdlQVS791/eOXs1NWCvOuXij8wH9FSborzwb1kSgYmO3aKBv2F0DG7UeL2kkP6CN+6ZMK/ojG546Jr4VyZtF+ddm+2eZlX8On5vW6/m5cSmoMIbTRubhF0Fw5KOQiQrpJSNpregYJW9UYJfEvpRjn2EZfltc7qvobteQPxkl+ol6Xllt4vQsLVpJLoAMUPy7HUZUdtBgiwEvrXNKVKVp5CS5jr6X9HzV+yrWrn/57K/rg9xZRP5YZ1EA8MwUvE0kpI+cO55KMxBEpItzt4/e+uFobLbksHup1Vr3TluEaXcc0yb4IMIVtBkMPiFMig59/sF9+/VI7IOy0EfjV9Vzlr9hrvAvqXBCFiJ6sVWb1tiKHuQlLFKoKDnCiz4JV7hL758t6v2WyJ0CjYKTqPwFaFYxF58VnIo5ech8xsene9MXLo0Jbu67mPdOqCFRmcINKdPts9w0HJoyngUuIQGnWPj7EO6v8JpIIT7kbKTPqpV7OkWTGqvQuHJ5bnMHmYljYT/bNzhSYRUCrcb+QNxYyRvrFLJwwezQH+vnHpM+J0FTdzrR4WYmKrBFLtK3hXpPB0gEQVEG+6/yPDGn/KFYGY1J/cBLW19Wa+31+RKvC6ZRo4RSNsjdFm6WJPqa5vrd5h0abYewJRg+dt22i2tQmiAWWFeQziGmfVhb/W8Qe3EDamAWRNvL6ydyewpprf07jNpr0WtWIy/oHTP/m6VTgsQQ1GXJQZhcCa8WBbl7kFqCVNLsRenjR/4esqC1QsHi2m8SA3CajXSxlCCVadsyQNjo+tYqljGapNbCpuJGPeb9BxRXZ3Sc0u956LybqnTWQTNtSxEYIOKP34kWqr53lQ7ruGF22d3Shh3wuDggfGN29gd9slvuc50wtMGqbAYuPfuc3St+nHkXiVL29HV1ILwM03QrMWAtam18/rqoyo9nce9RIGpa2wvMCZsXSTNecqxXGmnrboMpw2KXxb5VTxRTjVXAHDT/G5cB/9LmLJr+RJ6MD8J07m+3SNuBnS2A9OzDmk9x0LKpXJ9ZyB6u6WkOD4XmXQSHWQwlBPFvPqEMW0lL9YDd3UxS9HOQ5XpWLKGfAGvRsXzpIFxUCyTXyU54de7SwBZS/qsm51wOsCvKwRTrPOp+VxzxFC13sSz4Un8T6BHzWj8RPn9mtYBzzEVLhUb9H66f4q9VjIcRhLXgJKhLwE0T+AT5dM3PbZe/HG0qJA4xanGz338pSzrsNrpWOEHysZhBIKBlIaJ2Xq8PQT90m3ZgMrFoOYgpdjpSKSQSvZbBMKqLGI1smJMdJiYxC0rtLNmIY5N5QkL7rgYDVmMsh2/S8AGpu8X56hypH8KqSJjxS41WO9DAIopkhXZTStEhgGun5CvEbOO/SjVCpuOSRYWJwnfiEwHIaF97Akvw6PYpUmeQiJ17mN5DVK/iQT/AWd2V2v1kyFzWmvuqjSUt9wRCmn0Q1Uj27Tsm7fxISuuF2GlYTnfbYFIt7Amxi9GApVWH7wMRQ7m9frFfweUPwDDSxaYEoIHwDNlvs6UmkxbM+0ufW26JTjHBX37LWbtgtCizSQ3yoj5VM0Tzmb5uyPiTZxGH8aIDwb4nlk4nEDx44KHGUgy29gChSWgmdTi6rO1kl9XIXy0JDuC+Ab59t9DdeID4+mu9yviwFlIbB8xv3/ipIzOQYfp9gvGpHnR9XTCMP8Y/7IXBoAW7dBbKNZxiKrX6NeqHu+rcsyUZc4a9ZuTADaVPvQx1nB0LpIid8n0U6Tvn22zSy8ullnn9tZC4hLWM59R3eT8BFl0QdHvsqy+CkiB+EwIiCO1wSBpFGGlCHCL9nvD1o1eWwDFRsC+mOMWL8lI0kVDVtSSenZhfkCpJyzki3fh6/2p8kOWfUhhRDS+dQUgl3uz9cfTw3ByHABXSa7RC7wpLScGwUPjgvIOj0LXg+ELAxGr5LQJtyYh67bseezCgDUBgcfohufJK0syQolqoLSUKjc9NNVaYFq3A+Fv8dWheKtKmC4gWc/sa4pPCj6MrW8Ow7bGyTeFctfo4Jf//2gjxrCE1X7ShmkFAgwghjXqoFvNa22RGiioXfapWaUM+hoRe7cTBWdbSgTbVw65GzOtE+zqN2k+SKMv3s6g+vZsIHY2BDzTyTgx+48Zai2FeBkeFfhrUcSHE8SAi0gmFoj0FNBdhZEv3Sz8sjwOHvf1uvwIvubpJdPPkyoYgI5VEV7nDsnOO1lf8RsvkW7s8zgryfqaZK3LqR3bCE4zC+kMZtBhnWiII37PxxUiOKM4EewDJAyrmwCBtttm+TTvprtNUQrCrifmr4eDetGFKt2D6bOeq4BWt74hAF1mnOLq2cLhbRHQqXj/vairpQFWxKNVEvB897vQ6kIRH9qKid/c9C8D7B94fmxCHI9eLF7LNbszg78tFYxmNZoFKZdnAN/fkn5f9JTv+SHEqKsGI9UZnoGU39j5Q5MtzOPYdS9AnDWwIOYdPFrH3OSILuUwBssv9utCVn+ZliFMnoOHj6cbMh8EaIWzzPBb4ifMhU3mk8S92GOmthMgubgWBZBRw9TN/YAWgNTg8K08UjxNL6Jd4g3EVyB8XEPTJvBo+fP2JX7SGe9Ubv13e1XYmBd6QNVD38xytIzgUN13fxA+aG25mUXTrvXud4O4nEfIXw/4rK3W64wJxioV8bchO/i4I2BedAh50DCx5UjVU5nrrK3koI3++qTXsWuT2q/ixK9ceY5AHqqSNDeTiS/owR2Dv2sCQ4A5wyNOI3nQ4jyL+RoOrSU6gTxjuq7/QBg3MJXWMm5ZdNsZ3uEcR8i17ba+0m0QgjPOosZU+f5P25of+8ah0wHorQgC+Lq/Risr8fJj1qZFn+F57y/Atj69WJ7gvcwNtFgBW67hOudiOznyuNO+z9lgVKnEKHzc5Mj/nqn77rq321gEgnceZMtzzb0h7pACpMf2+8j0tBt69Et4AT54vQiHDuU9C+/Ry1IyhlDfPbTu9ROVR9Kym6KTIvL3WCJOVJ7tGPY5/UcRYbXSjA+xIbMN8r0V3f9+XgA239JLxNgqdemcdwHiL+7McGKRuL1DjBSx7lNsumDk2AndF5os8z+tvL7L+2UEv7KhumC/mld/7rpm+UElsbvDRcgoxxXpUonjgNZR0+7asU6RBfvKE9No0m9d/nS3ZjP98UD7mjq81KCwgVvdqh5qx6Md1X3sQFeMl5M8XO9iD/1ztiWBAPUIFV/3Kj9CloxUBrkHR7PqD+tHyaloVmIOBFDMekhSunIbHvpLZ11sjZDLjw5vAxuL7NkNN2HLCcwyh85e8Qt7iXKv8cUEJFGRBbYAtXxhex6QgNOMVnN5ZhftDVQSWjQLlrd+rh28iwcnv8kNNwksJkiAQkKGif9UnR2ccBacTyvF7ZBWceOqf14KUCNaMxRZvV7Jm5joLy1+8ePaN+5VproBUKopvonNm55nnINoBzobATqasiC4rjHz3Sflf6Jwmu1qknIY1iUkuuZUqYXbC+SO2+8lTKnKXtGoNDbRNUGm9hsd2/7DKfHWkUK0mMo5RId2xslnVK4ihdM6e+Uudw/hXuXNdaUacwBVG1W1zzpLKLkugMTGev4qAG98lLcZxZRINPHWy2htxrlr6XpRKGGefoN8TCJPp3rkYQVpQHv78qJBtwIdLa35tqdy2QRl7D9dmIDgzqp/+Cq+b+YvufrK/UQtIVu1/tWq2S+Tihd0Q5R0gH3S3FqNO6VHzaQRydx5ZcXm6OdnhkHfHiuMJVSJ9of4kxRcb90VNU2Cyg9huGBoi5HAWmkhWLq7sBnWlgNAGKrQu7NJAMeNKQjh53Nolnz6UTAh52Q0Qr+YaREiPwWZpOiN5umGOS9KsxNoji1c4x4uAomNV2fcGFLZj5OVqUCrPEy53EmkTgVts2bzxy5yPbhgiI9T6zJHzjCDsztQVik//eeOYnYxU/xtxktK/fqZsbBT0QHZYFayg0MphQTzk8Tf3Z2MV8XL1R8//5YKv31m4WYyOoMONNWyuVHC0JaaCHvQtMDdUiBfUprhTpuAHjwyWzx2i2Qzhb/ZZD9vqSDF9zV2eP+Io2ezgGKQabeM4kbZt6QYKz38HA/ub50kU5W/yhb2G1304rBn+1Nat3D+SfMzCYsh/b2S7VTiR8TpJobgJS5gKUW8fe180nxIKlZeaLgHD0pWWsB/QxBGJPdU/1n5vJ8pqfiewrxmCs3PS6vDtWvHGovE7I2W45kT/OK0cxK7EQEEtasKPA1Mg2dt1w12SQ2kbOwjSU6fJy0x+cuWVCD4o9BNfS1EMB7Jfn7GHQV7Vk4VvQULJY5kgurFCgphCP5Y5H0VxzfSbxP/GaBHdk4AnyPqrqMV2LfCMNTC2/gS++WKrerySX/Mvibkai1U2IwjC5GfupPkimtRbIGGNkr62WPfDGYH3MWNDJj843YgpgMXbHraTGtlC8Vli9qhbj/WeQ7hxyRHSdS8QD07SKiKBaQgkBPSi7J+QPfSIgT/OYlmsuDQr/P0bhCxCa6+PkE/9DXLr7vbVP2uiHdQP1NZwDb9tO0nICiot4h5ciGFeR3kAmEVVw1TvVGq4qsv0mpZWm3VTuunNlY7E76GPHPguCMhT0OrdXo0n+Ll4wiEiewPt4e4LSg7oPtwpTwUJNI9yPvGf6x/Al8FJLOUvruESV4m9jCAIaYNGc4w+Mx0dq+3eFNqkrwA+X6wBuUM8x5W44zEn2Bn6kVKHRAHTEWk6gNl49EupPjNq7aRzrg1F7MMaIj7zjoMpQB4cp5BFR5YsrBw6/Xw38GZ3YujIFUPeTYxmoLAeExTuw+qtQmXvri2S9zlu3tCY1Pq+5CWGpw7RQjLBwyOW2lojHOenxySXeZIJ1B2kQCyxHoWkWwWaJMFQcb0PxVAPy98TIPlGq/YUDgG1jFZ+N35l03qzbrGcvxqXiXFkdDr++x7ET0YDagQaAzv4j329/zkkMZKj1t0k+oq40X0ZHrzmvxGnVBouJpqYw1fSCRIW2W7rm/xfYG+gtqWCKo/czdllMzaOuBQh6DkbuFLonJafNvE4uGthhZZsymKIsKjnv7K8I4wXtjWoFPHnQRrD3dZ6Xo0H9PU6RFZK8kX3wuKQ4/764fbTELC4sEH9T62FLyc7+QQ7QZ4BgC9MiCpmv6hXkduRNjTKED8OQB5mhD9Gyn95dn4+myRbIEZLFfByEBsDt7heQyA7HL2sAZFHEOUjYq3c4z5mtDqOCwlCvSztrnyJHENxQk7nrZ1TjDFjNjU8NKyKs+9BmNASfWSXQoWcsOSCcDKB4HaEYL9gaiVbyHKn33KG40/0Sm9vakP6Z9LrKw4JS8TU3/lyiRdzmcyJoi46vrcjCKUwI0RZg0pT9q+IpJxByBb8v3KUq2TEWEoyi/mitVpMkluVfgORtppU300ZsSKMlwH5SBzEcqgiUDqmy7QEjGrI798kLKex1LwkL1l+e1ftbZEDnBWRoc9RaxkC0SZVXPxDejh1p2Lqa2IMWEKVLfhmDM23FqWmf/AglEs4jBj01Cv1pZ0biuQv41Ks123yO7Ge6Xfj0VpHFhpLQlo+HUzm7noAP2VlpM72zrZVmp3BRHLtIZfM/8bQ32VFrP3QKD2jfsQW6eS4QPE4CuOLu7r3L9NSC8bpdrYRvGr7mjmM0dNUkabWv4ZS53GyjtGRUF1IboEzRvTV3uMH0B42iPV2Lvj3dFLp/SrNzCbC9eWOhFld6QtTDbvkLQ7sg2woWrYPugKHicoGvtp80PpA2xxkmYIW5r5GaWV5tzYqnRlsv776UTuUjgyj8m5iOuA7DEGsoRMJlP/CXNzbV++waMBGp9vjeC/3HCkgK48BfXx+ywfzdBJ/EAxQfZ6sqHjYeQbU6yhejdjfXKb4ZrUqeXRANtAe80C2BuGmKutlwWCkalec/u8wQ/XLU9sZusrxA6VdFMmTHnwCR0Nhbdi12d9kmoYJP7Owzwk/Y6az6uZRJxJISk218SE3qYWOiDEtQIZyQFZcIKHslygcwmYyIqu75KdgWYaJbn8+RXfaFsFZpx1oYe+On5p5zzg3fJtOiX5VDxFB2X5p43yVOR/4JDHQ01XF0plkAQ3HXqVugtKBb8acUjP31XZYf6OX0dfr8UKjIqnujBwJrrhh619LphZpekDT5OJPUA7P9gsimnfivDEhArrsOwbfgUobkoeSoaRpOyY76b+bRvcZS376GtYmhXnx5/1GoBWf9QKRhUKa+EXtCTyddgMXccfI5ITMn1fhdaXUZu+DuN5Gx1MGg8O5TqYbPQXgThkajWVQr24g13DtdIZITM/L4QHTeP964C5Ddrey2Xrgs80EANuIPSQtWmCvSIRaMovCEZ1FwSzUT9qcP292ttBTa54RlSb7lHLLdWxpfQhl0zx9xKWIfLm4yFWAFVNG6cs/XdUX7yuI8koGR2oarxIyxl5dQbcUdmAo79fjkXFk9tk3MdTgOw7KHTnvMvCMxGfI78QK0Sj6Lz7qtVX9QGgYKPzJLOwAp3tTkCKnZjBcmJ/GpCzgeq9GVmEjiWQafS1i2GUU4WRftC2hVC0r8u2C0uUQx/p0tf7yK8LsjfgoMyH2kjQrTa6XJn80j2IlT6jL+HWOP/C5DdLXdpUlTzePnzWr7Kc4w/8QEVxoKHI9VXQ5IndHTpBKfe54ZWjTKiH9U/RCP5eC+3clM4kaNcfD0cp8vOPzAmeMDPrZVoUib4DYCiodzN+Wm8EQgoYQBMIjulIjUk3xlmFcrAN/Wo9bLhDJ57/ZAi1EiOmHOX+jwd9mAc9KEfH7zOukwNGd+CXB7fhp/7rzZRkESuN/+q1C8HR1xrxBSWFjmiw1/qYGizk74Z8/ZgR+o/1w0yI+ov1VIU38wDxFPFHXz+FGu+/CWTR/UfwP+lGPWAXFx9r+TahkD3AZAWs76iC00LeQcPbClwvuZm44/ya4fp5A87DiN9V2472a48cddflqDeXemt/NcoUeJfmH4rlkiDsnX5F4/KT/JhGyOnX8SHQuo8KpdXvZAheU6yTBiCBIBsyRLga0NZOEN/AIr3225mr5fcOmyRn/tECozesZEwNvzN5DlXeQzTS1YeJPEiU2+roYAcGpv6InDZHdCldFw+5K4gFuirTYSN1DzT54MXJ6eSxyWFdXLyXrjTyJpU/E1/8krIh5a3RQGRq0EVfP7atTGH4oI+0q96MMnr8s1PA8r9x83RY/1pt0Ttgu4QsVAxIrRj/AgamotVZIxevxFh6oisS8QreStsGG+tX2W0hKxGOzTBXMspaME/4wggWWPG3q9MlAjl1c22QiFBtdUkmFRWL0KakYrRdx+to4eaQmkOrAKZn6IR+5n9WF9Y7soL5o3/xa/ED8yNMMunNRhPojPhme9BXiJnQrM/kxB6G1CYhTFhq/hJ/XfAk1yFeew/sGB08ouVpgPiatpl84YopP+cg7tW7GN1FBZmDNaGK0C/xHdRl13yVRahGQWMtIzR28lN5jtqjsgTnO7Pab1T1V7EIWo+esKtO9xd3tevQflTRPuSipqqcXhU5jV3yJX3oFno7yWVey7JSeKX7ZZaOuWGQGysITMkTcJjNl5cj7xj7uwp3B+Y5GzkY1L53ZJxh3hjsrrRn4YTyxtQLms0+Tpw8mFtprRa9PiASKUnhOE9Te8GTJ3REvlFrUCpe/WScL4MiU3rRhOgNntosJ3xB9kZGro64HvgFsK+bS66ZS+qePdWqAp7lE4lZ1wBMWKCGVv+FFI70Qs7Gqg8rnKdbiJtqRiaTGKvUbzbrENzpTTOnW+kBgGGttJjIh2ZiPP0Vbg4kcIp1CJa9QMCP+VH4E8IZRzW9QfjfBaKgPmwXec+tg9oZuZPOBA29XmkEgonCMnZCFt0AazUaT+tmzWp9MEnPi20CfmfKqIJWkZqI5jYmJLxn+XcaCVw5ZEWtE7cztYXUknbFOeCsxs1GYwnRA8xGBtg5P9PgFuP0kYbNH//UanYyhf0KHMNjG750O/+tWROkpsUSgvOD8ePkIeAVYqDGZB5X6f6GWphGnQvbNqMY6mUv5FOsjo6OzT9RjsmZVBAjP7I6RedPnPRO/MdjxULgJs6+F8gdNA7jXnG+kzExYgRcSTqNGWMleTf1btUEviZNZ6kU6Dn46yGmKAwE0CvZ/45PxLJF/CJ1Jy05YUX7WT+3LKirVLQ9Og0PBQhS4Dv/MUEn03Sulsr8VLriyMJt0bSSimdnlsl5RRZXdTWwfaf+7MSrJGf4oxVaBce/kuIR09mNLyLm0oDhm9nM6hpWVw1fxrSjjQeryAEaRdAdbUtPD7CaDdsOo6wfGxoHbCGlHsAUvRcn0S1lUBP0kAUIDaQvXRQgrFwKqVfqSm4iswTp7cu1rrPEM0ge6Q7BXQnDpDlKCaC9iEUKtOQzCQX8NBcUOKrQ1bTdPHaFa7BWtwApUEGLg37X177vb45oViNVEUoRp/DpI08rZdm6GpW69LUDE7zpjJ8tNV1DN9j0xagMUYBReVL6QdpzCuBbd/Ex99IQzOMSXXU8KYnXNXKKI5bu73zMdw6fTpsn/jq6dkC7acxNjNW0U4ZVKXR48g/NzYIUPVbe1/gbhdcsd6g1HwTxfwJg2q+3FRtSEmyumOy4XJTjjPF0je+2zmtYl3UdjLAJRyx8YcbImjuHyS6X+3eVcfH2JAgR7ed1n8zy/RTalNcNLM1t0h05tzxNAMoeRazBjQPy7GNP2vwOKSp6OsV/DTZfHuSvywwezNxFUZvr8B457bKqYtD6XKPb3Wl7zBFuOMiReMvxd0Mkh+piOxQeNf+WrtW+jsn0lvRWtTktqfmrUKok94sKceNxxawmYpVhZwcw9BXPe2WvFZZbiD5d24fwqxl1GZoNecXJBJGlx5aQbR2uWxN3TA/0Um4Nbs+YjMNF4guyRc7GPQ2tptcn05UAgb2J+HJ9lG6pMhcZDdtBpdRzaRdkFrzBHCW0sYu6t18OIBni+sNrhYtxdOHIevH6iih7hnZr5aRaMrHssxdFacI5+uGSdRMewHxbS2XZzgoMb+31DAjhMFdEF8feSBPqVgItjoMLCutwIXtbyK9f549nhloWb9lXDISPBRpRAN/uUStY9CljMevTZ0KFsPz0oLna1DnvqIieXrtu3HpuOuYQKmsj2X5DzQ3fDq8kvBgEf9fv880vsbIqv7gGt62wvb9dAUdtrRI2u8A6hvruFkE7XEI3hDoZwtzDoSrPDL3CCvbmDwOsLn9W7PMtRHUMDXunCKlDvQj8ca2htlXZhZSPUrhM4aSE9Qk8lOiSk2kumJfIfdAKTUe30giLG5xRV4G/rtaH6uvh09OPHhkWLiLAB2Pu04WzJZxBS8W19B+bmRpR3abKyZ6uKLRjNHsW7pA7lBxu4r7pmhF5wAgevnagkFnSF9ZXiK1QJaSAtQEJyr9d0n9IwAi78Yiw1cZYyHQniHkwjfcU5INN+vx+svGM9/Q9CCE67AjsU/5IXn1c8QVSeStknYG7tPU4sKLMluMAIqKWKgO2dhfuq4QAbt1VfD52p0HAUoLmWuF+uGbxOnlX2yaFcrLdonqLi1HE64H18yA20d5KfIjIKS8TrC9kst+/FFFKQL2uiCzagIRksdiDQr8Wv9lkJU1/Cu7xqE5w+VUqv/vTivWA7K7orSz2D2YgEX4f2UrjREm0FAcbTsTkXtXYKpbL/NzmuZ/0LbGNiKO69MF7JSWpUNxnUD40Ihip+FpPH634FDHdpAEBcRyP4qyB8eyiTkBmoGLrUCy1gGzEVrvf3OoYzfpsZrEb4NdS2Ir6rE1CkzVGQnMEtqPU2m4SfCL7agR8EuOwC2X1Iyx3Thmms3y5zKMA7qJ5DIS9uzMdXvwQJV2pSmBm94SvagkycgNrtzcVOy5IxeKHBAj3Rx4+Y5JqpDblI53jAldomMnvbG8lQF2WBAd/Zc2P3N4fIxsq2isPfUygo+buAtDQS2PGOlGeptUAdyCd7XlouUyAqYigLBr6vwj2EQaJR07N9L/vWn6ZmiXmW9uhSA0knXdNytSGnhGbXgNP4naZQONpkyqtwCi28vHxNtNla1poEpGg/k+7LxFiG9fLv/CV07HVfJ05x99vFG+u0xPtJTQ43vb831sDoBFUi9d3NOKvT8CtDpP1eE3G5ErS/rE81HjvJOhwD3PvJGZTaxoFtUUWMke/5R4LEsR2W0bT2kwhg+TsjrPJ3+VvXzxtk90U0UK56I1jyVWdVCXvHQEy9P4Ka/4bklO3Vh4xoLBOHROWofAQ/VSPITfmAHrNGOr+EoC3GOJxwXrRskW5fJshTuTKKT48pNMmQOBA0iwi96mOiP58EN1832BjKXP9VQeNmO2SBRApfrdZTafyLkCbhLTt0zaNp+ldA3HfLJFbQAd2I8ld9UCLF4rroiZNcZzd/3DbRqhYQg52tmZgR9AVGwcV1f9N2xKQ1XbWlbS60vvm/fWRZfWgT9vuCln5n0wIX5KXGJf9JVeL3V0zbuWJfF+XPgIZ+ZWL3YZqTIde9sOgqwu+uCJCPDYAkcKRtsh24OCXhRYzZWZe/qz6sFbfReZfK9ZXj1yucFh7eCO2Imy61GXq8+udwtcMyaFrrUGemHvw4JdfCFRzSCxsV4KdZB4t9So9RWk7pJv8SJkh74OPbx4M5WmW7NqIcXRTLqed2guqhjsa7GYxvZTn+KMb4ddwf2IlaBMoWCUi258qXQ/gUS0aRplyPFpPyNTchqyFpgBCxWQp/2hjj5ekIttH/uEaiBJhoHlOqvZstAd8LQDdAIZkd/qs0or9oQWaYK5LuJgn6elUgcn0jbFpR9MA7Q/sX9UT3EbZTn70VAqrD4ka937SbVUHegpsdKezXy/QLHbiF5Ggy6RuCKmN79GxE1UOuwn+3MIooXWH641MTOXqwN5qdBsJMDo7PUGMqO6aM+MLRlN6pZLa6Y/ckuWiZFVFhLiGG8PjkeaIxf9GCM7uMtcUmBqgCGCKhV7Qt4SG/yheEAnxjPgxyuInORgsH5aZWSSjob2bNCuZnsBAURm31vM+waL560Em7Ft4X3FeNBgkPvKuZbz+ULB75lLeuHgQKD/SnBcEX4a889nIl6ZlBwCKPQSeANulRXEuMWcRf7cfY34octHzmFonoMLotOdXA/uU7WfqmIERt0sD3qoqinu/f1UqEbnt9WlTV7TIbxyKNEKEAbLR4M066Cmk/nKjqQWrrJS3PcwkFXI9PI3Qpu3Pv4i3mx8LEXEWaGBJFgGGd+hVIvW5UjXfWE//roGsi0YEweF0ya/m7udBr1jadFA6fShAgicaNjAdOQsYhRNe1XBrPcHJyEz3kgRhGVj7P7DHzWYrMDDmlO7wEi1zWLw6I2YzVHISgoeYHDPtgrTPxfTIT1Rcz4oLk7t+7w6Q7SiOraMenjuIEjKoUreuwDnd/n/hUDipPr3tf7OFFsdI9YvusXSxiaW4wD6UJEqCCj/TWXsR0txN3xda3i/GCkcZOJJQuIU52vfLbvCKbL+jiwxM3Dqpg7Wgoushe5AYzMS3IWFljaVaI3a5hFpZiWj6kEYs62TwUGnTxCDwybhHcTyKWRZzeHscsthMXHod+Wv6vVIEFjf4smFhknP3U5D35UQKXEdikzE88TuvgskOsWfV6Cc2gXgDnAcGZ+LoiHGFU/anj8fv7IpB2naiVlVhLKNr5PX6VlaZkQvtPESwRQfIXOgT+Z+LlMs7iQuZTlWl2PYMCcYIAMVNiSYq2FMI58jBj/sGRsS3l1xGwK2pw1TaCWHUByMp9RqJNdwz0rtfMnrl7UbJzfWmmdiXB2yTJGT7F6RMU26D9ZA3e90vTI4u9mDi37098QAtoEU8yBD5aizoGzns2e4ADHgl/lnv92dA8k88WUJsEtheylJOFAmaIUXSFUOVTM8Z28kcNJ9BLgg6FFVn26kpRK2M8VoOw9l4NIdH690DpmM6CI4iinHJ9yUONsKVWmXvP3DCjrfhyRJfQ+Dt0X76XOQTGHTWJHECnig9tSROhjs9i1biJcJFHt5+6pXtJCYrm3fjyF8dCm698+GEpK2Bb6dxZfa/J1OMf4ZLFe1pfL0Az5n5nmk0HGewzMZudIPHJXCzwSOkUuKmRw/erhGmDCc9SmoS9FgVeTGV5gr8Ls49/VdsOpGoVU8CDe5s/Nq0jeYpTIn5POtnBvCCWYhr/djsAsV4Eda3S+OUB55476I5lvl+TPj5/95SwLLxdIltGGnCpqROlTgb6JsS/ls0PZUP0xU8Bb96VLlkl2WBGSo33LGKi2yhgtsM32JfdPvYP1gzWDp0spysb+YD6wqgTfSdlquZ9UYf1yWR/aVn5k3zgBdolOWB2DA8lSjr/ioUy5nz2fVF9GkU/Oe2/pgek9hEidbjheiHxXBJz8ZouOuaxOY5XIsBc/Bx4vHy5NBRyln7pERP+TjnpTyLXloIEFmLRv2AuLf4++LE3/y7+A8mXxBF9EfbDXTghwE9G7Kc/4oQiuIHzbBdwL8uWfmdGWThYC5ZhOCF3v83xY0CX0xTkXFdlP2NusV6KkhXfcQ9N9t9k9ajVRLbESg01rS/Xp3/2X7sYhVCgdNL5DkyaDP1cTCkZ485Rc8OxmPowAh3v/+VGgIILN4XXKiF5xuMrRqhTiR/OoqVa60l0iCEeL9sYz8cnDpgGrjFENt6j98CM1VLJb786OKxafDRAV9/LKgQS5H6rLj0o735PrOjeCiazkwdCD4gqTQ+EwwViT1kgnEWJbGAoih6H5HAGZkKphiwSk6kaIhw4CuPIdzdRDU6sB1cEjweDTne0/eKQzUzM3j5en8dr/d2Zhnl3bpu5aGOg3mrbMo4ebIzQRvvtVNbhfcGddvazsdKkXmo0OdnmrYGm/t7XWIFP2a6k0bpDflUKoC5LnXvKmioX13xvAutnQ9e8hZZKVDuccK2wDB4zLyKt/PihL6X/a8iEyuULjT1yYfcrJMUZh5fli2aLwZTeCQszFl3Guxh0zDFJLcABAGpJcRFOQKKc+YLjYd3lpXS+2xALswr4qhfjOEjbtkZ0XGm/MXaFes2G06AzFkOBKn84+4Nb7+sxYCST8+Q9Ciwr/h5sFTsSEzKG6gKfb2oZhtN/4zi9SyRQ+CpNvh9AcD6VT3whf/SpVK3j9X7F+Ayiv28KRy5MNYPkQhXsIJ5ZcxuWjB8Lx2z0oypNoUHf2bbTjjPYj1WLyTFxzUdif/MUlsE63VI1+QGlYrp8eph19rOUkYdGysh6+gmZz3ePZJFMY9KV8jdViVle/66Xsy+ObJuzwqWgvhja7gsCfm7TjVznX+f0+X1fqYYJ97p/i5+R3cBP/F6JIs8L2gMEj5ZB9OTDa03kMBcT4xmdb7wtV3YXe8/Bd2oW2RUImOIvThmtiixZOBNvJW4cgFEizR8EuK6AbakndnBOYAPcb3PoIDL+MwDREn4+wX0OuUuZ9a+cd5/lHEhqykpCn9ACAV+Ls5oRz1ZNEyFdt3tbGCnRPySy8bJLqweJWDy6LGU5+Ub21lizRkN4STxy8f4pEb1BVBzqur9R39gkO68W3ZTK3hUUH6Bfbr4w9diCJIMCfiJgKK6WvLwk8DDkciBQKO8rKbfLO1+541j1tb3E+hEcxSrpEm4RVzPylDGib+IxCA0VVKHlWzf2HHmiN6WPf1NV/m6/0OsjU7PZypcngbrXX0q1oy1SunO/JZd16G65x1y+GVJm9gxNf9cQC5/M5PoYL7gHX6sUkvm4+fzds8c+X4Y8QFMe1PPT81dIe+vpFkTdtwaPY4RGitxqlR9ISMr9pU0B4geQ36BKIRmQ0qastvXpV3pi17601hGG0pyq7RpNS9WddFUQLuCycBh7xpW9AsQbWuzkhCutJdZ3OBhP2YXgpAOcqnSP09Cho9R4TUbm4PynWiEWl8ZVg5uBesFN6AvJQDKDPbAfUgUq/ELXrnqjizHdl4+obUurGTCreviblIxcL9QYn0oi4zgXDqeZuiOyqVZW5n9N32gzMVFvR8anqcOrCdylu0D5FFtJNC9tGQxCfAYIzAoG3GAvRjOaIndu4CA0+bnk0A0bqCouA9yjzcaOuKN8spNrOtR/4+/X1/W5cO3kjMDnnB3ioCWKfTUzeYs3/jfqs5YLH4agtDPsgV668nUm7cUwAfertcWMAra2BzSTqSbyK0TdlXD2qjS5JP85oUPaRlBWIVf2eyYFZGnGV00j+Gi5jX7nLyx+Of3voidzO34kPzknA6L3eU+DiyNYF8C+/Z6FWmWppNNHhVeBzGgel0oW4ntaF+OwGdGsf9ojSk+JgFSiLKu2+rpj4EqmdJEk2hbYy/Tez0GZvkHnhAQl7g1VdYkvYkhc1PglO+049C+DCwTGT7RlcidLBhE2KAAVUZPOgN+ZX0oJpnXZQ5IUh4SfOjGLqQhmTjodmMJg5By4mM0pkrG86Ugf3dGqGcao1CRMQtPi+SalRGOpRVf8GzmxLhTZP9c6CeqK9hcgYOppuQWOV90Bap/+ol/Bw9yrfdniI1cTjSXzh5/epxqiPknrNMUYZAoTDG58FvQJ/pvPbmRWTCORTJp50tow8yyLITLW/fnhGTSgo50gQYBErttQUL9pnjv2N47yjW2mDKTzN/SSpUXQ5s+PFHRG//HvbmN9lOKUGqKHaWh9/I2fRcLQ1eNPoQMKHBuRHyN07mV9ZnKZKg3sn5X6NcGJXd3ny/wk4+oKvPoumMCpiWddvyGwNZ/L4Xj5aSfQWkODxq8g4/QEXFIsQgnp0x+Ehi/sJQRXRuYD/1f/v2CH+FAJvDjM4ODkLJU/vaWHV5ZDiEeB2yb5slPGX/sKpM8yXiWKKkg/lTp+jfHfbPRexKpTNUtfqL8G87LFemZSjgcBT1H7UL5Efj3uMIVmAnUMzdCotSbkUfLxQUanq4yKyslBd1jG8LYrNGta83kvNejU04erwSm9Asc1vszE7y/5td4fW3NMiNhynOL7ORM7xeMVRioj59mAcH3cFqvqNP9lQe696n2wvySy9WBol6rj6K4xXNhGUGUd8oOCgEZCTt5EO7VX7J+x3Ln7MlAvKro4rNa/Ur16eq27Y+qw+SIOfH5MenNHOtOWX+qZhi9w70Z0Ff8vjvF6SvFAveNXFFLFrar6P5quI1tSIwheCW+WeGhovN/habxr3OlF9R9tpJHezHygsjIj0kRCTunGWD60tNyUaTsYa/p8VuPL4CB6RgD/Iydj8UrzRsM5A14rxpWP5FhBD72QXQ4jdwoexgsq9FT7sd4Snujl82353gu9F/KrIIFDX/ViTvZpMui2G2kuJVGKNV87Hnx37oRqZgIZ8aq7DMyXY76FQ700fzrkHWgFuuF9Nyu2YipzduDdLUGu5PFz3moyysdHNfpr2hQ5+hJ1IsXDr2OMKEU7ju1FerwZP1RqhsALsY3j0QpksTXteEE0jgss0Hbj29T3QNHvFwkaoELHlzQeb/hyreS2e7XFRqm2ewOPS1/qmFupZ9vVxAw0lH04pnmZIAxddAnhc/uCQXOgM2kjyoIiL0o6b5oyHxecXxryZ/GZ4RNRXvTZmxEYRUiur5swbO6/8RMcFcD8A8CTTWPk59tHvvgXO5aN0A0o288M++4Ua3HV0l3yN41PEtir+dsU2X48xHZCNQcJQSYuFCwBPi8lXnMi9dcss6vq8+SavjMTTAjeCvkyX3lAroGxkhCO+JynbhVa59zEbclvg5LJtoEs1Q4MJ9rkAFJDEs9LUB9n3/EBLQ1EMsoSKg6BmTW9Ph7f9RoVluHle0/YhBY88pKdeIRkJKeyNMQe+ls53RX6XZxHj8Wxx4Xm6RPiK/Ai7yV7l7etKC2rnjELZyKOEW3CLjVGvR+yH673p4vTKvctaF22uggfwP/KFzLrHlRohUSBVFWAglhEHZCuzv47nojGXLMb8k+oJZaIxzyEHsIlfjHjjdhvoSyjjoVA1mr4osWyE522iRbH0FKnzRKdMC4Ga8wTfyWtLyAR7p9w9JA6SR0RYy7ZqLgxFG/YsnOYsov9bp8W3D1ARPpUmovWy8iOOa3MMHmXx17toUsQ4sDqhs2xXuYY29pKMzslwQZWa4iuYLornoxwNuCXh80/IaE758cQkz3r9LnxSuWMtBrbnDz+JfXG0g8guCQidv10ZFRyBzd6hqOMD1s7YVHm/Ysf2zmOst0oXfV4hjUXZNhc+IeG9uU3FvVp0WJYMmRhbwb15ogJjIN35OKJgf8aBUBMs2xAdhSNjS+3RmiTVGkR0ep49RfqV917EdnH2yofy2TZchv7aUwSOuBdxKvbpBvMDXWoAmlrdv4872Nhg0VoEYhoYfvSkWr581R+8OHd2frNeJrF8wCf9PLykXX0L17ptKZOI8OInJNq97m/QWe50z7md7AfvYIhdNGi0QOthmJnZYsNlxx5gYQrrSLQ3ik+3FSsxTNLFDaAFCGId5N7ZNbEsFQmpSGMmZAhE54IOlYBmBT8dRS+bukF22hqqsQsdUNswT7jrnSsWBNWxpxWHuuaJ0EDlXqUPCSxh2l2RGntTPndHfAiV87xA5RHWWgFNE+ZrIl97C82NoylzilmX4rl5DSL20wBAy1bKc+gsnnrVBQ+3H68QgPbRh6Fk2U3nYwo62Yx30lEADo3JaQ1nCehMScTufG+sD7GjY8NGqrz8NTfirLIIY9VkHTxMxixPdToewSB8kXdTJocz5+zs88DwPM35/s8mIrSiXCFkxHpBiOr1rqNwgdOe4SRDNxJIM03oxECXYdjZaHlcBKfQamI58albRb7cnz7SF45wzIKgPkfyOJHCLTDiLS+jzZkMQarRwjEy6Ty/H4X8vJ9dImW6BunxK41KNh3X4Vv8zm9565ALCPzM9q/WbVJNFbgpfDEULtUqNYln0CTmkZCGRbCeAqIVOL+8r0cO2zf07WZPOeZafXGjTbWlk3ui04XZkvuDa1FfYIFkuIO0sxyvnB0Ilu176R3CTNv42M7ODdi+ceqwd7mtYV0cQR1C4yTxyKKWlMVJ7oJH28hWP2brozvexk3lKwW38tCqT7zWg4U9T2E1r66SmEnmYYyBbVQyoMd3RmiYhAijZmJIlaiUuhVhYsVhjBe9VUu8wJbi2/VznMYRI9DtHTNQYhkcW54/iydF2+1F3WGXIOET6WmBag8j6Mvl0fkhsE2DEqu+h8c7asoF7NTSTqf3cSiJP62xEeeEj4+mBSrVrIHEOMf1C24bg7z2q/mcGoxikothi2LtRQhG/tyC/GkIzqvx8YYRlcRajvZKgBSG+I+JJUWGYQHM7F/DixRZyXHPF+QbXwG2kgXM+sCdGyuftLAjw+YE//GNPWWhbvTPbiMeHPP3aCS/BGL/Ax4hOfrG+g35c5Lp2cr9Ty2mQokeXiJLImPfRmc7YPOQifzO0f6wq+WRhtG4SLgm+Y8jRu0amxNTi8CQRaQ+B5RYbF6gEfPFnhc/GGROny8tR1Ihwt3T/C3aZ5KRINACvyQsg03Ov0Wj55sTb550P+qtpr/hJjiM6MtDoxCDpvQg14/xxVkQfj9gjk/CmgDPX/SVncoz284S0/f8Ecba6DZrj66BX2SV+hn2b/tJGTUhHlzwo1aVerYgi1ItIfyMteec6AYjMCOsWpONPRTGwANqr2POjz5cCh/OqFCEfAhexPUbgXrwpb8dR9MxVn7Esp6IPdcCTfMcyFwfkkFFJSGXl71vI5pBxZp0EmCAWTu3j34pqDi/h0q6ZP09XT5lm7GLvXEToANmVkvv3+6USAp53ogA7OmcbiiLbVM93hD39EaSmFJHzxhrm9njooXvemhV8MC0V3RLsDuAw0QC5IeD5d/DutHDUsif9y5wCHbaeo+3jPAa54gTCQd8tuXDOE5FDC6CK7OgQYZaEIMwX8Mel/Zz001s8LUCd66PpIHb6slmL4ulx9ahr8bVw458EOxjIm2e1sV0kBQCDoIJkwTUE9jZvvruQG0aU98dRHliV0gDbSQkfRqcqCKfAuv8cF9HCW5dl+SLXcicT2ZN/VXKqu/+qdeirrhw9X8m9GCpddSzPgCtseAq6oJUuzxT0SdJGPkYmpkp/PNTYJ8bytvxDBlUk3/HNzKWKYSwXQQ3gc85iqy8nTuNwI1ZQvIFj8I1kieLzcDhtFH8dEsrvJwQoq4MEkYiKQ/XI5q5rme8gBJ5urFZVigjmjCjSBN0DPxtV2zftZHgqZpfu0ELnD8Fux4jpIpN5dqIc7bK4TbTHl3A6xn4gwrz98uTq1isuITY8Rvy5G+/pHujtrclmPezFxmCfotW5TdnujwLTaGG26mJx/Iz+Z/UX7mPWtb389vPjgovmgaAxqR4ivcmy473smNkTDmjqWtFDeIarnH8eOqU+8VuQD7Yd347i34pByZhzLg50dkHl/2mHQ2X+fQChR8rWR8MIZgfMZc+kRVU7Jdu7Z5emAw6pvVymMvP2ADFLRIZLJkwdIfRgdPokHyu5ougalkRmUc/r5SkAt4TRDaZki63SH9kYnEowcbCfxutX/D32sXRi/X3ETyK0cZ3i/WCNCG6sxveCr2xl3MHPQ7NQmVPqdkmtcy2IrLJm8LvugaERaPgMaZpx4c+vAmtS6q4jm8WIJzTr3CAl3Pk1o1N8pUp4Z8072xQWwwU6EJ/fHK3y/oYgGJlqkH6HY0OQjXUM6ffLs9CmHCwUMKz6mR2sM8NKFjebBcirE4Z50GdAJ1Cd1u/Ji0UjlHgoASHbF/TqoMrfojMkBzmlVrOSIA43Blp4/FuaVJjKhcEYehncfIUD8+b3nkmUTwiIcD4fVAr5u9A+lXEczXkjjaYcYX8EKZfhHYZPy1qORf31J3c22IuTaQiszcfv8payyPj70Mv1o8UHRmM01Dc2/m7VmqZ/8EyT2R/FW4QBi/ze9iZwhV5vGG3WkP8EBfZkH3OL38Awl2C4TfmuN550pavLI6HiamLfrOlx4kVT6a+J8cTjIZwCboBdFpWlyxe/NDQhApVklju5RPvGMYWG8Si4g6nNjVV9jsrRM9qCur3uLM3oNmwXEqmKvuyWSQTIOoaEyUzJ+vZP06joPhfcxj+h0NiYaJ34qZjoRJkACB7f6tm9sWpfznFtjZL8nyAXhZMnKQ5MgQ7Zp1oH6uBELmb+wZzeuMEQxvKUV7nMrBMmxiJQ9AA/y5CyyLQWvhCZVJbxcW+NZxLaRlPmv7QynJjfWqMOe5sqESjwFLNNiV4lvpRbpjQ9BwWSSi5wQqxxLModYdMoLMVcfGQjb9Fne92Ih++dM2z5L+q+dRWFjKsM/nNPOCN50jzIdGLZbDdxcFNChYNN6iJHd3zQqiU+eXgJvsIOGa00rWaad5BiPJPbaWQF/VD1AFcoadIC+/wOep1V1lfgI8I7yS4XzH3oQdYHv1pDT2PLFVtTsItL7Kg+AWbqcyf8BkO57ftJrvObuLR5mQD72C1CfE5GZYrZfsF5qYCEHTdEXyCgiOsd6M2ohDEFRIzwiYJuabbdeMMUn9KD6+BlUfmqaZWOatnS1+d/KuSIykH/BbzDl8vpguLyBKHEnit1sjPE1CWqvN/4g6mW7Au6So5/SvcvJVCPO+nhx+YpexwG9+btjIz2b6etwGxxhSvdBeocb7l7zEPSr/4kl2YsVULiMI0p/1lF025KEZ1/QF7EjOMw/7IgnEvKzdBNyn8obXr8QlwuZhS1ouq+Msf+WKQnRhASgGJBsHPgL9gEH56paKyIc3niIAdvZ7KL1Q/IuqaeZLU3if78GQAwgE/OXTdCFycn2/LsW9OwtIW5fUZh1E1beE3bF028MI82Mj/rsctIFigIYo6IJLH9aS7X9BE0+hxS5iQdt9ef6pSKuwVS7lSrGiF7XJ+ZCq19tin79GLWdziaRKHD9vaWLjexjZ7ScG1njX4aY+pDZ3jSBz3U0s7LP8e+3aMb7p6dTqg6OdMFBuEkLB8/95HrZCb4PaUP+mYWk0J8kidOmE9WCMQxqgC5iianaSP44euTne2/zRI7az6VKhCQeqTM130OX7Zmw2f5j2PJlJr9HxIlTmY2VqzOCKRbwX9RvHLZbLO9IACNVIvo0J7VAaFNi06N0YPyJPwOm59MYe23KEI5hewaUeqy45sW9KbqV/fDDJVAjzLPczO80o2bRGWO/BR/QQbBuCaqih1KW2KJaW6/jq+jkX6LiPufAZ8qaEMzBUF6QfoxYTkVYHjpgVHKPY8RNJluPuRGMyxPM8Pta1QJBYudKLvXtqRfh6R+MuyN7Q0kAYSsFkfBd/myJRTovwqDS73WbgDNvEl7GwltrgHtQgF6hfIX4LuhpBF7TsKl0y137HwJukPqjeTpXBGoYPIi1s/lPLE0fo11b2gB4W6uIx18+Yr3GO5+wlIaXymE8csHeaa3V7EDT0CY3EBaliHZ1Gz1ySexgfmgA5UIWjKXApBK7NX/q60lJBP9xGgl/bxip752Ug73zfn0BPaO4aKr/47T16a5ly8J6gyLYAdHNdnpmHkpT+ZTXb+vCcv8jsM+amTDwSshAMvZb5Kul9/XyO98Qlos2tYCjIM9VoNU/sNWbxYfYTDHeAJ/TB+APTqgJLfoXmIXfQy1lmcXs4Mmzb/1Y8CLN9N4FO905deRGCijmPLyCIXO0vS5IOIcbVvh3xGPRSnufn5I1/P2bUMjBzsZ5n5LjHcYmJqOFOTMIDNaNKOx1GD9EC9Aw814RfEnZO+Cp4OStaah60lmfEgQ0n3gHr0gSF6/m4TX8wyk+1pKV6tUYzkVIOxnjAS2jR7Ovsvl1lF4i5Oh9T8mW6uStgDJg343g262vJ18T1u0JpHux5rZjlNdVtn1oC83nRwR6cs44fJ+xFy6s0YxFDEdtI4xrfcNB3upwBw2T1lvytaBGuOBed7QUsrb8Q4b2kuHP2yw/g5y0+G1owMR6dYhm63INq1cQ1Sv0szbPAgpwJhr/q47ca8ya2w58rKKnHZAiW38Lar4hdWyCNEIGqlx+ARl2VLC4YeDzh02rvZgeEJXAvdOEbYGSyoojRE2kXr8O85Ks7jcT15E+w+gzcb4Gng5NlCe3b+h6B2iIz82Yf48L10ClSadjZYowMPuOCaPpylsdl4v2lsYG4ici7PvpQ2DyW9G6LYSsPrQ/vJu21bZYfSI2fQt88MABZueLyQZuuSENwPesgs/1NwgXy23nMxa32U0Q7l+0K2tqUJmKYjlDFt+AyAgZNHCIxkgP5jM/3/fBEXPEPO2uJ1+0+bqhw5ZBfRwSX0/vrvH86GV5MKrUvn+QZgjeq5Bz67gFIZvstwwoRyJ8lhv89ntixVfTGqV3dFinoTU6J/lJk/EG3vKLns4shL9jxjPJxIB3s4F0UMDd5dqWHJJuXFveD0YPHNzcOXWCR1GqG2cUJs5Etltn+vblzP087tLuYTlECjv3taX1z77sToVdmK5luvbhfrTtJH27qLz7z29jGMd+PciRzvPbCwo1zAJyk1HWHVWyZkJOxq3u8B7sIcDpCafk+u2HqAD/mOb3LHu13QMSXDxgW207ru78iZl6uvs5ztfd6jPrwiQOwq+cUfBzzWVyF/1ZbCsvDxUgkL4nYAHhtxlV0xkP0a9Ajxv/vAB6wOh6l4+nx6xNno2ynOtia0/lRsjsoGYx983B0nVBtos81+Eb85Py1Tu7gickT5Ow77prbh9qjYjr6Mj5Ux7Y/v5Sr2+xtY3K75axaNYZU8mVmUTtqS4KRFP9vSxMyvmV7X/Ezdz0q6KOvBNJFrvEbY/1DJSD298SKD6jK/b+MjXNoBPBibDR8Ptbh9HmwQfMloeHJ5LvDwof1rT8I0E7hB2wtetUDH0gTaoB+f8LChyWXuFX6JJsWYm4Geq2WJ9Ccv63SC96Fust9YDzo2dnevka7smgwvvcpwjYq8TGfdok3bTj4vdPDuzgy0XiQ55F0NW6r3+nfxr7g8ZYerTxowtyHYszwE92zxG/dXapjLQv/vQ2jskxk+lfsJKYpRkA7LeajRcSg4TlFE9w9M/teIVJsUEpBEjfXqPYAGDUUSx4evxJu/1ZZ09+HbV0L+YT03AO7uyg9UIYvHQafwa+3NXkQuua/P1S6WMN0JuaU1OW4hxny5cv+QZGg7vZ7JJGpHyhC9GXelnNix3krg0rKTwNBoOZvSIbEjP2/a+v1+BjC0HUEPrVK6vLTYkfHQdvP5+Fcf1Ktsfz95g5fKlarLO0sgTIyOOJseZA6mEORzvNN8KURAH1hdq2xY/PfK8xlUsIKSRnPD32x5u3vJ7KVC09wrs0xjbgFxha5enlUstH6eMMxYcXw/18WNy+5QX+aUosWwqr2SabMCZZ5o0w/L/TX/GPOtCCkXEGCWhSLhC+dmGd/t8X3+Fu/vYIrpIEPoA/TWLSYLW2uNsrU81aoRwGf04fMERBECWPgztLzbQ/tBUNtPnXz0rkVgIWN1469+uuZfPiUZWcKoL3IepINPGeReucu4TauXJpfp6BcbJJMYQZpsOpNsMdvWybWqBDn8M17bp3aZLiXwrNEL6os5Iym+1Z/evI9FlYDt6NSExOwxI59mloz12+n+EGyLzXYJwCo9XW9ceJhMSCboaq3LF+HyLmmRRiDaJVl6bSu9EYgPYSDxzfO3H1p7eZfw4HRw/3YeirXVa2F6YNNI0WzQL+E+IWU5o5gS188w0aitiy/N/ASrQBKTuKDIoDv0SOgacIM9WP13hUf6keL3jBfKaI9j8nH2QxHAHMJ6M8HGiwUYpRMbhYO/XoQsZP/LHyQJHVzfLCw0eAocZ0RAtb/64FUu9Yz9WTG3omffP1M4yQ7W7/4q1qj1wcaKRr7gBwRA+kleB7t1A7CdEsZZ/Bzvyiu8W0KzYg2rq3/N3VxzOuJOmZEnhZ+1RXufimL9q3H6keXiroXOZd5pmVibFS50R2HesASMAzQ0eCIrn3HRC/ZIIV7x1FBDNvVEXPxPVxZ95hL5x0cf8f+doA0S40fjZU9lLv9MZzjXfdot3e1dUIjshZyjwQoX6f6qSRmjr4LyiD2E9gHq3xubHj+PdcrNwLyUhg/bZQFFIOBRxR9kMG5CsT8CYqxM6u2EZxyTPpgaH2L+UbVnmjudB7F63lYxVurgqZ+bU9JZSXuMmkfank1d5sWGjw+gawkn8CGbpTuXoPrzO/vMu//73zkUNcYxZqmwVr6j/9zrURqD/t1IBL6ENuV/55/S+e0ux/kTGKMWT7UKn37hTAG41jNqosE/gPl+iP9yhgJT1NrMhb8SWfdtYn001cFB7G+3OkcQfms2EIgClvQT0nlfutNRw6KD0/hEyPiA6Hk2bnl15EMfZlxz9fNlPwVPP+OdMSgdx+vTCcT9wQwyFmtvXxxf9Fc+fgkRYoM82LAPKNPfu/rOlqv8bw1/jzYTsX1agCVkkTblv6kRXxNHk7gSwOSPTDjtRmqw9US6KdAYI8O6zF9sDWaQPY7Atm8OXcTa5cyzmKfS/IwlLpMXokXKWrwwUsatLOJ83rOsm0cXwBbIwmlNMBBWDoDaAg9kCz0J2zwtND6HsdDxPkx8o6Z1QjrGggg1z1PGj69cnMIgtXLTt+DFTuouIM0CmQRU3SqzNKDexaH64UmuEN2LdB29rlQpB511WcHywZmddyV8dyVkFDyvm1BgOqL2bXeYJnHLHpJzDERoHlEST6HcRYPH3OjYyENxtfi3LyOJ7JQfpGeRowT6Pskb2YFteD2QJwNYoFA1T70RvZSGNA4gg/6ca6BDi+2I3PBILhX3vKRmrI5RTWBHRAvdda9Y3s8M+0vZ2q0Xfr4KMubN6IIAVRgXWYs7iXwjO/0wcD3qhEekDJUmLcD7PtUPp5mnBveoFO2adktg7aea9FWHYcB2/KT+4YdQr4QiJtXU1OPH4p5G94tU3amn88Lz29ZFTt7yV9M51uH5gtny7Q9K0BDSG3B6pxunQtBi3M9XQAEH3wRLHEFyNjixWM1yNR6DQfAB39XUJPjEY9sAv+AfxlZqId3MlUSsdD+GtClemxkpS4eLnj3x9qmGfJKP6mAj61kZMrdkjPuE44oyJYYj7MCHspA5kb6y0/g7wASwwlUrz55bIxvbmb1pIPAdpd6AhOQ/sOPY9CUxZtC5M9XXU73x5opfeoo+BpjXepGWASd9+O8sIrWZBO5HmTl6eQ1Bu6r3Myd1fOHXjEZuJWXh0bQoDyG8byDr7K0tArZdfVavW9O3OGFY7x/2Eni7PtgSgwGPDBlg8DY7Dg2KLdVgT1QulC4Ep78Rdu8u0W+gvTxic3emaZN+rll9v41+TUeMe7DQsPRb5/ZoqWVrcYwpAd9GoZuVyT5W4oDkZWSDUp+UdDadiUIP5jZxFhPv+CF2L3zVKSlFkLNX4TnmrmHLn31WRj150RI0mQeTsDbUM83F33ev270tFdAVhwpbayjVkByblUvDPL0lAdjPv4RWjVsmbCilAOk/RKXHoLmXmZ8iXUu853Vhc4U2mOG8FDJtn3OW+sT5/jXLECaPc/6gelAkRzMXlykkPDrb23v11NHter4oJMmUi77BOJIcjSm92tOgVLP80hl3T534oKkypkKGx718b3yME2k3OiPb3jOJy0Huhj6qaaZFsAU/qApvkKCtChwF+cluNRQXf6t5s0+SCcvMJMVwaSTxL6V18SG4ATNCzSoiUJuBwtF2UP0Uq03q/AMYchq3fvfSUIIvHhPEDGS3c6bPI3WmfFh6owdJ8kePh6BdHXfLmA6bTcQT6IOKaz9cIMvwD+1KPrc8GNzwfSTFf5N5guaTMCe2Hm9TsgJA7GPpaWvOmStM6Xi1mMYg5fih/Zfr1sD6vUXUB8Rie30ltemPbyYTr4G4tPhlIIeWOETk6iUp9zSsKYp4fe9zWA6TJSTD928+RFLXZ9aCuf5IqneWHPkXNNDYsCCD6/yRRqls7/bJlpXV7AJH8Fa/QlKB3M/c69CHlTyKx48RDTOrweNZTlowXz16KSrG0njizzWNI66d3oOv5W3X2FOws9me8+NfhCCkDMwAMyFBdpGGfQnb2lRUl0U6W+PiraafvPVyEwepg4LCgacrFZl8Cm/LmChpRLDRfxg+ZatYOXSlZcj2HPWR1lqZBvv/TSN49Wo3dgQSoiYCJ/t9Z/WDahkr9+vbfMXIhHIcZo2NjxcAdal0bl1sgvmh9kBZmIBWfeN1kxs3nkGlxgCwMFkWWE1OfgEeiKHoEDeA4HtxT4AInupQMdERSWWN0hSgBZwfY9BEl/187DP/fFeqQl/WUjZNQ0kDN6CbH0f6POEZ5ddGj8JXvPYCmYZPeQeeNTDll9OkwQW1q6mQcfsTgzGlt9IENeTpzRC0kIxkXVVJeMkvuEL4qndRSwvLd3YI2kKiKCuePspm5vr9wg/px/nuEjeXD5dV6S94vhh7Sn03BZByYLX+1pTwFgtc4L4Phm1XzEyeF37E4a1wmX+TkPkgptiRgqziiQR2hC9ivxggPXcpffdM9zukAjOWiIUd+VhHOdopMVP6WvyFfn2xqIfJhqaYCj5gJBCwclIZU5aDDFtX2YdYrurhNFnx8VPJIUrJLsLHxJjDFKn2zyi6LuC7/y1sMH7Jm9YnW9Lb7Z5zT/CBt/46GnODGnmhEOS6IGuaGE6sfP7y3IcbwGhaBz6N0ULmn/0eq+sXB58L44j4cdxxY6tNx2n8vCNQZ/Xh+EPkGVXAnIoA5SMuQMx0X7EaA14FZIuAnUcg9Nf4OaaGy74Qi/0VQxTSHEEApMVoVzH3BRBAzmrgerb1UvsAmJ6RRj45n3LKZjHB2qicsGUCbWRoDsqXDAOGWZqvXU65h9ywNBDSWs/qm6AqQfqmAvGM6v1VJ6IxSnDQ1Z1tyGUgg1ohxwO+fiCKQfAdJ6XMHBq9DJn1orfPOlzjxBwj0xzNWWImbw+hO7jY/7SgAgxK6J3oLb8UECie81ejGVoCO/U1gZlGVBfFCvdI9F+hS7Z1cyAEKRZltY0q1cTtJKaRrYz1YLs9Rap0ANei1ziaZj0JZmMn7fDSpMJUjczb5/NRObNPWC+TFn5V3yrsuVxd5tUaDLoYtu/JzX2aAg0MUikKXBKbl6zfs+t8qAwVrjDvGrQDhQpTmzNG5Cb9l8mHFPMro5XML6l4caFPF/47bvn9vhB6SDI8OiVc893365UEplMJP7f4gBUD+p7PAeKaEID3n3OmovlO0Dmy3NmGPRiICV+yTbIoaRisPApW7HPRdJyh3wxVrZvv4skxV3aJtiO7EK3yZI0k1J2U1zf0in5y3sqd0j5h2An/Fw96HF8R0rziqAEQyQxdfoFfoUWnp1vAjH63vGH1n47cHI2/KjsUaaFJI3B9bhtbyTkwwfbSL1WlreYa/KnGcDN5rARD9HEy8zn1wdIMKGY8jWGL8Y/fKhgf3ubNJID/TEMc77RoTCIV3ZlijlRw704o8SwJqfcSWb7ry12TWkn1KEYSktiIEtXP5enThQhGvyaVd8NdkA/ILMgpaZB2eiDSOJy++F1kAf4lTfA3zHwpufXYy1GKenP2X4tC7TJnwlU00/Y6xszkRrQVWEpVkJm/YQf6JJyh1nJqUCzQ+GmsibWjP7g1yAxAlgl4EtSR3KJosRMECvptCcwAlTFPBGsNtQBmwrPLfXdLAy1ReOq2HQj1BfeSwLi9jIhHmEVNbKB+wL4+K2H2owTcISsDa17REBgVFDTu4ddq6xjtPk4w7oRxKGJFy23UDQndlb8d9PMPbPvuzakKjlEy/vVGgUruk0LUj+C7b81Z1MDc1CJiyahjLpFRAGkF1kntaiRU7In3fxWqaCb8/ibTAOsGuOZySRoywBcVbS/wFZpnTl6HZ6FzQzNX64pK185EouGbfwSYKBTy5cZ5VAbcYUThezPwUqDYcfvqn/MV1zbHrNv7V60I2ify2PyjQUtch3nBBDTfFVFelVkao7BgsrhUASPUWvi9lIXIuLSG0whCDlk4eH0+sVN8ycJa/7p+DCchejlnlLpss0gUfD81DIHLSKAQD63XJuKosHT55BqymypTM9H15oN5vX7ZiZCC4jml/XbiR+gCrsJBLCJS9M95IaqQ0LdG/S6wZC63ABUpIXzy9Htwcn6oKPDWxoJsS6dmpC1qy6oOUD54oBHFMAPhi+Jei9XSWd7INBkaEFMIz/V3pxsvijRS5g648M3Oy1lB6HaZhSF0KTT5HgicE/c5NTidYD7cQYW8/KDLbxziAJ7RD6kfAfS1Epqr9fBPnydz/49LDPPfeJxvt3P19BiEMLTn9dhnwDPcrP7YC4FTb4Lmeev6MHFtX/ejuWgPpjCecLkRSRC6v+Sa+/eB3xjwEPD9+0lJFvQrWjE1D0BxHG+ffQ3xqnjhVRrLmxZz3dVCYd1rUEuwxACMg4sNPf1/XiRbr82sAdr4+WMlHnJxvPgExtBG9/YHyS8RqXJMUdagh0okscokPFh38pvcjyTiiiTw7/O6gd/V1g0lWGCZKnHolJZc/rigAh8KiGjailevjAM+hIQwx7NWxzPxY3mLgAVeshNYb84x27j2xrullyc+XnWUpfb/Km7Horz0enW+rWjGc55Z6uXeLfIgQ418QNK1exlQGE66JDxwdbz9MKhr+P2CO0Cg0TwENzcg992lB5KlCAQvwuBEkCVRLvTA6W3+gP5QajS9MKsFwz+iC1wfJFi6JIbL5UHHTw4QkzvasIsifeEZMDmAJ1jVz4sPO5QCNA5vM/00SR6K5cIf1QAwUg3c5bS0Z/fdOJPEQfibd+S1+lciTIRoh08VkWN3AU6OWwkww0OsnU1IVSGrWQCCw7R+O0ORlFzZBtgB3SRLNf8cQ2HzEMAkm7aJe9fgJHlQ17PJkI7bON1HilPR4pzSLP4TLn5uRejbMjwxy+0sX65Rx+EcUxCDEpS11oaoMhwU6+PTKrr9w6DYIM0RvTSbCsCeGRJijxIJPtcuAj6EtYoyUlnRkf51xTRRvH3gTfsTyIpaD6r2dgvRs1o6B3YOY2uGiOxuwy984FyPtDzYy3iaB/a0IC7iOk75xG88zzbkDoWUO471Aptwx/rrNFLEnYBQVEDyzmIkR8Mk5IgdykNcbZGbNB3TgY0vdtof1gB00pGKBztC9j7tedorHAjxY4XZ0Fz7W3pdWRxjSVprgRoBHyE+EnD03uQ73OPSGmtWwx6NbmRQV6XhHTwDY0XDrJxBec3sBeT66c6fUSbT0PeqV1GxTKcgofgMCwHCXyKvQUq0gbAdlhDOwlt1uneVYzKrPjalwQX3j4BmuALY3CkXQTJnNCv8PrZMvcQYZCmezDBC6w7ZNPm20B9NU1bgcQEFHEHz+wdhhR9LRb2LZ1zMkM8dDfV450VUSna6/rlr3xZH3KRBoahSNUTGNh2aFirRzuT64jNtMlcsBGOSTOgfuNPlTDMrpLkTLXHv564m3MnF374qLZYDk4EDNCH4XjpwedzUBG690Uvi52v3dUonLzL5we5MWy5Rns1pj38rkRE/OYoBGbclVPb1JlSbzKF+syLf9C2dscbndgFSFiJQfsgfATkgGqyrgsajjbgRTBbEjxn47QClP7l8hiRgemmbv4e7eMMX5j4VaOqRekL/Vo/3bBM8BjS/TW4Wye66gzPwkhQm18A9VbLyUfzAAPjo5xi/AcU1jkKUkbcXKp3Y04FpiHVBbJuAmdZWxMdVHpRZl9cN2p/wzfD10xke9P7RVlta8QXNmPfF88e3v7ayfCb3SriEDnl2cn+3KMdUqjvQ7xDS2U9lZ553/ZgnpKLG/40InmuzPGCF2vWd+gIIQTnTMhV+urxspbQfEF3GQZtkuomviAGeMP/XJrwacy0v1Yp3plz1xiyANEMODd8H37jpjgz/LRknfcWqB2XYJT/aY2b+jok+dmAYgLiVqNgKfoUqSMVPqG4Q7Nt0+O7q2xaJq/OCnF3VBiNZa35bQY9VLDRYxna2QuEudh8/uujfE5ROWaAWhiuUsd8nykkLZSbSVhQJ8F59THJaYQxhmM9x+YZ5Dm78bjaIGC10QmH+OMNJTfVh65Xk9Nb3oPjq4+O1kpS+uiLmDX0xxrEGnpNXpf7l7JTpTLTjZRdvp3aEfDltPCOq4YBup8P3BSCQqsUTHyrnzZn/w3asF/2CYNexfvKzamwck3v75LD7xdXPCh6mMv1KOz9c8HYO/jq8ucCMtsgzXlcAc7P48o9gNCpoWQndqKGeWvtG8H+JVoX+7dBzrSN3CNvLKv9SORmyRIfp2DlhPTbGiuaEluWEsUZgbZKc5tXojDYnzHpGQzTgDYT0Hs569FQDfMGN6/cVaQ2auT5/yOsR+1qHZb/XIiSjmO+JW7N3MBhA1DRfT9NaTzOUwTACDCbqDCEYHsxpA7zyu40Nfa4AmfUBpBVUnwXk59/cqB4JELgBwfQZVQSWhtMUzvIJssvzAXJC+Xx4owxg61JqjPfCMY9RCp+fPkBra/ErYuAKJn4od/JrSnbFfH20gQOQAuj0XXUBuZgwNTBE06BplzMqzgFcyhnE3lnIk39YFPtxb5sMBstLs5d1ssNWnRIIUEfbMMq8OToSiC7N1bOY8M4jl5wDWBQ4ijYk5SBOxYYivTqfCkBYiAwQxDcSR54EuChT5AQnJJtSTJqUkMgJxmEI/lcuNc80z+9Xvyex+e6+4QFThGwJJ3+uLjTmhd9h4mAt8Bz2BqDlQ33O/A3/5emVmdCXhVpaNft/asevq6jT2tMgXhkdY2vrQ4Co8kxqRbCmz0ZAex2fYw47c63VOHoW+NGTS9X8sEHo2x7TzjBcmfV7SlBn2Ox+way424RR31lFHbR4ddk5oEYIejrFYetBtQ1AFr75mxGANSavG4TJFr6hKTvd0G+ftLFg0zSOMe6CnhCCAW+cLgQLJdxJ9e5Nxm/wFatZdoP50U3ZGVivIQ/8Jz2KooZ2865rm/9An2HGZKuNprD4cum+oAiPn47TKPZzIzR/OYnAVyBUW3M5aReDvKCpPs0C5OYn58NvljLrULZlHE72UYE5cZzogrIDifA1EIctdWqYjzuC06A+MZjMucrpkUOWM3Zsw9zDQODCyXphNnQe60Jm1nfjmL5QfCf1591BfH5dHgAireER5kuTAmbVxw4g3HvFj2VqjI6wvqAmn/TKl6esNFPe6r/Pj7/4MuYNhma/nKj3xkWH6wlc1cZimMIdWKp95zylggazlBrp5QmWFQPb1fYRtzciLhTWWD4xH2dEyPCmcO/q3Nsaeu2QFUMI9xz50RfusnHaR2cBmahgwfnwqYrZZ4TET0U5FcNE/TnWlebK2rVF6Rka8O7d7NvDfmTz36xlUU8gC7m6vvgJ8VxBUxqkaXoBgh0T1APDJ4pysTYVmVEMKbknZC8MB8dLLgTZfzhNIOUjdj9ntsnOLdq5MrW6kq7/8aBRNRPE3iW3pIiSLUp0FTMvpOILVgveQKgqqnEuzSylWnh77cXV1UbiI7JtdoLJsh/AkmP0uLDjhbNciDp043s1zuZrUvroGIZnSMgv4QbEPnru+mhPL3PhB3RfDdFqo/SWjYwkEPlkC2T1X7HXCsxa8pna5oykS8r2l3HTK0Q64uEUzljMHoMOw4t7IPnmSSv19Ewcsw8nn0qqFp34+akv1762T7eQoD4xPFVj9aTYs1jRiyDqvfL1/AgHeKnaMPbuaYgfbxNbCLbCIVfggB3a609iGyO0exh1uBKaeJjCbsgcj2w6uvxxDvo70gE3gWDVirY5P7eSo+ycjmfOksHWrd1GqEoQRYWUkEpj3EjwFTszhn1/QZp4McdaxhfH4yG9ItVcYzFF1nS3THKvnEICqvQZ77bj8IQ0rv2xUJVAgVRrGDMtJt9ez3T6pX47h8YmfLCKlEfcvI0/QrDjdfc5qUaGmOMKYn2DOG19ESPhSj2hGwn2eJX7q5V6RnCl1+MX5xNn2gSchj8hAy7233av4R3UrzbLjOg57KUBUiIdnR5L4h0yIz6EY9RW7UR6Lq6mxEi7yesRau6+XZrTNxgSz1nFdKIl0RZDm2TPLbLoqFslDShgZkGh/EWwetj3tyv0NRDB4QxKAK4LzvfSeMxtSbP7Y2pq2I5D7ppnqB8VbF6bwOyPazgdLnTWMsw8veHQB4vO/sGmN7kSzKUzmW89CPR3DcYa0uHJ2oI+UExgrpqi3sTMfEGTdiJ8JBkQTKgPacRr/a5b+y0PDTmUvGXQ2ahky7VyLfcB2bnGLoyloJQPgkNPIZcgTtmYfv6Igt2ipywIaAp5PZYkKXkI9CjhJJLAVmRx5hM8eAq48rP8XKU208rM2WPt4FFwQdMN/m5l57Vzm6199s2ANAUeIYigNrEpxkV33WzePkn27OJGAr6mHKcHlN9qDAdrwwFzZHDESogEldX4nUTF6XZ+61KXXSsjMZx++I6P+3ypY+fC4O7+Q7j8tqp39p/f4tj9nH0/pNmfYBqV79AUeV6UPqXKafKnKjcVktX5ICcAeaEdx3wKIljPgPbvjogum+gdCD5nw5saxMHZjbFkMonwQ+FeZ+Stnko8GxupVO8RdzkH8DkB2dpMrKrtOQH+QuxIEcL2AEDqYR6H7yKFQDnMriYTkU/pXDOrD4DMSObO1v5lhvwvj2BbSVLmEAZltWr/vaiO40D+beugFhe4BwvUUcqWlohSn9gcvvJFO2Estv2GyhADIjIT2Rg0wyN7zVH0rcY8WvY+tZl5mJTaD9tZ4jXKQiLrKKLKukk1HUayA4a2iF9ZaZrsYK6J5QVpnOJiAkT7bonqb45d1e2KyMCadBP0N9DRf8v6bIV7CZO0LthI1tqYf/Nv2k0NUZdn1/cKjdn4CxYG2JWXHLrwR4NUnSK0ZPFSezOTkuBZhGw57GEEkS0SR6XXhIfaFYlspi4Yu39ziMSkfVHTjV4xBR5W/exlEDBWAWLK0zFAFzDUPMXR/MWv1uSuL+S6fpsua2vLxhJzEzFBJgzPC3QLP/gNkH0itsRP2uki8y2gYFF1EDfX/dTw/7l2VWnExFPlBP2td06z0lULwNaFs6f+le7B1mH5HS2mPU9lFh2j6V7YIucbMNdV6S1EJr6Dqs48p/Iqp9vsTKtInr4q9T/M4+X55TCOozvh1DJuuuHXqqf2APyNf2LJm7wgZRcwdUJWeSheSec9zoEozXSWfNfQ4Ku83AEsn00da3d7nNj4XET5M8eNoq1rWFFywBfBxrPCunUX/Myv/5skuW0YR80sKxZlIsPaIcT12DC6KHXEdpSfnLwkIbkY65d0WZOXJZekam5HRpCBrHBHKNygzmPX65lTVn0sgYantCZ21PswohfwzD+++b5u99X6icMu00DfqCRaKq+oeUrX6Ghe4LKuSlGsFsx8ofXf4WaQCyKcTbE2sufMHHxBYZRekH43Hh+A7JapZEeROXM130xqkh7C/a395N3nr1KF/9JNm2EY8bhFYxdd0qfENQ4068/PwxgcibxK3+o28yLZRtjaTx9uivMwYPtiK14hfBB1pInFHUZ8aR2eifVSjgXoxv9lOEXH//Ze+H99uQRUiLpNRbhL/R9aQyFrt4tf965gC8xzySVkBJvttHdLsxDGOUP4iUy12Z6ID3Ub0m0K41NYPZ1l4O8l6n8Ubq95M61fQvLfiulO2JjvekENI30T0zlq7r4u2+XAJhglKQg+QX7Aa0MQ0IrgiXvQ3bVkJt8I/V0sDIPQNfByCUylD+emx+X+Vt2/CLRnzOF1d30X1yS6TWIVKnsKNoategdVyCXgxNiVkhc9oZVLahKqNEuc6VETJExVizszaefz1CodhrofLvO1WY3lBNl7Js/Pv1RqMl9ostPLtdVw/1BH9l+xG/zCSNQ/xam0rc6i04UuLTuN243mSlzbKBsBf+6ggeLARX560CrM8kgFUAH8fMgY45E5IdMcsygEs3gtEwuIr4d3yV4AxLWSLdf3nAMlmOJqUVfyFhMHwveXZHwm+Oy9yZRVEgMpLtXRX9SGFNXL7tEbNsBVMnvlqpNzMbyYUgp5rpekl4D2Tcr1JPaBdEAaWtsWt+/+atujWVdYD9dRyh0hnv7yj9ntVKnYpIT3rvlZ+T3APWrrSn4qQkEe6Bi7Ur0Haoy7UpT0dmpoc3F2OIYVHMiZubvsZ+KpuH9MsaFBMJ8j1dVov0u21vT8NQW/bxB3yOB4qW1voYuz5AkUOvvkK+MPx8s/YETh+HvoIerVtlsuCFr+as2+PBxZAZGlOK7HhFRWoP+gsmZmrEiD+fPQgT09fydGvt9NwQX3U6MZT5L2caXnh8Yq1HaBRL3d+oHiBbOsMVnJf+hL/ZIzAhn4o8pfhN/RvS5MirWqn876qsHEhqf7xw/Z3SabZRYGpbDQfC4joNQk0Uj/Z4KcKgOFtDHL0oJbkJdba+5iA5CbXZi/RMvS9HBqdeZYHjXWI8aniLvE9BEsVMuSgMOFDNwBxtiRs0LrS5Fn36Zz/YRFuwGUTU+Qd8QO0Onb4PmJnEHgCYBbxnAEry+ZwZ5vd9VQkqfAvR91cedbsEKQ5GxIjNIAuQPr1EaJ1ExJ2t+XZDsA634awseGwV+FMKBdQTHQxTuAZ6VBwuP39enQt5FEr465OuLfuZ6Jjf2A6gBSxoPgAIz6ldJTge8vdIBeo+YBmd50DHa8fjvVX2YspF8VXT3OdHJGH+yAs+EhvK3TMt0Lg//tjLwiFJd7H4oLQUDg5wkbbZUoYRehka9+Tz0gH3EmMemIiU/Qb4FIx+DqS9anyjjoRbRRC2kYoQYFbRvmPpKVimgZP7wGIf3KxR+gKOUuEQ2AR42ENBp0oTz6iK1CUPC79z+1w4oexh3AGEP+kMpDKHIjxWnV+DHvsiedK0kgh19EbEuMCPvTIzeBz7/AFdznakzjxh1fLpPjmakA9IZOA4HbcV6FIcFn936Guy0DVPu28nqdWI/HEQduRQtds/fxeeY5hJa9w3mpsF5xM9P0MxxpiyC+ljsnG1ZknF3ZirYOjak5yztJF/wwp++npyGhYc5CrQkVujPWxkgWLC2D28tH3gw+VOeI/fMO4QAB7J+TUUHp9BDQN0yGlVfsi7p/dNU4Gi8kh1zD3T5CBfhFKycjMeSgB8GB2zoo+zpG/QKgOhvx3y1cHIT69mWUsWvQRFVe/pGcrHbdBZyVfC1M+XwxRHd7e8nhkloYdEe0m6LjaDYAFZwr1EjpdMfoiBGgqamDCwtFH3Z7PbtNsKItvPBuPYz02p8ziK/Dt4oWdaoqc2fl1eQDWvHnTzn6sUMIewhkoeuu9Uv+BhNIXdiM2J2QLmDbRfTpEnQbS/idNgJJejBXnybCL8+fRkXhFU/oVPs+VCHxMTLEHxqglnG/bUksV7M0WvJaU824fIdxzicMK/Lj3Ywpiy+oA2KUAS54tCrzod95tespp9xDVp3/L7ROrEAuyfRSf/L1uSvudBGzy4WZo6XPrT1FYuQmxxeDMqVgxsUCjFFQ/d953jhqb/xvZh656au4ZRYPwF9fDX/tfdly64iS5Zfcx/rGvPwyCBAAjFP4qWMeRDzDF/fhPbJs3fem11VbVVl3dZW2zLtCIQC8PBwX8vdI+Imp/BqzVJuPYAmbDEC8HFL63vYR0pPxOvxmUQ9Z0vEEy6pQVqBPZMLs9l7ass9qE1WPVnKue4gMBfeeGiTRP6BDFntji6wXSaOlXaYhOGOClRES0QykwdBXGhpC535/QUo6heh+PnLuTfwBt25IqTn+02/sW3vt/mMzFSc1iAe6LD2oZqONgtJeuAh272JhUShOM1FeoAsGdreMrQS9hmWG4WgpjFrO47sST13F9HV9xpIQfZhat1fs+IZATe4BqQoVn/pvXNZXx2DVtwQnv4aO5J9uC6C4qK2xPMTULnrmotzU558CGdKDaQMNhzuDnid/Y3vlqZAkfQSvLPqr+Ls5OJl0kjHxl4vSKUo1BcAbBVFs0UbIGCNHNnmlMECrc6hsaOb0XeMesT35rSBDZP8137cJUaCjDEuTsE38UoyQO+LYI1CoVvXLQmokzDnKu7pyCDkB1gGtWayMhVvWRomgibPtC7pQR17UWzv+y3VUZ8tIeoyqTb9djjbijeuFEIIjmnM317KEQDrnitP1eIWM3Wq3RgO3GJRZaLlzxbhFp0IXmYG/i3OZnu57iuAsgKQbdWFxy1TFkibCUZkxPbQrIttEMqA2EZHl45/W/F0qNrgYDIxkd/nKta5oJZIqSMrkzSGDbtKNiPHWe8a7Z9jPK9Bf6l3lyH3eHm2c4jTnQ1EZoT2W1mq05uWvluCQsOgh3Az73SlrS9gjXRidZAZ/xosxMq3Qb1leXHcIWQIhPDmxzZhk2ewLMqETajzCVas6K3YwxuM0aRb7vN4Wc2OjmX9NLJP5aNYUq1U9iC5a9dP5hoTsTpiTroZ8kCe3sOCBukQuS6PYXS33q4FXB3vhCdFECj7Lr8sarG6XqtP0G6DaprqArrNlEHstj7e1OnqOQ1WlGdbgB6i5wMu4YynEM2X+5yH2PgXn4AGs3P0Bd7hwpMcdKY5rOokP6RHMW9YO/So58srPNR34VuGake2V6uRLFrpeQx+g/yqSzAeDUvo5tvyArBNwfgLwyIu3jlV/6jHqMHdExoBMUzbkOS2FKjtnMKsniH0uq8Rak++ZCN44FaezUOlDC1PEEzuX/5qRp2UVhd/D4mKELSIPMCeuU0/ZrEdAhSi3C9w/mLtpQFzOFoWvtWJ6g0NRJWq0tmwQUVyhMVxbz0zghbwWL9ZvV2pUXxPi5trXkQZfrSIJ6V1Lr3iGUmD6A0GTpmwHepBrl2u6VPFmwuMPBl5luHTndub3ewINnMXSovgLhVrmMjKmEqLwfYI6PLPaxGowWVtAFfWWvaJ8lAKrajiJZpeHNUxMReAVSTAhG5aTYyT1zEC5DG9sV98YxwbfOjJm5raPkGVWFU1+mWuwNVSY4IajW7G26sz1otXVQexjGD6aQhXjsaJl19QjFAX1T1o5gUQN42i9SMb5MeYvjKiVsNOa2MH5AZYJyuR9dX5M0VXsyPaLGQNbuU+z3GKjWnrZyNUOKxX0yiOI+0caqQfDpMooOTif6m7swo9LmkeXThcEi3SSvoptpk0Fza8NBRbveEthfp3Y1GeLpFLuV5Aj8rC6IpKSdtNQPypJ16n0Im42kFN4HOx5KyqSmD8xcAAtWW7W+BxZ3mHpvnGhOHbQuzHBC2vBxo9NOGgGAwMjz49LVjjCBmbU5G8VCch6Qyjx2aqLZh8Z/4+zRuhK2vkYeOKdfweqi/XSLgOWtT1IyUzyMC/WAMKUxSFxyMwaeQ6c3/d2wIGyVjtQagu/hwvPxamT9EsZcrTfQWrQ+Xo4uDCEBJv1s6HOeEwDWZa+xGyVYiOXr5LOVubT+BACiWLarQdRAdvO/VZzrCtgjZnQi7rVtSZepmBxBwVGUPs1JgQwHpG9QL3KdUXqS4z0RuxxyJLRgfoRJRA1KPA5mY7veykU6WUTXIA5itQzmYtNCGS2z7V8hITbfh4v6A7PzMiq15jLdy80gsRfPHjsWoE6OGpPkufg5tYObSsn1m813+rcmkvDBBRS7Y3yVoeWSrn/gtBty/fawFMPpVPZkS/RuIOi/P07BlEuSC/HTVG2Yk9+nCWebeTtJLEwvD8rAvzZcdC/rIpkC9CWtWnvBSctf94UtjoRtkcV0RMN0HJdGxT0mwHGyOt3lX2xfCYfHD+nktpIgGXhQrJfDltYILPBqct53p1vNJeLTzrNjqW27IsMAFQcb8S8W13bCxFi8+gMmiBCPLOGiSQPj22Np5WHdlTdMoqsBOiYTI86my3W2BmukXbJUZrt5hSaPRERySThRjMGZ0lOvns8vDAZRQwEvPy1VWNGeLdXw9aKCk821/tw3Thddyx0oL5OnxkXom7D5c3DkVZnVpVhiQ0edIL24so+Y9cLZthyrQo3EKm48VSD2wNkqvRek1cztTgPiymm6QsS28aW8n1fICuR+HZHEgQWH989dCEZ2uH+0RtXLpFcx2OWFPOyDDOui2nW74+70B2L4NsQtVv8E7Dxyc2PgFWuGWrX90oo7N5ernf3NgN2KeDkFbFRbAb00RbQ0MXSmyQeF0gzjuJnPTUyi3QnWO0ixNaXDUcX6fPsOtjGZHyzeCsZefnFIw9HOgXxGP0z4aNEkkBpZuiq7+ufxVC1sFbyPALky2XF3oLzp1cU1Z4G6AajQyYi5ae2gpHCeJMBAnc1wTdzOUmddWjg18X5nfXscfXEKVvjXpLmThK1uviNDy5ULINSlFGiPafYLUP8jdDo3HoWMmsxp5hDXX25V9zaizqKgMxSukpuCDncY3Skbs6YpNS5zEaCxkmnz16gDkmABrYJfg8HcrAcIzEmZyXi1sQ2W7pIJMTb31ARveh1JQMiyQQhbux+KT2YgEIgI8KmZLM4XgisQA77+q8U7A/tcCpUFElhv17OaQHWPCpksL6wBuxmksQjl9fm9/K1FHcItTNDzXrO70/G+114cz68mvDVirMxPC1jK9xCcu9i1q71Po7RaW7ZazxZw26x7HJm5J2sErLJOYKXpBixXw+EA5x6LXGYvKxUslb5F4nx0BuvckDv00Uepls+kwY0Jf0eTFYDYpjhT49BkKQlklmo0TGrB0ZyzKGpwQy7kKFPaQo3gGOZzZuvvN2mCrZhYD4TRPfFkiUBe/sTIABIvkxfXtu76+3ZyTM7zFBx2751Cc2NMswH1DrQgN5eAxycdoWL88TRkjCXgtAJbJWA+ueH5cwcsrucLn4rHsoWl1ciYFE2ct4MSUDgHzh7AqXhn2ElgMoxe6rQYik2NB3HaPcN+7p5duSvPmJkJT9ZCC2M8UZ9pKw2KNBnXumThmhYs8YGHqxWUnZediJOkev5JaVKAp5RfkW4DuWCpP/zJn0WIscOxDeo14PxKxQREGO6AYGL7LGCWQYHMDIzP2WVOFx8hgdPQZAnhZJvO+NbPHoY1LtPnkdLuqu0aVK5qi1amQi9u7O6J4IVKYlwPFPr/Y54Zx+mehHD6c5S+n9ngYu+dmmb77YWTNPPJxJY05rpDQzKkPEBhJM9B2F3SPEGI8h3p2NHt2YzNnYKNWbsR7vO3NafjxtpXlx+8vHhv6xhWWiU+OgmanHecgeEyYZpaEcRY3K9kUE8db90ksKkiDCJ21vTGu3vTwCV/GfOUP4U9mNGD9UHlmxfD12A9T+uskQ45H8lRljY/DcF5szTwlTT5v7bGboNSgIQ7kvXgQcP+tTfX+Nj+TlA/sisD0tJdOF6ZDa9dwqu+OXT5lNhuN4Z12ojFgmhGtQpEHWRw23e+t/paQusA/WocqIkekMRvCFOdMWArNF7RorrfzqWyuBAJAoPTxrxXw6xJtZCoJjMQTqiZmbtKFuQAbG71r3IW5CqRH9qSjUeoEe8m2iBSTTT9Vd0rFiA7yiqbIaxbMcJLNEZ64D8LTdiPbBGQzR0dbwXl1SxBZzVDEhf17Cf/hiUJHhTCCt2E5i6F3I4D0AnVxCj8zBkItNRjMceANjjsPUGZY++LIpD2dNuRihnQH3xrLsS8hhuI7kSk5Sak6rax6DtOtW0MU2yg1+Brw0MOFnL55RU+xMHo/AfYelDBM57eObWO1g2IrR8zQJrcafbR292m4isXTXP1kSx2MX5uzai6hTh/uuQFyeICqjj9xlY42OKG9xwdko9DjIzNjdOIbegStxM+INHSQXig3iKluhg/ngLm1unOGjADtG4pte/RCdzPJN7VD7YQydP3JVByVj9uoWNn3V4nvPBW548UQqNW4/4jmh4nkZXlARTGybxKEoAcoxY2+2FgXoW42TFJZgT7NS51FZ2lcoQ2K5EACG6N38uKws3z2n631NWkt3KWYvZIf1fY0Q7EbaxPBmHIbAL4RyPN80Hb7dZpqGowgXYeE57gLIIAej0zxJy2efy5bBGiICtTVBnruUTkV5c2fJ7FBfA2wAJ0BcAb5RW/M03o5JPnuTr+b2wpkjVoUJGDDApR1v4sJv6OVogqR8swxsSAvqnhGEmuZuqxLBX7TiLVdugOWFE30RSJlML8vGwXJOPTxOy1CU5njDfDPIfXu59Gz5kbTrw1B6QOk7fjQiwS9gsSkbClbI1HURxi2jC/hdqrQO7xQHBq1AMyp+PcLQtQChYbNZrtdRm8DSP+yBrQim5650f52JvRDvqUskDJr3YPIAa44CisthB1i3JLwBUQ3LSWoNsQz1NrlGy8xv4ra2elLypO5z0AUWh3m+Ka9OYlLvHXPG7bNSnHocSyMvezi+Y69hz6wX2ISYB85oNLV7HgTXlS7EqEMILZqdjYUIAhRF4ZoORdKPsbtuhsnnfROGO2yiXeYPGalraUgXt1dhnA2QYONzal1zGOyFHkILd9367JBwvDvpgO60/XDPT0QRgb2hXl/Dkx2RZwHi2mYaDJvBG6mfDpU5dl6M4HB/Y6DbtjMBKUoZVmtKv8+nGAQvSWUHXyCLW+y+BDTeTIHxyphj53mBwk6RUGC2EHMXBLi+u11V37UyLm7zw2bkJjC57nwvL2DhangOA2NQ2wopQMgVlsBgosVhZkCoYRSX6t6VRDh4y+NCnqP+aEcIOgqSULg7lNKivl7qnbKd6zs92cF2ZzHsMAnsliwqHiz0+sKP8BRLS5aCqvPBKNPz4c6ojzcoXws8x4PAFO0C3u6aIwwy3sJlFkOSSRbMq3htRwGFWbAdqj6YQJs8L2HilwampPj04p58LxEu9SmquYvbuGci2aASdzlEgOYswrweb0pz6v78GD0uJC5/Vpd1QuLVa6gpXJY7K910gXK5lHgicPMA00LRW6Ags2W+1IxmNnbubgAkdEj+sEGpgUuWICWSNYypmuTqIG3RhBco2Mqn0UQnlhlljFTQazxeAdOCSfwhK6xuBA1wC69uK23C5bhpceQGVfnkAiB50XcA8LbDf+/I84R8RrdEIxXI1klrhpsxJuRrMrfrrMNHqXYlCi60pqJcQUR0jSUQi4p52N1FTopHb1aGqysYYs9B2lrGlTojc+Xq2WxIoBWgdcF3EGJEiWgZrDdFckJAQ9WT6WCQo8rM0DVbTTDBa7vdSszQaowpqZQ8DQSwHpPGrOGyLYVFZJWK25VPGROTZZpqfPYT4lf5Q1m5AWxEIplP20OXehi0bI2v53Oxs7HNdUqU/XnPH9b1PtLHGbg3GLpYD8bMT2ZWieQZy7oO5htw2Qu/KCfqr2AuomAtqYJnCDwMTx14+V2jBD0GU4TYz3a55QICUQHNQmziLMW0Pqrp8paS62gCAL1tm+3U+hg+HhYBW0NR2JBSz8vfObFtqhqw8PhdPpOlDiAfCmuvwIpqpiEmq5ViRZHHTcm0XRBZgwF5XY5kYHGCDUE3UER62txQ4sqbRHttht7KxkOmdLY9CQpRMbJakBcqWSDcyKYwKqqJ1odEsFy6f65RIuW0MIGKHIxP2nkDYJZ9ERFZqBoWVTzEQZcrM9vciOmDfDrRq0BbA7AFl86HsaB4FgWLs7Eo1h/741x3ncAqCLUG7wZGDnffucqWL7cydxEJwRKE6TGyw7Nctsh2OONRChOVKlZw21VpCOUO1QpYX7nwXO71aM8uap+bmiDPRK2319GiUZ6EOx3ritbrghYmd2wnXeSGV5MP7EqrzUNpJbHqZ59UfOVFGhAEWIl2JT+5HMlso0sqJkBwLNy7od0UQSxTgGApQOU4hrvPS/2U3OyBrjpGksUpwXwykuvS3GyYQFMAl46PnwQagSY95C/orKWrNvsPX8BfrwdxWf4ljNGnNnnJhipL7F7EAr0dNDFbAFzERdvWsN6CNX1JYj3/9qs6iZV1kMTe2AEcI12MuWKJlGvCsdjcNjzkne5n0cIxiYzHoLA0uXkAtZ5maquKgOHz08bjZW1f8XLhFhi5WNIBqdRozLrUBfTWidkmMixWP6gAxiI9zg1loPxqUPLHvgsRIR8JkUCDNu7UoyYybSVyikHlJ4+5UlUorKi7Q0wEBLCyx23jmtkykMKh5I1tivNeMUvc7GBMZtwl4vEFove9T57yrHxlJAUI2L6Z5I3L5R+DruQJ1tTFTvAPbzwoKSC2jt0YsMtFnFSHd0/uyOXzAa1DlprD1UB/hgmj1Yh1LIY/JS0Ygy2OQiasG+xF1u7ulnqwYWprBXISa6GUzxt1F+uUmIHEYzsKdyYtYDd6ZG2dr+jlkpSNKyfnehUome6V2xczJ/JQuk+7sQjUV+1MABHz2Q1RhhBgZ1/gKiKeP0gqVCx3ADV1p2JDKBLyREIu0Utl9BLxbqwtLWyQbN5y7GW82Ax2sdLJgXZIidXzQJZyfAWuzTf6PETXqUOw/MdRbm6Np1ohxxkb+MDFg//lFIt7TGH4yxVaWyDNvGHzVaGynBPVAPlcQCIxn96FDf2zrP3enwd8d1KwMArcHFTDH2pJcbF/PFb8YmnvLDk+mYvKCxzjs8zIIDFGdnFXd2gUb2ng2X1qu3waMsNOlxGPFOxpH59nMfMTbfuIznbvGaR6WUamwUCCVT+1NiEI9jBx031QonePyrqkAanKNpS/7m6yvASsKGk4jxIumbboa0/PCxwI8U3mF/s96otBRslOCvfrQ/2MHzX9JfPj8r0yqi0Q/6mIvP4bj0+3HtKgvigfL0XjkVxSpcEPlBjrd07I5WpibnDhAPImOO7e3Xw6zS9rSpvAXM2IvJIWMq+Gi9BShvbnpKMo81n9IxE7PHl5FPP7T41KCX2dIximt+tly92/LUOgvL3IogMUGdw7RpQi20rhcLsJPr+843ghWjAwEKQBsy+37fLBjmg4vxvtpNfFjJhC9nPkYq9odz46ZaIq9aLNmYtEcnlh9sIU4mAnDUIXiGGYiYduV4/V1jK9oLmOQ8n6agOwbgHXk3Wh/e+H5ofecmj1yWJPp3XvKv1Ge0U3XWkKXSFbKaGrc9lTjpXqxl8ZnQDpH9KeyCMnX6x0SL+qmzR3b1R4fQh1+qyG9bMxa9PYzlpAwUnEw4LJv296Y2aXLS96nOs98S4JbX16+J2r2KzaPVtK4YMcCgfXt12cqH4+jLRC3MDRZcgSRnQx3AICdV2Rjdh3bOm6O8BEl5sCIrzRzd6mFfb9inoe5a7QlXq9NfUAxix+7KfvHyNXyighc3Lm4GfGfdRGsC9C8LqY4VJWprkSl5el/PZGaisciBbVFPDNpaU3dQ4d5e8Hqjla1cMuN37fj4lZaiZCoegmJY9fUY7CyOXGHykA9lPI38JTUEwK2GwxGLGl1JQNR2xA+g6QZNGG4d6z+nsAyJ7WuiSdG1yn62SBVNrRq2H+cFoRcjrf50S/TYH7UDQhQIfRVtjtj+fgmGUzxe4lllvDDd7alMUAk/pOazyJrqnjbtRJBYDRXppzdUXs3rTYwMVpBykJ9N5KB/bZ9BLgUvXwyVYvZtPN7JwlUCLGTLE6n0KC64CV+dyg29CP0fC+VzeZD0I89B6vQWTJZ0GrMhlV+AK0qpMfJDwQ3tn7vm8J4Qhftkft+yCiMMp/q6J2n1H5LhjfTdb59TapJJbQRSBZlL6IYDlURJMsQeh/0qLiE7aCpHKolQ64E/gA8lLabPC2QFBvRULwQqyRILee8UQmDspnJWWaflfGAIz3vTJ+aqkHNvtmytmx2uNJEDmSLsPpqHCuqtNcLopfNyhAEl8D443x86AdIOt+oxL54ufLSkwxf3o3s5x9Pf8e1cxdMMP7+96ZcBtJCQ2jtSlI64XfbTkgvjIm2wXYvfHiJV4h6DD+nIe7FV0ADpQ5IHab1VtaC3t6dZGbh4urv380z9zMQaj3bn6Eo093cThnoPKES+jRcc/n67TwYcjLCKF+VX22SlJpKQb5b0rtjIAaW6F+cx28mIRv+uoKb9TQ3lxTrRBfQFgDQjHgRj/Wt6X1WNnP9++bGxJ19f1dgZOjXLscoV/xIA748nyMdfSe04L+Etgyf+qjpJKqF5f1ZqgFRio9yK+oAEJUXxgI3SN8Rfzws52O6IkgXULegjutSCRWdZ8EX+IiK8N/ax9Lgb5zZrHUO7/2Zx/jSvyCQREeq+PA9VYErbwRiivfFRJnC+7F6mwsPDHfjES/25QWZIlkbCzr84hoL+nmy1oosOAmFl2xiR5xstxyk+o3AWZ/9ysn3i7LVnfrkE9pgKBSllyMUrPEw0T6DmCCVyJ0M4q/Yqgi1GEcpcWloOmim+kUDkS3ZK+Rxh659LtNLb9f9us1bblBzkdMismnZup0zTc9jmPx4h7i/JjJcxlWVp/MOdNfywBkB5R72HpZ6G9mgiZLRWZgbC2MmOVxilXeUIEsABoEP1SHLJ8XgpsQMJZinVULjtKjN4nIZEr5Rbc+8kxSQfQTzMi1KKi+Ga5hjvJyIk1eaMoAtBcZ16z1SALxQGfhabhGYqeVRzbDLKdGItvNRE5aTP7bVm0S8Ma3kIesA/7V8TDNTuudsvwxfVSFdmgwzM4zy8RZdLKh8nJhJ8jGpfb9d7xp7iwg5DBrVkXQZhlfeGscyI7w36cGtyaFt585UsQbvgbzD++Aay/W9h53mfvEeTulfUd5RFatcEGz5pJQynfYdmECHMZAzUAn8zMuPnWvU1xXgPfNQd2SpDOhvBB1VxZGmEXkguIo+xkhW7O6MfvbivFMml8C7ruL7z0z1/SmDvJp6LIty4AP9RKbEg9HoCiTHtf40pQeTWnavl5dw1QSknbyzR+SEyrHq9eVBd3raMbh2xwjTzYgPrur/nEnz/YAvmO2ZnCGYqFS6rEcl9hBVCAf3/2L96fTo1E8MVdX7EhWLImlOqiEly9oYy52KPAUvdhrEIE0kwt/mKzLQn6KBiscnVE8tvF4vKJnRs4dNmzK+1EZ999PMN9PRlaNyegAb9ZTGYVVDySPmtqZ2v0e855PbOUgNZ/5G2gJ+p4O6BUEejXOR59SmbwqHXNxSphj93UDYNZc/cgc/TZC646Ws2s8D8R5fL+3IVCgXky9ryw6e/eXIn0qFBZFz9XeK/jKdxN3J9znzSS9kSRu6vyAMw2YkaHroMu0f9YUc45u5WrrLh1kaAnxd/sAJwDJkoXoGlFWvkGlF1lbQQ48iD+jPttuW3OBAl6MPGgAFa3I7QC6NzjOs5qGcnk4cCIsCMLAEKhnZgMsmcNgSskMcJEw5hpdjT7rNnEEQuAq+3uc8N0DE0zurpsRv3KRVYOfZwdw2IcHv0E2Qr14UD/kHSE+Ie1J/iqFBwNWPTK890HGw0554fGKSJ6PfFD7XZwk6VrutmAXNwvJ+OoH9MkpSwczVC1Geq3ukoR3mxxOWJO6+deKvJ+/5k3ntUAo9XqTbh6puIa0q50MJtAeLUJrdLOsaJG6d4Y6WHf3Y3eSq/nlVQPQqpv5SPTqZervT99mK+52ng6YdEkODaDqfaoPUeuXpNl/8R3BpTQVJjaF+rb3IveZPdG7D4O+PmPRK7MsBJ5hDYZan95WUM3kuybgBjOBArwIqoHZzjqN9+Oyjw5vwS1JD7CBPTHKebunX94nLBoMoIyUj5Iv8ngp8tSTiEuhF+nvM5CQ//a4bAbqlDXs6hmODt0qhmVlhKBfDhYdcnE13xrbxVHmrmQEN7vnPqU4vBUP9SyfHTL3CME2Y0mT4loVBA4grua69at2v6Wd31+2KDtoN4yXWw88ynfJFNWOL4PJ5y4MhQK7J1pNiZZ2oRwn4mYJsmGr9O/Jzef9Fh9BVEtWLsPz3k0gBsIvIfhoaCRzuWHdpSGriLoVyKzEgbZTjx9v6YO35K63VIQ4DwtUiyASSfABMgzoWRWB6WTiJHDsHNjr0Xctmg4ZFKr3HcKbnFAfgGewIPwUu20pC6afDi4ImqjhoLZClNUQumFouBzSD+QH35K8fmIgI006bgMRFtqVW8FGMxKTfHrQfs9fjSsw5Jr+ze5sw7fYo3dvAqhTQGLToMO0lO8EQfekSGVWxgH4bT6snQjQaZH7bYy+UGHyjJHt24YZEsZ1qB2+eFuBUhxvfRDt0pJ6zXT/xj3COvNXg+L4JTXsfK1IQipllUYQUmDIJFp5MtlX9wUADelVMLI+yo+mJ3wRZpicJW16WdD+ztPj7qVGPRO/bw2wRFHWE3EH89FSak5bdAXS5vdlkwzKi8LD35IbMuuXl3ybhGsMlt8685ut1uO8v5b8IvRNxfQVektc46ZPvatFWg9wITeBWKQvSac+ERKEPQl/7i8S34yUVOOAI3iokzK/uQTfcZfd0Zr3xaHceYz2BT9jYKHOec5AZrx8LgJbYvIjIZycCDIM1U/0JIIL/MMe9aaqm9I9CxsHfZ2B8d1+9pfbN/RVjw6ICPnCSwGsm+hGgt9TW5VxQoldgOJ3Su8PgLTrTfwtH5V1mUGFoIa53MZnmv0BE1EId2ZGGVAWUfeLkswXkAZBWs6/WaZ6gDCTa6afVZ1PCAvZkfxElcFYg3GPE+CweIuKkCfchTiDZCW6IZZiDYwVGDxcDiawXbRaT5oxmWFf72h/w9Zsxr9tNJczGdnbV+PsoAdOvHLd1Jrti81DyHe33XjpZr4+2smUNJg7FIlqsrEg9vf6WN3FjuZkbwDSmLX5UxdK2YL38JNoEG17jyj7IMLJ/YH22YHOKbLhsKASX32he00B0nHAXHoMpCEnkzSWi3SzBOpG+k+sw9/12slIlVcwVLKAS6B2kBvakbSqEcmkdPPX9oljBNeimqSeDIIzutA/+/FbL7j8UQ5PqHIlNUJS6gTgYnVhHgja4zevQDJJu9yOj6d2vAwd0RjaXrhC8BScy3jQ4QGyFCwc8U0md83SHX2jEEefAULnPjUaqWOm/4FqYDCP57mFFeKQDbykmTNfeKwBtL6BRBRgTyGUpBza5+ZB3wbpvm7oE3Y7O1nHa+xf7AzoymykikEWN4vsQG2G6//waNq7zmuOfMDJnWYd54N+1315le8hH0Klh0CdCfZRpQ2i12J4BgCwUqgLmXksr118v1cgHXBLeVNiHhl8M5VZatCa+bZphNlwjt0VQwt7Y0lEBkLjtOAAHQN9HoCgCpoDHCGCKHUEbGN1F3Vstl0ZS0Q+FMATjPweJj5tVdSKrZ8yugtkEHcTyj7Lp7R6/iNCxUDv5633JcGkJOs9La16TiN9QFwz5M0nzVa5XANdtnCi7dvVeUGx6AYmcVRjH09sKZ467MQodr64DnhSHgIRJnnQNeJWzPPLOOY9oI2s3YgaofMfvWYZm1Vnk9tuSR3LZULYG9GGwtFXOhgjyRTNYeTc2x2VOqwaunOpd+AAWZLQQoXRymC/GbTfPkOeiNuv0ZjFfMotqBvSwU4AuanIt3y5rQOqSGIYiF6Q/BwqyWdHOCLjQrQgv8o5aumZ7XyQuEx+riOFIykN14koiSXVeIS/hQ8vj2Y6Mc8KMt4spMcX8A5i99xvfa3Urnv4uiYHnh6mKSDvdbjY35yGvZCQKxKv+4CF9oiKkomBFDlCC+mFdDeaJC+BQEpOdGKpAoIuyDjjZVsECoUtxZKsiBy0l1YJz9IaqqwQewmgkbj7GEXOGMQkdkGxu3Csd0X80dHqw2ZlATYeLh0Ee/bsjBoZXZaPx0Yg01IDKXAM29CWFkrJGKUJ6BIII62Y/m5ZSJ7ulAsLT92eGDC0pHTYQvEGaftFAJ304m5ovltpmrqX19qWWPl9Z5krABLLB8j1KX3+chKUAdpGnf5iEyBJAGJg0O7zLkRbn0CrM0TWQqaftd0x2boFl7fMuxTMNKUpKHgkCcAMQl/zH8XzsnwQZ0dzaOstQRB+uuUzVY5EPfoEA6w32dgfyudsXJFMYgefrXNr4JcK5j8XrPMak5rSBASBFGx1lN7lSs/f0DpCt3x0NyTw3xEwD2yrtGaUHQhKOXO/eOVAFx9biCs91yeTD1h1emkm2fghKE2tKTo6uc8qM5qf3b5t9Z2lPtKp3XAtEjQHRb8esuIpmLVyiUdb6YeSOFCmJkWaUKASGOopacBecEcqnRd7W8iKrLN9d7W4dBlaFMSoR5uP9AownJI0kV7IFsMY5it2H7pXC2I7UJo9qUuDPezWDYv4iXG3G9Jo8E/Ec4PAZry5QcgcGcbP9YQEvaWiS/Hbm6FctNRcoZH6xL+42gsPDEPmzwr2v/6kPFfSN/tSnpfb7DseMD4LoGeUDYQ4E1Vr6kjKo4+lcF7fzJjt9O5TC93spOShvUxWB/GyFwt2xR1bHztx79DGpCZAGzwsucHkkwfxUp8nAgPUMvyMfHKj9QZrgLII0ZIzyGr2lKxz2M5lhDF0dDxniveVe3KSn/iafRkXvlbOOqH4aB5TDXazLr2AGmM7oXCOiLfSYygg23cXQMIEMCI7TJIc7M6Y8+Tt6CQTRG1LfEB5rwSI22cJt/jMqNlD+rFlJ5aYOPcDNTwM87IW8smBEV1MMunre6U6i0cHx69N/US/OQ3hQqNE3Le61e/BREwXB5bGONd+RF81jv3oWTmwkJnALvtOKjHQZSwwB3Jruqf/qRup0w3SP8VQl/45N37OvyMj+S2+JOHDXeuT0cv3Y7NjT/zQ5NrwletJuyyx3jp8MmjG9VsJfds93njmcpU83b5Isv3BOBDIWbKEGgzjiQ7Tw0bVa6yAyLHDK+cr+6ylpheJkhRk2mYcNnG/H0OxfDB6gc1N7FO/mV0C6lKnpZTKeSoVpqcEQ1Lh3RtlIXWzz/ZMIIvV+fE3O5M5EKkExb46KFEhN3q+PcDMzaG5A5vDyZxj/IpK6tzAaOaozZwMIOehMGuGdcgnB5tVZmq3WvraoWKmLgnsrr5+qz7LLFvzhIrmsu+7i61TDSI502cza3GSNlcCa0Yk+fBym/2z6kWWAfa24nq/vDgNZBgIe0cb7vZZCuOiGBd1ofb3tzy0XMzdG/FwNReKZivVzSUUO1IaQeQA/hXPnThvYlHTZ6Ai8bKAxm3C31GVpTWbhL5ZM8Mn94q9yM8uTjRSyhI+E95KAMS20gtDGmwIKkl6jSU+ZaGLm5ebD706PXeFkTzRny09eFbGQUuwEZCIr/hzMg91cXX0fR0ZbwqU4xU9vEG62ofQJcWPLhWOd6cV9GX1qupPbZ2/2iJis2hHq5szIP/lg4rInEV+oDXmxoAyhViLrDmLW+0zsyaLnlEmPDz2B4m8M5/50pb9cUlI2odNfD3lXDumIfFJrjE//rT3fjFOQh56JBPXk3DqAnocQ8n8QDWGCGrAGwiM8/UVimbnCj8QHuOxC3tBvNUYVDX8NYHQc3+mATgmyYYvz3tBle3CRMIH2eKmICeCY58Bps30Z1YUiFjY7A+7v/Hdo1LvI9Di0L05+8V9V+E8CQibCT25PO1rzbRvu35p50u9OFxaNkMVBnJ70EO2IiiRAalt4Aahwf54tk0GM4+FQd0SaezKwrJ0G/tC2tZnh6Lnj6dhHlP9ZACGYd8pWg7Ky/bQqiQe2AqWRWQtSzKnrXy0c7P+lLMKVnBVr65+Iqnw9ky5oxfAM1TQ1B2sRcTqs0BYifXUDSAqvz6nlq79Hx7rYoI3Znj+bJaRsliPzp9nWGa+xubP/mMkMKkw/9M1+/9c8z/X/M81/59eYwaOCewXD8FhUmQigafJ55tbLdhvazEajvsbypZNmKdsGL/zsVva5G8o33Zten3xN7B9M9SHY9rO19nrEP46tabjnO4/TqG3v6Fcs4tp16QzKEWBfn2LUfjXT46vYwSFvo63MpmLX+egX+eKtMyLX3fCsL8TyNfpcPo6lf9u/bOW1eeeIA6yc2ld//EIn88IVCZfv9ntDLXVDXpbuJjO6cVtb96/wNRXw9N81OnXdWmSp9avw26ciy7v2rC+fZ9lP7JJQbPQdfR9jdJ1/XUSvk5W6TwfVnmCRsJl7q5TxdzUv75N93L2f3x+gab+jv864vdfLX8Ojj8O2ut9/Z8HP34FDr9/9jn643dTESbd9sfN2oQZx8/hr469zgglkNnXxfPYvVPvV4eAprOunX+9BwKuyK6Lua7uxo+wQKF5Gse/f/njm4jCMRz6rTpAqn9SnL/QpalbxvjXVQ215rOK/cu/ev9qE6Z0/CsDx/+CEl8XzuGYp/O/cSFO/rUqjmkdzuX65yf5K6X6/PQSVXj8uKDvyhZsuf67ZR2c+NZwEoP+pOE4DP1U0H/3eoyG/kGhv57gW71/v8p/SOP/TRmuYb38koPXjW+wtwxYcRz6p/Hwre1Ag7ainFOrDz/9tI1h/2fN/gf1SPCUSrC/Ug8KiVCC+Ef9zOtwmn7pYtw1Zfzr8z9qYR1Gaf1tp/5o95dOf33bjUk6/sM3f9Zv7FfLQtiUNegDZYlLsGs4xHXt1NX/RYYPh6m/w+hf9vRP2wejf2X7oP+84fvrEYL/kxpYZbOA4dG1/zkd+A/16L/dg/+O1UnClMr+0uoQn7//QL/+syb8F/Q0Bf3ZxaF/SPlnN2N/0c3of1s3k//Tzf/t3YyR/9e7+Z+N+t8Qor5uyyblen3Mwcffdh7+48to/OO7P878DawCtf7j2R+n9HScymlOP47sP9GOmLbpGM5g1aO//sX/27r5vwc+/3d1E4ewv6PUP1ihv3A2BPx3BP9nDaX+zzUU5OK6bv6JUq7+KJ5dkoIr/hc= \ No newline at end of file diff --git a/docs/images/distributed_Bb.png b/docs/images/distributed_Bb.png deleted file mode 100644 index 1674eb671..000000000 Binary files a/docs/images/distributed_Bb.png and /dev/null differ diff --git a/docs/images/distributed_Bb.xml b/docs/images/distributed_Bb.xml deleted file mode 100644 index f7a074344..000000000 --- a/docs/images/distributed_Bb.xml +++ /dev/null @@ -1,2 +0,0 @@ - -7VpLd6IwFP41Lp3DwwAuW9tOF23PnOOi7WpOhAiZAmFC8DG/fhIJQng4fWhxrLqQ3JubhPt9l3yAA3MSrb5TmAT3xEPhwNC81cC8GhiGbpsW/xGWdW4Bxig3+BR7slNpmOI/SBo1ac2wh1KlIyMkZDhRjS6JY+QyxQYpJUu125yE6qwJ9FHDMHVh2LQ+Yo8FudUBWmm/RdgPipl1TXoiWHSWhjSAHllWTOb1wJxQQlh+FK0mKBTJK/KSx910eLcLoyhmrwlIHpKltXIe7sHN08/HbHEb3JGhDfJhFjDM5BkPDCvkA14mYs1sLRNh/c7EQi9DHKNhIM/4gnfRbT4tKDvwI1/+boaZ0bqFLzEpbYYyjUFJFntILFnn7mWAGZom0BXeJWcYtwUsCqWbEgYZJjFvarw5x2E4ISGhm6HMORBfbk8ZJS+o4rE2HxFBYlax5x8RUWAlZvFDmKZyinyoggnG9gwWiDK06kRG3+LNCwWRCDG65l1kwNB0JEdkkRTNZck4vaBRUGFb0Q9KkvvboUse8ANJhbfQwuqkhUiZKKQckxL3u8zFHuSeCYlTwuFsMOKBXxy439jJjHz4DnLwFDOVASq0MYlRjQfSBEPsC5q4HBLE7ZcCMMzL/EI6Iux5YppWypWk1PaF+AgoiI+biIMWwI2DAW53Aj4r4Hgk9IXn7l8IzvZT2/ViniPLdduK2bPHM02r1alLIuwWNVst5gMVsK0W8LZaK3haLXiah8KzuDJU8ExxtFdEPIiceSsiluug2XxfqdVqqdVBI7XmZ5aKo59sas2+U9u97ZzVyKepEaAfmRpxujenD6sR/axGjOHIPi414jhvUCO7EfyKagTUr+u9q5FxA08fxftFxHFROyIzB4zA3irFPDI1Mj4ZoddIbd9qZGye1Uj/asQaqbQAr1Qjun4wXowavJBiQmtAdjoawdJUjWD1rBHGzXuF/3RPAdax7SlNvX0qqe17T9E1s5FH5PloKpuEsoD4JIbhdWmtVXPZ546QROb3F2JsLV+fwIwRNftohdmTCP9mANl8rriuVnLoTWNdNGJ+xtUo0X6uOsu4TasI3O4Qucu7EO9hyisbt9xgkbaWXQO8S2vnKRV53E0UnnaSUXmr2MF9eevBIPUR29URtFOPopDvuwt1JQfg0ahfHr2LRgqL9DOLijvd/lgE+mWRfabRXmiUv775AI02oTw1cF3pkBAcs7Qy8g9hqG6vjqoJdaf2CroeYNbEfC2AH+RrKCm9PZkPyJlXPE26hzH0xeOkL/3gyKrdgjtNtfSpz43G3f9OaD4H1M7gqbV1uKd+vFn+byUv0/LfP+b1Xw== \ No newline at end of file diff --git a/docs/images/distributed_Bb_sepM.png b/docs/images/distributed_Bb_sepM.png deleted file mode 100644 index 086c8b1ae..000000000 Binary files a/docs/images/distributed_Bb_sepM.png and /dev/null differ diff --git a/docs/images/distributed_Bb_sepM.xml b/docs/images/distributed_Bb_sepM.xml deleted file mode 100644 index 57c73a0b5..000000000 --- a/docs/images/distributed_Bb_sepM.xml +++ /dev/null @@ -1,95 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/images/distributed_new.png b/docs/images/distributed_new.png new file mode 100644 index 000000000..367bf1539 Binary files /dev/null and b/docs/images/distributed_new.png differ diff --git a/docs/images/libE_logo_only.png b/docs/images/libE_logo_only.png deleted file mode 100644 index 174935a1d..000000000 Binary files a/docs/images/libE_logo_only.png and /dev/null differ diff --git a/docs/images/libE_logo_smaller.png b/docs/images/libE_logo_smaller.png new file mode 100644 index 000000000..7a5df0460 Binary files /dev/null and b/docs/images/libE_logo_smaller.png differ diff --git a/docs/images/libe_opal_complete_v_killed_511w_2044sims_1030nodes.png b/docs/images/libe_opal_complete_v_killed_511w_2044sims_1030nodes.png new file mode 100644 index 000000000..9d89b6ddf Binary files /dev/null and b/docs/images/libe_opal_complete_v_killed_511w_2044sims_1030nodes.png differ diff --git a/docs/images/libe_opal_util_v_time_511w_2044sims_1030nodes.png b/docs/images/libe_opal_util_v_time_511w_2044sims_1030nodes.png new file mode 100644 index 000000000..4847395c9 Binary files /dev/null and b/docs/images/libe_opal_util_v_time_511w_2044sims_1030nodes.png differ diff --git a/docs/images/localopt_6hc.png b/docs/images/localopt_6hc.png new file mode 100644 index 000000000..fd8e318e3 Binary files /dev/null and b/docs/images/localopt_6hc.png differ diff --git a/docs/images/logo_manager_worker.png b/docs/images/logo_manager_worker.png new file mode 100644 index 000000000..823ea9353 Binary files /dev/null and b/docs/images/logo_manager_worker.png differ diff --git a/docs/images/persis_add_worker.png b/docs/images/persis_add_worker.png new file mode 100644 index 000000000..a689da4ac Binary files /dev/null and b/docs/images/persis_add_worker.png differ diff --git a/docs/images/persis_wasted_node.png b/docs/images/persis_wasted_node.png new file mode 100644 index 000000000..fecec3876 Binary files /dev/null and b/docs/images/persis_wasted_node.png differ diff --git a/docs/images/sampling_6hc.png b/docs/images/sampling_6hc.png new file mode 100644 index 000000000..d6adbcaed Binary files /dev/null and b/docs/images/sampling_6hc.png differ diff --git a/docs/images/using_new.png b/docs/images/using_new.png new file mode 100644 index 000000000..05807aa0d Binary files /dev/null and b/docs/images/using_new.png differ diff --git a/docs/images/white.png b/docs/images/white.png new file mode 100644 index 000000000..d04873f1a Binary files /dev/null and b/docs/images/white.png differ diff --git a/docs/index.rst b/docs/index.rst index 28e1dd7d7..50e63bcaf 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -22,6 +22,7 @@ tutorials/local_sine_tutorial tutorials/executor_forces_tutorial + tutorials/aposmm_tutorial examples/examples_index .. toctree:: @@ -32,6 +33,7 @@ known_issues release_notes contributing + posters .. toctree:: :maxdepth: 2 diff --git a/docs/introduction_latex.rst b/docs/introduction_latex.rst index 05036c610..00f132ddd 100644 --- a/docs/introduction_latex.rst +++ b/docs/introduction_latex.rst @@ -37,7 +37,8 @@ We now present further information on running and testing libEnsemble. .. _NumPy: http://www.numpy.org .. _petsc4py: https://bitbucket.org/petsc/petsc4py .. _PETSc: http://www.mcs.anl.gov/petsc -.. _poster: https://figshare.com/articles/LibEnsemble_PETSc_TAO-_Sustaining_a_library_for_dynamic_ensemble-based_computations/7765454 +.. _poster: https://figshare.com/articles/libEnsemble_A_Python_Library_for_Dynamic_Ensemble-Based_Computations/12559520 +.. _psutil: https://pypi.org/project/psutil/ .. _PyPI: https://pypi.org .. _pytest-cov: https://pypi.org/project/pytest-cov/ .. _pytest-timeout: https://pypi.org/project/pytest-timeout/ diff --git a/docs/known_issues.rst b/docs/known_issues.rst index f63422caa..f321fde99 100644 --- a/docs/known_issues.rst +++ b/docs/known_issues.rst @@ -4,9 +4,9 @@ Known Issues The following selection describes known bugs, errors, or other difficulties that may occur when using libEnsemble. -* When using the executor: OpenMPI does not work with direct MPI task +* When using the Executor: OpenMPI does not work with direct MPI task submissions in mpi4py comms mode, since OpenMPI does not support nested MPI - executions. Use either local mode or the Balsam executor instead. + executions. Use either local mode or the Balsam Executor instead. * Local comms mode (multiprocessing) may fail if MPI is initialized before forking processors. This is thought to be responsible for issues combining multiprocessing with PETSc on some platforms. @@ -14,7 +14,7 @@ may occur when using libEnsemble. number of physical cores as SMT info not available. * TCP mode does not support (1) more than one libEnsemble call in a given script or - (2) the auto-resources option to the executor. + (2) the auto-resources option to the Executor. * libEnsemble may hang on systems with matching probes not enabled on the native fabric, like on Intel's Truescale (TMI) fabric for instance. See the :doc:`FAQ` for more information. diff --git a/docs/overview_usecases.rst b/docs/overview_usecases.rst index aed057b9c..4306fbca4 100644 --- a/docs/overview_usecases.rst +++ b/docs/overview_usecases.rst @@ -87,13 +87,17 @@ it's capabilities. generation and simulation routines, submit additional tasks for execution, and return results to the manager. +* **Calling Script**: libEnsemble is typically imported, parameterized, and + initiated in a single Python file referred to as a *calling script*. ``sim_f`` + and ``gen_f`` functions are also commonly configured and parameterized here. + * **Submit**: Enqueue or indicate that one or more jobs or tasks needs to be - launched. When using the libEnsemble executor, a *submitted* task is executed + launched. When using the libEnsemble Executor, a *submitted* task is executed immediately or queued for execution. * **Tasks**: Sub-processes or independent units of work. Workers perform *tasks* as directed by the manager; tasks may include submitting external - programs for execution using the executor. + programs for execution using the Executor. * **Persistent**: Typically, a worker communicates with the manager before and after initiating a user ``gen_f`` or ``sim_f`` calculation. However, user diff --git a/docs/platforms/bebop.rst b/docs/platforms/bebop.rst index 707d0702b..eed570ed5 100644 --- a/docs/platforms/bebop.rst +++ b/docs/platforms/bebop.rst @@ -73,9 +73,9 @@ With your nodes allocated, queue your job to start with four MPI ranks:: ``mpirun`` should also work. This line launches libEnsemble with a manager and **three** workers to one allocated compute node, with three nodes available for -the workers to launch calculations with the executor or a launch command. +the workers to launch calculations with the Executor or a launch command. This is an example of running in :doc:`centralized` mode, and, -if using the :doc:`executor<../executor/mpi_executor>`, it should +if using the :doc:`Executor<../executor/mpi_executor>`, it should be initiated with ``central_mode=True`` .. note:: diff --git a/docs/platforms/cori.rst b/docs/platforms/cori.rst index 336ec5c1b..d4c06609f 100644 --- a/docs/platforms/cori.rst +++ b/docs/platforms/cori.rst @@ -120,10 +120,10 @@ With your nodes allocated, queue your job to start with four MPI ranks:: This line launches libEnsemble with a manager and **three** workers to one allocated compute node, with three nodes available for the workers to launch -user applications (via the executor or a direct run command such as ``mpiexec``). +user applications (via the Executor or a direct run command such as ``mpiexec``). This is an example of running in :doc:`centralized` mode; -if using the :doc:`executor<../executor/ex_index>`, it should +if using the :doc:`Executor<../executor/ex_index>`, it should be initiated with ``central_mode=True``. libEnsemble must be run in central mode on Cori because jobs cannot share nodes. diff --git a/docs/platforms/platforms_index.rst b/docs/platforms/platforms_index.rst index b6ceafd3b..b16c82d47 100644 --- a/docs/platforms/platforms_index.rst +++ b/docs/platforms/platforms_index.rst @@ -13,18 +13,18 @@ The first mode we refer to as **central** mode, where the libEnsemble manager an are grouped on to one or more dedicated nodes. Workers' launch applications on to the remaining allocated nodes: -.. image:: ../images/centralized_Bb.png +.. image:: ../images/centralized_new.png :alt: centralized - :scale: 40 + :scale: 30 :align: center Alternatively, in **distributed** mode, libEnsemble is launched with the processes spread across nodes. The worker processes will share nodes with the applications they launch. There may be multiple nodes per worker, or multiple workers per node: -.. image:: ../images/distributed_Bb.png +.. image:: ../images/distributed_new.png :alt: distributed - :scale: 40 + :scale: 30 :align: center The distributed approach allows the libEnsemble worker to read files produced by the @@ -90,14 +90,14 @@ gen scripts are not doing too much work (other than launching applications). Thi is inherently centralized. The entire node allocation is available for the worker-launched tasks. -To run libEnsemble on the compute nodes of these systems requires an altervative Executor, +To run libEnsemble on the compute nodes of these systems requires an alternative Executor, such as :doc:`Balsam<../executor/balsam_executor>`, which runs on the -launch nodes and launches tasks submitted by workers. Running on compute nodes is potentially -more scalable and will better manage sim and gen functions that contain considerable -computational work or I/O. +launch nodes and launches tasks submitted by workers. Running libEnsemble on the compute +nodes is potentially more scalable and will better manage ``sim_f`` and ``gen_f`` functions +that contain considerable computational work or I/O. - .. image:: ../images/combined_ThS.png - :alt: central_MOM + .. image:: ../images/central_balsam.png + :alt: central_balsam :scale: 40 :align: center @@ -128,12 +128,42 @@ as follows:: Resource detection can be disabled by initializing the Executor with the argument ``auto_resources=False``, and users' can simply supply run -configuration on the executor submit line. This will usually work sufficiently on systems that +configuration on the Executor submit line. This will usually work sufficiently on systems that have application level scheduling (e.g: ``aprun``, ``jsrun``) as these will slot each run into available nodes where possible. ``jsrun`` can also queue runs. However, on other cluster and multi-node systems, if auto-resources is disabled, then runs without a hostlist or machinefile supplied may be undesirably scheduled to the same nodes. +Zero-resource workers +~~~~~~~~~~~~~~~~~~~~~ + +Users with persistent ``gen_f`` functions may notice that the persistent workers +are still automatically assigned system resources. This can be wasteful since those +workers only run ``gen_f`` routines in-place and don't use the Executor to submit +applications to allocated nodes: + +.. image:: ../images/persis_wasted_node.png + :alt: persis_wasted_node + :scale: 40 + :align: center + +This can be resolved within the Executor definition in the calling script. Set the +parameter ``zero_resource_workers`` to a list of worker IDs that shouldn't have +system resources assigned. For example, when using a single instance of Persistent +:doc:`APOSMM<../examples/aposmm>` as your ``gen_f``, the Executor definition +may resemble:: + + exctr = MPIExecutor(central_mode=True, zero_resource_workers=[1]) + +Worker 1 will now not be allocated resources. Note that additional worker +processes can be added to take advantage of the free resources (if using the +same resource set) for simulation instances: + +.. image:: ../images/persis_add_worker.png + :alt: persis_add_worker + :scale: 40 + :align: center + Overriding Auto-detection ------------------------- diff --git a/docs/platforms/summit.rst b/docs/platforms/summit.rst index 438083c27..85c348aa2 100644 --- a/docs/platforms/summit.rst +++ b/docs/platforms/summit.rst @@ -58,6 +58,60 @@ Or, you can install via ``conda``: See :doc:`here<../advanced_installation>` for more information on advanced options for installing libEnsemble. +Special note on resource sets and Executor submit options +--------------------------------------------------------- + +When using the portable MPI run configuration options (e.g., num_nodes) to the +:doc:`MPIExecutor<../executor/mpi_executor>` ``submit`` function, it is important +to note that, due to the `resource sets`_ used on Summit, the options refer to +resource sets as follows: + +- num_procs (int, optional) – The total number resource sets for this run. + +- num_nodes (int, optional) – The number of nodes on which to submit the run. + +- ranks_per_node (int, optional) – The number of resource sets per node. + +It is recommended that the user defines a resource set as the minimal configuration +of CPU cores/processes and GPUs. These can be added to the ``extra_args`` option +of the *submit* function. Alternatively, the portable options can be ignored and +everything expressed in ``extra_args``. + +For example, the following *jsrun* line would run three resource sets, +each having one core (with one process), and one GPU, along with some extra options:: + + jsrun -n 3 -a 1 -g 1 -c 1 --bind=packed:1 --smpiargs="-gpu" + +To express this line in the ``submit`` function may look +something like the following:: + + exctr = Executor.executor + task = exctr.submit(app_name='mycode', + num_procs=3, + extra_args='-a 1 -g 1 -c 1 --bind=packed:1 --smpiargs="-gpu"' + app_args="-i input") + +This would be equivalent to:: + + exctr = Executor.executor + task = exctr.submit(app_name='mycode', + extra_args='-n 3 -a 1 -g 1 -c 1 --bind=packed:1 --smpiargs="-gpu"' + app_args="-i input") + +The auto-resources in the Executor works out the resources available to each worker, +but unlike some other systems, ``jsrun`` on Summit dynamically schedules runs to +available slots across and within nodes. It can also queue tasks. This allows variable +size runs to easily be handled on Summit. If these runs over-use the auto-resource +allocations, auto_resources can be turned off in the Executor setup. E.g: In the +calling script:: + + from libensemble.executors.mpi_executor import MPIExecutor + exctr = MPIExecutor(central_mode=True, auto_resources=False) + +In the above example, the task being submitted used three GPUs, which is half those +available on a Summit node, and thus two such tasks may be allocated to each node +(from different workers), if they were running at the same time. + Job Submission -------------- @@ -153,3 +207,4 @@ See the OLCF guides_ for more information about Summit. .. _guides: https://www.olcf.ornl.gov/for-users/system-user-guides/summit/ .. _conda: https://conda.io/en/latest/ .. _mpi4py: https://mpi4py.readthedocs.io/en/stable/ +.. _resource sets: https://docs.olcf.ornl.gov/systems/summit_user_guide.html#job-launcher-jsrun \ No newline at end of file diff --git a/docs/platforms/theta.rst b/docs/platforms/theta.rst index 0d14ff08e..b5bd81ddd 100644 --- a/docs/platforms/theta.rst +++ b/docs/platforms/theta.rst @@ -9,7 +9,7 @@ Theta features three tiers of nodes: login, MOM, and compute nodes. Users on login nodes submit batch jobs to the MOM nodes. MOM nodes execute user batch scripts to run on the compute nodes via ``aprun``. -Theta does not allow more than one MPI application per compute node. +Theta will not schedule more than one MPI application per compute node. Configuring Python ------------------ @@ -18,10 +18,11 @@ Begin by loading the Python 3 Miniconda_ module:: $ module load miniconda-3/latest -Create a conda_ virtual environment, cloning the base environment. This -environment will contain mpi4py_ and many other packages you may find useful:: +Create a conda_ virtual environment. We recommend cloning the base +environment. This environment will contain mpi4py_ and many other packages that +are configured correctly for Theta:: - $ conda create --name my_env --clone $MINICONDA_INSTALL_PATH + $ conda create --name my_env --clone $CONDA_PREFIX .. note:: The "executing transaction" step of creating your new environment may take a while! @@ -32,8 +33,20 @@ instructions to configure your shell with conda. Activate your virtual environment with :: + $ export PYTHONNOUSERSITE=1 $ conda activate my_env +Alternative +^^^^^^^^^^^ + +If you do not wish to clone the miniconda environment and instead create your own, and +you are using ``mpi4py`` make sure the install picks up Cray's compiler drivers. E.g:: + + $ conda create --name my_env python=3.7 + $ export PYTHONNOUSERSITE=1 + $ conda activate my_env + $ CC=cc MPICC=cc pip install mpi4py --no-binary mpi4py + More information_ on using conda on Theta is also available. Installing libEnsemble and Balsam @@ -53,7 +66,7 @@ Your prompt should be similar to the following line: .. note:: If you encounter pip errors, run ``python -m pip install --upgrade pip`` first. -Or, you can install via ``conda``: +Or, you can install via ``conda`` (which comes with some common dependencies): .. code-block:: console @@ -66,68 +79,66 @@ for installing libEnsemble. Balsam (Optional) ^^^^^^^^^^^^^^^^^ -Balsam_ is an ALCF Python utility for coordinating and executing workflows of -computations on systems such as Theta. Balsam can stage in tasks to a database hosted -on a MOM node and submit these tasks dynamically to the compute nodes. libEnsemble -can also be submitted to Balsam for centralized execution on a compute-node. -libEnsemble can then submit tasks to Balsam through libEnsemble's Balsam -executor for execution on additional allocated nodes. +Balsam_ allows libEnsemble to be run on compute nodes, and still submit tasks +from workers (see Job Submission below). The Balsam Executor will stage in tasks +to a database hosted on a MOM node, which will submit these tasks dynamically to +the compute nodes. -Load the Balsam module with :: +Balsam can be installed with:: - $ module load balsam/0.3.5.1 + pip install balsam-flow -Initialize a new database similarly to the following (from the Balsam docs): +Initialize a Balsam database at a location of your choice. E.g:: -.. code-block:: bash + balsam init ~/myWorkflow - $ balsam init ~/libe-workflow - $ source balsamactivate libe-workflow - $ balsam app --name libe-app --executable "calling.py" - $ balsam job --name libe-job --workflow test --application libe-app --args "hello!" - $ balsam submit-launch -A [project] -q default -t 5 -n 1 --job-mode=mpi - $ watch balsam ls # follow status in realtime from command-line +Further notes on using Balsam: + +* Call ``balsamactivate`` in the batch script (see below). Make sure no active postgres databases are running on either login or MOM nodes before calling ``qsub``. You can check with the script ps_nodes_. + +* Balsam requires PostgreSQL version 9.6.4 or later, but problems may be encountered when using the default ``pg_ctl`` and PostgreSQL 10.12 installation installed in ``/usr/bin``. This may be resolved by loading the postgresql/9.6.12 modules within submission scripts that use Balsam. + +* By default there are a maximum of 128 concurrent database connections. Each worker will use a connection and a few extra are needed. Increase the number of connections by appending a new ``max_connections=`` line to ``balsamdb/postgresql.conf`` in the database directory. E.g.~ ``max_connections=1024`` + +* There is a Balsam module available (balsam/0.3.8), but the module's Python installation supersedes others when loaded. In practice, libEnsemble or other Python packages installed into another environment become inaccessible. Installing Balsam into a separate Python virtual environment is recommended instead. Read Balsam's documentation here_. .. note:: - Balsam will create the run directories inside the data subdirectory within the database - directory. From here, files can be staged out to the user directory (see the example - batch script below). + Balsam creates run-specific directories inside ``data/my_workflow`` in the database + directory. For example: ``$HOME/my_balsam_db/data/libe_workflow/job_run_libe_forces_b7073fa9/``. + From here, files can be staged out (see the example batch script below). Job Submission -------------- -Theta uses Cobalt_ for job management and submission. For libEnsemble, the most -important command is ``qsub``, for submitting batch scripts from the login nodes -to execute on the MOM nodes. - On Theta, libEnsemble can be launched to two locations: 1. **A MOM Node**: All of libEnsemble's manager and worker processes - run on a front-end MOM node. libEnsemble's MPI executor takes + run centrally on a front-end MOM node. libEnsemble's MPI Executor takes responsibility for direct user-application submission to allocated compute nodes. libEnsemble must be configured to run with *multiprocessing* communications, since mpi4py isn't configured for use on the MOM nodes. 2. **The Compute Nodes**: libEnsemble is submitted to Balsam, and all manager - and worker processes are tasked to a back-end compute node. libEnsemble's - Balsam executor interfaces with Balsam running on a MOM node for dynamic + and worker processes are tasked to a back-end compute node and run centrally. libEnsemble's + Balsam Executor interfaces with Balsam running on a MOM node for dynamic user-application submission to the compute nodes. - .. image:: ../images/combined_ThS.png - :alt: central_MOM + .. image:: ../images/central_balsam.png + :alt: central_Balsam :scale: 40 :align: center -When considering on which nodes to run libEnsemble, consider whether your user -functions execute computationally expensive code or code built for specific -architectures. Recall also that only the MOM nodes can launch MPI applications. +When considering on which nodes to run libEnsemble, consider whether your ``sim_f`` +or ``gen_f`` user functions (not applications) execute computationally expensive +code, or code built specifically for the compute node architecture. Recall also +that only the MOM nodes can launch MPI applications. Although libEnsemble workers on the MOM nodes can technically submit user applications to the compute nodes directly via ``aprun`` within user functions, it is highly recommended that the aforementioned :doc:`executor<../executor/overview>` -interface be used instead. The libEnsemble executor features advantages such as +interface be used instead. The libEnsemble Executor features advantages such as automatic resource detection, portability, launch failure resilience, and ease of use. Theta features one default production queue, ``default``, and two debug queues, @@ -185,15 +196,15 @@ convenience function from libEnsemble's :doc:`tools module<../utilities>`. # --- Prepare Python --- - # Load conda module - module load miniconda-3/latest + # Obtain Conda PATH from miniconda-3/latest module + CONDA_DIR=/soft/datascience/conda/miniconda3/latest/bin # Name of conda environment export CONDA_ENV_NAME=my_env # Activate conda environment export PYTHONNOUSERSITE=1 - source activate $CONDA_ENV_NAME + source $CONDA_DIR/activate $CONDA_ENV_NAME # --- Prepare libEnsemble --- @@ -206,12 +217,7 @@ convenience function from libEnsemble's :doc:`tools module<../utilities>`. # Number of workers. export NWORKERS='--nworkers 128' - # Conda location - theta specific - export PATH=/home/user/path/to/packages/:$PATH - export LD_LIBRARY_PATH=/home/user/path/to/packages/:$LD_LIBRARY_PATH - export PYTHONPATH=/home/user/path/to/env/packages:$PYTHONPATH - - # Required for python kills on Theta + # Required for killing tasks from workers on Theta export PMI_NO_FORK=1 # Unload Theta modules that may interfere with task monitoring/kills @@ -229,14 +235,17 @@ libEnsemble on Theta is achieved by running :: Balsam Runs ^^^^^^^^^^^ -Here is an example Balsam submission script: +Here is an example Balsam submission script. It requires a pre-initialized (but not activated) +postgresql_ database. Note, the example runs libEnsemble over two dedicated nodes, reserving the +other 127 nodes for launched applications. libEnsemble is run with MPI on 128 processors +(one manager and 127 workers).: .. code-block:: bash #!/bin/bash -x #COBALT -t 60 #COBALT -O libE_test - #COBALT -n 128 + #COBALT -n 129 #COBALT -q default #COBALT -A [project] @@ -244,7 +253,10 @@ Here is an example Balsam submission script: export EXE=calling_script.py # Number of workers. - export NUM_WORKERS=128 + export NUM_WORKERS=127 + + # Number of nodes to run libE + export LIBE_NODES=2 # Wall-clock for entire libE run (supplied to Balsam) export LIBE_WALLCLOCK=45 @@ -252,20 +264,15 @@ Here is an example Balsam submission script: # Name of working directory where Balsam places running jobs/output export WORKFLOW_NAME=libe_workflow - #Tell libE manager to stop workers, dump timing.dat and exit after time. - export SCRIPT_ARGS=$(($LIBE_WALLCLOCK-3)) + # If user script takes ``elapsed_wallclock_time`` argument. + # export SCRIPT_ARGS=$(($LIBE_WALLCLOCK-3)) + export SCRIPT_ARGS="" # Name of conda environment export CONDA_ENV_NAME=my_env + export BALSAM_DB_NAME=myWorkflow - # Conda location - theta specific - export PATH=/path/to/python/bin:$PATH - export LD_LIBRARY_PATH=~/path/to/conda/env/lib:$LD_LIBRARY_PATH - - #Ensure environment isolated - export PYTHONNOUSERSITE=1 - - # Required for python kills on Theta + # Required for killing tasks from workers on Theta export PMI_NO_FORK=1 # Unload Theta modules that may interfere with task monitoring/kills @@ -273,17 +280,25 @@ Here is an example Balsam submission script: module unload darshan module unload xalt + # Obtain Conda PATH from miniconda-3/latest module + CONDA_DIR=/soft/datascience/conda/miniconda3/latest/bin + + # Ensure environment is isolated + export PYTHONNOUSERSITE=1 + # Activate conda environment - . activate $CONDA_ENV_NAME + source $CONDA_DIR/activate $CONDA_ENV_NAME # Activate Balsam database - . balsamactivate default + source balsamactivate $BALSAM_DB_NAME # Currently need at least one DB connection per worker (for postgres). - if [[ $NUM_WORKERS -gt 128 ]] + if [[ $NUM_WORKERS -gt 100 ]] then - #Add a margin - echo -e "max_connections=$(($NUM_WORKERS+10)) #Appended by submission script" >> $BALSAM_DB_PATH/balsamdb/postgresql.conf + # Add a margin + export BALSAM_DB_PATH=~/$BALSAM_DB_NAME # Pre-pend with PATH + echo -e "max_connections=$(($NUM_WORKERS+20)) # Appended by submission script" \ + >> $BALSAM_DB_PATH/balsamdb/postgresql.conf fi wait @@ -294,18 +309,28 @@ Here is an example Balsam submission script: sleep 3 # Add calling script to Balsam database as app and job. - THIS_DIR=$PWD - SCRIPT_BASENAME=${EXE%.*} + export THIS_DIR=$PWD + export SCRIPT_BASENAME=${EXE%.*} + + export LIBE_PROCS=$((NUM_WORKERS+1)) # Manager and workers + export PROCS_PER_NODE=$((LIBE_PROCS/LIBE_NODES)) # Must divide evenly balsam app --name $SCRIPT_BASENAME.app --exec $EXE --desc "Run $SCRIPT_BASENAME" - # Running libE on one node - one manager and upto 63 workers - balsam job --name job_$SCRIPT_BASENAME --workflow $WORKFLOW_NAME --application $SCRIPT_BASENAME.app --args $SCRIPT_ARGS --wall-time-minutes $LIBE_WALLCLOCK --num-nodes 1 --ranks-per-node $((NUM_WORKERS+1)) --url-out="local:/$THIS_DIR" --stage-out-files="*.out *.txt *.log" --url-in="local:/$THIS_DIR/*" --yes + balsam job --name job_$SCRIPT_BASENAME --workflow $WORKFLOW_NAME \ + --application $SCRIPT_BASENAME.app --args $SCRIPT_ARGS \ + --wall-time-minutes $LIBE_WALLCLOCK \ + --num-nodes $LIBE_NODES --ranks-per-node $PROCS_PER_NODE \ + --url-out="local:/$THIS_DIR" --stage-out-files="*.out *.txt *.log" \ + --url-in="local:/$THIS_DIR/*" --yes - #Run job + # Run job balsam launcher --consume-all --job-mode=mpi --num-transition-threads=1 - . balsamdeactivate + wait + source balsamdeactivate + +Further examples of Balsam submission scripts can be be found in the :doc:`examples`. Debugging Strategies -------------------- @@ -331,6 +356,9 @@ Read the documentation for Balsam here_. .. _Cobalt: https://www.alcf.anl.gov/support-center/theta/submit-job-theta .. _`Support Center`: https://www.alcf.anl.gov/support-center/theta .. _here: https://balsam.readthedocs.io/en/latest/ +.. .. _Balsam install: https://balsam.readthedocs.io/en/latest/#quick-setup +.. _ps_nodes: https://github.com/Libensemble/libensemble/blob/develop/examples/misc/ps_nodes.sh +.. _postgresql: https://www.alcf.anl.gov/support-center/theta/postgresql-and-sqlite .. _Miniconda: https://docs.conda.io/en/latest/miniconda.html .. _conda: https://conda.io/en/latest/ .. _information: https://www.alcf.anl.gov/user-guides/conda diff --git a/docs/posters.rst b/docs/posters.rst new file mode 100644 index 000000000..cbc50d094 --- /dev/null +++ b/docs/posters.rst @@ -0,0 +1,24 @@ +Posters and Presentations +========================= + +SciPy 2020 Virtual Poster +------------------------- + +.. toctree:: + :maxdepth: 3 + + scipy2020 + +SciPy 2020 PDF Poster +--------------------- + +.. raw:: html + + + +CSE 2019 Poster +--------------- + +.. raw:: html + + diff --git a/docs/programming_libE.rst b/docs/programming_libE.rst index 121dc1540..874fced1f 100644 --- a/docs/programming_libE.rst +++ b/docs/programming_libE.rst @@ -12,6 +12,7 @@ We now give greater detail in programming with libEnsemble. :caption: libEnsemble User Functions: sim_gen_alloc_funcs + function_guides/function_guide_index .. toctree:: executor/ex_index diff --git a/docs/requirements.txt b/docs/requirements.txt index c6ce35f68..662dda543 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,3 @@ sphinx==2.4.1 sphinxcontrib-bibtex +ipykernel diff --git a/docs/running_libE.rst b/docs/running_libE.rst index ea726160a..bb16321b1 100644 --- a/docs/running_libE.rst +++ b/docs/running_libE.rst @@ -103,8 +103,14 @@ The ``libE_specs`` options for TCP are:: 'authkey' [String]: Authkey. +Limitations of TCP mode +^^^^^^^^^^^^^^^^^^^^^^^ + +- There cannot be two calls to ``libE`` in the same script. + Persistent Workers ------------------ +.. _persis_worker: In a regular (non-persistent) worker, the user's generator or simulation function is called whenever the worker receives work. A persistent worker is one that continues to run the generator or simulation function between work units, diff --git a/docs/scipy2020.rst b/docs/scipy2020.rst new file mode 100644 index 000000000..8fbd0c98e --- /dev/null +++ b/docs/scipy2020.rst @@ -0,0 +1,204 @@ +.. image:: images/ECP_logo.png + :alt: ECP + :width: 23 % + :align: left + +.. image:: images/ANL_CMYK.png + :alt: ANL + :width: 33 % + :align: right + +.. image:: images/white.png + :align: center + :width: 33 % + :height: 1.2 in + +========================================================================= +**libEnsemble**: A Python Library for Dynamic Ensemble-Based Computations +========================================================================= + +*David Bindel, Stephen Hudson, Jeffrey Larson, John-Luke Navarro and Stefan Wild* + +A PDF poster version of this content is available on figshare_. + +.. _FigShare: https://figshare.com/articles/libEnsemble_A_Python_Library_for_Dynamic_Ensemble-Based_Computations/12559520 + +Overview +-------- + +**libEnsemble** is a Python library for coordinating the concurrent evaluation of +dynamic ensembles of calculations. The library is developed to use massively +parallel resources to accelerate the solution of design, decision, and +inference problems and to expand the class of problems that can benefit from +increased concurrency levels. + +libEnsemble aims for the following: + +• Extreme scaling +• Resilience/fault tolerance +• Monitoring/killing of tasks (and recovering resources) +• Portability and flexibility +• Exploitation of persistent data/control flow + +libEnsemble is most commonly used to coordinate large numbers of parallel +instances (ensembles) of simulations at huge scales. + +Using libEnsemble +----------------- + +The user selects or supplies a ``gen_f`` function that generates simulation +input and a ``sim_f`` function that performs and monitors simulations. The user +parameterizes these functions and initiates libEnsemble in a *calling script*. +Examples and templates of such scripts and functions are included in the library. + +.. image:: images/using_new.png + :alt: Using libEnsemble + :scale: 30 % + :align: center + +For example, the ``gen_f`` may contain an optimization routine to generate new +simulation parameters on-the-fly based on results from previous ``sim_f`` +simulations. + +Other potential use-cases include: + +==================== ===================== +Generator Functions: Simulation Functions: +==================== ===================== +Parameter estimation Particle-in-cell +Surrogate models Subsurface flow +Sensitivity analysis PETSc simulations +Design optimization DFT simulations +Supervised learning Quantum chemistry +==================== ===================== + +Manager and Workers +------------------- + +libEnsemble employs a manager/worker scheme that can communicate through **MPI**, +Python's **multiprocessing**, or **TCP**. Each *worker* +can control and monitor any level of work, from small sub-node tasks to huge +many-node simulations. The *manager* allocates workers to asynchronously execute +``gen_f`` generation functions and ``sim_f`` simulation functions based on +produced output, directed by a provided ``alloc_f`` allocation function. + +.. image:: images/logo_manager_worker.png + :alt: Managers and Workers + :align: center + :scale: 40 % + +Flexible Run Mechanisms +----------------------- + +libEnsemble has been developed, supported, and tested on systems of highly +varying scales, from laptops to machines with thousands of compute nodes. +On multi-node systems, there are two basic modes of configuring libEnsemble to +run and launch tasks (user applications) on available nodes. + +* **Distributed**: Workers are distributed across allocated nodes and launch tasks in-place. Workers share nodes with their applications. + +.. image:: images/distributed_new.png + :alt: Distributed + :align: center + :scale: 30 % + +* **Centralized**: Workers run on one or more dedicated nodes and launch tasks to the remaining allocated nodes. + +.. image:: images/centralized_new.png + :alt: Centralized + :align: center + :scale: 30 % + +.. note:: + Dividing up workers and tasks to allocated nodes is highly configurable. + Multiple workers (and thus multiple tasks or user function instances) can be + assigned to a single node. Alternatively, multiple nodes may be assigned to + a single worker and each routine it performs. + +Executor Module +--------------- + +An *Executor* interface is provided to ensure libEnsemble routines that +coordinate user applications are portable, resilient, and flexible. The Executor +automatically detects allocated nodes and available cores and can split up tasks +if resource data isn't supplied. + +The Executor is agnostic of both the job launch/management system and selected +manager/worker communication method on each machine. The main functions are +``submit()``, ``poll()``, and ``kill()``. + +On machines that do not support launches from compute nodes, libEnsemble's +Executor can interface with the **Balsam** library, which functions as a proxy +job launcher that maintains and submits jobs from a database on front end launch +nodes. + +.. image:: images/central_balsam.png + :alt: Central Balsam + :align: center + :scale: 40 % + +Supported Research Machines +--------------------------- + +libEnsemble is tested and supported on the following high-performance research machines: + +======== ======================================================= ======== ================================================ +Machine Location Facility Info +======== ======================================================= ======== ================================================ +Summit_ `Oak Ridge National Laboratory`_ OLCF_ IBM AC922, IBM POWER9 nodes w/ NVIDIA Volta GPUs +Theta_ `Argonne National Laboratory`_ ALCF_ Cray XC40, Intel KNL nodes +Cori_ `National Energy Research Scientific Computing Center`_ Cray XC40, Intel Haswell & KNL nodes +Bridges_ `Pittsburgh Supercomputing Center`_ HPE, Intel Haswell nodes, NVIDIA GPU nodes +======== ======================================================= ======== ================================================ + +.. _Summit: https://www.olcf.ornl.gov/olcf-resources/compute-systems/summit/ +.. _Theta: https://www.alcf.anl.gov/alcf-resources/theta +.. _Cori: https://docs.nersc.gov/systems/cori/ +.. _Bridges: https://www.psc.edu/resources/computing/bridges + +.. _`Oak Ridge National Laboratory`: https://www.ornl.gov/ +.. _`Argonne National Laboratory`: https://www.anl.gov/ +.. _`National Energy Research Scientific Computing Center`: https://www.nersc.gov/ +.. _`Pittsburgh Supercomputing Center`: https://www.psc.edu/ + +.. _OLCF: https://www.olcf.ornl.gov/ +.. _ALCF: https://www.alcf.anl.gov/ + +Running at Scale +---------------- + +**OPAL Simulations** + +* ALCF/Theta (Cray XC40) with Balsam, at Argonne National Laboratory +* 1030 node allocation, 511 workers, MPI communications. +* 2044 2-node simulations +* Object Oriented Parallel Accelerator Library (OPAL) simulation functions. + +.. list-table:: + + * - .. figure:: images/libe_opal_complete_v_killed_511w_2044sims_1030nodes.png + + Histogram of completed and killed simulations, binned by run time. + + * - .. figure:: images/libe_opal_util_v_time_511w_2044sims_1030nodes.png + + Total number of Balsam-launched applications running over time. + +Try libEnsemble Online +---------------------- + +Try libEnsemble online with two Jupyter notebook examples. + +The first notebook demonstrates the basics of parallel ensemble calculations +with libEnsemble through a Simple Functions Tutorial. The second notebook, an +Executor Tutorial, contains an example similar to most use-cases: simulation +functions that launch and coordinate user applications. + +.. note:: + The Executor Tutorial notebook may take a couple minutes to initiate. + +.. image:: https://img.shields.io/badge/libEnsemble-Simple%20Functions%20Tutorial-579ACA.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFkAAABZCAMAAABi1XidAAAB8lBMVEX///9XmsrmZYH1olJXmsr1olJXmsrmZYH1olJXmsr1olJXmsrmZYH1olL1olJXmsr1olJXmsrmZYH1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olJXmsrmZYH1olL1olL0nFf1olJXmsrmZYH1olJXmsq8dZb1olJXmsrmZYH1olJXmspXmspXmsr1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olLeaIVXmsrmZYH1olL1olL1olJXmsrmZYH1olLna31Xmsr1olJXmsr1olJXmsrmZYH1olLqoVr1olJXmsr1olJXmsrmZYH1olL1olKkfaPobXvviGabgadXmsqThKuofKHmZ4Dobnr1olJXmsr1olJXmspXmsr1olJXmsrfZ4TuhWn1olL1olJXmsqBi7X1olJXmspZmslbmMhbmsdemsVfl8ZgmsNim8Jpk8F0m7R4m7F5nLB6jbh7jbiDirOEibOGnKaMhq+PnaCVg6qWg6qegKaff6WhnpKofKGtnomxeZy3noG6dZi+n3vCcpPDcpPGn3bLb4/Mb47UbIrVa4rYoGjdaIbeaIXhoWHmZYHobXvpcHjqdHXreHLroVrsfG/uhGnuh2bwj2Hxk17yl1vzmljzm1j0nlX1olL3AJXWAAAAbXRSTlMAEBAQHx8gICAuLjAwMDw9PUBAQEpQUFBXV1hgYGBkcHBwcXl8gICAgoiIkJCQlJicnJ2goKCmqK+wsLC4usDAwMjP0NDQ1NbW3Nzg4ODi5+3v8PDw8/T09PX29vb39/f5+fr7+/z8/Pz9/v7+zczCxgAABC5JREFUeAHN1ul3k0UUBvCb1CTVpmpaitAGSLSpSuKCLWpbTKNJFGlcSMAFF63iUmRccNG6gLbuxkXU66JAUef/9LSpmXnyLr3T5AO/rzl5zj137p136BISy44fKJXuGN/d19PUfYeO67Znqtf2KH33Id1psXoFdW30sPZ1sMvs2D060AHqws4FHeJojLZqnw53cmfvg+XR8mC0OEjuxrXEkX5ydeVJLVIlV0e10PXk5k7dYeHu7Cj1j+49uKg7uLU61tGLw1lq27ugQYlclHC4bgv7VQ+TAyj5Zc/UjsPvs1sd5cWryWObtvWT2EPa4rtnWW3JkpjggEpbOsPr7F7EyNewtpBIslA7p43HCsnwooXTEc3UmPmCNn5lrqTJxy6nRmcavGZVt/3Da2pD5NHvsOHJCrdc1G2r3DITpU7yic7w/7Rxnjc0kt5GC4djiv2Sz3Fb2iEZg41/ddsFDoyuYrIkmFehz0HR2thPgQqMyQYb2OtB0WxsZ3BeG3+wpRb1vzl2UYBog8FfGhttFKjtAclnZYrRo9ryG9uG/FZQU4AEg8ZE9LjGMzTmqKXPLnlWVnIlQQTvxJf8ip7VgjZjyVPrjw1te5otM7RmP7xm+sK2Gv9I8Gi++BRbEkR9EBw8zRUcKxwp73xkaLiqQb+kGduJTNHG72zcW9LoJgqQxpP3/Tj//c3yB0tqzaml05/+orHLksVO+95kX7/7qgJvnjlrfr2Ggsyx0eoy9uPzN5SPd86aXggOsEKW2Prz7du3VID3/tzs/sSRs2w7ovVHKtjrX2pd7ZMlTxAYfBAL9jiDwfLkq55Tm7ifhMlTGPyCAs7RFRhn47JnlcB9RM5T97ASuZXIcVNuUDIndpDbdsfrqsOppeXl5Y+XVKdjFCTh+zGaVuj0d9zy05PPK3QzBamxdwtTCrzyg/2Rvf2EstUjordGwa/kx9mSJLr8mLLtCW8HHGJc2R5hS219IiF6PnTusOqcMl57gm0Z8kanKMAQg0qSyuZfn7zItsbGyO9QlnxY0eCuD1XL2ys/MsrQhltE7Ug0uFOzufJFE2PxBo/YAx8XPPdDwWN0MrDRYIZF0mSMKCNHgaIVFoBbNoLJ7tEQDKxGF0kcLQimojCZopv0OkNOyWCCg9XMVAi7ARJzQdM2QUh0gmBozjc3Skg6dSBRqDGYSUOu66Zg+I2fNZs/M3/f/Grl/XnyF1Gw3VKCez0PN5IUfFLqvgUN4C0qNqYs5YhPL+aVZYDE4IpUk57oSFnJm4FyCqqOE0jhY2SMyLFoo56zyo6becOS5UVDdj7Vih0zp+tcMhwRpBeLyqtIjlJKAIZSbI8SGSF3k0pA3mR5tHuwPFoa7N7reoq2bqCsAk1HqCu5uvI1n6JuRXI+S1Mco54YmYTwcn6Aeic+kssXi8XpXC4V3t7/ADuTNKaQJdScAAAAAElFTkSuQmCC + :target: https://mybinder.org/v2/gh/Libensemble/libensemble/develop?filepath=examples%2Ftutorials%2Fsine_tutorial_notebook.ipynb + +.. image:: https://img.shields.io/badge/libEnsemble-Executor%20Tutorial-E66581.svg?logo=data:image/png;base64,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 + :target: https://mybinder.org/v2/gh/Libensemble/libensemble/develop?filepath=examples%2Ftutorials%2Fforces_tutorial_notebook.ipynb diff --git a/docs/tutorials/aposmm_tutorial.rst b/docs/tutorials/aposmm_tutorial.rst new file mode 100644 index 000000000..6614ad2b5 --- /dev/null +++ b/docs/tutorials/aposmm_tutorial.rst @@ -0,0 +1,292 @@ +================================= +Parallel Optimization with APOSMM +================================= + +This tutorial demonstrates libEnsemble's capability to identify multiple minima +of simulation output using the built-in :doc:`APOSMM<../examples/aposmm>` +(Asynchronously Parallel Optimization Solver for finding Multiple Minima) +:ref:`gen_f`. In this tutorial, we'll create a simple +simulation :ref:`sim_f` that defines a function with +multiple minima, then write a libEnsemble calling script that imports APOSMM and +parameterizes it to check for minima over a domain of outputs from our ``sim_f``. + +Six-Hump Camel Simulation Function +---------------------------------- + +Describing APOSMM's operations is simpler with a given function on which to +depict evaluations. We'll use the `Six-Hump Camel function`_, known to have six +global minima. A sample space of this function, containing all minima, appears +below: + +.. image:: ../images/basic_6hc.png + :alt: Six-Hump Camel + :scale: 60 + :align: center + +Create a new Python file named ``six_hump_camel.py``. This will be our +``sim_f``, incorporating the above function. Write the following: + +.. code-block:: python + :linenos: + + import numpy as np + + def six_hump_camel(H, persis_info, sim_specs, _): + """Six-Hump Camel sim_f.""" + + batch = len(H['x']) # Num evaluations each sim_f call. + H_o = np.zeros(batch, dtype=sim_specs['out']) # Define output array H + + for i, x in enumerate(H['x']): + H_o['f'][i] = three_hump_camel_func(x) # Function evaluations placed into H + + return H_o, persis_info + + def six_hump_camel_func(x): + """ Six-Hump Camel function definition """ + x1 = x[0] + x2 = x[1] + term1 = (4-2.1*x1**2+(x1**4)/3) * x1**2 + term2 = x1*x2 + term3 = (-4+4*x2**2) * x2**2 + + return term1 + term2 + term3 + +APOSMM Operations +----------------- + +APOSMM coordinates multiple local optimization runs starting from a collection +of sample points. These local optimization runs occur concurrently, +and can incorporate a variety of optimization methods, including from NLopt_, +`PETSc/TAO`_, SciPy_, or other external scripts. + +Before APOSMM can start local optimization runs, some number of uniformly +sampled points must be evaluated (if no prior simulation evaluations are +provided). User-requested sample points can also be provided to APOSMM: + +.. image:: ../images/sampling_6hc.png + :alt: Six-Hump Camel Sampling + :scale: 60 + :align: center + +Specifically, APOSMM will begin local optimization runs from evaluated points that +don't have points with smaller function values nearby (within a threshold +``r_k``). For the above example, after APOSMM receives the evaluations of the +uniformly sampled points, it will begin at most ``max_active_runs`` local +optimization runs. + +As function values are returned to APOSMM, APOSMM gives them to each local +optimization run in order to generate the next point(s); these are returned to +the manager to be evaluated by the simulation routine. As runs complete (a +minimum is found, or some termination criteria for the local optimization run +is satisfied), +additional local optimization runs may be started or additional uniformly +sampled points may be evaluated. This continues until a ``STOP_TAG`` is sent by +the manager, for example when the budget of simulation evaluations has been +exhausted, or when a sufficiently "good" simulation output has been observed. + +.. image:: ../images/localopt_6hc.png + :alt: Six-Hump Camel Local Optimization Points + :scale: 60 + :align: center + +Throughout, generated and evaluated points are appended to the +:ref:`History` array, with the field +``'local_pt'`` being ``True`` if the point is part of a local optimization run, +and ``'local_min'`` being ``True`` if the point has been ruled a local minimum. + +APOSMM Persistence +------------------ + +The most recent version of APOSMM included with libEnsemble is referred to as +Persistent APOSMM. Unlike most other user functions that are initiated and +completed by workers multiple times based on allocation, a single worker process +initiates APOSMM so that it "persists" and keeps running over the course of the +entire libEnsemble routine. APOSMM begins it's own parallel evaluations and +communicates points back and forth with the manager, which are then given to +workers and evaluated by simulation routines. + +In practice, since a single worker becomes "persistent" for APOSMM, users must +ensure that enough workers or MPI ranks are initiated to +support libEnsemble's manager, a persistent worker to run APOSMM, and +simulation routines. The following:: + + mpiexec -n 3 python my_aposmm_routine.py + +results in only one worker process available to perform simulation routines. + +Calling Script +-------------- + +Create a new Python file named ``my_first_aposmm.py``. Start by importing NumPy, +libEnsemble routines, APOSMM, our ``sim_f``, and a specialized allocation +function: + +.. code-block:: python + :linenos: + + import numpy as np + + from six_hump_camel import six_hump_camel + + from libensemble.libE import libE + from libensemble.gen_funcs.persistent_aposmm import aposmm + from libensemble.alloc_funcs.persistent_aposmm_alloc import persistent_aposmm_alloc + from libensemble.tools import parse_args, add_unique_random_streams + +This allocation function starts a single Persistent APOSMM routine and provides +``sim_f`` output for points requested by APOSMM. Points can be sampled points +or points from local optimization runs. + +APOSMM supports a wide variety of external optimizers. The following statements +set optimizer settings to ``'scipy'`` to indicate to APOSMM which optimization +method to use, and help prevent unnecessary imports or package installations: + +.. code-block:: python + :linenos: + + import libensemble.gen_funcs + libensemble.gen_funcs.rc.aposmm_optimizers = 'scipy' + +Set up :doc:`parse_args()<../utilities>`, +our :doc:`sim_specs<../data_structures/sim_specs>`, +:doc:`gen_specs<../data_structures/gen_specs>`, +and :doc:`alloc_specs<../data_structures/alloc_specs>`: + +.. code-block:: python + :linenos: + + nworkers, is_master, libE_specs, _ = parse_args() + + sim_specs = {'sim_f': six_hump_camel, # Simulation function + 'in': ['x'], # Accepts 'x' values + 'out': [('f', float)]} # Returns f(x) values + + gen_out = [('x', float, 2), # Produces 'x' values + ('x_on_cube', float, 2), # 'x' values scaled to unit cube + ('sim_id', int), # Produces sim_id's for History array indexing + ('local_min', bool), # Is a point a local minimum? + ('local_pt', bool)] # Is a point from a local opt run? + + gen_specs = {'gen_f': aposmm, # APOSMM generator function + 'in': [], + 'out': gen_out, # Output defined like above dict + 'user': {'initial_sample_size': 100, # Random sample 100 points to start + 'localopt_method': 'scipy_Nelder-Mead', + 'opt_return_codes': [0], # Status integers specific to localopt_method + 'max_active_runs': 6, # Occur in parallel + 'lb': np.array([-2, -1]), # Lower bound of search domain + 'ub': np.array([2, 1])} # Upper bound of search domain + } + + alloc_specs = {'alloc_f': persistent_aposmm_alloc, + 'out': [('given_back', bool)], 'user': {}} + +``gen_specs['user']`` fields above that are required for APOSMM are: + + * ``'lb'`` - Search domain lower bound + * ``'ub'`` - Search domain upper bound + * ``'localopt_method'`` - Chosen local optimization method + * ``'initial_sample_size'`` - Number of uniformly sampled points generated + before local optimization runs. + * ``'opt_return_codes'`` - A list of integers that local optimization + methods return when a minimum is detected. SciPy's Nelder-Mead returns 0, + but other methods (not used in this tutorial) return 1. + +Also note the following: + + * ``gen_specs['in']`` is empty. For other ``gen_f``'s this defines what + fields to give to the ``gen_f`` when called, but here APOSMM's + ``alloc_f`` defines those fields. + * ``'x_on_cube'`` in ``gen_specs['out']``. APOSMM works internally on + ``'x'`` values scaled to the unit cube. To avoid back-and-forth scaling + issues, both types of ``'x'``'s are communicated back, even though the + simulation will likely use ``'x'`` values. (APOSMM performs handshake to + ensure that the ``x_on_cube`` that was given to be evaluated is the same + the one that is given back.) + * ``'sim_id'`` in ``gen_specs['out']``. APOSMM produces points in it's + local History array that it will need to update later, and can best + reference those points (and avoid a search) if APOSMM produces the IDs + itself, instead of libEnsemble. + +Other options and configurations for APOSMM can be found in the +APOSMM :doc:`API reference<../examples/aposmm>`. + +Set :ref:`exit_criteria` so libEnsemble knows +when to complete, and :ref:`persis_info` for +random sampling seeding: + +.. code-block:: python + :linenos: + + exit_criteria = {'sim_max': 2000} + persis_info = add_unique_random_streams({}, nworkers + 1) + +Finally, add statements to :doc:`initiate libEnsemble<../libe_module>`, and quickly +check calculated minima: + +.. code-block:: python + :linenos: + + H, persis_info, flag = libE(sim_specs, gen_specs, exit_criteria, persis_info, + alloc_specs, libE_specs) + if is_master: + print('Minima:', H[np.where(H['local_min'])]['x']) + +Final Setup, Run, and Output +---------------------------- + +If you haven't already, install SciPy so APOSMM can access the required +optimization method:: + + pip install scipy + +Finally, run this libEnsemble / APOSMM optimization routine with the following:: + + python my_first_aposmm.py --comms local --nworkers 4 + +Please note that one worker will be "persistent" for APOSMM for the duration of +the routine. + +After a couple seconds, the output should resemble the following:: + + [0] libensemble.libE (MANAGER_WARNING): + ******************************************************************************* + User generator script will be creating sim_id. + Take care to do this sequentially. + Also, any information given back for existing sim_id values will be overwritten! + So everything in gen_specs['out'] should be in gen_specs['in']! + ******************************************************************************* + + Minima: [[ 0.08993295 -0.71265804] + [ 1.70360676 -0.79614982] + [-1.70368421 0.79606073] + [-0.08988064 0.71270945] + [-1.60699361 -0.56859108] + [ 1.60713962 0.56869567]] + +The first section labeled ``MANAGER_WARNING`` is a default libEnsemble warning +for generator functions that create ``sim_id``'s, like APOSMM. It does not +indicate a failure. + +The local minima for the Six-Hump Camel simulation function as evaluated by +APOSMM with libEnsemble should be listed directly below the warning. + +Please see the API reference :doc:`here<../examples/aposmm>` for +more APOSMM configuration options and other information. + +Applications +------------ + +APOSMM is not limited to evaluating minima from pure Python simulation functions. +Many common libEnsemble use-cases involve using +libEnsemble's :doc:`MPI Executor<../executor/overview>` to launch user +applications with parameters requested by APOSMM, then evaluate their output using +APOSMM, and repeat until minima are identified. A currently supported example +can be found in libEnsemble's `WarpX Scaling Test`_. + +.. _`Six-Hump Camel function`: https://www.sfu.ca/~ssurjano/camel6.html +.. _NLopt: https://nlopt.readthedocs.io/en/latest/ +.. _`PETSc/TAO`: https://www.mcs.anl.gov/petsc/ +.. _SciPy: https://www.scipy.org/scipylib/index.html +.. _`WarpX Scaling Test`: https://github.com/Libensemble/libensemble/tree/master/libensemble/tests/scaling_tests/warpx diff --git a/docs/tutorials/executor_forces_tutorial.rst b/docs/tutorials/executor_forces_tutorial.rst index c1d5e1184..a97924cfa 100644 --- a/docs/tutorials/executor_forces_tutorial.rst +++ b/docs/tutorials/executor_forces_tutorial.rst @@ -296,6 +296,30 @@ Load output data from our task and return to the libEnsemble manager: return output, persis_info, calc_status +This completes our ``sim_f`` and calling script. Run libEnsemble with: + +.. code-block:: bash + + $ python my_calling_script.py --comms local --nworkers 4 + +This may take about a minute to complete. Output should appear in a new +directory ``./ensemble``, with sub-directories labeled by ``sim_id`` and worker. + +The following optional lines parse and display some output: + +.. code-block:: python + :linenos: + + import os + + for dir in os.listdir('./ensemble'): + with open(os.path.join('./ensemble', dir, 'out.txt')) as f: + out = f.readlines() + print(dir + ':') + for line in out: + print(line) + print('-'*60) + Executor Variants ----------------- diff --git a/docs/tutorials/local_sine_tutorial.rst b/docs/tutorials/local_sine_tutorial.rst index 246d23dac..6c3614366 100644 --- a/docs/tutorials/local_sine_tutorial.rst +++ b/docs/tutorials/local_sine_tutorial.rst @@ -10,7 +10,7 @@ The foundation of writing libEnsemble routines is accounting for four components 1. The generator function :ref:`gen_f`, which produces values for simulations 2. The simulator function :ref:`sim_f`, which performs simulations based on values from ``gen_f`` - 3. The allocation function :ref:`alloc_f`, which decides which of the previous two functions should be called when + 3. The allocation function :ref:`alloc_f`, which decides which of the previous two functions should be called 4. The calling script, which defines parameters and information about these functions and the libEnsemble task, then begins execution libEnsemble initializes a *manager* process and as many *worker* processes as the diff --git a/docs/tutorials/tutorials.rst b/docs/tutorials/tutorials.rst index 2db82ad14..efa5ccf91 100644 --- a/docs/tutorials/tutorials.rst +++ b/docs/tutorials/tutorials.rst @@ -5,3 +5,4 @@ Tutorials local_sine_tutorial executor_forces_tutorial + aposmm_tutorial diff --git a/docs/utilities.rst b/docs/utilities.rst index 00dcdb210..c91311a23 100644 --- a/docs/utilities.rst +++ b/docs/utilities.rst @@ -10,6 +10,7 @@ Calling Script Function Support Generator Function Support -------------------------- +.. _p_gen_routines: These routines are commonly used within persistent generator functions like ``persistent_aposmm`` in ``libensemble/gen_funcs/`` for intermediate diff --git a/examples/libE_submission_scripts/bridges_submit_slurm_central.sh b/examples/libE_submission_scripts/bridges_submit_slurm_central.sh index ba1ee7062..d00f3d041 100644 --- a/examples/libE_submission_scripts/bridges_submit_slurm_central.sh +++ b/examples/libE_submission_scripts/bridges_submit_slurm_central.sh @@ -16,7 +16,7 @@ export EXE=libE_calling_script.py export NUM_WORKERS=4 -mpirun -np $(($NUM_WORKERS+1)) python $EXE +mpirun -np $(($NUM_WORKERS+1)) -ppn $(($NUM_WORKERS+1)) python $EXE # To use local mode instead of mpi4py (with parse_args()) # python $EXE --comms local --nworkers $NUM_WORKERS diff --git a/examples/libE_submission_scripts/theta_submit_balsam.sh b/examples/libE_submission_scripts/theta_submit_balsam.sh index 5351bcb8a..c4100a888 100644 --- a/examples/libE_submission_scripts/theta_submit_balsam.sh +++ b/examples/libE_submission_scripts/theta_submit_balsam.sh @@ -1,10 +1,9 @@ #!/bin/bash -x #COBALT -t 30 #COBALT -O libE_MPI_balsam -#COBALT -n 5 # No. nodes -#COBALT -q debug-flat-quad # Up to 8 nodes only -##COBALT -q default # For large jobs >=128 nodes -##COBALT -A +#COBALT -n 5 +#COBALT -q debug-flat-quad # Up to 8 nodes only # Use default for >=128 nodes +#COBALT -A # Script to launch libEnsemble using Balsam. # Assumes Conda environment is set up. @@ -14,16 +13,15 @@ # - Manager and workers run on one node (or a dedicated set of nodes). # - Workers submit tasks to the rest of the nodes in the pool. -# Constaint: - As set up - only uses one node (up to 63 workers) for libE. -# To use more, modifiy "balsam job" line to use hyper-threading -# and/or more than one node for libE. - # Name of calling script export EXE=libE_calling_script.py # Number of workers. export NUM_WORKERS=4 +# Number of nodes to run libE +export LIBE_NODES=1 + # Balsam wall-clock in minutes - make few mins smaller than batch wallclock export BALSAM_WALLCLOCK=25 @@ -37,44 +35,42 @@ export LIBE_WALLCLOCK=$(($BALSAM_WALLCLOCK-3)) # libEnsemble calling script arguments (some alternatives shown) # No args. All defined in calling script -# export SCRIPT_ARGS='' +export SCRIPT_ARGS='' -# If calling script is using parse_args() -# export SCRIPT_ARGS="--comms mpi --nworkers $NUM_WORKERS - -# If calling script takes wall-clock as positional arg and uses parse_args() -export SCRIPT_ARGS="$LIBE_WALLCLOCK --comms mpi --nworkers $NUM_WORKERS" +# If calling script takes wall-clock as positional argument. +# export SCRIPT_ARGS="$LIBE_WALLCLOCK" # Name of Conda environment export CONDA_ENV_NAME= # Name of database -export DBASE_NAME= # default - to use default database. - -# Conda location - theta specific -export PATH=/opt/intel/python/2017.0.035/intelpython35/bin:$PATH -export LD_LIBRARY_PATH=~/.conda/envs/$CONDA_ENV_NAME/lib:$LD_LIBRARY_PATH +export BALSAM_DB_NAME= # default - to use default database. -export PYTHONNOUSERSITE=1 # Ensure environment isolated - -export PMI_NO_FORK=1 # Required for python kills on Theta - -# Activate conda environment -. activate $CONDA_ENV_NAME +# Required for killing tasks from workers on Theta +export PMI_NO_FORK=1 # Unload Theta modules that may interfere with job monitoring/kills module unload trackdeps module unload darshan module unload xalt +# Obtain Conda PATH from miniconda-3/latest module +CONDA_DIR=/soft/datascience/conda/miniconda3/latest/bin + +# Ensure environment isolated +export PYTHONNOUSERSITE=1 + +# Activate conda environment +source $CONDA_DIR/activate $CONDA_ENV_NAME + # Activate Balsam database -. balsamactivate $DBASE_NAME +source balsamactivate $BALSAM_DB_NAME # Currently need atleast one DB connection per worker (for postgres). -if [[ $NUM_WORKERS -gt 128 ]] +if [[ $NUM_WORKERS -gt 100 ]] then #Add a margin - echo -e "max_connections=$(($NUM_WORKERS+10)) #Appended by submission script" \ + echo -e "max_connections=$(($NUM_WORKERS+20)) #Appended by submission script" \ >> $BALSAM_DB_PATH/balsamdb/postgresql.conf fi wait @@ -86,29 +82,33 @@ wait sleep 3 # Add calling script to Balsam database as app and job. -THIS_DIR=$PWD -SCRIPT_BASENAME=${EXE%.*} +export THIS_DIR=$PWD +export SCRIPT_BASENAME=${EXE%.*} + +# Multiple nodes +export LIBE_PROCS=$((NUM_WORKERS+1)) # Manager and workers +export PROCS_PER_NODE=$((LIBE_PROCS/LIBE_NODES)) # Must divide evenly balsam app --name $SCRIPT_BASENAME.app --exec $EXE --desc "Run $SCRIPT_BASENAME" -# Running libE on one node - one manager and upto 63 workers balsam job --name job_$SCRIPT_BASENAME --workflow $WORKFLOW_NAME \ - --application $SCRIPT_BASENAME.app --args $SCRIPT_ARGS \ - --wall-time-minutes $BALSAM_WALLCLOCK \ - --num-nodes 1 --ranks-per-node $((NUM_WORKERS+1)) \ - --url-out="local:/$THIS_DIR" --stage-out-files="*.out *.txt *.log" \ - --url-in="local:/$THIS_DIR/*" --yes + --application $SCRIPT_BASENAME.app --args $SCRIPT_ARGS \ + --wall-time-minutes $LIBE_WALLCLOCK \ + --num-nodes $LIBE_NODES --ranks-per-node $PROCS_PER_NODE \ + --url-out="local:/$THIS_DIR" --stage-out-files="*.out *.txt *.log" \ + --url-in="local:/$THIS_DIR/*" --yes # Hyper-thread libE (note this will not affect HT status of user calcs - only libE itself) -# Running 255 workers and one manager on one libE node. -# balsam job --name job_$SCRIPT_BASENAME --workflow $WORKFLOW_NAME --application $SCRIPT_BASENAME.app --args $SCRIPT_ARGS --wall-time-minutes $BALSAM_WALLCLOCK --num-nodes 1 --ranks-per-node 256 --threads-per-core 4 --url-out="local:/$THIS_DIR" --stage-out-files="*.out *.txt *.log" --url-in="local:/$THIS_DIR/*" --yes - -# Multiple nodes for libE -# Running 127 workers and one manager - launch script on 129 nodes (if one node per worker) -# balsam job --name job_$SCRIPT_BASENAME --workflow $WORKFLOW_NAME --application $SCRIPT_BASENAME.app --args $SCRIPT_ARGS --wall-time-minutes $BALSAM_WALLCLOCK --num-nodes 2 --ranks-per-node 64 --url-out="local:/$THIS_DIR" --stage-out-files="*.out *.txt *.log" --url-in="local:/$THIS_DIR/*" --yes +# E.g. Running 255 workers and one manager on one libE node. +# balsam job --name job_$SCRIPT_BASENAME --workflow $WORKFLOW_NAME \ +# --application $SCRIPT_BASENAME.app --args $SCRIPT_ARGS \ +# --wall-time-minutes $LIBE_WALLCLOCK \ +# --num-nodes 1 --ranks-per-node 256 --threads-per-core 4 \ +# --url-out="local:/$THIS_DIR" --stage-out-files="*.out *.txt *.log" \ +# --url-in="local:/$THIS_DIR/*" --yes # Run job balsam launcher --consume-all --job-mode=mpi --num-transition-threads=1 -# Deactivate Balsam database -. balsamdeactivate +wait +source balsamdeactivate diff --git a/examples/misc/ps_nodes.sh b/examples/misc/ps_nodes.sh new file mode 100755 index 000000000..108d83a7b --- /dev/null +++ b/examples/misc/ps_nodes.sh @@ -0,0 +1,29 @@ +##!/bin/bash + +# If a prosgres process is running, ssh to node and kill process +export uname=$USER +export appname='postgres \-D' + +# Check 6 login nodes +for i in {1..6} +do + hname=thetalogin$i + if [[ "$HOSTNAME" = $hname ]] + then + hostname; ps aux|grep $uname|grep "$appname" + else + ssh $hname "hostname; ps aux|grep $uname|grep '$appname'" + fi +done + +# Check 3 MOM nodes +for i in {1..3} +do + hname=thetamom$i + if [[ "$HOSTNAME" = $hname ]] + then + hostname; ps aux|grep $uname|grep '$appname' + else + ssh $hname "hostname; ps aux|grep $uname|grep '$appname'" + fi +done diff --git a/examples/tutorials/aposmm_tutorial_notebook.ipynb b/examples/tutorials/aposmm_tutorial_notebook.ipynb new file mode 100644 index 000000000..ba10f0a7d --- /dev/null +++ b/examples/tutorials/aposmm_tutorial_notebook.ipynb @@ -0,0 +1,327 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Parallel Optimization with APOSMM\n", + "\n", + "This tutorial demonstrates libEnsemble’s capability to identify multiple minima of simulation output using the built-in APOSMM (Asynchronously Parallel Optimization Solver for finding Multiple Minima) ``gen_f``. In this tutorial, we’ll create a simple simulation ``sim_f`` that defines a function with multiple minima, then write a libEnsemble calling script that imports APOSMM and parameterizes it to check for minima over a domain of outputs from our ``sim_f``.\n", + "\n", + "Besides libEnsemble and NumPy, SciPy is also a required dependency.\n", + "\n", + "## Six-Hump Camel Simulation Function\n", + "\n", + "Describing APOSMM’s operations is simpler with a given function on which to depict evaluations. We’ll use the Six-Hump Camel function, known to have six global minima. A sample space of this function, containing all minima, appears below:\n", + "\n", + "![6humpcamel](images/basic_6hc.png)\n", + "\n", + "*Note: The following ``sim_f`` won't operate stand-alone since it has not yet been parameterized and called by libEnsemble. The full routine should work as expected.*\n", + "\n", + "Create a new Python file named ``tutorial_six_hump_camel.py``. This will be our ``sim_f``, incorporating the above function. Write the following:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "def six_hump_camel(H, persis_info, sim_specs, _):\n", + " \"\"\"Six-Hump Camel sim_f.\"\"\"\n", + "\n", + " batch = len(H['x']) # Num evaluations each sim_f call.\n", + " H_o = np.zeros(batch, dtype=sim_specs['out']) # Define output array H\n", + "\n", + " for i, x in enumerate(H['x']):\n", + " H_o['f'][i] = three_hump_camel_func(x) # Function evaluations placed into H\n", + "\n", + " return H_o, persis_info\n", + "\n", + "\n", + "def six_hump_camel_func(x):\n", + " \"\"\" Six-Hump Camel function definition \"\"\"\n", + " x1 = x[0]\n", + " x2 = x[1]\n", + " term1 = (4-2.1*x1**2+(x1**4)/3) * x1**2\n", + " term2 = x1*x2\n", + " term3 = (-4+4*x2**2) * x2**2\n", + "\n", + " return term1 + term2 + term3" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## APOSMM Operations\n", + "\n", + "APOSMM coordinates multiple local optimization runs starting from a collection of sample points. These local optimization runs occur in parallel, and can incorporate a variety of optimization methods, including from NLopt, PETSc/TAO, and SciPy. Some number of uniformly sampled points is returned by APOSMM for simulation evaluations before local optimization runs can occur, if no prior simulation evaluations are provided. User-requested sample points can also be provided to APOSMM:\n", + "\n", + "![6hcsampling](images/sampling_6hc.png)\n", + "\n", + "Specifically, APOSMM will begin local optimization runs from those points that don’t have better (more minimal) points nearby within a threshold ``r_k``. For the above example, after APOSMM has returned the uniformly sampled points, for simulation evaluations it will likely begin local optimization runs from the user-requested approximate minima. Providing these isn’t required, but can offer performance benefits.\n", + "\n", + "Each local optimization run chooses new points and determines if they’re better by passing them back to be evaluated by the simulation routine. If so, new local optimization runs are started from those points. This continues until each run converges to a minimum:\n", + "\n", + "![6hclocalopt](images/localopt_6hc.png)\n", + "\n", + "Throughout, generated and evaluated points are appended to the History array, with the field ``'local_pt'`` being ``True`` if the point is part of a local optimization run, and ``'local_min'`` being ``True`` if the point has been ruled a local minimum.\n", + "\n", + "## APOSMM Persistence\n", + "\n", + "The most recent version of APOSMM included with libEnsemble is referred to as Persistent APOSMM. Unlike most other user functions that are initiated and completed by workers multiple times based on allocation, a single worker process initiates APOSMM so that it “persists” and keeps running over the course of the entire libEnsemble routine. APOSMM begins it’s own parallel evaluations and communicates points back and forth with the manager, which are then given to workers and evaluated by simulation routines.\n", + "\n", + "In practice, since a single worker becomes “persistent” for APOSMM, users must ensure that enough workers or MPI ranks are initiated to support libEnsemble’s manager, a persistent worker to run APOSMM, and simulation routines. The following:\n", + "\n", + " mpiexec -n 3 python my_aposmm_routine.py\n", + " \n", + "results in only one worker process available to perform simulation routines.\n", + "\n", + "## Calling Script\n", + "\n", + "Create a new Python file named ``my_first_aposmm.py``. Start by importing NumPy, libEnsemble routines, APOSMM, our ``sim_f``, and a specialized allocation function:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "from tutorial_six_hump_camel import six_hump_camel\n", + "\n", + "from libensemble.libE import libE\n", + "from libensemble.gen_funcs.persistent_aposmm import aposmm\n", + "from libensemble.alloc_funcs.persistent_aposmm_alloc import persistent_aposmm_alloc\n", + "from libensemble.tools import parse_args, add_unique_random_streams" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This allocation function starts a single Persistent APOSMM routine and provides ``sim_f`` output for points requested by APOSMM. Points can be sampled points or points from local optimization runs.\n", + "\n", + "APOSMM supports a wide variety of external optimizers. The following statements set optimizer settings to ``'scipy'`` to indicate to APOSMM which optimization method to use, and help prevent unnecessary imports or package installations:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "import libensemble.gen_funcs\n", + "libensemble.gen_funcs.rc.aposmm_optimizers = 'scipy'" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Set up ``nworkers``, ``libE_specs``, ``sim_specs``, ``gen_specs``, and ``alloc_specs``:" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "nworkers = 4\n", + "libE_specs = {'nworkers': 4, 'comms': 'local'}\n", + "\n", + "sim_specs = {'sim_f': six_hump_camel, # Simulation function\n", + " 'in': ['x'], # Accepts 'x' values\n", + " 'out': [('f', float)]} # Returns f(x) values\n", + "\n", + "gen_out = [('x', float, 2), # Produces 'x' values\n", + " ('x_on_cube', float, 2), # 'x' values scaled to unit cube\n", + " ('sim_id', int), # Produces sim_id's for History array indexing\n", + " ('local_min', bool), # Is a point a local minimum?\n", + " ('local_pt', bool)] # Is a point from a local opt run?\n", + "\n", + "gen_specs = {'gen_f': aposmm, # APOSMM generator function\n", + " 'in': [],\n", + " 'out': gen_out, # Output defined like above dict\n", + " 'user': {'initial_sample_size': 100, # Random sample 100 points to start\n", + " 'localopt_method': 'scipy_Nelder-Mead',\n", + " 'opt_return_codes': [0], # Return code specific to localopt_method\n", + " 'max_active_runs': 6, # Occur in parallel\n", + " 'lb': np.array([-2, -1]), # Lower bound of search domain\n", + " 'ub': np.array([2, 1])} # Upper bound of search domain\n", + " }\n", + "\n", + "alloc_specs = {'alloc_f': persistent_aposmm_alloc,\n", + " 'out': [('given_back', bool)], 'user': {}}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "``gen_specs['user']`` fields above that are required for APOSMM are:\n", + "\n", + "* ``'lb'`` - Search domain lower bound\n", + "* ``'ub'`` - Search domain upper bound\n", + "* ``'localopt_method'`` - Chosen local optimization method\n", + "* ``'initial_sample_size'`` - Number of uniformly sampled points generated\n", + " before local optimization runs.\n", + "* ``'opt_return_codes'`` - A list of integers that local optimization\n", + " methods return when a minimum is detected. SciPy's Nelder-Mead returns 0,\n", + " but other methods (not used in this tutorial) return 1.\n", + "\n", + "Also note the following:\n", + "\n", + "* ``gen_specs['in']`` is empty. For other ``gen_f``'s this defines what\n", + " fields to give to the ``gen_f`` when called, but here APOSMM's\n", + " ``alloc_f`` defines those fields.\n", + "* ``'x_on_cube'`` in ``gen_specs['out']``. APOSMM works internally on\n", + " ``'x'`` values scaled to the unit cube. To avoid back-and-forth scaling\n", + " issues, both types of ``'x'``'s are communicated back, even though the\n", + " simulation will likely use ``'x'`` values. (APOSMM performs handshake to\n", + " ensure that the ``x_on_cube`` that was given to be evaluated is the same\n", + " the one that is given back.)\n", + "* ``'sim_id'`` in ``gen_specs['out']``. APOSMM produces points in it's\n", + " local History array that it will need to update later, and can best\n", + " reference those points (and avoid a search) if APOSMM produces the IDs\n", + " itself, instead of libEnsemble.\n", + "\n", + "Other options and configurations can be found in the APOSMM [API reference](https://libensemble.readthedocs.io/en/master/examples/aposmm.html).\n", + "\n", + "Set ``exit_criteria`` so libEnsemble knows when to complete, and ``persis_info`` for random sampling seeding:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "exit_criteria = {'sim_max': 2000}\n", + "persis_info = add_unique_random_streams({}, nworkers + 1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, add statements to initiate libEnsemble, and quickly check calculated minima:" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[0] libensemble.libE (MANAGER_WARNING): \n", + "*******************************************************************************\n", + "User generator script will be creating sim_id.\n", + "Take care to do this sequentially.\n", + "Also, any information given back for existing sim_id values will be overwritten!\n", + "So everything in gen_specs['out'] should be in gen_specs['in']!\n", + "*******************************************************************************\n", + "\n", + "\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Minima: [[ 0.08993295 -0.71265804]\n", + " [ 1.70360676 -0.79614982]\n", + " [-1.70368421 0.79606073]\n", + " [-0.08988064 0.71270945]\n", + " [-1.60699361 -0.56859108]\n", + " [ 1.60713962 0.56869567]]\n" + ] + } + ], + "source": [ + "H, persis_info, flag = libE(sim_specs, gen_specs, exit_criteria, persis_info,\n", + " alloc_specs, libE_specs)\n", + "\n", + "print('Minima:', H[np.where(H['local_min'])]['x'])\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Output\n", + "\n", + "Please note that one worker will be “persistent” for APOSMM for the duration of the routine.\n", + "\n", + "After a couple seconds, the above output should resemble the following:\n", + "\n", + " [0] libensemble.libE (MANAGER_WARNING):\n", + " *******************************************************************************\n", + " User generator script will be creating sim_id.\n", + " Take care to do this sequentially.\n", + " Also, any information given back for existing sim_id values will be overwritten!\n", + " So everything in gen_specs['out'] should be in gen_specs['in']!\n", + " *******************************************************************************\n", + "\n", + "\n", + " Minima: [[ 0.08993295 -0.71265804]\n", + " [ 1.70360676 -0.79614982]\n", + " [-1.70368421 0.79606073]\n", + " [-0.08988064 0.71270945]\n", + " [-1.60699361 -0.56859108]\n", + " [ 1.60713962 0.56869567]]\n", + " \n", + "The first section labeled ``MANAGER_WARNING`` is a default libEnsemble warning for generator functions that create ``sim_id``’s, like APOSMM. It does not indicate a failure.\n", + "\n", + "The local minima for the Six-Hump Camel simulation function as evaluated by APOSMM with libEnsemble should be listed directly below the warning.\n", + "\n", + "Please see the [API reference](https://libensemble.readthedocs.io/en/master/examples/aposmm.html) for more APOSMM configuration options and other information.\n", + "\n", + "## Applications\n", + "\n", + "APOSMM is not limited to evaluating minima from pure Python simulation functions.\n", + "Many common libEnsemble use-cases involve using libEnsemble's Executor to launch user\n", + "applications with parameters requested by APOSMM, then evaluate their output using\n", + "APOSMM, and repeat until minima are identified. A currently supported example\n", + "can be found in libEnsemble's [WarpX Scaling Test](https://github.com/Libensemble/libensemble/tree/master/libensemble/tests/scaling_tests/warpx)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/tutorials/forces_tutorial_notebook.ipynb b/examples/tutorials/forces_tutorial_notebook.ipynb new file mode 100644 index 000000000..6e7593d96 --- /dev/null +++ b/examples/tutorials/forces_tutorial_notebook.ipynb @@ -0,0 +1,403 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Executor with Electrostatic Forces\n", + "----------------------------------------\n", + "\n", + "This tutorial highlights libEnsemble’s capability to execute and monitor external scripts or user applications within simulation or generator functions using the executor. In this tutorial, our calling script registers an external C executable that simulates electrostatic forces between a collection of particles. The ``sim_f`` routine then launches and polls this executable.\n", + "\n", + "It is possible to use ``subprocess`` calls from Python to issue commands such as ``jsrun`` or ``aprun`` to run applications. Unfortunately, hard-coding such commands within user scripts isn’t portable. Furthermore, many systems like Argonne’s Theta do not allow libEnsemble to submit additional tasks from the compute nodes. On these systems a proxy launch mechanism (such as Balsam) is required. libEnsemble’s Executor was developed to directly address such issues.\n", + "\n", + "Getting Started\n", + "------------------\n", + "\n", + "**An MPI distribution and ``mpi4py`` are required to use this notebook locally**.\n", + "\n", + "A simulation source file ``forces.c`` is available in the libEnsemble repository for compiling into an application ``forces.x``. This app will be registered by the Executor for launching by libEnsemble's workers." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import subprocess\n", + "import requests\n", + "\n", + "url = 'https://raw.githubusercontent.com/Libensemble/libensemble/master/libensemble/tests/scaling_tests/forces/forces.c'\n", + "forces = requests.get(url)\n", + "open('./forces.c', 'wb').write(forces.content)\n", + "\n", + "subprocess.run('mpicc -O3 -o forces.x forces.c -lm'.split())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Simulation Function\n", + "-----------------------\n", + "\n", + "__*Note: Several of these stand-alone Simulation Function code-cells may not execute properly since the calling script with all expected parameters isn't defined until later. The complete libEnsemble routine should still function as expected.*__\n", + "\n", + "Our ``sim_f`` is where we’ll use libEnsemble’s executor to configure and submit for execution our compiled simulation code. We will poll this task’s state while it runs, and once we’ve detected it has finished we will send any results or exit statuses back to the manager.\n", + "\n", + "Create a Python file named ``tutorial_forces_simf.py`` containing:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import time\n", + "import numpy as np\n", + "\n", + "from libensemble.executors.executor import Executor\n", + "from libensemble.message_numbers import WORKER_DONE, WORKER_KILL, TASK_FAILED\n", + "\n", + "MAX_SEED = 32767\n", + "\n", + "def perturb(particles, seed, max_fraction):\n", + " \"\"\"Modify particle count\"\"\"\n", + " seed_fraction = seed/MAX_SEED\n", + " max_delta = particles * max_fraction\n", + " delta = seed_fraction * max_delta\n", + " delta = delta - max_delta/2 # translate so -/+\n", + " new_particles = particles + delta\n", + " return int(new_particles)\n", + "\n", + "def read_last_line(filepath):\n", + " \"\"\"Read last line of statfile\"\"\"\n", + " try:\n", + " with open(filepath, 'rb') as fh:\n", + " line = fh.readlines()[-1].decode().rstrip()\n", + " except Exception:\n", + " line = \"\" # In case file is empty or not yet created\n", + " return line" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We use libEnsemble’s message number tags to communicate the worker’s status to the manager. For testing purposes, the ``perturb()`` function randomizes the resources used for each calculation. The second function parses forces values and statuses in the ``.stat`` file produced by our compiled code. Now we can write the actual ``sim_f``. We’ll first write the function definition, extract our parameters from ``sim_specs``, define a random seed, and use ``perturb()`` to randomize our particle counts." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def run_forces(H, persis_info, sim_specs, libE_info):\n", + " calc_status = 0\n", + "\n", + " x = H['x']\n", + " sim_particles = sim_specs['user']['sim_particles']\n", + " sim_timesteps = sim_specs['user']['sim_timesteps']\n", + " time_limit = sim_specs['user']['sim_kill_minutes'] * 60.0\n", + "\n", + " cores = sim_specs['user'].get('cores', None)\n", + " kill_rate = sim_specs['user'].get('kill_rate', 0)\n", + " particle_variance = sim_specs['user'].get('particle_variance', 0)\n", + "\n", + " seed = int(np.rint(x[0][0]))\n", + "\n", + " # To give a random variance of work-load\n", + " sim_particles = perturb(sim_particles, seed, particle_variance)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next we will instantiate our executor and submit our registered application for execution." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + " # Use pre-defined executor object\n", + " exctr = Executor.executor\n", + "\n", + " # Arguments for our registered simulation\n", + " args = str(int(sim_particles)) + ' ' + str(sim_timesteps) + ' ' + str(seed) + ' ' + str(kill_rate)\n", + "\n", + " # Submit our simulation for execution.\n", + " if cores:\n", + " task = exctr.submit(calc_type='sim', num_procs=cores, app_args=args,\n", + " stdout='out.txt', stderr='err.txt', wait_on_run=True)\n", + " else:\n", + " task = exctr.submit(calc_type='sim', app_args=args, stdout='out.txt',\n", + " stderr='err.txt', wait_on_run=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In each executor ``submit()`` routine, we define the type of calculation being performed, optionally the number of processors to run the task on, additional arguments for the simulation code, and files for ``stdout`` and ``stderr`` output. The ``wait_on_run`` argument pauses sim_f execution until the task is confirmed to be running. See the docs for more information about these and other options.\n", + "\n", + "The rest of our ``sim_f`` polls the task’s dynamically updated attributes for its status, determines if a successful run occurred after the task completes, then formats and returns the output data to the manager.\n", + "\n", + "We can poll the task and kill it in certain circumstances:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + " # Stat file to check for bad runs\n", + " statfile = 'forces.stat'\n", + " filepath = os.path.join(task.workdir, statfile)\n", + " line = None\n", + "\n", + " poll_interval = 1\n", + " while not task.finished :\n", + " line = read_last_line(filepath) # Parse some output from the task\n", + " if line == \"kill\":\n", + " task.kill()\n", + " elif task.runtime > time_limit:\n", + " task.kill()\n", + " else:\n", + " time.sleep(poll_interval)\n", + " task.poll() # updates the task's attributes\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once our task finishes, adjust ``calc_status`` (our “exit code”) and report to the user based on the task’s final state:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + " if task.finished:\n", + " if task.state == 'FINISHED':\n", + " print(\"Task {} completed\".format(task.name))\n", + " calc_status = WORKER_DONE\n", + " if read_last_line(filepath) == \"kill\":\n", + " print(\"Warning: Task complete but marked bad (kill flag in forces.stat)\")\n", + " elif task.state == 'FAILED':\n", + " print(\"Warning: Task {} failed: Error code {}\".format(task.name, task.errcode))\n", + " calc_status = TASK_FAILED\n", + " elif task.state == 'USER_KILLED':\n", + " print(\"Warning: Task {} has been killed\".format(task.name))\n", + " calc_status = WORKER_KILL\n", + " else:\n", + " print(\"Warning: Task {} in unknown state {}. Error code {}\".format(task.name, task.state, task.errcode))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Load output data from our task and return to the libEnsemble manager:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + " time.sleep(0.2) # Small buffer to guarantee data has been written\n", + " try:\n", + " data = np.loadtxt(filepath)\n", + " final_energy = data[-1]\n", + " except Exception:\n", + " final_energy = np.nan\n", + "\n", + " outspecs = sim_specs['out']\n", + " output = np.zeros(1, dtype=outspecs)\n", + " output['energy'][0] = final_energy\n", + "\n", + " return output, persis_info, calc_status\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Calling Script\n", + "----------------\n", + "\n", + "Finally, lets write our calling script to parameterize our simulation and generation functions and call libEnsemble. Create a Python file containing:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#!/usr/bin/env python\n", + "import os\n", + "import numpy as np\n", + "from tutorial_forces_simf import run_forces # Sim func from current dir\n", + "\n", + "from libensemble.libE import libE\n", + "from libensemble.gen_funcs.sampling import uniform_random_sample\n", + "from libensemble.tools import parse_args, add_unique_random_streams\n", + "from libensemble.executors.mpi_executor import MPIExecutor\n", + "\n", + "nworkers = 4\n", + "libE_specs = {'nworkers': nworkers, 'comms': 'local'}\n", + "\n", + "# Create executor and register sim to it\n", + "exctr = MPIExecutor() # Use auto_resources=False to oversubscribe\n", + "\n", + "# Create empty simulation input directory\n", + "if not os.path.isdir('./sim'):\n", + " os.mkdir('./sim')\n", + "\n", + "# Register simulation executable with executor\n", + "sim_app = os.path.join(os.getcwd(), 'forces.x')\n", + "exctr.register_calc(full_path=sim_app, calc_type='sim')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "On line 4 we import our not-yet-written ``sim_f``. We also import necessary libEnsemble components and a convenience function to initiate ``persis_info``.\n", + "\n", + "Next we define our executor class instance. This instance can be customized with many of the settings defined here. We’ll register our simulation with the executor and use the same instance within our ``sim_f``.\n", + "\n", + "libEnsemble can perform and write every simulation (within the ensemble) in a separate directory for organization and potential I/O benefits. In this example, libEnsemble copies a source directory and its contents to create these simulation directories. For our purposes, an empty directory ``./sim`` is sufficient.\n", + "\n", + "Next define the ``sim_specs`` and ``gen_specs`` data structures:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# State the sim_f, its arguments, output, and parameters (and their sizes)\n", + "sim_specs = {'sim_f': run_forces, # sim_f, imported above\n", + " 'in': ['x'], # Name of input for sim_f\n", + " 'out': [('energy', float)], # Name, type of output from sim_f\n", + " 'user': {'simdir_basename': 'forces', # User parameters for sim_f\n", + " 'cores': 1,\n", + " 'sim_particles': 1e3,\n", + " 'sim_timesteps': 5,\n", + " 'sim_kill_minutes': 10.0,\n", + " 'particle_variance': 0.2,\n", + " 'kill_rate': 0.5}\n", + " }\n", + "\n", + "# State the gen_f, its arguments, output, and necessary parameters.\n", + "gen_specs = {'gen_f': uniform_random_sample, # Generator function\n", + " 'in': ['sim_id'], # Generator input\n", + " 'out': [('x', float, (1,))], # Name, type and size of data from gen_f\n", + " 'user': {'lb': np.array([0]), # User parameters for gen_f\n", + " 'ub': np.array([32767]),\n", + " 'gen_batch_size': 1000,\n", + " 'batch_mode': True,\n", + " 'num_active_gens': 1,\n", + " }\n", + " }" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "These dictionaries configure our generation function ``gen_f`` and our simulation function ``sim_f``, respectively, as the ``uniform_random_sample`` and ``run_forces`` functions. Our ``gen_f`` will generate random seeds when initializing each ``sim_f`` call.\n", + "\n", + "After some additions to ``libE_specs`` and defining our ``exit_criteria`` and ``persis_info``, our script calls the main libE routine:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "libE_specs['save_every_k_gens'] = 1000 # Save every K steps\n", + "libE_specs['sim_input_dir'] = './sim' # Sim dir to be copied for each worker\n", + "\n", + "exit_criteria = {'sim_max': 8}\n", + "\n", + "persis_info = add_unique_random_streams({}, nworkers + 1)\n", + "\n", + "H, persis_info, flag = libE(sim_specs, gen_specs, exit_criteria,\n", + " persis_info=persis_info, libE_specs=libE_specs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This may take about a minute to complete. Output should appear in a new\n", + "directory ``./ensemble``, with sub-directories labeled by ``sim_id`` and worker.\n", + "\n", + "The following lines parse and display some output." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "for dir in os.listdir('./ensemble'):\n", + " with open(os.path.join('./ensemble', dir, 'out.txt')) as f:\n", + " out = f.readlines()\n", + " print(dir + ':')\n", + " for line in out:\n", + " print(line)\n", + " print('-'*60)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/tutorials/images/basic_6hc.png b/examples/tutorials/images/basic_6hc.png new file mode 120000 index 000000000..760f245db --- /dev/null +++ b/examples/tutorials/images/basic_6hc.png @@ -0,0 +1 @@ +../../../docs/images/basic_6hc.png \ No newline at end of file diff --git a/examples/tutorials/images/localopt_6hc.png b/examples/tutorials/images/localopt_6hc.png new file mode 120000 index 000000000..ad6a8415a --- /dev/null +++ b/examples/tutorials/images/localopt_6hc.png @@ -0,0 +1 @@ +../../../docs/images/localopt_6hc.png \ No newline at end of file diff --git a/examples/tutorials/images/sampling_6hc.png b/examples/tutorials/images/sampling_6hc.png new file mode 120000 index 000000000..ceeb5c24e --- /dev/null +++ b/examples/tutorials/images/sampling_6hc.png @@ -0,0 +1 @@ +../../../docs/images/sampling_6hc.png \ No newline at end of file diff --git a/examples/tutorials/sine_tutorial_notebook.ipynb b/examples/tutorials/sine_tutorial_notebook.ipynb index 645087e88..a9ac5ac52 100644 --- a/examples/tutorials/sine_tutorial_notebook.ipynb +++ b/examples/tutorials/sine_tutorial_notebook.ipynb @@ -62,7 +62,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -105,7 +105,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -141,7 +141,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ diff --git a/examples/tutorials/tutorial_aposmm.py b/examples/tutorials/tutorial_aposmm.py new file mode 100644 index 000000000..93c59ebbd --- /dev/null +++ b/examples/tutorials/tutorial_aposmm.py @@ -0,0 +1,45 @@ +import numpy as np + +from tutorial_six_hump_camel import six_hump_camel + +from libensemble.libE import libE +from libensemble.gen_funcs.persistent_aposmm import aposmm +from libensemble.alloc_funcs.persistent_aposmm_alloc import persistent_aposmm_alloc +from libensemble.tools import parse_args, add_unique_random_streams + +import libensemble.gen_funcs +libensemble.gen_funcs.rc.aposmm_optimizers = 'scipy' + +nworkers, is_master, libE_specs, _ = parse_args() + +sim_specs = {'sim_f': six_hump_camel, # Simulation function + 'in': ['x'], # Accepts 'x' values + 'out': [('f', float)]} # Returns f(x) values + +gen_out = [('x', float, 2), # Produces 'x' values + ('x_on_cube', float, 2), # 'x' values scaled to unit cube + ('sim_id', int), # Produces IDs for sim order + ('local_min', bool), # Is a point a local minimum? + ('local_pt', bool)] # Is a point from a local opt run? + +gen_specs = {'gen_f': aposmm, # APOSMM generator function + 'in': [], + 'out': gen_out, # Output defined like above dict + 'user': {'initial_sample_size': 100, # Random sample 100 points to start + 'localopt_method': 'scipy_Nelder-Mead', + 'opt_return_codes': [0], # Return code specific to localopt_method + 'max_active_runs': 6, # Occur in parallel + 'lb': np.array([-2, -1]), # Lower bound of search domain + 'ub': np.array([2, 1])} # Upper bound of search domain + } + +alloc_specs = {'alloc_f': persistent_aposmm_alloc, + 'out': [('given_back', bool)], 'user': {}} + +exit_criteria = {'sim_max': 2000} +persis_info = add_unique_random_streams({}, nworkers + 1) + +H, persis_info, flag = libE(sim_specs, gen_specs, exit_criteria, persis_info, + alloc_specs, libE_specs) +if is_master: + print('Minima:', H[np.where(H['local_min'])]['x']) diff --git a/examples/tutorials/tutorial_six_hump_camel.py b/examples/tutorials/tutorial_six_hump_camel.py new file mode 100644 index 000000000..96b9d8c81 --- /dev/null +++ b/examples/tutorials/tutorial_six_hump_camel.py @@ -0,0 +1,24 @@ +import numpy as np + + +def six_hump_camel(H, persis_info, sim_specs, _): + """Six-Hump Camel sim_f.""" + + batch = len(H['x']) # Num evaluations each sim_f call. + H_o = np.zeros(batch, dtype=sim_specs['out']) # Define output array H + + for i, x in enumerate(H['x']): + H_o['f'][i] = six_hump_camel_func(x) # Function evaluations placed into H + + return H_o, persis_info + + +def six_hump_camel_func(x): + """ Six-Hump Camel function definition """ + x1 = x[0] + x2 = x[1] + term1 = (4-2.1*x1**2+(x1**4)/3) * x1**2 + term2 = x1*x2 + term3 = (-4+4*x2**2) * x2**2 + + return term1 + term2 + term3 diff --git a/install/run_travis_locally/build_mpich_libE.sh b/install/run_travis_locally/build_mpich_libE.sh index 27f8a6679..2550e3188 100755 --- a/install/run_travis_locally/build_mpich_libE.sh +++ b/install/run_travis_locally/build_mpich_libE.sh @@ -84,12 +84,16 @@ pip install coveralls || return git clone -b $LIBE_BRANCH https://github.com/Libensemble/libensemble.git || return cd libensemble/ || return pip install -e . || return + +wget https://github.com/balsam-alcf/balsam/archive/0.3.8.tar.gz +mkdir ../balsam; tar xf 0.3.8.tar.gz -C ../balsam python install/configure_balsam_install.py export BALSAM_DB_PATH=~/test-balsam ulimit -Sn 10000 if [ "$RUN_TESTS" = true ]; then ./libensemble/tests/run-tests.sh -z +fi echo -e "\n\nScript completed...\n\n" set +ex diff --git a/install/run_travis_locally/quick_run.md b/install/run_travis_locally/quick_run.md new file mode 100644 index 000000000..bb935b033 --- /dev/null +++ b/install/run_travis_locally/quick_run.md @@ -0,0 +1,41 @@ +# Quick run + +If you have followed the ``readme`` to obtain ``docker`` and set up a container, this gives a quick example of running it. + +Note: In this examnple the tests are not automatically run (-i in option to build_mpich_libE.sh). + +For details see the ``readme``. + +Two windows: +Window 1: In install/run_travis_locally direcory. +Window 2: Will create and run container. + +Windows 1 and 2 - name container. E.g: + + export CONTAINER=travis-debug-2020-07-20-py3.5 + +Window 2: + + sudo docker run --name $CONTAINER -dit travisci/ci-garnet:packer-1512502276-986baf0 /sbin/init + sudo docker exec -it $CONTAINER bash -l + +Window 1: + + docker cp build_mpich_libE.sh $CONTAINER:/home/travis + +Window 1 Optional - user scripts to help navigate: + + docker cp ~/.bashrc $CONTAINER:/home/travis + docker cp ~/.alias $CONTAINER:/home/travis + +WWindow 2 (Example: Do not run tests python 3.5 - git branch feature/register_apps): + + chown travis:travis /home/travis/build_mpich_libE.sh + su - travis + . ./build_mpich_libE.sh -p 3.5 -b feature/register_apps -i + +Window 2 Optional - user scripts to help navigate: + + . ~/.bashrc + +Note: libEnsemble will be git cloned and checked out at the given branch. diff --git a/install/test_balsam_hworld.py b/install/test_balsam_hworld.py index 79f1bfbac..d927f547f 100644 --- a/install/test_balsam_hworld.py +++ b/install/test_balsam_hworld.py @@ -20,46 +20,66 @@ def run_Balsam_job(): def wait_for_job_dir(basedb): sleeptime = 0 + limit = 15 - while not os.path.isdir(basedb) and sleeptime < 15: + # Stop sleeping once database directory detected + print('Waiting for Balsam Database directory.') + while sleeptime < limit: + if os.path.isdir(basedb): + break time.sleep(1) sleeptime += 1 + assert sleeptime < limit, \ + "Balsam Database directory not created within {} seconds.".format(limit) + + # Stop sleeping once job directory detected within database directory print('Waiting for Job Directory {}'.format(sleeptime)) - while len(os.listdir(basedb)) == 0 and sleeptime < 15: + while sleeptime < limit: + if len(os.listdir(basedb)) > 0: + break print(sleeptime, end=" ", flush=True) time.sleep(1) sleeptime += 1 - jobdirname = os.listdir(basedb)[0] - jobdir = os.path.join(basedb, jobdirname) + assert sleeptime < limit, \ + "Balsam Job directory not created within {} seconds.".format(limit) + + # Assumes database dir was empty, now contains single job dir + jobdir = os.path.join(basedb, os.listdir(basedb)[0]) return jobdir def wait_for_job_output(jobdir): sleeptime = 0 + limit = 40 output = os.path.join(jobdir, 'job_script_test_balsam_hworld.out') print('Checking for Balsam output file: {}'.format(output)) - while not os.path.isfile(output) and sleeptime < 40: + while sleeptime < limit: + if os.path.isfile(output): + break print(sleeptime, end=" ", flush=True) time.sleep(1) sleeptime += 1 + assert sleeptime < limit, \ + "Balsam output file not created within {} seconds.".format(limit) + return output def print_job_output(outscript): sleeptime = 0 + limit = 80 - print('Output file found. Waiting for complete Balsam Job Output.') - lastlines = ['Job 4 done on worker 1\n', 'Job 4 done on worker 2\n', - 'Run completed.\n'] + print('Blank output file found. Waiting for expected complete Balsam Job Output.') + succeed_line = 'Received: [34 34 31 31 34 34 32 32 33 33 0 0]\n' lastposition = 0 - while sleeptime < 60: + while sleeptime < limit: with open(outscript, 'r') as f: f.seek(lastposition) new = f.read() @@ -70,12 +90,16 @@ def print_job_output(outscript): else: print(sleeptime, end=" ", flush=True) - if any(new.endswith(line) for line in lastlines): + if succeed_line in new: + print('Success. Received task statuses match expected.') break time.sleep(1) sleeptime += 1 + assert sleeptime < limit, \ + "Expected Balsam Job output-file contents not detected after {} seconds.".format(limit) + def move_job_coverage(jobdir): # Move coverage files from Balsam DB to ./regression_tests (for concatenation) diff --git a/libensemble/__init__.py b/libensemble/__init__.py index c964ccc63..7740392a5 100644 --- a/libensemble/__init__.py +++ b/libensemble/__init__.py @@ -4,7 +4,7 @@ Library to coordinate the concurrent evaluation of dynamic ensembles of calculations. """ -__version__ = "0.7.0" +__version__ = "0.7.1" __author__ = 'Jeffrey Larson, Stephen Hudson, Stefan M. Wild, David Bindel and John-Luke Navarro' __credits__ = 'Argonne National Laboratory' diff --git a/libensemble/alloc_funcs/persistent_aposmm_alloc.py b/libensemble/alloc_funcs/persistent_aposmm_alloc.py index c93dde521..b985c1725 100644 --- a/libensemble/alloc_funcs/persistent_aposmm_alloc.py +++ b/libensemble/alloc_funcs/persistent_aposmm_alloc.py @@ -1,6 +1,6 @@ import numpy as np -from libensemble.tools.alloc_support import avail_worker_ids, sim_work, gen_work +from libensemble.tools.alloc_support import avail_worker_ids, sim_work, gen_work, count_persis_gens def persistent_aposmm_alloc(W, H, sim_specs, gen_specs, alloc_specs, persis_info): @@ -18,6 +18,8 @@ def persistent_aposmm_alloc(W, H, sim_specs, gen_specs, alloc_specs, persis_info """ Work = {} + gen_count = count_persis_gens(W) + if persis_info.get('first_call', True): assert np.all(H['given']), "Initial points in H have never been given." assert np.all(H['given_back']), "Initial points in H have never been given_back." @@ -33,6 +35,9 @@ def persistent_aposmm_alloc(W, H, sim_specs, gen_specs, alloc_specs, persis_info persis_info['samples_in_H0'] = sum(H['local_pt'] == 0) persis_info['next_to_give'] = len(H) # persis_info['first_call'] = False + elif gen_count == 0: + # The one persistent gen is done. Exiting + return Work, persis_info, 1 # If any persistent worker's calculated values have returned, give them back. for i in avail_worker_ids(W, persistent=True): diff --git a/libensemble/alloc_funcs/start_only_persistent.py b/libensemble/alloc_funcs/start_only_persistent.py index 4329a9cd0..5de788a8c 100644 --- a/libensemble/alloc_funcs/start_only_persistent.py +++ b/libensemble/alloc_funcs/start_only_persistent.py @@ -17,7 +17,7 @@ def only_persistent_gens(W, H, sim_specs, gen_specs, alloc_specs, persis_info): Work = {} gen_count = count_persis_gens(W) - if len(H) and gen_count == 0: + if persis_info.get('gen_started') and gen_count == 0: # The one persistent worker is done. Exiting return Work, persis_info, 1 @@ -45,7 +45,8 @@ def only_persistent_gens(W, H, sim_specs, gen_specs, alloc_specs, persis_info): elif gen_count == 0: # Finally, call a persistent generator as there is nothing else to do. gen_count += 1 - gen_work(Work, i, gen_specs['in'], [], persis_info[i], + gen_work(Work, i, gen_specs['in'], range(len(H)), persis_info[i], persistent=True) + persis_info['gen_started'] = True return Work, persis_info, 0 diff --git a/libensemble/executors/balsam_executor.py b/libensemble/executors/balsam_executor.py index 7a41b2df2..a16a765d4 100644 --- a/libensemble/executors/balsam_executor.py +++ b/libensemble/executors/balsam_executor.py @@ -20,7 +20,7 @@ from libensemble.resources.mpi_resources import MPIResources from libensemble.executors.executor import \ - Task, ExecutorException, jassert, STATES + Application, Task, ExecutorException, TimeoutExpired, jassert, STATES from libensemble.executors.mpi_executor import MPIExecutor import balsam.launcher.dag as dag @@ -85,23 +85,17 @@ def calc_task_timing(self): if self.total_time is None: self.total_time = time.time() - self.submit_time - def poll(self): - """Polls and updates the status attributes of the supplied task""" - if not self.check_poll(): - return - - # Get current state of tasks from Balsam database - self.process.refresh_from_db() - balsam_state = self.process.state - self.runtime = self._get_time_since_balsam_submit() - - if balsam_state in models.END_STATES: - self.finished = True - self.calc_task_timing() + def _set_complete(self, dry_run=False): + """Set task as complete""" + self.finished = True + if dry_run: + self.success = True + self.state = 'FINISHED' + else: + balsam_state = self.process.state self.workdir = self.workdir or self.process.working_directory + self.calc_task_timing() self.success = (balsam_state == 'JOB_FINISHED') - # self.errcode - requested feature from Balsam devs - if balsam_state == 'JOB_FINISHED': self.state = 'FINISHED' elif balsam_state == 'PARENT_KILLED': # Not currently used @@ -116,6 +110,22 @@ def poll(self): logger.info("Task {} ended with state {}". format(self.name, self.state)) + def poll(self): + """Polls and updates the status attributes of the supplied task""" + if self.dry_run: + return + + if not self._check_poll(): + return + + # Get current state of tasks from Balsam database + self.process.refresh_from_db() + balsam_state = self.process.state + self.runtime = self._get_time_since_balsam_submit() + + if balsam_state in models.END_STATES: + self._set_complete() + elif balsam_state in models.ACTIVE_STATES: self.state = 'RUNNING' self.workdir = self.workdir or self.process.working_directory @@ -129,6 +139,36 @@ def poll(self): "Task state returned from Balsam is not in known list of " "Balsam states. Task state is {}".format(balsam_state)) + def wait(self, timeout=None): + """Waits on completion of the task or raises TimeoutExpired exception + + Status attributes of task are updated on completion. + + Parameters + ---------- + + timeout: + Time in seconds after which a TimeoutExpired exception is raised""" + + if self.dry_run: + return + + if not self._check_poll(): + return + + # Wait on the task + start = time.time() + self.process.refresh_from_db() + while self.process.state not in models.END_STATES: + time.sleep(0.2) + self.process.refresh_from_db() + if timeout and time.time() - start > timeout: + self.runtime = self._get_time_since_balsam_submit() + raise TimeoutExpired(self.name, timeout) + + self.runtime = self._get_time_since_balsam_submit() + self._set_complete() + def kill(self, wait_time=None): """ Kills or cancels the supplied task """ @@ -152,6 +192,7 @@ class BalsamMPIExecutor(MPIExecutor): def __init__(self, auto_resources=True, allow_oversubscribe=True, central_mode=True, + zero_resource_workers=[], nodelist_env_slurm=None, nodelist_env_cobalt=None, nodelist_env_lsf=None, @@ -173,6 +214,7 @@ def __init__(self, auto_resources=True, super().__init__(auto_resources, allow_oversubscribe, central_mode, + zero_resource_workers, nodelist_env_slurm, nodelist_env_cobalt, nodelist_env_lsf, @@ -185,20 +227,19 @@ def _serial_setup(self): BalsamMPIExecutor.del_apps() BalsamMPIExecutor.del_tasks() - for calc_type in self.default_apps: - if self.default_apps[calc_type] is not None: - calc_name = self.default_apps[calc_type].name - desc = self.default_apps[calc_type].desc - full_path = self.default_apps[calc_type].full_path - self.add_app(calc_name, full_path, desc) + for app in self.apps.values(): + calc_name = app.gname + desc = app.desc + full_path = app.full_path + self.add_app(calc_name, full_path, desc) @staticmethod def del_apps(): - """Deletes all Balsam apps whose names contains .simfunc or .genfunc""" + """Deletes all Balsam apps in the libe_app namespace""" AppDef = models.ApplicationDefinition # Some error handling on deletes.... is it internal - for app_type in ['.simfunc', '.genfunc']: + for app_type in [Application.prefix]: deletion_objs = AppDef.objects.filter(name__contains=app_type) if deletion_objs: for del_app in deletion_objs.iterator(): @@ -207,8 +248,8 @@ def del_apps(): @staticmethod def del_tasks(): - """Deletes all Balsam tasks whose names contains .simfunc or .genfunc""" - for app_type in ['.simfunc', '.genfunc']: + """Deletes all Balsam tasks """ + for app_type in [Task.prefix]: deletion_objs = models.BalsamJob.objects.filter( name__contains=app_type) if deletion_objs: @@ -216,12 +257,6 @@ def del_tasks(): logger.debug("Deleting task {}".format(del_task.name)) deletion_objs.delete() - # May be able to use union function - to combine - see queryset help. - # Eg (not tested) - # del_simfuncs = Task.objects.filter(name__contains='.simfunc') - # del_genfuncs = Task.objects.filter(name__contains='.genfunc') - # deletion_objs = deletion_objs.union() - @staticmethod def add_app(name, exepath, desc): """ Add application to Balsam database """ @@ -235,9 +270,9 @@ def add_app(name, exepath, desc): app.save() logger.debug("Added App {}".format(app.name)) - def submit(self, calc_type, num_procs=None, num_nodes=None, - ranks_per_node=None, machinefile=None, app_args=None, - stdout=None, stderr=None, stage_inout=None, + def submit(self, calc_type=None, app_name=None, num_procs=None, + num_nodes=None, ranks_per_node=None, machinefile=None, + app_args=None, stdout=None, stderr=None, stage_inout=None, hyperthreads=False, dry_run=False, wait_on_run=False, extra_args=None): """Creates a new task, and either executes or schedules to execute @@ -245,7 +280,13 @@ def submit(self, calc_type, num_procs=None, num_nodes=None, The created task object is returned. """ - app = self.default_app(calc_type) + + if app_name is not None: + app = self.get_app(app_name) + elif calc_type is not None: + app = self.default_app(calc_type) + else: + raise ExecutorException("Either app_name or calc_type must be set") # Specific to this class if machinefile is not None: @@ -280,7 +321,7 @@ def submit(self, calc_type, num_procs=None, num_nodes=None, add_task_args = {'name': task.name, 'workflow': self.workflow_name, 'user_workdir': default_workdir, - 'application': app.name, + 'application': app.gname, 'args': task.app_args, 'num_nodes': num_nodes, 'ranks_per_node': ranks_per_node, @@ -295,7 +336,7 @@ def submit(self, calc_type, num_procs=None, num_nodes=None, if dry_run: task.dry_run = True logger.info('Test (No submit) Runline: {}'.format(' '.join(add_task_args))) - task.set_as_complete() + task._set_complete(dry_run=True) else: task.process = dag.add_job(**add_task_args) diff --git a/libensemble/executors/executor.py b/libensemble/executors/executor.py index 02ca62ad4..3ad5adb9c 100644 --- a/libensemble/executors/executor.py +++ b/libensemble/executors/executor.py @@ -49,6 +49,16 @@ class ExecutorException(Exception): "Raised for any exception in the Executor" +class TimeoutExpired(Exception): + """Timeout exception raised when Timeout expires""" + def __init__(self, task, timeout): + self.task = task + self.timeout = timeout + + def __str__(self): + return ("Task {} timed out after {} seconds".format(self.task, self.timeout)) + + def jassert(test, *args): "Version of assert that raises a ExecutorException" if not test: @@ -59,7 +69,9 @@ class Application: """An application is an executable user-program (e.g., implementing a sim/gen function).""" - def __init__(self, full_path, calc_type='sim', desc=None): + prefix = 'libe_app' + + def __init__(self, full_path, name=None, calc_type='sim', desc=None): """Instantiates a new Application instance.""" self.full_path = full_path self.calc_type = calc_type @@ -67,10 +79,9 @@ def __init__(self, full_path, calc_type='sim', desc=None): if self.exe.endswith('.py'): self.full_path = ' '.join((sys.executable, full_path)) - - # Use this name to delete tasks in database - see del_apps(), del_tasks() - self.name = self.exe + '.' + self.calc_type + 'func' - self.desc = desc or (self.exe + ' ' + self.calc_type + ' function') + self.name = name or self.exe + self.desc = desc or (self.exe + ' app') + self.gname = '_'.join([Application.prefix, self.name]) class Task: @@ -79,6 +90,7 @@ class Task: """ + prefix = 'libe_task' newid = itertools.count() def __init__(self, app=None, app_args=None, workdir=None, @@ -104,7 +116,7 @@ def __init__(self, app=None, app_args=None, workdir=None, format(self.id)) worker_name = "_worker{}".format(self.workerID) if self.workerID else "" - self.name = "task_{}{}_{}".format(app.name, worker_name, self.id) + self.name = Task.prefix + "_{}{}_{}".format(app.name, worker_name, self.id) self.stdout = stdout or self.name + '.out' self.stderr = stderr or self.name + '.err' self.workdir = workdir @@ -123,11 +135,6 @@ def reset(self): self.runtime = 0 # Time since task started to latest poll (or finished). self.total_time = None # Time from task submission until polled as finished. - def set_as_complete(self): - self.finished = True - self.success = True - self.state = 'FINISHED' - def workdir_exists(self): """Returns true if the task's workdir exists""" return self.workdir and os.path.exists(self.workdir) @@ -174,24 +181,38 @@ def calc_task_timing(self): self.runtime = self.timer.elapsed self.total_time = self.runtime # For direct launched tasks - def check_poll(self): + def _check_poll(self): """Check whether polling this task makes sense.""" jassert(self.process is not None, "Polled task {} has no process ID - check tasks been launched". format(self.name)) if self.finished: - logger.warning("Polled task {} has already finished. " - "Not re-polling. Status is {}". - format(self.name, self.state)) + logger.debug("Polled task {} has already finished. " + "Not re-polling. Status is {}". + format(self.name, self.state)) return False return True + def _set_complete(self, dry_run=False): + """Set task as complete""" + self.finished = True + if dry_run: + self.success = True + self.state = 'FINISHED' + else: + self.calc_task_timing() + self.errcode = self.process.returncode + self.success = (self.errcode == 0) + self.state = 'FINISHED' if self.success else 'FAILED' + logger.info("Task {} finished with errcode {} ({})". + format(self.name, self.errcode, self.state)) + def poll(self): """Polls and updates the status attributes of the task""" if self.dry_run: return - if not self.check_poll(): + if not self._check_poll(): return # Poll the task @@ -201,15 +222,31 @@ def poll(self): self.runtime = self.timer.elapsed return - self.finished = True - self.calc_task_timing() + self._set_complete() - # Want to be more fine-grained about non-success (fail vs user kill?) - self.errcode = self.process.returncode - self.success = (self.errcode == 0) - self.state = 'FINISHED' if self.success else 'FAILED' - logger.info("Task {} finished with errcode {} ({})". - format(self.name, self.errcode, self.state)) + def wait(self, timeout=None): + """Waits on completion of the task or raises TimeoutExpired exception + + Status attributes of task are updated on completion. + + Parameters + ---------- + + timeout: + Time in seconds after which a TimeoutExpired exception is raised""" + + if self.dry_run: + return + + if not self._check_poll(): + return + + # Wait on the task + rc = launcher.wait(self.process, timeout) + if rc is None: + raise TimeoutExpired(self.name, timeout) + + self._set_complete() def kill(self, wait_time=60): """Kills or cancels the supplied task @@ -295,6 +332,7 @@ def __init__(self): self.top_level_dir = os.getcwd() self.manager_signal = 'none' self.default_apps = {'sim': None, 'gen': None} + self.apps = {} self.wait_time = 60 self.list_of_tasks = [] @@ -314,36 +352,53 @@ def gen_default_app(self): """Returns the default generator app""" return self.default_apps['gen'] + def get_app(self, app_name): + """Gets the app for a given app_name or raise exception""" + try: + app = self.apps[app_name] + except KeyError: + app_keys = list(self.apps.keys()) + raise ExecutorException("Application {} not found in registry".format(app_name), + "Registered applications: {}".format(app_keys)) + return app + def default_app(self, calc_type): - "Gets the default app for a given calc type." + """Gets the default app for a given calc type""" app = self.default_apps.get(calc_type) jassert(calc_type in ['sim', 'gen'], "Unrecognized calculation type", calc_type) jassert(app, "Default {} app is not set".format(calc_type)) return app - def register_calc(self, full_path, calc_type='sim', desc=None): + def register_calc(self, full_path, app_name=None, calc_type=None, desc=None): """Registers a user application to libEnsemble Parameters ---------- + app_name: String + Name to identify this application. + full_path: String The full path of the user application to be registered calc_type: String - Calculation type: Is this application part of a 'sim' - or 'gen' function + Calculation type: Set this application as the default 'sim' + or 'gen' function. desc: String, optional Description of this application """ - jassert(calc_type in self.default_apps, - "Unrecognized calculation type", calc_type) - jassert(self.default_apps[calc_type] is None, - "Default {} app already set".format(calc_type)) - self.default_apps[calc_type] = Application(full_path, calc_type, desc) + if not app_name: + app_name = os.path.split(full_path)[1] + self.apps[app_name] = Application(full_path, app_name, calc_type, desc) + + # Default sim/gen apps will be deprecated. Just use names. + if calc_type is not None: + jassert(calc_type in self.default_apps, + "Unrecognized calculation type", calc_type) + self.default_apps[calc_type] = self.apps[app_name] def manager_poll(self, comm): """ Polls for a manager signal @@ -351,6 +406,7 @@ def manager_poll(self, comm): The executor manager_signal attribute will be updated. """ + self.manager_signal = 'none' # Reset # Check for messages; disregard anything but a stop signal if not comm.mail_flag(): diff --git a/libensemble/executors/mpi_executor.py b/libensemble/executors/mpi_executor.py index f4b93796e..e3354c855 100644 --- a/libensemble/executors/mpi_executor.py +++ b/libensemble/executors/mpi_executor.py @@ -14,7 +14,7 @@ import libensemble.utils.launcher as launcher from libensemble.resources.mpi_resources import MPIResources -from libensemble.executors.executor import Executor, Task +from libensemble.executors.executor import Executor, Task, ExecutorException from libensemble.executors.mpi_runner import MPIRunner logger = logging.getLogger(__name__) @@ -29,6 +29,7 @@ class MPIExecutor(Executor): def __init__(self, auto_resources=True, allow_oversubscribe=True, central_mode=False, + zero_resource_workers=[], nodelist_env_slurm=None, nodelist_env_cobalt=None, nodelist_env_lsf=None, @@ -63,6 +64,9 @@ def __init__(self, auto_resources=True, grouped together and do not share nodes with applications. Distributed mode means workers share nodes with applications. + zero_resource_workers: list of ints, optional + List of workers that require no resources. + nodelist_env_slurm: String, optional The environment variable giving a node list in Slurm format (Default: Uses SLURM_NODELIST). Note: This is queried only if @@ -118,6 +122,7 @@ def __init__(self, auto_resources=True, self.resources = \ MPIResources(top_level_dir=self.top_level_dir, central_mode=central_mode, + zero_resource_workers=zero_resource_workers, allow_oversubscribe=allow_oversubscribe, launcher=self.mpi_runner.run_command, cores_on_node=cores_on_node, @@ -175,9 +180,9 @@ def _launch_with_retries(self, task, runline, subgroup_launch, wait_on_run): else: break - def submit(self, calc_type, num_procs=None, num_nodes=None, - ranks_per_node=None, machinefile=None, app_args=None, - stdout=None, stderr=None, stage_inout=None, + def submit(self, calc_type=None, app_name=None, num_procs=None, + num_nodes=None, ranks_per_node=None, machinefile=None, + app_args=None, stdout=None, stderr=None, stage_inout=None, hyperthreads=False, dry_run=False, wait_on_run=False, extra_args=None): """Creates a new task, and either executes or schedules execution. @@ -187,8 +192,12 @@ def submit(self, calc_type, num_procs=None, num_nodes=None, Parameters ---------- - calc_type: String + calc_type: String, optional The calculation type: 'sim' or 'gen' + Only used if app_name is not supplied. Uses default sim or gen application. + + app_name: String, optional + The application name. Must be supplied if calc_type is not. num_procs: int, optional The total number of MPI tasks on which to submit the task @@ -247,7 +256,13 @@ def submit(self, calc_type, num_procs=None, num_nodes=None, then the available resources will be divided among workers. """ - app = self.default_app(calc_type) + if app_name is not None: + app = self.get_app(app_name) + elif calc_type is not None: + app = self.default_app(calc_type) + else: + raise ExecutorException("Either app_name or calc_type must be set") + default_workdir = os.getcwd() task = Task(app, app_args, default_workdir, stdout, stderr, self.workerID) @@ -274,7 +289,7 @@ def submit(self, calc_type, num_procs=None, num_nodes=None, if dry_run: task.dry_run = True logger.info('Test (No submit) Runline: {}'.format(' '.join(runline))) - task.set_as_complete() + task._set_complete(dry_run=True) else: # Launch Task self._launch_with_retries(task, runline, sglaunch, wait_on_run) diff --git a/libensemble/gen_funcs/aposmm_localopt_support.py b/libensemble/gen_funcs/aposmm_localopt_support.py index 2727edbb6..d277550dd 100644 --- a/libensemble/gen_funcs/aposmm_localopt_support.py +++ b/libensemble/gen_funcs/aposmm_localopt_support.py @@ -5,11 +5,12 @@ __all__ = ['LocalOptInterfacer', 'run_local_nlopt', 'run_local_tao', 'run_local_dfols', 'run_local_scipy_opt', 'run_external_localopt'] +import psutil import numpy as np from libensemble.message_numbers import STOP_TAG, EVAL_GEN_TAG # Only used to simulate receiving from manager from multiprocessing import Event, Process, Queue - import libensemble.gen_funcs + optimizer_list = ['petsc', 'nlopt', 'dfols', 'scipy', 'external'] optimizers = libensemble.gen_funcs.rc.aposmm_optimizers @@ -83,8 +84,8 @@ def __init__(self, user_specs, x0, f0, grad0=None): immediately after creating the class. """ - self.parent_can_read = Event() + self.parent_can_read = Event() self.comm_queue = Queue() self.child_can_read = Event() @@ -133,6 +134,7 @@ def iterate(self, data): :param grad: A numpy array of the function's gradient. :param fvec: A numpy array of the function's component values. """ + self.parent_can_read.clear() if 'grad' in data.dtype.names: @@ -149,29 +151,23 @@ def iterate(self, data): if isinstance(x_new, ErrorMsg): raise APOSMMException(x_new.x) elif isinstance(x_new, ConvergedMsg): - self.process.join() - self.comm_queue.close() - self.comm_queue.join_thread() - self.is_running = False + self.close() else: x_new = np.atleast_2d(x_new) return x_new - def destroy(self, previous_x): - - while not isinstance(previous_x, ConvergedMsg): - self.parent_can_read.clear() - if self.grad0 is None: - self.comm_queue.put((previous_x, 0*np.ones_like(self.f0),)) - else: - self.comm_queue.put((previous_x, 0*np.ones_like(self.f0), np.zeros_like(self.grad0))) - - self.child_can_read.set() - self.parent_can_read.wait() - - previous_x = self.comm_queue.get() - assert isinstance(previous_x, ConvergedMsg) + def destroy(self): + """Recursively kill any optimizer processes still running""" + if self.process.is_alive(): + process = psutil.Process(self.process.pid) + for child in process.children(recursive=True): + child.kill() + process.kill() + self.close() + + def close(self): + """Join process and close queue""" self.process.join() self.comm_queue.close() self.comm_queue.join_thread() @@ -230,13 +226,13 @@ def run_local_nlopt(user_specs, comm_queue, x0, f0, child_can_read, parent_can_r # https://nlopt.readthedocs.io/en/latest/NLopt_Reference/#return-values opt_flag = 1 elif return_val >= 5: - print("The run started from " + str(x0) + " reached it maximum number " + print("[APOSMM] The run started from " + str(x0) + " reached its maximum number " "of function evaluations: " + str(run_max_eval) + ". No point from " "this run will be ruled as a minimum! APOSMM may start a new run " "from some point in this run.") opt_flag = 0 else: - print("NLopt returned with a negative return value, which indicates an error") + print("[APOSMM] NLopt returned with a negative return value, which indicates an error") opt_flag = 0 if user_specs.get('periodic'): @@ -274,7 +270,7 @@ def run_local_scipy_opt(user_specs, comm_queue, x0, f0, child_can_read, parent_c if res['status'] in user_specs['opt_return_codes']: opt_flag = 1 else: - print("The SciPy localopt run started from " + str(x0) + " stopped" + print("[APOSMM] The SciPy localopt run started from " + str(x0) + " stopped" " without finding a local min.\nThe 'status' of the run is " + str(res['status']) + " and the message is: \"" + res['message'] + "\".\nNo point from this run will be ruled as a minimum! APOSMM may " @@ -367,7 +363,7 @@ def run_local_dfols(user_specs, comm_queue, x0, f0, child_can_read, parent_can_r if soln.flag == soln.EXIT_SUCCESS: opt_flag = 1 else: - print("The DFO-LS run started from " + str(x0) + " stopped with an exit " + print("[APOSMM] The DFO-LS run started from " + str(x0) + " stopped with an exit " "flag of " + str(soln.flag) + ". No point from this run will be " "ruled as a minimum! APOSMM may start a new run from some point " "in this run.") @@ -446,7 +442,7 @@ def run_local_tao(user_specs, comm_queue, x0, f0, child_can_read, parent_can_rea opt_flag = 1 else: # https://www.mcs.anl.gov/petsc/petsc-current/docs/manualpages/Tao/TaoGetConvergedReason.html - print("The run started from " + str(x0) + " exited with a nonpositive reason. No point from " + print("[APOSMM] The run started from " + str(x0) + " exited with a nonpositive reason. No point from " "this run will be ruled as a minimum! APOSMM may start a new run from some point in this run.") opt_flag = 0 @@ -524,7 +520,7 @@ def tao_callback_fun_grad(tao, x, g, comm_queue, child_can_read, parent_can_read def finish_queue(x_opt, opt_flag, comm_queue, parent_can_read, user_specs): if user_specs.get('print') and opt_flag: - print('Local optimum on the [0,1]^n domain', x_opt, flush=True) + print('[APOSMM] Local optimum on the [0,1]^n domain', x_opt, flush=True) comm_queue.put(ConvergedMsg(x_opt, opt_flag)) parent_can_read.set() diff --git a/libensemble/gen_funcs/persistent_aposmm.py b/libensemble/gen_funcs/persistent_aposmm.py index 9f44c439e..510bb5909 100644 --- a/libensemble/gen_funcs/persistent_aposmm.py +++ b/libensemble/gen_funcs/persistent_aposmm.py @@ -10,7 +10,8 @@ import numpy as np from scipy.spatial.distance import cdist -from math import log, gamma, pi, sqrt +from math import log, pi, sqrt +from mpmath import gamma from libensemble.gen_funcs.aposmm_localopt_support import LocalOptInterfacer, ConvergedMsg, simulate_recv_from_manager from libensemble.message_numbers import STOP_TAG, PERSIS_STOP, FINISHED_PERSISTENT_GEN_TAG @@ -45,6 +46,7 @@ def aposmm(H, persis_info, gen_specs, libE_info): - ``'grad' [n floats]``: The gradient (if available) of the objective with respect to `x`. Note: + - If any of the above fields are desired after a libEnsemble run, name them in ``gen_specs['out']``. - If intitializing APOSMM with past function values, make sure to include @@ -57,7 +59,12 @@ def aposmm(H, persis_info, gen_specs, libE_info): - ``'lb' [n floats]``: Lower bound on search domain - ``'ub' [n floats]``: Upper bound on search domain - ``'localopt_method' [str]``: Name of an NLopt, PETSc/TAO, or SciPy method - (see 'advance_local_run' below for supported methods) + (see 'advance_local_run' below for supported methods). When using a SciPy + method, must supply ``'opt_return_codes'``, a list of integers that will + be used to determine if the x produced by the localopt method should be + ruled a local minimum. (For example, SciPy's COBYLA has a 'status' of 1 if + at an optimum, but SciPy's Nelder-Mead and BFGS have a 'status' of 0 if at + an optimum.) - ``'initial_sample_size' [int]``: Number of uniformly sampled points must be returned (non-nan value) before a local opt run is started. Can be zero if no additional sampling is desired, but if zero there must be past @@ -133,107 +140,116 @@ def aposmm(H, persis_info, gen_specs, libE_info): persis_info['old_runs']: Sequence of indices of points in finished runs """ - user_specs = gen_specs['user'] - - n, n_s, rk_const, ld, mu, nu, comm, local_H = initialize_APOSMM(H, user_specs, libE_info) - local_opters, sim_id_to_child_inds, run_order, run_pts, total_runs, fields_to_pass = initialize_children(user_specs) - - if user_specs['initial_sample_size'] != 0: - # Send our initial sample. We don't need to check that n_s is large enough: - # the alloc_func only returns when the initial sample has function values. - persis_info = add_k_sample_points_to_local_H(user_specs['initial_sample_size'], user_specs, - persis_info, n, comm, local_H, - sim_id_to_child_inds) - if not user_specs.get('standalone'): - send_mgr_worker_msg(comm, local_H[-user_specs['initial_sample_size']:][[i[0] for i in gen_specs['out']]]) - something_sent = True - else: - something_sent = False - tag = None - first_pass = True - while 1: - new_opt_inds_to_send_mgr = [] - new_inds_to_send_mgr = [] + try: + user_specs = gen_specs['user'] + n, n_s, rk_const, ld, mu, nu, comm, local_H = initialize_APOSMM(H, user_specs, libE_info) + local_opters, sim_id_to_child_inds, run_order, run_pts, total_runs, fields_to_pass = initialize_children(user_specs) + if user_specs['initial_sample_size'] != 0: + # Send our initial sample. We don't need to check that n_s is large enough: + # the alloc_func only returns when the initial sample has function values. + persis_info = add_k_sample_points_to_local_H(user_specs['initial_sample_size'], user_specs, + persis_info, n, comm, local_H, + sim_id_to_child_inds) + if not user_specs.get('standalone'): + send_mgr_worker_msg(comm, local_H[-user_specs['initial_sample_size']:][[i[0] for i in gen_specs['out']]]) + something_sent = True + else: + something_sent = False - if something_sent: - if user_specs.get('standalone'): - tag, Work, calc_in = simulate_recv_from_manager(local_H, gen_specs) - else: - tag, Work, calc_in = get_mgr_worker_msg(comm) - - if tag in [STOP_TAG, PERSIS_STOP]: - clean_up_and_stop(local_H, local_opters, run_order) - persis_info['run_order'] = run_order - break - - n_s, n_r = update_local_H_after_receiving(local_H, n, n_s, user_specs, Work, calc_in, fields_to_pass) - - for row in calc_in: - if sim_id_to_child_inds.get(row['sim_id']): - # Point came from a child local opt run - for child_idx in sim_id_to_child_inds[row['sim_id']]: - x_new = local_opters[child_idx].iterate(row[fields_to_pass]) - if isinstance(x_new, ConvergedMsg): - x_opt = x_new.x - opt_flag = x_new.opt_flag - opt_ind = update_history_optimal(x_opt, opt_flag, local_H, run_order[child_idx]) - new_opt_inds_to_send_mgr.append(opt_ind) - local_opters.pop(child_idx) - else: - add_to_local_H(local_H, x_new, user_specs, local_flag=1, on_cube=True) - new_inds_to_send_mgr.append(len(local_H)-1) - - run_order[child_idx].append(local_H[-1]['sim_id']) - run_pts[child_idx].append(x_new) - if local_H[-1]['sim_id'] in sim_id_to_child_inds: - sim_id_to_child_inds[local_H[-1]['sim_id']] += (child_idx, ) + tag = None + first_pass = True + while 1: + new_opt_inds_to_send_mgr = [] + new_inds_to_send_mgr = [] + + if something_sent: + if user_specs.get('standalone'): + tag, Work, calc_in = simulate_recv_from_manager(local_H, gen_specs) + else: + tag, Work, calc_in = get_mgr_worker_msg(comm) + + if tag in [STOP_TAG, PERSIS_STOP]: + clean_up_and_stop(local_opters) + persis_info['run_order'] = run_order + break + + if np.sum(local_H['local_min']) >= user_specs.get('stop_after_this_many_minima', np.inf): + # This break happens here so the manager can be informed about the last minima. + break + + n_s, n_r = update_local_H_after_receiving(local_H, n, n_s, user_specs, Work, calc_in, fields_to_pass) + + for row in calc_in: + if sim_id_to_child_inds.get(row['sim_id']): + # Point came from a child local opt run + for child_idx in sim_id_to_child_inds[row['sim_id']]: + x_new = local_opters[child_idx].iterate(row[fields_to_pass]) + if isinstance(x_new, ConvergedMsg): + x_opt = x_new.x + opt_flag = x_new.opt_flag + opt_ind = update_history_optimal(x_opt, opt_flag, local_H, run_order[child_idx]) + new_opt_inds_to_send_mgr.append(opt_ind) + local_opters.pop(child_idx) else: - sim_id_to_child_inds[local_H[-1]['sim_id']] = (child_idx, ) + add_to_local_H(local_H, x_new, user_specs, local_flag=1, on_cube=True) + new_inds_to_send_mgr.append(len(local_H)-1) - starting_inds = decide_where_to_start_localopt(local_H, n, n_s, rk_const, ld, mu, nu) + run_order[child_idx].append(local_H[-1]['sim_id']) + run_pts[child_idx].append(x_new) + if local_H[-1]['sim_id'] in sim_id_to_child_inds: + sim_id_to_child_inds[local_H[-1]['sim_id']] += (child_idx, ) + else: + sim_id_to_child_inds[local_H[-1]['sim_id']] = (child_idx, ) - for ind in starting_inds: - if len([p for p in local_opters.values() if p.is_running]) < user_specs.get('max_active_runs', np.inf): - local_H['started_run'][ind] = 1 + starting_inds = decide_where_to_start_localopt(local_H, n, n_s, rk_const, ld, mu, nu) - # Initialize a local opt run - local_opter = LocalOptInterfacer(user_specs, local_H[ind]['x_on_cube'], - local_H[ind]['f'] if 'f' in fields_to_pass else local_H[ind]['fvec'], - local_H[ind]['grad'] if 'grad' in fields_to_pass else None) + for ind in starting_inds: + if len([p for p in local_opters.values() if p.is_running]) < user_specs.get('max_active_runs', np.inf): + local_H['started_run'][ind] = 1 - local_opters[total_runs] = local_opter + # Initialize a local opt run + local_opter = LocalOptInterfacer(user_specs, local_H[ind]['x_on_cube'], + local_H[ind]['f'] if 'f' in fields_to_pass else local_H[ind]['fvec'], + local_H[ind]['grad'] if 'grad' in fields_to_pass else None) - x_new = local_opter.iterate(local_H[ind][fields_to_pass]) # Assuming the second point can't be ruled optimal + local_opters[total_runs] = local_opter - add_to_local_H(local_H, x_new, user_specs, local_flag=1, on_cube=True) - new_inds_to_send_mgr.append(len(local_H)-1) + x_new = local_opter.iterate(local_H[ind][fields_to_pass]) # Assuming the second point can't be ruled optimal - run_order[total_runs] = [ind, local_H[-1]['sim_id']] - run_pts[total_runs] = [local_H['x_on_cube'], x_new] + add_to_local_H(local_H, x_new, user_specs, local_flag=1, on_cube=True) + new_inds_to_send_mgr.append(len(local_H)-1) - if local_H[-1]['sim_id'] in sim_id_to_child_inds: - sim_id_to_child_inds[local_H[-1]['sim_id']] += (total_runs, ) - else: - sim_id_to_child_inds[local_H[-1]['sim_id']] = (total_runs, ) + run_order[total_runs] = [ind, local_H[-1]['sim_id']] + run_pts[total_runs] = [local_H['x_on_cube'], x_new] - total_runs += 1 + if local_H[-1]['sim_id'] in sim_id_to_child_inds: + sim_id_to_child_inds[local_H[-1]['sim_id']] += (total_runs, ) + else: + sim_id_to_child_inds[local_H[-1]['sim_id']] = (total_runs, ) - if first_pass: - num_samples_needed = persis_info['nworkers'] - 1 - len(new_inds_to_send_mgr) - first_pass = False - else: - num_samples_needed = n_r-len(new_inds_to_send_mgr) + total_runs += 1 + + if first_pass: + num_samples_needed = persis_info['nworkers'] - 1 - len(new_inds_to_send_mgr) + first_pass = False + else: + num_samples_needed = n_r-len(new_inds_to_send_mgr) - if num_samples_needed > 0: - persis_info = add_k_sample_points_to_local_H(num_samples_needed, user_specs, persis_info, n, comm, local_H, sim_id_to_child_inds) - new_inds_to_send_mgr = new_inds_to_send_mgr + list(range(len(local_H)-num_samples_needed, len(local_H))) + if num_samples_needed > 0: + persis_info = add_k_sample_points_to_local_H(num_samples_needed, user_specs, persis_info, n, comm, local_H, sim_id_to_child_inds) + new_inds_to_send_mgr = new_inds_to_send_mgr + list(range(len(local_H)-num_samples_needed, len(local_H))) - if not user_specs.get('standalone'): - send_mgr_worker_msg(comm, local_H[new_inds_to_send_mgr + new_opt_inds_to_send_mgr][[i[0] for i in gen_specs['out']]]) - something_sent = True + if not user_specs.get('standalone'): + send_mgr_worker_msg(comm, local_H[new_inds_to_send_mgr + new_opt_inds_to_send_mgr][[i[0] for i in gen_specs['out']]]) + something_sent = True - return local_H, persis_info, FINISHED_PERSISTENT_GEN_TAG + return local_H, persis_info, FINISHED_PERSISTENT_GEN_TAG + finally: + try: + clean_up_and_stop(local_opters) + except NameError: + pass def update_local_H_after_receiving(local_H, n, n_s, user_specs, Work, calc_in, fields_to_pass): @@ -397,19 +413,19 @@ def update_history_optimal(x_opt, opt_flag, H, run_inds): # assert dists[ind] <= tol_x1, "Closest point to x_opt not within {}?".format(tol_x1) if dists[ind] > tol_x1: - print("Dist from reported x_opt to closest evaluated point is: " + str(dists[ind]) + "\n" + - "Check that the local optimizer is working correctly\n", x_opt, run_inds, flush=True) + print("[APOSMM] Dist from reported x_opt to closest evaluated point is: " + str(dists[ind]) + "\n" + + "[APOSMM] Check that the local optimizer is working correctly\n", x_opt, run_inds, flush=True) tol_x2 = 1e-8 failsafe = np.logical_and(H['f'][run_inds] < H['f'][opt_ind], dists < tol_x2) if opt_flag: if np.any(failsafe): - print("This run has {} point(s) with smaller 'f' value within {} of " + print("[APOSMM] This run has {} point(s) with smaller 'f' value within {} of " "the point ruled to be the run minimum. \nMarking all as being " "a 'local_min' to prevent APOSMM from starting another run " "immediately from these points.".format(sum(failsafe), tol_x2)) - print("Sim_ids to be marked optimal: ", opt_ind, run_inds[failsafe]) - print("Check that the local optimizer is working correctly", flush=True) + print("[APOSMM] Sim_ids to be marked optimal: ", opt_ind, run_inds[failsafe]) + print("[APOSMM] Check that the local optimizer is working correctly", flush=True) H['local_min'][run_inds[failsafe]] = 1 H['local_min'][opt_ind] = 1 @@ -611,7 +627,7 @@ def initialize_APOSMM(H, user_specs, libE_info): over_written_fields = ['dist_to_unit_bounds', 'dist_to_better_l', 'dist_to_better_s', 'ind_of_better_l', 'ind_of_better_s'] if any([i in H.dtype.names for i in over_written_fields]): - print("\n persistent_aposmm ignores any given values in these fields: " + str(over_written_fields) + "\n") + print("\n[APOSMM] persistent_aposmm ignores any given values in these fields: " + str(over_written_fields) + "\n") initialize_dists_and_inds(local_H, len(H)) @@ -666,14 +682,15 @@ def add_k_sample_points_to_local_H(k, user_specs, persis_info, n, comm, local_H, return persis_info -def clean_up_and_stop(local_H, local_opters, run_order): +# def clean_up_and_stop(local_H, local_opters): +def clean_up_and_stop(local_opters): # FIXME: This has to be a clean exit. # print('[Parent]: The optimal points and values are:\n', # local_H[np.where(local_H['local_min'])][['x', 'f']], flush=True) for i, p in local_opters.items(): - p.destroy(local_H['x_on_cube'][run_order[i][-1]]) + p.destroy() # def display_exception(e): diff --git a/libensemble/gen_funcs/persistent_deap_nsga2.py b/libensemble/gen_funcs/persistent_deap_nsga2.py index a71ee20fb..d448b986b 100644 --- a/libensemble/gen_funcs/persistent_deap_nsga2.py +++ b/libensemble/gen_funcs/persistent_deap_nsga2.py @@ -16,11 +16,8 @@ from libensemble.tools.gen_support import sendrecv_mgr_worker_msg -def uniform(low, up, size=None): - try: - return [np.random.uniform(a, b) for a, b in zip(low, up)] - except TypeError: - return [np.random.uniform(a, b) for a, b in zip([low] * size, [up] * size)] +def uniform(low, up): + return [np.random.uniform(a, b) for a, b in zip(low, up)] def nsga2_toolbox(gen_specs): @@ -33,13 +30,22 @@ def nsga2_toolbox(gen_specs): inp = gen_specs['user']['indpb'] lb = gen_specs['user']['lb'] ub = gen_specs['user']['ub'] - dim = gen_specs['user']['indiv_size'] + + try: + del creator.MyFitness + except Exception: + pass + + try: + del creator.Individual + except Exception: + pass creator.create('MyFitness', base.Fitness, weights=w) creator.create('Individual', array.array, typecode='d', fitness=creator.MyFitness) toolbox = base.Toolbox() - toolbox.register('attr_float', uniform, lb, ub, dim) + toolbox.register('attr_float', uniform, lb, ub) toolbox.register('individual', tools.initIterate, creator.Individual, toolbox.attr_float) toolbox.register('population', tools.initRepeat, list, toolbox.individual) @@ -68,11 +74,8 @@ def evaluate_pop(g, deap_object, Out, comm): for i, ind in enumerate(deap_object): # Attaching fitness values from sim to population # i.e. replacing values with those generated by the sim - - if isinstance(calc_in['fitness_values'][i], tuple): - ind.fitness.values = calc_in['fitness_values'][i] - else: - ind.fitness.values = (calc_in['fitness_values'][i],) + fvals = calc_in['fitness_values'][i] + ind.fitness.values = [fvals] if isinstance(fvals, float) else list(fvals) return deap_object, tag @@ -82,27 +85,36 @@ def deap_nsga2(H, persis_info, gen_specs, libE_info): An implementation of the NSGA2 evolutionary algorithm. ''' # Check to make sure boundaries are list, not array - if isinstance(gen_specs['user']['lb'], list): - if isinstance(gen_specs['user']['ub'], list): - pass - else: - print('Lower or Upper bound is not a list') - print('This will break DEAP crossover function') - assert isinstance(gen_specs['user']['lb'], list), "lb is wrong type" - assert isinstance(gen_specs['user']['ub'], list), "ub is wrong type" + assert isinstance(gen_specs['user']['lb'], list), "lb is wrong type, must be a list!" + assert isinstance(gen_specs['user']['ub'], list), "ub is wrong type, must be a list!" # Initialize NSGA2 DEAP toolbox toolbox = nsga2_toolbox(gen_specs) - - g = 0 # generation count + pop_size = gen_specs['user']['pop_size'] # CXPB is the probability with which two individuals are crossed - MU, CXPB = gen_specs['user']['pop_size'], gen_specs['user']['cxpb'] - pop = toolbox.population(n=MU) # MU is Population size ( # of individuals) + MU, CXPB = pop_size, gen_specs['user']['cxpb'] comm = libE_info['comm'] - - # Running fitness calc for first generation - Out = np.zeros(gen_specs['user']['pop_size'], dtype=gen_specs['out']) - pop, tag = evaluate_pop(g, pop, Out, comm) + pop = toolbox.population(n=MU) # MU is Population size ( # of individuals) + Out = np.zeros(pop_size, dtype=gen_specs['out']) + + if len(H): + tag = None + g = max(H['generation']) + individuals = H['individual'][-pop_size:] + fvals = H['fitness_values'][-pop_size:] + print("Loading initial collection of points as generation ", g, '.') + + for i, ind in enumerate(pop): + # Fill in first pop and output with provided points + ind[:] = array.array('d', individuals[i]) + ind.fitness.values = [fvals[i]] if isinstance(fvals[i], float) else list(fvals[i]) + Out['individual'][i] = individuals[i] + Out['generation'][i] = g + else: + print('No initial sample provided, starting from scratch.') + g = 0 # generation count + # Running fitness calc for first generation + pop, tag = evaluate_pop(g, pop, Out, comm) # This is just to assign the crowding distance to the individuals # no actual selection is done diff --git a/libensemble/gen_funcs/vtmop/vtmop_initializer.f90 b/libensemble/gen_funcs/vtmop/vtmop_initializer.f90 index ab41dc41b..217d97fde 100644 --- a/libensemble/gen_funcs/vtmop/vtmop_initializer.f90 +++ b/libensemble/gen_funcs/vtmop/vtmop_initializer.f90 @@ -40,7 +40,7 @@ PROGRAM VTMOP_INITIALIZER CLOSE(12) ! Initialize the VTMOP status object. -CALL VTMOP_INIT( VTMOP, D, P, LB, UB, IERR, TRUST_RAD=TRUST_RAD, ICHKPT=1 ) +CALL VTMOP_INIT( VTMOP, D, P, LB, UB, IERR, TRUST_RADF=TRUST_RAD, ICHKPT=1 ) IF (IERR .NE. 0) THEN WRITE(ERROR_UNIT, "(A,I4)") & "An error occurred while initializing. Error code: ", IERR diff --git a/libensemble/libE.py b/libensemble/libE.py index daee5b55b..ef1f990e7 100644 --- a/libensemble/libE.py +++ b/libensemble/libE.py @@ -309,6 +309,15 @@ def libE_local(sim_specs, gen_specs, exit_criteria, hist = History(alloc_specs, sim_specs, gen_specs, exit_criteria, H0) + # On Python 3.8 on macOS, the default start method for new processes was + # switched to 'spawn' by default due to 'fork' potentially causing crashes. + # These crashes haven't yet been observed with libE, but with 'spawn' runs, + # warnings about leaked semaphore objects are displayed instead. + # The next several statements enforce 'fork' on macOS (Python 3.8) + if os.uname().sysname == 'Darwin': + from multiprocessing import set_start_method + set_start_method('fork', force=True) + # Launch worker team and set up logger wcomms = start_proc_team(nworkers, sim_specs, gen_specs, libE_specs) diff --git a/libensemble/libE_manager.py b/libensemble/libE_manager.py index 0d9bed2a5..452c37798 100644 --- a/libensemble/libE_manager.py +++ b/libensemble/libE_manager.py @@ -20,11 +20,14 @@ MAN_SIGNAL_FINISH, MAN_SIGNAL_KILL from libensemble.comms.comms import CommFinishedException from libensemble.libE_worker import WorkerErrMsg -from libensemble.tools.tools import _USER_SIM_DIR_WARNING -from libensemble.tools.fields_keys import libE_spec_calc_dir_keys +from libensemble.tools.tools import _USER_CALC_DIR_WARNING +from libensemble.tools.fields_keys import libE_spec_calc_dir_combined import cProfile import pstats +if tuple(np.__version__.split('.')) >= ('1', '15'): + from numpy.lib.recfunctions import repack_fields + logger = logging.getLogger(__name__) # For debug messages - uncomment # logger.setLevel(logging.DEBUG) @@ -145,7 +148,7 @@ def __init__(self, hist, libE_specs, alloc_specs, (1, 'gen_max', self.term_test_gen_max), (1, 'stop_val', self.term_test_stop_val)] - if any([setting in self.libE_specs for setting in libE_spec_calc_dir_keys]): + if any([setting in self.libE_specs for setting in libE_spec_calc_dir_combined]): self.check_ensemble_dir(libE_specs) if libE_specs.get('ensemble_copy_back', False): Manager.make_copyback_dir(libE_specs) @@ -165,7 +168,7 @@ def check_ensemble_dir(self, libE_specs): except FileNotFoundError: # Ensemble dir doesn't exist. pass except OSError as e: # Ensemble dir exists and isn't empty. - logger.manager_warning(_USER_SIM_DIR_WARNING.format(prefix)) + logger.manager_warning(_USER_CALC_DIR_WARNING.format(prefix)) self._kill_workers() raise ManagerException('Manager errored on initialization', 'Ensemble directory already existed and wasn\'t empty.', e) @@ -258,7 +261,10 @@ def _send_work_order(self, Work, w): self.wcomms[w-1].send(Work['tag'], Work) work_rows = Work['libE_info']['H_rows'] if len(work_rows): - self.wcomms[w-1].send(0, self.hist.H[Work['H_fields']][work_rows]) + if 'repack_fields' in dir(): + self.wcomms[w-1].send(0, repack_fields(self.hist.H[Work['H_fields']][work_rows])) + else: + self.wcomms[w-1].send(0, self.hist.H[Work['H_fields']][work_rows]) def _update_state_on_alloc(self, Work, w): """Updates a workers' active/idle status following an allocation order""" diff --git a/libensemble/libE_worker.py b/libensemble/libE_worker.py index 0f327e837..2d48ef597 100644 --- a/libensemble/libE_worker.py +++ b/libensemble/libE_worker.py @@ -7,6 +7,7 @@ import logging import os import shutil +import re import logging.handlers from itertools import count, groupby from operator import itemgetter @@ -19,7 +20,7 @@ UNSET_TAG, STOP_TAG, PERSIS_STOP, CALC_EXCEPTION from libensemble.message_numbers import MAN_SIGNAL_FINISH from libensemble.message_numbers import calc_type_strings, calc_status_strings -from libensemble.tools.fields_keys import libE_spec_calc_dir_keys +from libensemble.tools.fields_keys import libE_spec_sim_dir_keys, libE_spec_gen_dir_keys from libensemble.utils.loc_stack import LocationStack from libensemble.utils.timer import Timer @@ -148,24 +149,30 @@ def __init__(self, comm, dtypes, workerID, sim_specs, gen_specs, libE_specs): def _make_calc_dir(libE_specs, workerID, H_rows, calc_str, locs): "Create calc dirs and intermediate dirs, copy inputs, based on libE_specs" - sim_input_dir = libE_specs.get('sim_input_dir', '').rstrip('/') + if calc_str == 'sim': + calc_input_dir = libE_specs.get('sim_input_dir', '').rstrip('/') + do_calc_dirs = libE_specs.get('sim_dirs_make', True) + copy_files = libE_specs.get('sim_dir_copy_files', []) + symlink_files = libE_specs.get('sim_dir_symlink_files', []) + else: # calc_str is 'gen' + calc_input_dir = libE_specs.get('gen_input_dir', '').rstrip('/') + do_calc_dirs = libE_specs.get('gen_dirs_make', True) + copy_files = libE_specs.get('gen_dir_copy_files', []) + symlink_files = libE_specs.get('gen_dir_symlink_files', []) - do_sim_dirs = libE_specs.get('sim_dirs_make', True) prefix = libE_specs.get('ensemble_dir_path', './ensemble') - copy_files = libE_specs.get('sim_dir_copy_files', []) - symlink_files = libE_specs.get('sim_dir_symlink_files', []) do_work_dirs = libE_specs.get('use_worker_dirs', False) - # If using sim_input_dir, set of files to copy is contents of provided dir - if sim_input_dir: - copy_files = set(copy_files + [os.path.join(sim_input_dir, i) for i in os.listdir(sim_input_dir)]) + # If using calc_input_dir, set of files to copy is contents of provided dir + if calc_input_dir: + copy_files = set(copy_files + [os.path.join(calc_input_dir, i) for i in os.listdir(calc_input_dir)]) # If identical paths to copy and symlink, remove those paths from symlink_files if len(symlink_files): symlink_files = [i for i in symlink_files if i not in copy_files] # Cases where individual sim_dirs not created. - if not do_sim_dirs: + if not do_calc_dirs: if do_work_dirs: # Each worker does work in worker dirs key = workerID dir = "worker" + str(workerID) @@ -257,41 +264,71 @@ def _extract_H_ranges(Work): return '_'.join(ranges) def _copy_back(self): - """ Cleanup indication file & copy output to init dir, if specified""" + """Copy back all ensemble dir contents to launch location""" if os.path.isdir(self.prefix) and self.libE_specs.get('ensemble_copy_back', False): + no_calc_dirs = not self.libE_specs.get('sim_dirs_make', True) or \ + not self.libE_specs.get('gen_dirs_make', True) + ensemble_dir_path = self.libE_specs.get('ensemble_dir_path', './ensemble') copybackdir = os.path.basename(ensemble_dir_path) + if os.path.relpath(ensemble_dir_path) == os.path.relpath(copybackdir): copybackdir += '_back' + for dir in self.loc_stack.dirs.values(): - try: - shutil.copytree(dir, os.path.join(copybackdir, os.path.basename(dir)), symlinks=True) - if os.path.basename(dir).startswith('worker'): - break # Worker dir (with all sim_dirs) copied. - except FileExistsError: - if not self.libE_specs.get('sim_dirs_make', True): + dest_path = os.path.join(copybackdir, os.path.basename(dir)) + if dir == self.prefix: # occurs when no_calc_dirs is True + continue # otherwise, entire ensemble dir copied into copyback dir + + shutil.copytree(dir, dest_path, symlinks=True) + if os.path.basename(dir).startswith('worker'): + return # Worker dir (with all contents) has been copied. + + # If not using calc dirs, likely miscellaneous files to copy back + if no_calc_dirs: + p = re.compile(r"((^sim)|(^gen))\d+_worker\d+") + for file in [i for i in os.listdir(self.prefix) if not p.match(i)]: # each non-calc_dir file + source_path = os.path.join(self.prefix, file) + dest_path = os.path.join(copybackdir, file) + try: + if os.path.isdir(source_path): + shutil.copytree(source_path, dest_path, symlinks=True) + else: + shutil.copy(source_path, dest_path, follow_symlinks=False) + except FileExistsError: + continue + except shutil.SameFileError: # creating an identical symlink continue - else: - raise def _determine_dir_then_calc(self, Work, calc_type, calc_in, calc): - "Determines choice for sim_dir structure, then performs calculation." + "Determines choice for calc_dir structure, then performs calculation." if not self.loc_stack: self.loc_stack = LocationStack() - H_rows = Worker._extract_H_ranges(Work) + if calc_type == EVAL_SIM_TAG: + H_rows = Worker._extract_H_ranges(Work) + else: + H_rows = str(self.calc_iter[calc_type]) + calc_str = calc_type_strings[calc_type] - if any([setting in self.libE_specs for setting in libE_spec_calc_dir_keys]): - calc_dir = Worker._make_calc_dir(self.libE_specs, self.workerID, - H_rows, calc_str, self.loc_stack) + calc_dir = Worker._make_calc_dir(self.libE_specs, self.workerID, + H_rows, calc_str, self.loc_stack) + + with self.loc_stack.loc(calc_dir): # Switching to calc_dir + return calc(calc_in, Work['persis_info'], Work['libE_info']) - with self.loc_stack.loc(calc_dir): # Switching to calc_dir - return calc(calc_in, Work['persis_info'], Work['libE_info']) + def _use_calc_dirs(self, type): + "Determines calc_dirs enabling for each calc type" + + if type == EVAL_SIM_TAG: + dir_type_keys = libE_spec_sim_dir_keys + else: + dir_type_keys = libE_spec_gen_dir_keys - return calc(calc_in, Work['persis_info'], Work['libE_info']) + return any([setting in self.libE_specs for setting in dir_type_keys]) def _handle_calc(self, Work, calc_in): """Runs a calculation on this worker object. @@ -322,7 +359,7 @@ def _handle_calc(self, Work, calc_in): with timer: logger.debug("Calling calc {}".format(calc_type)) - if calc_type == EVAL_SIM_TAG: + if self._use_calc_dirs(calc_type): out = self._determine_dir_then_calc(Work, calc_type, calc_in, calc) else: out = calc(calc_in, Work['persis_info'], Work['libE_info']) diff --git a/libensemble/resources/env_resources.py b/libensemble/resources/env_resources.py index 333e114eb..22608a5a0 100644 --- a/libensemble/resources/env_resources.py +++ b/libensemble/resources/env_resources.py @@ -23,8 +23,8 @@ class EnvResources: These are set on initialization. - :ivar dict nodelists: Environment variable names to query for nodelists by schedular - :ivar dict ndlist_funcs: Functions to extract nodelists from environment by schedular + :ivar dict nodelists: Environment variable names to query for nodelists by scheduler + :ivar dict ndlist_funcs: Functions to extract nodelists from environment by scheduler """ default_nodelist_env_slurm = 'SLURM_NODELIST' @@ -63,7 +63,7 @@ def __init__(self, Note: This is queried only if a node_list file is not provided and auto_resources=True. """ - self.schedular = None + self.scheduler = None self.nodelists = {} self.nodelists['Slurm'] = nodelist_env_slurm or EnvResources.default_nodelist_env_slurm self.nodelists['Cobalt'] = nodelist_env_cobalt or EnvResources.default_nodelist_env_cobalt @@ -76,23 +76,41 @@ def __init__(self, self.ndlist_funcs['LSF'] = EnvResources.get_lsf_nodelist self.ndlist_funcs['LSF_shortform'] = EnvResources.get_lsf_nodelist_frm_shortform - def get_nodelist(self): - """Returns nodelist from environment or an empty list""" for env, env_var in self.nodelists.items(): if os.environ.get(env_var): - self.schedular = env - logger.debug("{0} env found - getting nodelist from {0}".format(env)) - get_list_func = self.ndlist_funcs[env] - global_nodelist = get_list_func(env_var) - return global_nodelist + self.scheduler = env + break + + def get_nodelist(self): + """Returns nodelist from environment or an empty list""" + if self.scheduler: + env = self.scheduler + env_var = self.nodelists[env] + logger.debug("{} env found - getting nodelist from {}".format(env, env_var)) + get_list_func = self.ndlist_funcs[env] + global_nodelist = get_list_func(env_var) + return global_nodelist return [] - def abbrev_nodenames(self, node_list): + @staticmethod + def abbrev_nodenames(node_list, prefix=None): + """Returns nodelist with only string upto first dot""" + newlist = [s.split(".", 1)[0] for s in node_list] + return newlist + + @staticmethod + def cobalt_abbrev_nodenames(node_list, prefix='nid'): + """Returns nodelist with prefix and leading zeros stripped""" + newlist = [s.lstrip(prefix) for s in node_list] + newlist = [s.lstrip('0') for s in newlist] + return newlist + + def shortnames(self, node_list): """Returns nodelist with entries in abbreviated form""" - if self.schedular == 'Slurm': - return EnvResources.slurm_abbrev_nodenames(node_list) - if self.schedular == 'Cobalt': + if self.scheduler == 'Cobalt': return EnvResources.cobalt_abbrev_nodenames(node_list) + elif self.scheduler is not None: + return EnvResources.abbrev_nodenames(node_list) return node_list @staticmethod @@ -156,19 +174,6 @@ def get_cobalt_nodelist(node_list_env): nidlst.append(str(nid)) return sorted(nidlst, key=int) - @staticmethod - def slurm_abbrev_nodenames(node_list, prefix=None): - """Returns nodelist with only string upto first dot""" - newlist = [s.split(".", 1)[0] for s in node_list] - return newlist - - @staticmethod - def cobalt_abbrev_nodenames(node_list, prefix='nid'): - """Returns nodelist with prefix and leading zeros stripped""" - newlist = [s.lstrip(prefix) for s in node_list] - newlist = [s.lstrip('0') for s in newlist] - return newlist - @staticmethod def get_lsf_nodelist(node_list_env): """Gets global libEnsemble nodelist from the LSF environment""" diff --git a/libensemble/resources/node_resources.py b/libensemble/resources/node_resources.py index ba95149a6..e0a116682 100644 --- a/libensemble/resources/node_resources.py +++ b/libensemble/resources/node_resources.py @@ -4,6 +4,7 @@ """ import os +import psutil import logging import collections @@ -12,36 +13,6 @@ REMOTE_LAUNCH_LIST = ['aprun', 'jsrun', 'srun'] # Move to feature of mpi_runner -def _open_binary(fname, **kwargs): - return open(fname, "rb", **kwargs) - - -def _cpu_count_physical(): - """Returns the number of physical cores on the node""" - mapping = {} - current_info = {} - # macOS method for physical cpus - if os.uname().sysname == 'Darwin': - return int(os.popen('sysctl -n hw.physicalcpu').read().strip()) - else: - with _open_binary('/proc/cpuinfo') as f: - for line in f: - line = line.strip().lower() - if not line: - # new section - if (b'physical id' in current_info and - b'cpu cores' in current_info): - mapping[current_info[b'physical id']] = current_info[b'cpu cores'] - current_info = {} - else: - if (line.startswith(b'physical id') or - line.startswith(b'cpu cores')): - key, value = line.split(b'\t:', 1) - current_info[key] = int(value) - - return sum(mapping.values()) or None - - def get_cpu_cores(hyperthreads=False): """Returns the number of cores on the node. @@ -51,23 +22,7 @@ def get_cpu_cores(hyperthreads=False): Note: This returns cores available on the current node. It will not work for systems of multiple node types """ - try: - import psutil - ranks_per_node = psutil.cpu_count(logical=hyperthreads) - except ImportError: - # logger - if hyperthreads: - import multiprocessing - ranks_per_node = multiprocessing.cpu_count() - else: - try: - ranks_per_node = _cpu_count_physical() - except Exception as e: - logger.warning("Could not detect physical cores - Logical cores (with hyperthreads) " - "returned - " "specify ranks_per_node to override. Exception {}".format(e)) - import multiprocessing - ranks_per_node = multiprocessing.cpu_count() - return ranks_per_node # This is ranks available per node + return psutil.cpu_count(logical=hyperthreads) # This is ranks available per node def _get_local_cpu_resources(): diff --git a/libensemble/resources/resources.py b/libensemble/resources/resources.py index 5882a1421..249da5220 100644 --- a/libensemble/resources/resources.py +++ b/libensemble/resources/resources.py @@ -43,6 +43,7 @@ class Resources: def __init__(self, top_level_dir=None, central_mode=False, + zero_resource_workers=[], allow_oversubscribe=False, launcher=None, cores_on_node=None, @@ -69,6 +70,9 @@ def __init__(self, top_level_dir=None, do not share nodes with applications. Distributed mode means Workers share nodes with applications. + zero_resource_workers: list of ints, optional + List of workers that require no resources. + allow_oversubscribe: boolean, optional If false, then resources will raise an error if task process counts exceed the CPUs available to the worker, as detected by @@ -119,15 +123,15 @@ def __init__(self, top_level_dir=None, nodelist_env_lsf_shortform=nodelist_env_lsf_shortform) # This is global nodelist avail to workers - may change to global_worker_nodelist + self.local_host = self.env_resources.shortnames([socket.gethostname()])[0] if node_file is None: node_file = Resources.DEFAULT_NODEFILE self.global_nodelist = Resources.get_global_nodelist(node_file=node_file, rundir=self.top_level_dir, env_resources=self.env_resources) - self.launcher = launcher remote_detect = False - if socket.gethostname() not in self.global_nodelist: + if self.local_host not in self.global_nodelist: remote_detect = True if not cores_on_node: @@ -139,13 +143,14 @@ def __init__(self, top_level_dir=None, self.logical_cores_avail_per_node = cores_on_node[1] self.libE_nodes = None self.worker_resources = None + self.zero_resource_workers = zero_resource_workers def add_comm_info(self, libE_nodes): """Adds comms-specific information to resources Removes libEnsemble nodes from nodelist if in central_mode. """ - self.libE_nodes = self.env_resources.abbrev_nodenames(libE_nodes) + self.libE_nodes = self.env_resources.shortnames(libE_nodes) libE_nodes_in_list = list(filter(lambda x: x in self.libE_nodes, self.global_nodelist)) if libE_nodes_in_list: if self.central_mode and len(self.global_nodelist) > 1: @@ -232,6 +237,8 @@ def get_global_nodelist(node_file=DEFAULT_NODEFILE, with open(node_filepath, 'r') as f: for line in f: global_nodelist.append(line.rstrip()) + if env_resources: + global_nodelist = env_resources.shortnames(global_nodelist) else: logger.debug("No node_file found - searching for nodelist in environment") if env_resources: @@ -240,7 +247,7 @@ def get_global_nodelist(node_file=DEFAULT_NODEFILE, if not global_nodelist: # Assume a standalone machine logger.info("Can not find nodelist from environment. Assuming standalone") - global_nodelist.append(socket.gethostname()) + global_nodelist.append(env_resources.shortnames([socket.gethostname()])[0]) if global_nodelist: return global_nodelist @@ -294,6 +301,19 @@ def get_workers_on_a_node(num_workers, resources): workers_per_node = num_workers//num_nodes + (num_workers % num_nodes > 0) return workers_per_node + @staticmethod + def map_workerid_to_index(num_workers, workerID, zero_resource_list): + """Map WorkerID to index into a nodelist""" + index = workerID - 1 + if zero_resource_list: + for i in range(1, num_workers+1): + if i in zero_resource_list: + index -= 1 + if index < i: + return index + raise ResourcesException("Error mapping workerID {} to nodelist index {}".format(workerID, index)) + return index + @staticmethod def get_local_nodelist(num_workers, workerID, resources): """Returns the list of nodes available to the current worker @@ -304,37 +324,42 @@ def get_local_nodelist(num_workers, workerID, resources): global_nodelist = resources.global_nodelist num_nodes = len(global_nodelist) + zero_resource_list = resources.zero_resource_workers + num_workers_2assign2 = num_workers - len(zero_resource_list) # Check if current host in nodelist - if it is then in distributed mode. - local_host = socket.gethostname() - distrib_mode = local_host in global_nodelist + distrib_mode = resources.local_host in global_nodelist # If multiple workers per node - create global node_list with N duplicates (for N workers per node) - sub_node_workers = (num_workers >= num_nodes) + sub_node_workers = (num_workers_2assign2 >= num_nodes) if sub_node_workers: - workers_per_node = num_workers//num_nodes + workers_per_node = num_workers_2assign2//num_nodes dup_list = itertools.chain.from_iterable(itertools.repeat(x, workers_per_node) for x in global_nodelist) global_nodelist = list(dup_list) # Currently require even split for distrib mode - to match machinefile - throw away remainder if distrib_mode and not sub_node_workers: - # Could just read in the libe machinefile and use that - but this should match - # Alt. create machinefile/host-list with same algorithm as best_split - future soln. - nodes_per_worker, remainder = divmod(num_nodes, num_workers) + nodes_per_worker, remainder = divmod(num_nodes, num_workers_2assign2) if remainder != 0: # Worker node may not be at head of list after truncation - should perhaps be warning or enforced logger.warning("Nodes to workers not evenly distributed. Wasted nodes. " - "{} workers and {} nodes".format(num_workers, num_nodes)) + "{} workers and {} nodes".format(num_workers_2assign2, num_nodes)) num_nodes = num_nodes - remainder global_nodelist = global_nodelist[0:num_nodes] # Divide global list between workers - split_list = list(Resources.best_split(global_nodelist, num_workers)) + split_list = list(Resources.best_split(global_nodelist, num_workers_2assign2)) logger.debug("split_list is {}".format(split_list)) if workerID is None: raise ResourcesException("Worker has no workerID - aborting") - local_nodelist = split_list[workerID - 1] - logger.debug("local_nodelist is {}".format(local_nodelist)) + if workerID in zero_resource_list: + local_nodelist = [] + logger.debug("Worker is a zero-resource worker") + else: + index = WorkerResources.map_workerid_to_index(num_workers, workerID, zero_resource_list) + local_nodelist = split_list[index] + logger.debug("Worker's local_nodelist is {}".format(local_nodelist)) + return local_nodelist diff --git a/libensemble/sim_funcs/executor_hworld.py b/libensemble/sim_funcs/executor_hworld.py index 08ec89adf..079d3eb21 100644 --- a/libensemble/sim_funcs/executor_hworld.py +++ b/libensemble/sim_funcs/executor_hworld.py @@ -71,26 +71,57 @@ def executor_hworld(H, persis_info, sim_specs, libE_info): cores = sim_specs['user']['cores'] comm = libE_info['comm'] + use_balsam = 'balsam_test' in sim_specs['user'] + args_for_sim = 'sleep 1' # pref send this in X as a sim_in from calling script global sim_count sim_count += 1 timeout = 6.0 + wait = False + launch_shc = False if sim_count == 1: args_for_sim = 'sleep 1' # Should finish elif sim_count == 2: args_for_sim = 'sleep 1 Error' # Worker kill on error - elif sim_count == 3: + if sim_count == 3: + wait = True + args_for_sim = 'sleep 1' # Should finish + launch_shc = True + elif sim_count == 4: args_for_sim = 'sleep 3' # Worker kill on timeout timeout = 1.0 - elif sim_count == 4: - args_for_sim = 'sleep 1 Fail' # Manager kill - if signal received else completes elif sim_count == 5: - args_for_sim = 'sleep 18' # Manager kill - if signal received else completes - timeout = 20.0 + args_for_sim = 'sleep 1 Fail' # Manager kill - if signal received else completes + elif sim_count == 6: + args_for_sim = 'sleep 60' # Manager kill - if signal received else completes + timeout = 65.0 + + if use_balsam: + task = exctr.submit(calc_type='sim', num_procs=cores, app_args=args_for_sim, + hyperthreads=True, machinefile='notused', stdout='notused', + wait_on_run=True) + else: + task = exctr.submit(calc_type='sim', num_procs=cores, app_args=args_for_sim, hyperthreads=True) + + if wait: + task.wait() + if not task.finished: + calc_status = UNSET_TAG + if task.state == 'FINISHED': + calc_status = WORKER_DONE + elif task.state == 'FAILED': + calc_status = TASK_FAILED + + else: + task, calc_status = polling_loop(comm, exctr, task, timeout) - task = exctr.submit(calc_type='sim', num_procs=cores, app_args=args_for_sim, hyperthreads=True) - task, calc_status = polling_loop(comm, exctr, task, timeout) + if use_balsam: + task.read_file_in_workdir('ensemble.log') + try: + task.read_stderr() + except ValueError: + pass # assert task.finished, "task.finished should be True. Returned " + str(task.finished) # assert task.state == 'FINISHED', "task.state should be FINISHED. Returned " + str(task.state) @@ -100,6 +131,13 @@ def executor_hworld(H, persis_info, sim_specs, libE_info): H_o = np.zeros(batch, dtype=sim_specs['out']) for i, x in enumerate(H['x']): H_o['f'][i] = six_hump_camel_func(x) + if launch_shc: + # Test launching a named app. + app_args = ' '.join(str(val) for val in list(x[:])) + task = exctr.submit(app_name='six_hump_camel', num_procs=1, app_args=app_args) + task.wait() + output = np.float64(task.read_stdout()) + assert np.isclose(H_o['f'][i], output) # This is just for testing at calling script level - status of each task H_o['cstat'] = calc_status diff --git a/libensemble/sim_funcs/six_hump_camel.py b/libensemble/sim_funcs/six_hump_camel.py index d992f4db4..5c96fd47d 100644 --- a/libensemble/sim_funcs/six_hump_camel.py +++ b/libensemble/sim_funcs/six_hump_camel.py @@ -5,6 +5,7 @@ # import subprocess import os +import sys import numpy as np import time from libensemble.executors.executor import Executor @@ -156,3 +157,9 @@ def six_hump_camel_grad(x): grad[1] = x1 + 16*x2**3 - 8*x2 return grad + + +if __name__ == "__main__": + x = (float(sys.argv[1]), float(sys.argv[2])) + result = six_hump_camel_func(x) + print(result) diff --git a/libensemble/tests/.coveragerc b/libensemble/tests/.coveragerc index cd55b1320..bed6acb7b 100644 --- a/libensemble/tests/.coveragerc +++ b/libensemble/tests/.coveragerc @@ -14,7 +14,6 @@ omit = */__init__.py */.tox/* */setup.py - */forkpdb.py */unit_tests/* */unit_tests_nompi/* */unit_tests_logger/* diff --git a/libensemble/tests/regression_tests/script_test_balsam_hworld.py b/libensemble/tests/regression_tests/script_test_balsam_hworld.py index 8fc9295c4..fa15257a4 100644 --- a/libensemble/tests/regression_tests/script_test_balsam_hworld.py +++ b/libensemble/tests/regression_tests/script_test_balsam_hworld.py @@ -12,6 +12,7 @@ from libensemble.sim_funcs.executor_hworld import executor_hworld from libensemble.gen_funcs.sampling import uniform_random_sample from libensemble.tools import add_unique_random_streams +import libensemble.sim_funcs.six_hump_camel as six_hump_camel mpi4py.rc.recv_mprobe = False # Disable matching probes @@ -38,14 +39,18 @@ def build_simfunc(): sim_app = './my_simtask.x' if not os.path.isfile(sim_app): build_simfunc() +sim_app2 = six_hump_camel.__file__ -exctr = BalsamMPIExecutor(auto_resources=False) -exctr.register_calc(full_path=sim_app, calc_type='sim') +exctr = BalsamMPIExecutor(auto_resources=False, central_mode=False, custom_info={'not': 'used'}) +exctr.register_calc(full_path=sim_app, calc_type='sim') # Default 'sim' app - backward compatible +exctr.register_calc(full_path=sim_app2, app_name='six_hump_camel') # Named app sim_specs = {'sim_f': executor_hworld, 'in': ['x'], 'out': [('f', float), ('cstat', int)], - 'user': {'cores': cores_per_task}} + 'user': {'cores': cores_per_task, + 'balsam_test': True} + } gen_specs = {'gen_f': uniform_random_sample, 'in': ['sim_id'], @@ -57,7 +62,7 @@ def build_simfunc(): persis_info = add_unique_random_streams({}, nworkers + 1) -exit_criteria = {'elapsed_wallclock_time': 35} +exit_criteria = {'elapsed_wallclock_time': 60} # Perform the run H, persis_info, flag = libE(sim_specs, gen_specs, exit_criteria, @@ -66,7 +71,7 @@ def build_simfunc(): if is_master: print('\nChecking expected task status against Workers ...\n') calc_status_list_in = np.asarray([WORKER_DONE, WORKER_KILL_ON_ERR, - WORKER_KILL_ON_TIMEOUT, + WORKER_DONE, WORKER_KILL_ON_TIMEOUT, TASK_FAILED, 0]) calc_status_list = np.repeat(calc_status_list_in, nworkers) @@ -78,11 +83,15 @@ def build_simfunc(): # Check summary file: print('Checking expected task status against task summary file ...\n') - calc_desc_list_in = ['Completed', 'Worker killed task on Error', + calc_desc_list_in = ['Completed', 'Worker killed task on Error', 'Completed', 'Worker killed task on Timeout', 'Task Failed', 'Manager killed on finish'] # Repeat N times for N workers and insert Completed at start for generator calc_desc_list = ['Completed'] + calc_desc_list_in*nworkers + # Cleanup (maybe cover del_apps() and del_tasks()) + exctr.del_apps() + exctr.del_tasks() + print("\n\n\nRun completed.") diff --git a/libensemble/tests/regression_tests/support.py b/libensemble/tests/regression_tests/support.py index 446682446..9423443c8 100644 --- a/libensemble/tests/regression_tests/support.py +++ b/libensemble/tests/regression_tests/support.py @@ -17,14 +17,26 @@ def nan_func(calc_in, persis_info, sim_specs, libE_info): return (H, persis_info) -def write_func(calc_in, persis_info, sim_specs, libE_info): +def write_sim_func(calc_in, persis_info, sim_specs, libE_info): out = np.zeros(1, dtype=sim_specs['out']) out['f'] = calc_in['x'] - with open('test_out.txt', 'a') as f: + with open('test_sim_out.txt', 'a') as f: f.write('sim_f received: {}\n'.format(out['f'])) return out, persis_info +def write_uniform_gen_func(H, persis_info, gen_specs, _): + ub = gen_specs['user']['ub'] + lb = gen_specs['user']['lb'] + n = len(lb) + b = gen_specs['user']['gen_batch_size'] + H_o = np.zeros(b, dtype=gen_specs['out']) + H_o['x'] = persis_info['rand_stream'].uniform(lb, ub, (b, n)) + with open('test_gen_out.txt', 'a') as f: + f.write('gen_f produced: {}\n'.format(H_o['x'])) + return H_o, persis_info + + uniform_or_localopt_gen_out = [('priority', float), ('local_pt', bool), ('known_to_aposmm', bool), diff --git a/libensemble/tests/regression_tests/test_deap_nsga2.py b/libensemble/tests/regression_tests/test_deap_nsga2.py index 210399e47..54e99f6c0 100644 --- a/libensemble/tests/regression_tests/test_deap_nsga2.py +++ b/libensemble/tests/regression_tests/test_deap_nsga2.py @@ -4,7 +4,7 @@ # """ # Do not change these lines - they are parsed by run-tests.sh -# TESTSUITE_COMMS: mpi local tcp +# TESTSUITE_COMMS: mpi local # TESTSUITE_NPROCS: 3 4 import numpy as np @@ -52,7 +52,7 @@ def deap_six_hump(H, persis_info, sim_specs, _): # State the generating function, its arguments, output, and necessary parameters. gen_specs = {'gen_f': gen_f, - 'in': ['sim_id'], + 'in': ['sim_id', 'generation', 'individual', 'fitness_values'], 'out': [('individual', float, ind_size), ('generation', int)], 'user': {'lb': lb, 'ub': ub, @@ -66,19 +66,45 @@ def deap_six_hump(H, persis_info, sim_specs, _): # libE Allocation function alloc_specs = {'out': [('given_back', bool)], 'alloc_f': alloc_f} -persis_info = add_unique_random_streams({}, nworkers + 1) # Tell libEnsemble when to stop # 'sim_max' = number of simulation calls # For deap, this should be pop_size*number of generations+1 exit_criteria = {'sim_max': pop_size*(ngen+1)} +for run in range(2): -# Perform the run -H, persis_info, flag = libE(sim_specs, gen_specs, exit_criteria, persis_info, alloc_specs, libE_specs) + persis_info = add_unique_random_streams({}, nworkers + 1) -if is_master: - script_name = os.path.splitext(os.path.basename(__file__))[0] - assert flag == 0, script_name + " didn't exit correctly" - assert sum(H['returned']) >= exit_criteria['sim_max'], script_name + " didn't evaluate the sim_max points." - assert min(H['fitness_values']) <= -1.0315, script_name + " didn't find the global minimum for this problem." + if run == 1: + # Test loading in a previous set of (x,f)-pairs, or (individual, fitness_values)-pairs + + # Number of points in the sample + num_samp = 100 + + H0 = np.zeros(num_samp, dtype=[('individual', float, ind_size), ('generation', int), ('fitness_values', float), + ('sim_id', int), ('returned', bool), ('given_back', bool), ('given', bool)]) + + # Mark these points as already have been given to be evaluated, and returned, but not given_back. + H0[['given', 'given_back', 'returned']] = True + H0['generation'][:] = 1 + # Give these points sim_ids + H0['sim_id'] = range(num_samp) + + # "Load in" the points and their function values. (In this script, we are + # actually evaluating them, but in many cases, they are available from past + # evaluations + H0['individual'] = np.random.uniform(lb, ub, (num_samp, len(lb))) + for i, x in enumerate(H0['individual']): + H0['fitness_values'][i] = six_hump_camel_func(x) + else: + H0 = None + + # Perform the run + H, persis_info, flag = libE(sim_specs, gen_specs, exit_criteria, persis_info, alloc_specs, libE_specs, H0=H0) + + if is_master: + script_name = os.path.splitext(os.path.basename(__file__))[0] + assert flag == 0, script_name + " didn't exit correctly" + assert sum(H['returned']) >= exit_criteria['sim_max'], script_name + " didn't evaluate the sim_max points." + assert min(H['fitness_values']) <= -1.0315, script_name + " didn't find the global minimum for this problem." diff --git a/libensemble/tests/regression_tests/test_executor_hworld.py b/libensemble/tests/regression_tests/test_executor_hworld.py index fc7d5a66b..7c5c1f154 100644 --- a/libensemble/tests/regression_tests/test_executor_hworld.py +++ b/libensemble/tests/regression_tests/test_executor_hworld.py @@ -20,6 +20,7 @@ from libensemble.message_numbers import WORKER_DONE, WORKER_KILL_ON_ERR, WORKER_KILL_ON_TIMEOUT, TASK_FAILED from libensemble.libE import libE from libensemble.sim_funcs.executor_hworld import executor_hworld as sim_f +import libensemble.sim_funcs.six_hump_camel as six_hump_camel from libensemble.gen_funcs.sampling import uniform_random_sample as gen_f from libensemble.tools import parse_args, add_unique_random_streams from libensemble.tests.regression_tests.common import build_simfunc @@ -52,6 +53,7 @@ sim_app = './my_simtask.x' if not os.path.isfile(sim_app): build_simfunc() +sim_app2 = six_hump_camel.__file__ if USE_BALSAM: from libensemble.executors.balsam_executor import BalsamMPIExecutor @@ -59,7 +61,8 @@ else: from libensemble.executors.mpi_executor import MPIExecutor exctr = MPIExecutor(auto_resources=use_auto_resources) -exctr.register_calc(full_path=sim_app, calc_type='sim') +exctr.register_calc(full_path=sim_app, calc_type='sim') # Default 'sim' app - backward compatible +exctr.register_calc(full_path=sim_app2, app_name='six_hump_camel') # Named app # if nworkers == 3: # CalcInfo.keep_worker_stat_files = True # Testing this functionality @@ -83,7 +86,7 @@ persis_info = add_unique_random_streams({}, nworkers + 1) -exit_criteria = {'elapsed_wallclock_time': 10} +exit_criteria = {'elapsed_wallclock_time': 20} # Perform the run H, persis_info, flag = libE(sim_specs, gen_specs, exit_criteria, persis_info, @@ -96,7 +99,8 @@ # manager kill - but should show in the summary file. # Repeat expected lists nworkers times and compare with list of status's # received from workers - calc_status_list_in = np.asarray([WORKER_DONE, WORKER_KILL_ON_ERR, WORKER_KILL_ON_TIMEOUT, TASK_FAILED, 0]) + calc_status_list_in = np.asarray([WORKER_DONE, WORKER_KILL_ON_ERR, WORKER_DONE, + WORKER_KILL_ON_TIMEOUT, TASK_FAILED, 0]) calc_status_list = np.repeat(calc_status_list_in, nworkers) # For debug @@ -108,7 +112,7 @@ # Check summary file: print('Checking expected task status against task summary file ...\n') - calc_desc_list_in = ['Completed', 'Worker killed task on Error', + calc_desc_list_in = ['Completed', 'Worker killed task on Error', 'Completed', 'Worker killed task on Timeout', 'Task Failed', 'Manager killed on finish'] diff --git a/libensemble/tests/regression_tests/test_fast_alloc.py b/libensemble/tests/regression_tests/test_fast_alloc.py index 78a68e503..35099f37d 100644 --- a/libensemble/tests/regression_tests/test_fast_alloc.py +++ b/libensemble/tests/regression_tests/test_fast_alloc.py @@ -28,7 +28,7 @@ num_pts = 30*(nworkers - 1) -sim_specs = {'sim_f': sim_f, 'in': ['x'], 'out': [('f', float)], 'user': {}} +sim_specs = {'sim_f': sim_f, 'in': ['x'], 'out': [('f', float), ('large', float, 1000000)], 'user': {}} gen_specs = {'gen_f': gen_f, 'in': ['sim_id'], @@ -40,7 +40,7 @@ persis_info = add_unique_random_streams({}, nworkers + 1) -exit_criteria = {'sim_max': num_pts, 'elapsed_wallclock_time': 300} +exit_criteria = {'sim_max': 2*num_pts, 'elapsed_wallclock_time': 300} if libE_specs['comms'] == 'tcp': # Can't use the same interface for manager and worker if we want @@ -69,4 +69,4 @@ if is_master: assert flag == 0 - assert len(H) == num_pts + assert len(H) == 2*num_pts diff --git a/libensemble/tests/regression_tests/test_mpi_runners.py b/libensemble/tests/regression_tests/test_mpi_runners.py index 26ec1a92a..0dcffe808 100644 --- a/libensemble/tests/regression_tests/test_mpi_runners.py +++ b/libensemble/tests/regression_tests/test_mpi_runners.py @@ -69,13 +69,13 @@ def runline_check(H, persis_info, sim_specs, libE_info): dry_run=True) outline = task.runline - exp_list[i] = exp_nodelist_for_worker(exp_list[i], libE_info['workerID']) + new_exp_list = exp_nodelist_for_worker(exp_list[i], libE_info['workerID']) - if outline != exp_list[i]: + if outline != new_exp_list: print('outline is: {}'.format(outline), flush=True) - print('exp is: {}'.format(exp_list[i]), flush=True) + print('exp is: {}'.format(new_exp_list), flush=True) - assert(outline == exp_list[i]) + assert(outline == new_exp_list) calc_status = WORKER_DONE output = np.zeros(1, dtype=sim_specs['out']) diff --git a/libensemble/tests/regression_tests/test_persistent_aposmm_dfols.py b/libensemble/tests/regression_tests/test_persistent_aposmm_dfols.py index 26379b6f8..06b006a56 100644 --- a/libensemble/tests/regression_tests/test_persistent_aposmm_dfols.py +++ b/libensemble/tests/regression_tests/test_persistent_aposmm_dfols.py @@ -66,7 +66,7 @@ persis_info = add_unique_random_streams({}, nworkers + 1) -exit_criteria = {'sim_max': 10000} +exit_criteria = {'sim_max': 1000} # Perform the run H, persis_info, flag = libE(sim_specs, gen_specs, exit_criteria, persis_info, diff --git a/libensemble/tests/regression_tests/test_persistent_aposmm_scipy.py b/libensemble/tests/regression_tests/test_persistent_aposmm_scipy.py index 9017c7e98..516739296 100644 --- a/libensemble/tests/regression_tests/test_persistent_aposmm_scipy.py +++ b/libensemble/tests/regression_tests/test_persistent_aposmm_scipy.py @@ -94,3 +94,22 @@ assert min_found >= 4, "Found {} minima".format(min_found) save_libE_output(H, persis_info, __file__, nworkers) + +# Now let's run on the same problem with a really large n (but we won't test +# convergence to all local min). Note that sim_f uses only entries x[0:2] +n = 400 +persis_info = add_unique_random_streams({}, nworkers + 1) +gen_specs['out'][0:2] = [('x', float, n), ('x_on_cube', float, n)] +gen_specs['user']['lb'] = np.zeros(n) +gen_specs['user']['ub'] = np.ones(n) +gen_specs['user']['lb'][:2] = [-3, -2] +gen_specs['user']['ub'][:2] = [3, 2] +gen_specs['user']['rk_const'] = 4.90247 +gen_specs['user'].pop('sample_points') +gen_specs['user']['localopt_method'] = 'scipy_Nelder-Mead' +sim_specs['out'] = [('f', float)] + +H, persis_info, flag = libE(sim_specs, gen_specs, exit_criteria, persis_info, alloc_specs, libE_specs) + +if is_master: + assert np.sum(H['returned']) >= exit_criteria['sim_max'], "Run didn't finish" diff --git a/libensemble/tests/regression_tests/test_persistent_aposmm_timeout.py b/libensemble/tests/regression_tests/test_persistent_aposmm_timeout.py index ecfc32b86..7a42cc9de 100644 --- a/libensemble/tests/regression_tests/test_persistent_aposmm_timeout.py +++ b/libensemble/tests/regression_tests/test_persistent_aposmm_timeout.py @@ -43,6 +43,7 @@ 'localopt_method': 'LN_BOBYQA', 'xtol_abs': 1e-8, 'ftol_abs': 1e-8, + 'run_max_eval': 30, 'lb': np.array([0, -np.pi/2]), 'ub': np.array([2*np.pi, 3*np.pi/2]), 'periodic': True, diff --git a/libensemble/tests/regression_tests/test_persistent_aposmm_with_grad.py b/libensemble/tests/regression_tests/test_persistent_aposmm_with_grad.py index bd40c371f..7ad11bb2a 100644 --- a/libensemble/tests/regression_tests/test_persistent_aposmm_with_grad.py +++ b/libensemble/tests/regression_tests/test_persistent_aposmm_with_grad.py @@ -53,6 +53,7 @@ 'user': {'initial_sample_size': 0, # Don't need to do evaluations because the sampling already done below 'localopt_method': 'LD_MMA', 'rk_const': 0.5*((gamma(1+(n/2))*5)**(1/n))/sqrt(pi), + 'stop_after_this_many_minima': 25, 'xtol_rel': 1e-6, 'ftol_rel': 1e-6, 'max_active_runs': 6, @@ -99,4 +100,6 @@ print(np.min(np.sum((H[H['local_min']]['x'] - m)**2, 1)), flush=True) assert np.min(np.sum((H[H['local_min']]['x'] - m)**2, 1)) < tol + assert len(H) < exit_criteria['sim_max'], "Test should have stopped early" + save_libE_output(H, persis_info, __file__, nworkers) diff --git a/libensemble/tests/regression_tests/test_sim_dirs_per_calc.py b/libensemble/tests/regression_tests/test_sim_dirs_per_calc.py index 545d4f851..a2746940c 100644 --- a/libensemble/tests/regression_tests/test_sim_dirs_per_calc.py +++ b/libensemble/tests/regression_tests/test_sim_dirs_per_calc.py @@ -18,7 +18,7 @@ import os from libensemble.libE import libE -from libensemble.tests.regression_tests.support import write_func as sim_f +from libensemble.tests.regression_tests.support import write_sim_func as sim_f from libensemble.gen_funcs.sampling import uniform_random_sample as gen_f from libensemble.tools import parse_args, add_unique_random_streams diff --git a/libensemble/tests/regression_tests/test_sim_dirs_per_worker.py b/libensemble/tests/regression_tests/test_sim_dirs_per_worker.py index dbe017391..01301254b 100644 --- a/libensemble/tests/regression_tests/test_sim_dirs_per_worker.py +++ b/libensemble/tests/regression_tests/test_sim_dirs_per_worker.py @@ -18,7 +18,7 @@ import os from libensemble.libE import libE -from libensemble.tests.regression_tests.support import write_func as sim_f +from libensemble.tests.regression_tests.support import write_sim_func as sim_f from libensemble.gen_funcs.sampling import uniform_random_sample as gen_f from libensemble.tools import parse_args, add_unique_random_streams diff --git a/libensemble/tests/regression_tests/test_sim_dirs_with_exception.py b/libensemble/tests/regression_tests/test_sim_dirs_with_exception.py index 3d9d594ff..2af0c849c 100644 --- a/libensemble/tests/regression_tests/test_sim_dirs_with_exception.py +++ b/libensemble/tests/regression_tests/test_sim_dirs_with_exception.py @@ -18,7 +18,7 @@ import os from libensemble.libE import libE -from libensemble.tests.regression_tests.support import write_func as sim_f +from libensemble.tests.regression_tests.support import write_sim_func as sim_f from libensemble.gen_funcs.sampling import uniform_random_sample as gen_f from libensemble.tools import parse_args, add_unique_random_streams from libensemble.libE_manager import ManagerException diff --git a/libensemble/tests/regression_tests/test_sim_dirs_with_gen_dirs.py b/libensemble/tests/regression_tests/test_sim_dirs_with_gen_dirs.py new file mode 100644 index 000000000..7759d9e12 --- /dev/null +++ b/libensemble/tests/regression_tests/test_sim_dirs_with_gen_dirs.py @@ -0,0 +1,102 @@ +# """ +# Runs libEnsemble with uniform random sampling and writes results into sim dirs. +# tests per-calculation sim_dir capabilities +# +# Execute via one of the following commands (e.g. 3 workers): +# mpiexec -np 4 python3 test_worker_exceptions.py +# python3 test_worker_exceptions.py --nworkers 3 --comms local +# python3 test_worker_exceptions.py --nworkers 3 --comms tcp +# +# The number of concurrent evaluations of the objective function will be 4-1=3. +# """ + +# Do not change these lines - they are parsed by run-tests.sh +# TESTSUITE_COMMS: mpi local tcp +# TESTSUITE_NPROCS: 2 4 + +import numpy as np +import os + +from libensemble.libE import libE +from libensemble.tests.regression_tests.support import write_sim_func as sim_f +from libensemble.tests.regression_tests.support import write_uniform_gen_func as gen_f +from libensemble.tools import parse_args, add_unique_random_streams + +nworkers, is_master, libE_specs, _ = parse_args() + +sim_input_dir = './sim_input_dir' +dir_to_copy_sim = sim_input_dir + '/copy_this_sim' +dir_to_symlink_sim = sim_input_dir + '/symlink_this_sim' + +gen_input_dir = './gen_input_dir' +dir_to_copy_gen = gen_input_dir + '/copy_this_gen' +dir_to_symlink_gen = gen_input_dir + '/symlink_this_gen' + +c_ensemble = './ensemble_combined_calcdirs_w' + str(nworkers) + '_' + libE_specs.get('comms') +print('creating ensemble dir: ', c_ensemble, flush=True) + +for dir in [sim_input_dir, dir_to_copy_sim, dir_to_symlink_sim, + gen_input_dir, dir_to_copy_gen, dir_to_symlink_gen]: + if is_master and not os.path.isdir(dir): + os.makedirs(dir, exist_ok=True) + +libE_specs['sim_dirs_make'] = True +libE_specs['gen_dirs_make'] = True + +libE_specs['ensemble_dir_path'] = c_ensemble +libE_specs['use_worker_dirs'] = False + +libE_specs['sim_dir_copy_files'] = [dir_to_copy_sim] +libE_specs['sim_dir_symlink_files'] = [dir_to_symlink_sim] + +libE_specs['gen_dir_copy_files'] = [dir_to_copy_gen] +libE_specs['gen_dir_symlink_files'] = [dir_to_symlink_gen] + +libE_specs['ensemble_copy_back'] = True + +sim_specs = {'sim_f': sim_f, 'in': ['x'], 'out': [('f', float)]} + +gen_specs = {'gen_f': gen_f, + 'out': [('x', float, (1,))], + 'user': {'gen_batch_size': 20, + 'lb': np.array([-3]), + 'ub': np.array([3]), + } + } + +persis_info = add_unique_random_streams({}, nworkers + 1) + +exit_criteria = {'sim_max': 21} + +H, persis_info, flag = libE(sim_specs, gen_specs, exit_criteria, + persis_info, libE_specs=libE_specs) + + +def check_copied(type): + input_copied = [] + for base, files, _ in os.walk(c_ensemble): + basedir = base.split('/')[-1] + if basedir.startswith(type): + input_copied.append(all([os.path.basename(j) in files for j in + libE_specs[type + '_dir_copy_files'] + + libE_specs[type + '_dir_symlink_files']])) + + assert all(input_copied), \ + 'All input files not copied or symlinked to each {} calc dir'.format(type) + + +if is_master: + assert os.path.isdir(c_ensemble), 'Ensemble directory {} not created.'.format(c_ensemble) + sim_dir_sum = sum(['sim' in i for i in os.listdir(c_ensemble)]) + assert sim_dir_sum == exit_criteria['sim_max'], \ + 'Number of sim directories ({}) does not match sim_max ({}).' \ + .format(sim_dir_sum, exit_criteria['sim_max']) + + assert any(['gen' in i for i in os.listdir(c_ensemble)]), \ + 'No gen directories created.' + + check_copied('sim') + check_copied('gen') + + assert all([i in os.listdir(c_ensemble) for i in os.listdir(c_ensemble+'_back')]), \ + "Copyback dir doesn't contain the same contents as ensemble dir" diff --git a/libensemble/tests/regression_tests/test_sim_input_dir_option.py b/libensemble/tests/regression_tests/test_sim_input_dir_option.py index aef950256..95e2dfaa4 100644 --- a/libensemble/tests/regression_tests/test_sim_input_dir_option.py +++ b/libensemble/tests/regression_tests/test_sim_input_dir_option.py @@ -18,7 +18,7 @@ import os from libensemble.libE import libE -from libensemble.tests.regression_tests.support import write_func as sim_f +from libensemble.tests.regression_tests.support import write_sim_func as sim_f from libensemble.gen_funcs.sampling import uniform_random_sample as gen_f from libensemble.tools import parse_args, add_unique_random_streams @@ -36,6 +36,7 @@ libE_specs['ensemble_dir_path'] = o_ensemble libE_specs['sim_dirs_make'] = False libE_specs['sim_dir_symlink_files'] = ['./test_sim_input_dir_option.py'] # to cover FileExistsError catch +libE_specs['ensemble_copy_back'] = True sim_specs = {'sim_f': sim_f, 'in': ['x'], 'out': [('f', float)]} @@ -59,7 +60,7 @@ 'Ensemble directory {} not created.'.format(o_ensemble) assert os.path.basename(dir_to_copy) in os.listdir(o_ensemble), \ 'Input file not copied over.' - with open(os.path.join(o_ensemble, 'test_out.txt'), 'r') as f: + with open(os.path.join(o_ensemble, 'test_sim_out.txt'), 'r') as f: lines = f.readlines() assert len(lines) == exit_criteria['sim_max'], \ diff --git a/libensemble/tests/regression_tests/test_vtmop.py b/libensemble/tests/regression_tests/test_vtmop.py index 35d03fe5e..994e29bb4 100644 --- a/libensemble/tests/regression_tests/test_vtmop.py +++ b/libensemble/tests/regression_tests/test_vtmop.py @@ -23,6 +23,9 @@ # TESTSUITE_NPROCS: import numpy as np +import os +import time +from libensemble.utils.timer import Timer # Import libEnsemble items for this test from libensemble.libE import libE @@ -32,6 +35,8 @@ from libensemble.alloc_funcs.only_one_gen_alloc import ensure_one_active_gen as alloc_f from libensemble.tools import parse_args, save_libE_output, add_unique_random_streams +timer = Timer() + # Set the problem dimensions here num_dims = 5 num_objs = 3 @@ -66,18 +71,18 @@ def sim_f(H, *unused): 'in': ['x', 'f'], 'out': [('x', float, num_dims)], 'user': { - # Set the number of objectives. The number of design variables is - # inferred based on the length of lb. + # Set the number of objectives. The number of design variables + # is inferred based on the length of lb. 'num_obj': num_objs, # Set the bound constraints. 'lb': lower_bounds, 'ub': upper_bounds, # search_batch_size is the number of points used to search # each local trust region (using Latin hypercube design). - # This should be a multiple of the number of concurrent function - # evaluations and on the order of 2*d (where d is the number of - # design variables) - 'search_batch_size': int(np.ceil(2*num_dims/nworkers)*nworkers), + # This should be a multiple of the number of concurrent + # function evaluations and on the order of 4*d (where d is + # the number of design variables) + 'search_batch_size': int(np.ceil(4*num_dims/nworkers)*nworkers), # opt_batch_size is the preferred number of candidate designs. # When the actual number of candidates is not a multiple of # opt_batch_size, additional candidates are randomly generated @@ -92,55 +97,101 @@ def sim_f(H, *unused): # initial database will cause an error since the surrogates # cannot be fit without sufficient data. 'first_batch_size': 1000, - # Set the trust region radius. This setting is problem - # dependent. A good starting place would be between 10% and - # 25% of the median edge length of the bounding box (err on - # the smaller side when the number of design variables is - # greater than 5 or 6). - 'trust_rad': np.median(upper_bounds - lower_bounds)*0.1, - # Are you reloading from a checkpoint + # Set the trust region radius as a fraction of ub[:]-lb[:]. + # This setting is problem dependent. A good starting place + # would be between 0.1 and 0.2. + 'trust_rad': 0.1, + # Are you reloading from a checkpoint? 'use_chkpt': False}, } # Set up the allocator alloc_specs = {'alloc_f': alloc_f, 'out': []} -for run in range(2): - if run == 1: - # In the second run, we initialize VTMOP with an initial sample: +s1 = [] +H = [] + +for run in range(3): + if run == 0: + # Run for 1100 evaluations or 300 seconds + H0 = None + exit_criteria = {'sim_max': 1100, 'elapsed_wallclock_time': 300} + + elif run == 1: + # In the second run, we initialize VTMOP with an initial sample of previously evaluated points np.random.seed(0) - sample_size = 1000 - X = np.random.uniform(gen_specs['user']['lb'], gen_specs['user']['ub'], (sample_size, num_dims)) - f = np.zeros((sample_size, num_objs)) + size = 1000 - H0 = np.zeros(sample_size, dtype=[('x', float, num_dims), ('f', float, num_objs), ('sim_id', int), - ('returned', bool), ('given', bool)]) + # Generate the sample + X = np.random.uniform(gen_specs['user']['lb'], gen_specs['user']['ub'], (size, num_dims)) + f = np.zeros((size, num_objs)) + + # Initialize H0 + H0 = np.zeros(size, dtype=[('x', float, num_dims), ('f', float, num_objs), ('sim_id', int), + ('returned', bool), ('given', bool)]) H0['x'] = X - H0['sim_id'] = range(sample_size) + H0['sim_id'] = range(size) H0[['given', 'returned']] = True - for i in range(sample_size): + # Perform objective function evaluations + for i in range(size): Out, _ = sim_f(H0[[i]]) H0['f'][i] = Out['f'] - gen_specs['user']['use_chkpt'] = True + # Run for 200 more evaluations or 300 seconds + exit_criteria = {'sim_max': 200, 'elapsed_wallclock_time': 300} + gen_specs['user']['first_batch_size'] = 0 - else: - H0 = None + gen_specs['user']['use_chkpt'] = False # Need to set this as it can be overwritten within the libE call. + + elif run == 2: + # In the third run, we restart VTMOP by loading in the history array saved in run==1 + gen_specs['user']['use_chkpt'] = True + + # Inelegant way to have the manager copy over the VTMOP checkpoint + # file, and have every worker get the H value from the run==1 case to + # use in the restart. + try: + os.remove('manager_done_file') + except OSError: + pass + + if is_master: + os.rename('vtmop.chkpt_finishing_' + s1, 'vtmop.chkpt') + np.save('H_for_vtmop_restart.npy', H) + open('manager_done_file', 'w').close() + else: + while not os.path.isfile('manager_done_file'): + time.sleep(0.1) + H = np.load('H_for_vtmop_restart.npy') + + # Initialize H0 with values from H (from the run==1 case) + size = sum(H['returned']) + H0 = np.zeros(size, dtype=[('x', float, num_dims), ('f', float, num_objs), ('sim_id', int), + ('returned', bool), ('given', bool)]) + H0['x'] = H['x'][:size] + H0['sim_id'] = range(size) + H0[['given', 'returned']] = True + H0['f'] = H['f'][:size] + + # Run for 200 more evaluations or 300 seconds + exit_criteria = {'sim_max': 200, 'elapsed_wallclock_time': 300} # Persistent info between iterations persis_info = add_unique_random_streams({}, nworkers + 1) - persis_info['next_to_give'] = 0 + persis_info['next_to_give'] = 0 if H0 is None else len(H0) persis_info['total_gen_calls'] = 0 - # Run for 2000 evaluations or 300 seconds - exit_criteria = {'sim_max': 1100, 'elapsed_wallclock_time': 300} - # Perform the run H, persis_info, flag = libE(sim_specs, gen_specs, exit_criteria, persis_info, alloc_specs=alloc_specs, libE_specs=libE_specs, H0=H0) - # The master takes care of checkpointint/output + # The master takes care of checkpointing/output if is_master: + # Renaming vtmop checkpointing file, if needed for later use. + timer.start() + s1 = timer.date_start.replace(' ', '_') + os.rename('vtmop.chkpt', 'vtmop.chkpt_finishing_' + s1) + assert flag == 0 save_libE_output(H, persis_info, __file__, nworkers) diff --git a/libensemble/tests/regression_tests/test_zero_resource_workers.py b/libensemble/tests/regression_tests/test_zero_resource_workers.py new file mode 100644 index 000000000..0410e50ef --- /dev/null +++ b/libensemble/tests/regression_tests/test_zero_resource_workers.py @@ -0,0 +1,170 @@ +# """ +# Runs libEnsemble testing the zero_resource_workers argument. +# +# Execute via one of the following commands (e.g. 3 workers): +# mpiexec -np 4 python3 test_zero_resource_workers.py +# python3 test_zero_resource_workers.py --nworkers 3 --comms local +# python3 test_zero_resource_workers.py --nworkers 3 --comms tcp +# +# The number of concurrent evaluations of the objective function will be 4-1=3. +# """ + +import os +import sys +import numpy as np + +from libensemble.message_numbers import WORKER_DONE +from libensemble.libE import libE +from libensemble.gen_funcs.persistent_uniform_sampling import persistent_uniform as gen_f +from libensemble.alloc_funcs.start_only_persistent import only_persistent_gens as alloc_f +from libensemble.tools import parse_args, add_unique_random_streams +from libensemble.executors.mpi_executor import MPIExecutor +from libensemble import libE_logger + +# libE_logger.set_level('DEBUG') # For testing the test +libE_logger.set_level('INFO') + +# Do not change these lines - they are parsed by run-tests.sh +# TESTSUITE_COMMS: mpi local +# TESTSUITE_NPROCS: 3 4 + +nodes_per_worker = 2 + + +def exp_nodelist_for_worker(exp_list, workerID): + """Modify expected node-lists based on workerID""" + comps = exp_list.split() + new_line = [] + for comp in comps: + if comp.startswith('node-'): + new_node_list = [] + node_list = comp.split(',') + for node in node_list: + node_name, node_num = node.split('-') + new_num = int(node_num) + nodes_per_worker*(workerID - 2) # For 1 persistent gen + new_node = '-'.join([node_name, str(new_num)]) + new_node_list.append(new_node) + new_list = ','.join(new_node_list) + new_line.append(new_list) + else: + new_line.append(comp) + return ' '.join(new_line) + + +def runline_check(H, persis_info, sim_specs, libE_info): + """Check run-lines produced by executor provided by a list""" + calc_status = 0 + x = H['x'][0][0] + exctr = MPIExecutor.executor + test_list = sim_specs['user']['tests'] + exp_list = sim_specs['user']['expect'] + + for i, test in enumerate(test_list): + task = exctr.submit(calc_type='sim', + num_procs=test.get('nprocs', None), + num_nodes=test.get('nnodes', None), + ranks_per_node=test.get('ppn', None), + extra_args=test.get('e_args', None), + app_args='--testid ' + test.get('testid', None), + stdout='out.txt', + stderr='err.txt', + hyperthreads=test.get('ht', None), + dry_run=True) + + outline = task.runline + new_exp_list = exp_nodelist_for_worker(exp_list[i], libE_info['workerID']) + + if outline != new_exp_list: + print('outline is: {}\nexp is: {}'.format(outline, new_exp_list), flush=True) + + assert(outline == new_exp_list) + + calc_status = WORKER_DONE + output = np.zeros(1, dtype=sim_specs['out']) + output['f'][0] = np.linalg.norm(x) + return output, persis_info, calc_status + +# -------------------------------------------------------------------- + + +nworkers, is_master, libE_specs, _ = parse_args() +rounds = 1 +sim_app = '/path/to/fakeapp.x' +comms = libE_specs['comms'] +libE_specs['zero_resource_workers'] = [1] + + +# To allow visual checking - log file not used in test +log_file = 'ensemble_zrw_comms_' + str(comms) + '_wrks_' + str(nworkers) + '.log' +libE_logger.set_filename(log_file) + +# For varying size test - relate node count to nworkers +in_place = libE_specs['zero_resource_workers'] +nsim_workers = nworkers-len(in_place) +comms = libE_specs['comms'] +nodes_per_worker = 2 +node_file = 'nodelist_zero_resource_workers_' + str(comms) + '_wrks_' + str(nworkers) +if is_master: + if os.path.exists(node_file): + os.remove(node_file) + with open(node_file, 'w') as f: + for i in range(1, (nsim_workers)*nodes_per_worker + 1): + f.write('node-' + str(i) + '\n') + f.flush() + os.fsync(f) +if comms == 'mpi': + libE_specs['comm'].Barrier() + + +# Mock up system +customizer = {'mpi_runner': 'mpich', # Select runner: mpich, openmpi, aprun, srun, jsrun + 'runner_name': 'mpirun', # Runner name: Replaces run command if not None + 'cores_on_node': (16, 64), # Tuple (physical cores, logical cores) + 'node_file': node_file} # Name of file containing a node-list + +# Create executor and register sim to it. +exctr = MPIExecutor(zero_resource_workers=in_place, central_mode=True, auto_resources=True, custom_info=customizer) +exctr.register_calc(full_path=sim_app, calc_type='sim') + + +if nworkers < 2: + sys.exit("Cannot run with a persistent worker if only one worker -- aborting...") + +n = 2 +sim_specs = {'sim_f': runline_check, + 'in': ['x'], + 'out': [('f', float)], + } + +gen_specs = {'gen_f': gen_f, + 'in': [], + 'out': [('x', float, (n,))], + 'user': {'gen_batch_size': 20, + 'lb': np.array([-3, -2]), + 'ub': np.array([3, 2])} + } + +alloc_specs = {'alloc_f': alloc_f, 'out': [('given_back', bool)]} +persis_info = add_unique_random_streams({}, nworkers + 1) +exit_criteria = {'sim_max': (nsim_workers)*rounds} + +# Each worker has 2 nodes. Basic test list for portable options +test_list_base = [{'testid': 'base1', 'nprocs': 2, 'nnodes': 1, 'ppn': 2, 'e_args': '--xarg 1'}, # Under use + {'testid': 'base2'}, # Give no config and no extra_args + ] + +exp_mpich = \ + ['mpirun -hosts node-1 -np 2 --ppn 2 --xarg 1 /path/to/fakeapp.x --testid base1', + 'mpirun -hosts node-1,node-2 -np 32 --ppn 16 /path/to/fakeapp.x --testid base2', + ] + +test_list = test_list_base +exp_list = exp_mpich +sim_specs['user'] = {'tests': test_list, 'expect': exp_list} + + +# Perform the run +H, persis_info, flag = libE(sim_specs, gen_specs, exit_criteria, persis_info, + alloc_specs, libE_specs) + +# All asserts are in sim func diff --git a/libensemble/tests/run-tests.sh b/libensemble/tests/run-tests.sh index c500b08fc..f63d94fa8 100755 --- a/libensemble/tests/run-tests.sh +++ b/libensemble/tests/run-tests.sh @@ -9,10 +9,11 @@ export RUN_UNIT_TESTS=true #Recommended for pre-push / CI tests export RUN_COV_TESTS=true #Provide coverage report export RUN_REG_TESTS=true #Recommended for pre-push / CI tests export RUN_PEP_TESTS=false #Code syle conventions +export PYTHON_FLAGS='' #Flags for PYTHON_RUN # Regression test options #export REG_TEST_LIST='test_number1.py test_number2.py' #selected/ordered -export REG_TEST_LIST=test_*.py #unordered +export REG_TEST_LIST=test_*.py #unordered # override with -y # export REG_TEST_PROCESS_COUNT_LIST='2 4' export REG_USE_PYTEST=false export REG_TEST_OUTPUT_EXT=std.out #/dev/null @@ -127,39 +128,42 @@ cleanup() { THISDIR=${PWD} cd $ROOT_DIR/$TESTING_DIR filelist=(.cov_merge_out*); [ -e ${filelist[0]} ] && rm .cov_merge_out* + filelist=(ensemble_*); [ -e ${filelist[0]} ] && rm -r ensemble_* for DIR in $UNIT_TEST_SUBDIR $UNIT_TEST_NOMPI_SUBDIR $UNIT_TEST_LOGGER_SUBDIR ; do cd $ROOT_DIR/$DIR - filelist=(libE_history_at_abort_*.npy); [ -e ${filelist[0]} ] && rm libE_history_at_abort_*.npy - filelist=(*.out); [ -e ${filelist[0]} ] && rm *.out - filelist=(*.err); [ -e ${filelist[0]} ] && rm *.err - filelist=(*.pickle); [ -e ${filelist[0]} ] && rm *.pickle - filelist=(.cov_unit_out*); [ -e ${filelist[0]} ] && rm .cov_unit_out* - filelist=(my_simtask.x); [ -e ${filelist[0]} ] && rm my_simtask.x - filelist=(task_my_simtask.x*.out); [ -e ${filelist[0]} ] && rm task_my_simtask.x*.out - filelist=(*libe_summary.txt*); [ -e ${filelist[0]} ] && rm *libe_summary.txt* - filelist=(*libE_stats.txt*); [ -e ${filelist[0]} ] && rm *libE_stats.txt* - filelist=(my_machinefile); [ -e ${filelist[0]} ] && rm my_machinefile - filelist=(libe_stat_files); [ -e ${filelist[0]} ] && rm -r libe_stat_files - filelist=(ensemble.log); [ -e ${filelist[0]} ] && rm ensemble.log + filelist=(libE_history_at_abort_*.npy); [ -e ${filelist[0]} ] && rm libE_history_at_abort_*.npy + filelist=(*.out); [ -e ${filelist[0]} ] && rm *.out + filelist=(*.err); [ -e ${filelist[0]} ] && rm *.err + filelist=(*.pickle); [ -e ${filelist[0]} ] && rm *.pickle + filelist=(.cov_unit_out*); [ -e ${filelist[0]} ] && rm .cov_unit_out* + filelist=(my_simtask.x); [ -e ${filelist[0]} ] && rm my_simtask.x + filelist=(libe_task_*.out); [ -e ${filelist[0]} ] && rm libe_task_*.out + filelist=(*libE_stats.txt*); [ -e ${filelist[0]} ] && rm *libE_stats.txt* + filelist=(my_machinefile); [ -e ${filelist[0]} ] && rm my_machinefile + filelist=(libe_stat_files); [ -e ${filelist[0]} ] && rm -r libe_stat_files + filelist=(ensemble.log); [ -e ${filelist[0]} ] && rm ensemble.log + filelist=(H_test.npy); [ -e ${filelist[0]} ] && rm H_test.npy done cd $ROOT_DIR/$REG_TEST_SUBDIR - filelist=(*.$REG_TEST_OUTPUT_EXT); [ -e ${filelist[0]} ] && rm *.$REG_TEST_OUTPUT_EXT - filelist=(*.npy); [ -e ${filelist[0]} ] && rm *.npy - filelist=(*.pickle); [ -e ${filelist[0]} ] && rm *.pickle - filelist=(.cov_reg_out*); [ -e ${filelist[0]} ] && rm .cov_reg_out* - filelist=(*active_runs.txt); [ -e ${filelist[0]} ] && rm *active_runs.txt - filelist=(*.err); [ -e ${filelist[0]} ] && rm *.err - filelist=(outfile*.txt); [ -e ${filelist[0]} ] && rm outfile*.txt - filelist=(machinefile*); [ -e ${filelist[0]} ] && rm machinefile* - filelist=(task_my_simtask.x.*.out); [ -e ${filelist[0]} ] && rm task_my_simtask.x.*.out - filelist=(*libe_summary.txt*); [ -e ${filelist[0]} ] && rm *libe_summary.txt* - filelist=(*libE_stats.txt*); [ -e ${filelist[0]} ] && rm *libE_stats.txt* - filelist=(my_simtask.x); [ -e ${filelist[0]} ] && rm my_simtask.x - filelist=(libe_stat_files); [ -e ${filelist[0]} ] && rm -r libe_stat_files - filelist=(ensemble.log); [ -e ${filelist[0]} ] && rm ensemble.log - filelist=(ensemble_*); [ -e ${filelist[0]} ] && rm -r ensemble_* - filelist=(sim_*); [ -e ${filelist[0]} ] && rm -r sim_* - filelist=(nodelist_*); [ -e ${filelist[0]} ] && rm nodelist_* + filelist=(*.$REG_TEST_OUTPUT_EXT); [ -e ${filelist[0]} ] && rm *.$REG_TEST_OUTPUT_EXT + filelist=(*.npy); [ -e ${filelist[0]} ] && rm *.npy + filelist=(*.pickle); [ -e ${filelist[0]} ] && rm *.pickle + filelist=(.cov_reg_out*); [ -e ${filelist[0]} ] && rm .cov_reg_out* + filelist=(*active_runs.txt); [ -e ${filelist[0]} ] && rm *active_runs.txt + filelist=(*.err); [ -e ${filelist[0]} ] && rm *.err + filelist=(outfile*.txt); [ -e ${filelist[0]} ] && rm outfile*.txt + filelist=(machinefile*); [ -e ${filelist[0]} ] && rm machinefile* + filelist=(libe_task_*.out); [ -e ${filelist[0]} ] && rm libe_task_*.out + filelist=(*libE_stats.txt*); [ -e ${filelist[0]} ] && rm *libE_stats.txt* + filelist=(my_simtask.x); [ -e ${filelist[0]} ] && rm my_simtask.x + filelist=(libe_stat_files); [ -e ${filelist[0]} ] && rm -r libe_stat_files + filelist=(ensemble.log); [ -e ${filelist[0]} ] && rm ensemble.log + filelist=(ensemble_*); [ -e ${filelist[0]} ] && rm -r ensemble_* + filelist=(sim_*); [ -e ${filelist[0]} ] && rm -r sim_* + filelist=(gen_*); [ -e ${filelist[0]} ] && rm -r gen_* + filelist=(nodelist_*); [ -e ${filelist[0]} ] && rm nodelist_* + filelist=(x_*.txt y_*.txt); [ -e ${filelist[0]} ] && rm x_*.txt y_*.txt + filelist=(opt_*.txt_flag); [ -e ${filelist[0]} ] && rm opt_*.txt_flag cd $THISDIR } @@ -199,15 +203,17 @@ usage() { echo " -t Run the regression tests using TCP comms" echo " -p {version} Select a version of python. E.g. -p 2 will run with the python2 exe" echo " Note: This will literally run the python2/python3 exe. Default runs python" + echo " -A {-flag arg} Supply arguments to python" echo " -n {name} Supply a name to this test run" echo " -a {args} Supply a string of args to add to mpiexec line" + echo " -y {args} Supply a list of regression tests as a reg. expression e.g. '-y test_persistent_aposmm*'" echo "" echo "Note: If none of [-mlt] are given, the default is to run tests for all comms" echo "" exit 1 } -while getopts ":p:n:a:hcszurmlt" opt; do +while getopts ":p:n:a:y:A:hcszurmlt" opt; do case $opt in p) echo "Parameter supplied for Python version: $OPTARG" >&2 @@ -253,6 +259,14 @@ while getopts ":p:n:a:hcszurmlt" opt; do echo "Running only the MPI regression tests" export RUN_MPI=true ;; + y) + echo "Running with user supplied test list" + export REG_TEST_LIST="$OPTARG" + ;; + A) + echo "Python arguments passed: $OPTARG" >&2 + PYTHON_FLAGS="$PYTHON_FLAGS $OPTARG" + ;; h) usage ;; @@ -312,14 +326,14 @@ if [ $CLEAN_ONLY = "true" ]; then fi; #If not supplied will go to just python (no number) - eg. with tox/virtual envs -PYTHON_RUN=python$PYTHON_VER +PYTHON_RUN="python$PYTHON_VER $PYTHON_FLAGS" echo -e "Python run: $PYTHON_RUN" textreset=$(tput sgr0) fail_color=$(tput bold;tput setaf 1) #red pass_color=$(tput bold;tput setaf 2) #green -titl_colour=$(tput bold;tput setaf 6) #cyan -hint_colour=$(tput bold;tput setaf 4) #blue +titl_color=$(tput bold;tput setaf 6) #cyan +hint_color=$(tput bold;tput setaf 4) #blue # Note - pytest exit codes # Exit code 0: All tests were collected and passed successfully @@ -469,6 +483,8 @@ if [ "$root_found" = true ]; then test_num=$((test_num+1)) test_start=$(current_time) + echo -e "\n ${titl_color}---Test $test_num: $TEST_SCRIPT starting with $LAUNCHER on $NPROCS processes ${textreset}" + if [ "$REG_USE_PYTEST" = true ]; then if [ "$LAUNCHER" = mpi ]; then mpiexec -np $NPROCS $MPIEXEC_FLAGS $PYTHON_RUN -m pytest $TEST_SCRIPT >> $TEST_SCRIPT.$NPROCS'procs'.$REG_TEST_OUTPUT_EXT 2>test.err diff --git a/libensemble/tests/scaling_tests/forces/theta_submit_balsam.sh b/libensemble/tests/scaling_tests/forces/theta_submit_balsam.sh index c91614316..87775bbd2 100755 --- a/libensemble/tests/scaling_tests/forces/theta_submit_balsam.sh +++ b/libensemble/tests/scaling_tests/forces/theta_submit_balsam.sh @@ -1,12 +1,13 @@ #!/bin/bash -x #COBALT -t 00:30:00 +#COBALT -O libE_forces_MPI_balsam #COBALT -n 129 #COBALT -q default #COBALT -A -# Script to launch libEnsemble using Balsam within Conda. Conda environment must be set up. - -# Requires Balsam is installed and a database initialized (this can be the default database). +# Script to launch libEnsemble using Balsam. +# Assumes Conda environment is set up. +# Requires Balsam is installed and a database initialized. # To be run with central job management # - Manager and workers run on one node (or a dedicated set of nodes). @@ -18,43 +19,48 @@ export EXE=run_libe_forces.py # Number of workers. export NUM_WORKERS=127 +# Number of nodes to run libE +export LIBE_NODES=2 + # Wallclock for libE job in minutes (supplied to Balsam - make at least several mins smaller than wallclock for this submission to ensure job is launched) export LIBE_WALLCLOCK=25 # Name of working directory where Balsam places running jobs/output (inside the database directory) export WORKFLOW_NAME=libe_workflow -# export SCRIPT_ARGS='' #Default No args -# export SCRIPT_ARGS=$(($LIBE_WALLCLOCK-5)) -export SCRIPT_ARGS="--comms mpi --nworkers $NUM_WORKERS" +# If user script takes ``elapsed_wallclock_time`` argument. +# export SCRIPT_ARGS=$(($LIBE_WALLCLOCK-3)) +export SCRIPT_ARGS="" # Name of Conda environment export CONDA_ENV_NAME= # Name of database -export DBASE_NAME= - -# Conda location - theta specific -# export PATH=/opt/intel/python/2017.0.035/intelpython35/bin:$PATH -# export LD_LIBRARY_PATH=~/.conda/envs/$CONDA_ENV_NAME/lib:$LD_LIBRARY_PATH - -export PYTHONNOUSERSITE=1 #Ensure environment isolated - -export PMI_NO_FORK=1 # Required for python kills on Theta +export BALSAM_DB_NAME= export LIBE_PLOTS=true # Require plot scripts (see at end) export BALSAM_PLOTS=true # Require plot scripts (see at end) export PLOT_DIR=.. -# Activate conda environment -. activate $CONDA_ENV_NAME +# Required for killing tasks from workers on Theta +export PMI_NO_FORK=1 -# Unload Theta modules that may interfere with job monitoring/kills +# Unload Theta modules that may interfere with task monitoring/kills module unload trackdeps module unload darshan module unload xalt -. balsamactivate $DBASE_NAME +# Obtain Conda PATH from miniconda-3/latest module +CONDA_DIR=/soft/datascience/conda/miniconda3/latest/bin + +# Ensure environment isolated +export PYTHONNOUSERSITE=1 + +# Activate conda environment +source $CONDA_DIR/activate $CONDA_ENV_NAME + +# Activate Balsam database +source balsamactivate $BALSAM_DB_NAME # Make sure no existing apps/jobs balsam rm apps --all --force @@ -63,27 +69,30 @@ wait sleep 3 # Add calling script to Balsam database as app and job. -THIS_DIR=$PWD -SCRIPT_BASENAME=${EXE%.*} - -# Running libE on one node - one manager and upto 63 workers -# NUM_NODES=1 -# RANKS_PER_NODE=$((NUM_WORKERS+1)) # One node auto +export THIS_DIR=$PWD +export SCRIPT_BASENAME=${EXE%.*} # Multiple nodes -NUM_NODES=2 -RANKS_PER_NODE=64 - -# All tasks -OUT_FILES_TO_RETURN="*.out *.txt *.log" +export LIBE_PROCS=$((NUM_WORKERS+1)) # Manager and workers +export PROCS_PER_NODE=$((LIBE_PROCS/LIBE_NODES)) # Must divide evenly balsam app --name $SCRIPT_BASENAME.app --exec $EXE --desc "Run $SCRIPT_BASENAME" -balsam job --name job_$SCRIPT_BASENAME --workflow $WORKFLOW_NAME --application $SCRIPT_BASENAME.app --args $SCRIPT_ARGS --wall-time-minutes $LIBE_WALLCLOCK --num-nodes $NUM_NODES --ranks-per-node $RANKS_PER_NODE --url-out="local:/$THIS_DIR" --stage-out-files="${OUT_FILES_TO_RETURN}" --url-in="local:/$THIS_DIR/*" --yes +balsam job --name job_$SCRIPT_BASENAME --workflow $WORKFLOW_NAME \ + --application $SCRIPT_BASENAME.app --args $SCRIPT_ARGS \ + --wall-time-minutes $LIBE_WALLCLOCK \ + --num-nodes $LIBE_NODES --ranks-per-node $PROCS_PER_NODE \ + --url-out="local:/$THIS_DIR" --stage-out-files="*.out *.txt *.log" \ + --url-in="local:/$THIS_DIR/*" --yes # Hyper-thread libE (note this will not affect HT status of user calcs - only libE itself) # E.g. Running 255 workers and one manager on one libE node. -# balsam job --name job_$SCRIPT_BASENAME --workflow $WORKFLOW_NAME --application $SCRIPT_BASENAME.app --args $SCRIPT_ARGS --wall-time-minutes $LIBE_WALLCLOCK --num-nodes $NUM_NODES --ranks-per-node $RANKS_PER_NODE --threads-per-core 4 --url-out="local:/$THIS_DIR" --stage-out-files="${OUT_FILES_TO_RETURN}" --url-in="local:/$THIS_DIR/*" --yes +# balsam job --name job_$SCRIPT_BASENAME --workflow $WORKFLOW_NAME \ +# --application $SCRIPT_BASENAME.app --args $SCRIPT_ARGS \ +# --wall-time-minutes $LIBE_WALLCLOCK \ +# --num-nodes 1 --ranks-per-node 256 --threads-per-core 4 \ +# --url-out="local:/$THIS_DIR" --stage-out-files="*.out *.txt *.log" \ +# --url-in="local:/$THIS_DIR/*" --yes #Run job balsam launcher --consume-all --job-mode=mpi --num-transition-threads=1 @@ -94,10 +103,10 @@ if [[ $LIBE_PLOTS = "true" ]]; then python $PLOT_DIR/plot_libe_histogram.py if [[ $BALSAM_PLOTS = "true" ]]; then -# export MPLBACKEND=TkAgg python $PLOT_DIR/plot_util_v_time.py python $PLOT_DIR/plot_jobs_v_time.py python $PLOT_DIR/plot_waiting_v_time.py fi -. balsamdeactivate +wait +source balsamdeactivate diff --git a/libensemble/tests/scaling_tests/warpx/readme.txt b/libensemble/tests/scaling_tests/warpx/readme.txt index 2a87c5317..84e29d9dc 100644 --- a/libensemble/tests/scaling_tests/warpx/readme.txt +++ b/libensemble/tests/scaling_tests/warpx/readme.txt @@ -3,8 +3,10 @@ # with the input file in sim/inputs on Summit. ##################################################### -A convergence study, starting from an input file similar to ./sim/inputs (with a different set of physical parameters generated the APOSMM generator in a LibEnsemble run) is presented below. -The only parameter changed to go from one resolution to the next is amr.n_cell. +A convergence study, starting from an input file similar to ./sim/inputs (with +a different set of physical parameters generated the APOSMM generator in a +LibEnsemble run) is presented below. The only parameter changed to go from one +resolution to the next is amr.n_cell. resolution amr.n_cell duration f res0 16 1024 10s 2.6340181298685122e-05 @@ -13,14 +15,21 @@ res2 64 4096 54s 4.557730935993e-06 res3 128 8192 214s 5.683672880520962e-06 res4 256 16384 1092s 5.667607648747023e-06 -I ran the last run on 2 GPUs instead of 1, and it took 780s instead of 1092s, meaning we can run it on either 1 GPU or 2. -That way, we can explore it either having all resolutions on the same number of GPUs, or on different number of GPUs. -In the end, we would probably be interested in the former, which can also be further investigating my going to higher resolution. +I ran the last run on 2 GPUs instead of 1, and it took 780s instead of 1092s, +meaning we can run it on either 1 GPU or 2. That way, we can explore it either +having all resolutions on the same number of GPUs, or on different number of +GPUs. In the end, we would probably be interested in the former, which can also +be further investigating my going to higher resolution. The simulations can be run with the same input file, with the following changes: -- The timeout in the batch script should be larger for higher resolutions (see numbers above) -- The jsrun command should be modified as, e.g., "jsrun ... warpx.exe inputs amr.n_cell=128 8192" for resolution res3 +- The timeout in the batch script should be larger for higher resolutions (see + numbers above) +- The jsrun command should be modified as, e.g., "jsrun ... warpx.exe inputs + amr.n_cell=128 8192" for resolution res3 Note that this test shows a nice converge for a case with relatively high emittance. -In the optimization process, some runs will have a luwer emittance (potentially 10x), and they may require a higher resolution to reach the same level of convergence. -Let us keep this in mind, and maybe add a res5 to the list above. This high-resolution one should also fit on 1 GPU, but it will be worth testing. +In the optimization process, some runs will have a lower emittance (potentially +10x), and they may require a higher resolution to reach the same level of +convergence. Let us keep this in mind, and maybe add a res5 to the list above. +This high-resolution one should also fit on 1 GPU, but it will be worth +testing. diff --git a/libensemble/tests/unit_tests/test_env_resources.py b/libensemble/tests/unit_tests/test_env_resources.py index 2f72c8e13..9bdffa255 100644 --- a/libensemble/tests/unit_tests/test_env_resources.py +++ b/libensemble/tests/unit_tests/test_env_resources.py @@ -176,44 +176,44 @@ def test_lsf_nodelist_shortform_seq(): assert nodelist == exp_out, "Nodelist returned does not match expected" -def test_abbrev_nodenames_nochange_slurm(): +def test_shortnames_nochange_slurm(): env_resources = EnvResources() # Test Slurm abbrev exp_names = ['knl-0019', 'knl-0021', 'knl-0022', 'knl-0137', 'knl-0138', 'knl-0139', 'knl-2345'] - env_resources.schedular = 'Slurm' - abbrev_names = env_resources.abbrev_nodenames(exp_names) + env_resources.scheduler = 'Slurm' + abbrev_names = env_resources.shortnames(exp_names) assert abbrev_names == exp_names, "Abbreviated names returned do not match expected" del env_resources -def test_abbrev_nodenames_slurm(): +def test_shortnames_slurm(): env_resources = EnvResources() # Test Slurm abbrev exp_names = ['knl-0019', 'knl-0021', 'knl-0022'] full_names = ['knl-0019.some.suffix', 'knl-0021.some.suffix', 'knl-0022.diff_suffix'] - env_resources.schedular = 'Slurm' - abbrev_names = env_resources.abbrev_nodenames(full_names) + env_resources.scheduler = 'Slurm' + abbrev_names = env_resources.shortnames(full_names) assert abbrev_names == exp_names, "Abbreviated names returned do not match expected" del env_resources -def test_abbrev_nodenames_nochange_cobalt(): +def test_shortnames_nochange_cobalt(): env_resources = EnvResources() # Test Cobalt abbrev exp_names = ['21', '22', '137', '138', '1234', '11234'] - env_resources.schedular = 'Cobalt' - abbrev_names = env_resources.abbrev_nodenames(exp_names) + env_resources.scheduler = 'Cobalt' + abbrev_names = env_resources.shortnames(exp_names) assert abbrev_names == exp_names, "Abbreviated names returned do not match expected" del env_resources -def test_abbrev_nodenames_cobalt(): +def test_shortnames_cobalt(): env_resources = EnvResources() # Test Cobalt abbrev exp_names = ['20', '21', '22', '137', '138', '1234', '11234'] full_names = ['nid00020', 'nid00021', 'nid00022', 'nid00137', 'nid00138', 'nid01234', 'nid11234'] - env_resources.schedular = 'Cobalt' - abbrev_names = env_resources.abbrev_nodenames(full_names) + env_resources.scheduler = 'Cobalt' + abbrev_names = env_resources.shortnames(full_names) assert abbrev_names == exp_names, "Abbreviated names returned do not match expected" del env_resources @@ -246,9 +246,9 @@ def test_abbrev_nodenames_cobalt(): test_lsf_nodelist_shortform_single() test_lsf_nodelist_shortform_seq() - test_abbrev_nodenames_nochange_slurm() - test_abbrev_nodenames_slurm() - test_abbrev_nodenames_nochange_cobalt() - test_abbrev_nodenames_cobalt() + test_shortnames_nochange_slurm() + test_shortnames_slurm() + test_shortnames_nochange_cobalt() + test_shortnames_cobalt() teardown_standalone_run() diff --git a/libensemble/tests/unit_tests/test_executor.py b/libensemble/tests/unit_tests/test_executor.py index a3a8d8cc1..edea3d94c 100644 --- a/libensemble/tests/unit_tests/test_executor.py +++ b/libensemble/tests/unit_tests/test_executor.py @@ -2,12 +2,13 @@ # Integration Test of executor module for libensemble # Test does not require running full libensemble import os +import re import sys import time import pytest import socket from libensemble.resources.resources import ResourcesException -from libensemble.executors.executor import Executor, ExecutorException +from libensemble.executors.executor import Executor, ExecutorException, TimeoutExpired from libensemble.executors.executor import NOT_STARTED_STATES @@ -195,6 +196,39 @@ def test_launch_and_poll(): assert task.run_attempts == 1, "task.run_attempts should be 1. Returned " + str(task.run_attempts) +def test_launch_and_wait(): + """ Test of launching and waiting on task""" + print("\nTest: {}\n".format(sys._getframe().f_code.co_name)) + setup_executor() + exctr = Executor.executor + cores = NCORES + args_for_sim = 'sleep 1' + task = exctr.submit(calc_type='sim', num_procs=cores, app_args=args_for_sim) + task.wait() + assert task.finished, "task.finished should be True. Returned " + str(task.finished) + assert task.state == 'FINISHED', "task.state should be FINISHED. Returned " + str(task.state) + task.wait() # Already complete + assert task.finished, "task.finished should be True. Returned " + str(task.finished) + assert task.state == 'FINISHED', "task.state should be FINISHED. Returned " + str(task.state) + + +def test_launch_and_wait_timeout(): + """ Test of launching and waiting on task timeout (and kill)""" + print("\nTest: {}\n".format(sys._getframe().f_code.co_name)) + setup_executor() + exctr = Executor.executor + cores = NCORES + args_for_sim = 'sleep 5' + task = exctr.submit(calc_type='sim', num_procs=cores, app_args=args_for_sim) + try: + task.wait(timeout=0.5) + except TimeoutExpired: + assert not task.finished, "task.finished should be False. Returned " + str(task.finished) + task.kill() + assert task.finished, "task.finished should be True. Returned " + str(task.finished) + assert task.state == 'USER_KILLED', "task.state should be USER_KILLED. Returned " + str(task.state) + + def test_launch_wait_on_run(): """ Test of launching task with wait_on_run """ print("\nTest: {}\n".format(sys._getframe().f_code.co_name)) @@ -473,6 +507,12 @@ def test_launch_no_app(): assert e.args[0] == 'Default sim app is not set' else: assert 0 + try: + _ = exctr.submit(num_procs=cores, app_args=args_for_sim) + except ExecutorException as e: + assert e.args[0] == 'Either app_name or calc_type must be set' + else: + assert 0 def test_kill_task_with_no_submit(): @@ -490,13 +530,17 @@ def test_kill_task_with_no_submit(): assert 0 # Create a task directly with no submit (Not supported for users) + # Debatably make taskID 0 as executor should be deleted if use setup function. + # But this allows any task ID. + exp_msg = ('Attempting to kill task libe_task_my_simtask.x_.+that has ' + 'no process ID - check tasks been launched') + exp_re = re.compile(exp_msg) myapp = exctr.sim_default_app task1 = Task(app=myapp, stdout='stdout.txt') try: exctr.kill(task1) except ExecutorException as e: - assert e.args[0][:50] == 'Attempting to kill task task_my_simtask.x.simfunc_' - assert e.args[0][52:] == ' that has no process ID - check tasks been launched' + assert bool(re.match(exp_re, e.args[0])) else: assert 0 @@ -508,13 +552,15 @@ def test_poll_task_with_no_submit(): exctr = Executor.executor # Create a task directly with no submit (Not supported for users) + exp_msg = ('Polled task libe_task_my_simtask.x_.+ ' + 'has no process ID - check tasks been launched') + exp_re = re.compile(exp_msg) myapp = exctr.sim_default_app task1 = Task(app=myapp, stdout='stdout.txt') try: task1.poll() except ExecutorException as e: - assert e.args[0][:38] == 'Polled task task_my_simtask.x.simfunc_' - assert e.args[0][40:] == ' has no process ID - check tasks been launched' + assert bool(re.match(exp_re, e.args[0])) else: assert 0 @@ -556,9 +602,45 @@ def test_retries_run_fail(): assert task.run_attempts == 5, "task.run_attempts should be 5. Returned " + str(task.run_attempts) +def test_register_apps(): + print("\nTest: {}\n".format(sys._getframe().f_code.co_name)) + setup_executor() # This registers an app my_simtask.x (default sim) + exctr = Executor.executor + exctr.register_calc(full_path='/path/to/fake_app1.x', app_name='fake_app1') + exctr.register_calc(full_path='/path/to/fake_app2.py', app_name='fake_app2') + + # Check selected attributes + app = exctr.get_app('my_simtask.x') + assert app.name == 'my_simtask.x' + assert app.gname == 'libe_app_my_simtask.x' + + app = exctr.get_app('fake_app1') + assert app.name == 'fake_app1' + assert app.gname == 'libe_app_fake_app1' + assert app.exe == 'fake_app1.x' + assert app.calc_dir == '/path/to' + + app = exctr.get_app('fake_app2') + assert app.name == 'fake_app2' + assert app.gname == 'libe_app_fake_app2' + + py_exe, app_exe = app.full_path.split() + assert os.path.split(py_exe)[1].startswith('python') + assert app_exe == '/path/to/fake_app2.py' + + try: + app = exctr.get_app('fake_app3') + except ExecutorException as e: + assert e.args[0] == 'Application fake_app3 not found in registry' + # Ordering of dictionary may vary + # assert e.args[1] == "Registered applications: ['my_simtask.x', 'fake_app1', 'fake_app2']" + + if __name__ == "__main__": # setup_module(__file__) test_launch_and_poll() + test_launch_and_wait() + test_launch_and_wait_timeout() test_launch_wait_on_run() test_kill_on_file() test_kill_on_timeout() @@ -576,4 +658,5 @@ def test_retries_run_fail(): test_task_failure() test_retries_launch_fail() test_retries_run_fail() + test_register_apps() # teardown_module(__file__) diff --git a/libensemble/tests/unit_tests/test_persistent_aposmm.py b/libensemble/tests/unit_tests/test_persistent_aposmm.py index 01939aa41..0f9bf8d98 100644 --- a/libensemble/tests/unit_tests/test_persistent_aposmm.py +++ b/libensemble/tests/unit_tests/test_persistent_aposmm.py @@ -5,7 +5,7 @@ libE_specs = {'comm': {}} -def test_persis_apossm_localopt_test(): +def test_persis_aposmm_localopt_test(): _, _, gen_specs_0, _, _ = setup.hist_setup1() H = np.zeros(4, dtype=[('f', float), ('sim_id', bool), ('dist_to_unit_bounds', float), ('returned', bool)]) @@ -107,6 +107,6 @@ def combined_func(x): if __name__ == "__main__": - test_persis_apossm_localopt_test() + test_persis_aposmm_localopt_test() test_update_history_optimal() test_standalone_persistent_aposmm() diff --git a/libensemble/tests/unit_tests/test_resources.py b/libensemble/tests/unit_tests/test_resources.py index fc18af1fc..0a610b590 100644 --- a/libensemble/tests/unit_tests/test_resources.py +++ b/libensemble/tests/unit_tests/test_resources.py @@ -38,6 +38,11 @@ def teardown_function(function): os.remove('node_list') +def sname(name): + print('sname being set') + return name.split(".", 1)[0] + + # Tests ======================================================================================== # Tests Resources.get_global_nodelist (This requires above tests to work) @@ -87,12 +92,13 @@ def test_get_global_nodelist_frm_lsf_shortform(): def test_get_global_nodelist_standalone(): mynode = socket.gethostname() + exp_node = mynode # sname(mynode) env_resources = EnvResources(nodelist_env_slurm="THIS_ENV_VARIABLE_IS_DEF_NOT_SET", nodelist_env_cobalt="THIS_ENV_VARIABLE_IS_DEF_NOT_SET", nodelist_env_lsf="THIS_ENV_VARIABLE_IS_DEF_NOT_SET", nodelist_env_lsf_shortform="THIS_ENV_VARIABLE_IS_DEF_NOT_SET") global_nodelist = Resources.get_global_nodelist(rundir=os.getcwd(), env_resources=env_resources) - assert global_nodelist == [mynode], "global_nodelist returned does not match expected" + assert global_nodelist == [exp_node], "global_nodelist returned does not match expected" def test_get_global_nodelist_frm_wrklst_file(): @@ -196,7 +202,7 @@ def test_get_local_nodelist_central_mode_remove_libE_proc(): f.write(mynode + '\n') resources = Resources(central_mode=True) - resources.add_comm_info(libE_nodes=mynode) + resources.add_comm_info(libE_nodes=[mynode]) # Now mock up some more stuff - so consistent @@ -283,19 +289,20 @@ def test_get_local_nodelist_distrib_mode(): # assert 0 workerID = 5 - exp_out = [mynode] + exp_node = mynode # sname(mynode) + exp_out = [exp_node] local_nodelist = WorkerResources.get_local_nodelist(num_workers, workerID, resources) assert local_nodelist == exp_out, "local_nodelist returned does not match expected" num_workers = 1 workerID = 1 - exp_out = ['knl-0020', 'knl-0021', 'knl-0022', 'knl-0036', mynode, 'knl-0137', 'knl-0138', 'knl-0139'] + exp_out = ['knl-0020', 'knl-0021', 'knl-0022', 'knl-0036', exp_node, 'knl-0137', 'knl-0138', 'knl-0139'] local_nodelist = WorkerResources.get_local_nodelist(num_workers, workerID, resources) assert local_nodelist == exp_out, "local_nodelist returned does not match expected" num_workers = 4 workerID = 3 - exp_out = [mynode, 'knl-0137'] + exp_out = [exp_node, 'knl-0137'] local_nodelist = WorkerResources.get_local_nodelist(num_workers, workerID, resources) assert local_nodelist == exp_out, "local_nodelist returned does not match expected" @@ -303,12 +310,12 @@ def test_get_local_nodelist_distrib_mode(): num_workers = 16 workerID = 9 - exp_out = [mynode] + exp_out = [exp_node] local_nodelist = WorkerResources.get_local_nodelist(num_workers, workerID, resources) assert local_nodelist == exp_out, "local_nodelist returned does not match expected" workerID = 10 - exp_out = [mynode] + exp_out = [exp_node] local_nodelist = WorkerResources.get_local_nodelist(num_workers, workerID, resources) assert local_nodelist == exp_out, "local_nodelist returned does not match expected" @@ -317,6 +324,7 @@ def test_get_local_nodelist_distrib_mode(): def test_get_local_nodelist_distrib_mode_uneven_split(): mynode = socket.gethostname() + exp_node = mynode # sname(mynode) nodelist_in = ['knl-0020', 'knl-0021', 'knl-0022', 'knl-0036', 'knl-0137', 'knl-0138', 'knl-0139', 'knl-1234'] with open('node_list', 'w') as f: for i, node in enumerate(nodelist_in): @@ -329,7 +337,7 @@ def test_get_local_nodelist_distrib_mode_uneven_split(): # May not be at head of list - should perhaps be warning or enforced workerID = 2 - exp_out = ['knl-0137', mynode, 'knl-0138', 'knl-0139'] + exp_out = ['knl-0137', exp_node, 'knl-0138', 'knl-0139'] local_nodelist = WorkerResources.get_local_nodelist(num_workers, workerID, resources) assert local_nodelist == exp_out, "local_nodelist returned does not match expected" os.remove('node_list') @@ -387,6 +395,34 @@ def test_worker_resources(): assert worker.workers_per_node == 2, 'worker.workers_per_node does not match' +def test_map_workerid_to_index(): + num_workers = 4 + zero_resource_list = [] + + for workerID in range(1, num_workers+1): + index = WorkerResources.map_workerid_to_index(num_workers, workerID, zero_resource_list) + assert index == workerID - 1, "index incorrect. Received: " + str(index) + + zero_resource_list = [1] + for workerID in range(2, num_workers+1): + index = WorkerResources.map_workerid_to_index(num_workers, workerID, zero_resource_list) + assert index == workerID - 2, "index incorrect. Received: " + str(index) + + zero_resource_list = [1, 2] + for workerID in range(3, num_workers+1): + index = WorkerResources.map_workerid_to_index(num_workers, workerID, zero_resource_list) + assert index == workerID - 3, "index incorrect. Received: " + str(index) + + zero_resource_list = [1, 3] + workerID = 2 + index = WorkerResources.map_workerid_to_index(num_workers, workerID, zero_resource_list) + assert index == 0, "index incorrect. Received: " + str(index) + + workerID = 4 + index = WorkerResources.map_workerid_to_index(num_workers, workerID, zero_resource_list) + assert index == 1, "index incorrect. Received: " + str(index) + + if __name__ == "__main__": setup_standalone_run() @@ -405,5 +441,6 @@ def test_worker_resources(): test_get_local_nodelist_distrib_mode_uneven_split() test_worker_resources() + test_map_workerid_to_index() teardown_standalone_run() diff --git a/libensemble/tests/unit_tests/test_sim_dir_properties.py b/libensemble/tests/unit_tests/test_sim_dir_properties.py index 7b3c345e2..204af4ba5 100644 --- a/libensemble/tests/unit_tests/test_sim_dir_properties.py +++ b/libensemble/tests/unit_tests/test_sim_dir_properties.py @@ -76,54 +76,6 @@ def __init__(self, libE_specs, prefix, startdir, loc_stack): shutil.rmtree(dir) -def test_copy_back_exception(): - """ Test _copy_back handling of FileExistsError with certain - settings""" - class FakeWorker: - """ Enough information to test _copy_back() """ - def __init__(self, libE_specs, prefix, startdir, loc_stack): - self.libE_specs = libE_specs - self.prefix = prefix - self.startdir = startdir - self.loc_stack = loc_stack - - inputdir = './calc' - copybackdir = './calc_back' - inputfile = './calc/file' - - for dir in [inputdir, copybackdir]: - os.makedirs(dir, exist_ok=True) - - libE_specs = {'sim_dirs_make': False, 'ensemble_dir_path': inputdir, - 'ensemble_copy_back': True} - - ls = LocationStack() - ls.register_loc('test', inputfile) - fake_worker = FakeWorker(libE_specs, inputdir, '.', ls) - - # Testing catch and continue - for i in range(2): - Worker._copy_back(fake_worker) - assert 'file' in os.listdir(copybackdir), \ - 'File not copied back to starting dir' - - libE_specs = {'sim_dirs_make': True, 'ensemble_dir_path': inputdir, - 'ensemble_copy_back': True} - fake_worker = FakeWorker(libE_specs, inputdir, '.', ls) - - flag = 1 - - # Testing catch and raise - try: - Worker._copy_back(fake_worker) - except FileExistsError: - flag = 0 - assert flag == 0 - - for dir in [inputdir, copybackdir]: - shutil.rmtree(dir) - - def test_worker_dirs_but_no_sim_dirs(): """Test Worker._make_calc_dir() directory structure without sim_dirs""" inputdir = './calc' @@ -204,6 +156,5 @@ def test_loc_stack_FileExists_exceptions(): test_range_two_ranges() test_range_mixes() test_copy_back() - test_copy_back_exception() test_worker_dirs_but_no_sim_dirs() test_loc_stack_FileExists_exceptions() diff --git a/libensemble/tools/alloc_support.py b/libensemble/tools/alloc_support.py index 3a39692a8..19e5718b5 100644 --- a/libensemble/tools/alloc_support.py +++ b/libensemble/tools/alloc_support.py @@ -3,7 +3,11 @@ def avail_worker_ids(W, persistent=None): - "Get available workers (active == 0), filtered by persis_state." + """Returns available workers (``active == 0``), as an array, filtered by ``persis_state``. + + :param W: :doc:`Worker array<../data_structures/worker_array>` + :param persistent: Optional Boolean. If specified, also return workers with given persis_state. + """ if persistent is None: return W['worker_id'][W['active'] == 0] if persistent: @@ -14,22 +18,39 @@ def avail_worker_ids(W, persistent=None): def count_gens(W): - "Return the number of generators in a set of workers." + """Return the number of active generators in a set of workers. + + :param W: :doc:`Worker array<../data_structures/worker_array>` + """ return sum(W['active'] == EVAL_GEN_TAG) def test_any_gen(W): - "Return True if a generator worker is active." + """Return True if a generator worker is active. + + :param W: :doc:`Worker array<../data_structures/worker_array>` + """ return any(W['active'] == EVAL_GEN_TAG) def count_persis_gens(W): - "Return the number of persistent generators in a set of workers." + """Return the number of active persistent generators in a set of workers. + + :param W: :doc:`Worker array<../data_structures/worker_array>` + """ return sum(W['persis_state'] == EVAL_GEN_TAG) def sim_work(Work, i, H_fields, H_rows, persis_info, **libE_info): - "Add sim work record to work array." + """Add sim work record to given Work array. + + :param W: :doc:`Worker array<../data_structures/worker_array>` + :param i: Worker ID. + :param H_fields: Which fields from H to send + :param persis_info: current persis_info dictionary + + :returns: None + """ libE_info['H_rows'] = H_rows Work[i] = {'H_fields': H_fields, 'persis_info': persis_info, @@ -38,7 +59,15 @@ def sim_work(Work, i, H_fields, H_rows, persis_info, **libE_info): def gen_work(Work, i, H_fields, H_rows, persis_info, **libE_info): - "Add gen work record to work array." + """Add gen work record to given Work array. + + :param W: :doc:`Worker array<../data_structures/worker_array>` + :param i: Worker ID. + :param H_fields: Which fields from H to send + :param persis_info: current persis_info dictionary + + :returns: None + """ libE_info['H_rows'] = H_rows Work[i] = {'H_fields': H_fields, 'persis_info': persis_info, diff --git a/libensemble/tools/fields_keys.py b/libensemble/tools/fields_keys.py index b54e74432..0c4549704 100644 --- a/libensemble/tools/fields_keys.py +++ b/libensemble/tools/fields_keys.py @@ -27,13 +27,23 @@ 'out', # 'user'] # -libE_spec_calc_dir_keys = ['sim_dirs_make', - 'ensemble_copy_back', - 'sim_dir_copy_files', +libE_spec_calc_dir_misc = ['ensemble_copy_back', 'ensemble_dir_path', - 'use_worker_dirs', - 'sim_dir_symlink_files', - 'sim_input_dir'] + 'use_worker_dirs'] + +libE_spec_sim_dir_keys = ['sim_dirs_make', + 'sim_dir_copy_files', + 'sim_dir_symlink_files', + 'sim_input_dir'] + +libE_spec_gen_dir_keys = ['gen_dirs_make', + 'gen_dir_copy_files', + 'gen_dir_symlink_files', + 'gen_input_dir'] + +libE_spec_calc_dir_combined = libE_spec_calc_dir_misc + \ + libE_spec_sim_dir_keys + \ + libE_spec_gen_dir_keys allowed_libE_spec_keys = ['abort_on_exception', # 'authkey', # @@ -49,4 +59,5 @@ 'save_H_and_persis_on_abort', # 'workerID', # 'worker_timeout', # - 'worker_cmd'] + libE_spec_calc_dir_keys + 'zero_resource_workers', # + 'worker_cmd'] + libE_spec_calc_dir_combined diff --git a/libensemble/tools/gen_support.py b/libensemble/tools/gen_support.py index 344468f7b..2f129f133 100644 --- a/libensemble/tools/gen_support.py +++ b/libensemble/tools/gen_support.py @@ -1,15 +1,23 @@ from libensemble.message_numbers import STOP_TAG, PERSIS_STOP, UNSET_TAG, EVAL_GEN_TAG -def sendrecv_mgr_worker_msg(comm, output, status=None): +def sendrecv_mgr_worker_msg(comm, output): """Send message from worker to manager and receive response. + + :param comm: libEnsemble communicator object + :param output: Output array to be sent to manager + :returns: message tag, Work dictionary, calc_in array """ send_mgr_worker_msg(comm, output) - return get_mgr_worker_msg(comm, status=status) + return get_mgr_worker_msg(comm) def send_mgr_worker_msg(comm, output): """Send message from worker to manager. + + :param comm: libEnsemble communicator object + :param output: Output array to be sent to manager + :returns: None """ D = {'calc_out': output, 'libE_info': {'persistent': True}, @@ -19,8 +27,11 @@ def send_mgr_worker_msg(comm, output): comm.send(EVAL_GEN_TAG, D) -def get_mgr_worker_msg(comm, status=None): +def get_mgr_worker_msg(comm): """Get message to worker from manager. + + :param comm: libEnsemble communicator object + :returns: message tag, Work dictionary, calc_in array """ tag, Work = comm.recv() if tag in [STOP_TAG, PERSIS_STOP]: diff --git a/libensemble/tools/tools.py b/libensemble/tools/tools.py index 3909b84a2..96cfa3bd9 100644 --- a/libensemble/tools/tools.py +++ b/libensemble/tools/tools.py @@ -35,15 +35,14 @@ ('\n' + 79*'*' + '\n' + "User generator script will be creating sim_id.\n" + "Take care to do this sequentially.\n" + - "Also, any information given back for existing sim_id values will be overwritten!\n" + - "So everything in gen_specs['out'] should be in gen_specs['in']!" + + "Information given back to the gen_f for existing sim_id values may be overwritten!\n" + '\n' + 79*'*' + '\n\n') # ==================== Ensemble directory re-use error ========================= -_USER_SIM_DIR_WARNING = \ +_USER_CALC_DIR_WARNING = \ ('\n' + 79*'*' + '\n' + - "libEnsemble attempted to reuse {} as a parent directory for sim_dirs.\n" + + "libEnsemble attempted to reuse {} as a parent directory for calc dirs.\n" + "If allowed to continue, previous results may have been overwritten!\n" + "Resolve this by ensuring libE_specs['ensemble_dir_path'] is unique for each run." + '\n' + 79*'*' + '\n\n') diff --git a/postproc_scripts/compare_npy.py b/postproc_scripts/compare_npy.py index c408552ce..b1e38ffae 100755 --- a/postproc_scripts/compare_npy.py +++ b/postproc_scripts/compare_npy.py @@ -2,34 +2,58 @@ '''Script to compare libEnsemble history arrays in files. +E.g., ./compare_npy.py out1.npy out2.npy + If two *.npy files are provided they are compared with each other. If one *.npy file is provided if is compared with a hard-coded expected file (by default located at ../expected.npy) -Default NumPy tolerances are used for comparison (rtol=1e-05, atol=1e-08) and + +Default tolerances used for comparison are (rtol=1e-05, atol=1e-08). These +can be overwritten with -r (--rtol) and -a (--atol) flags. + +E.g., ./compare_npy.py out1.npy out2.npy -r 1e-03 + Nans compare as equal. Variable fields (such as those containing a time) -are ignored. -''' +are ignored. In some cases you may have to ignore further user-defined fields +''' import sys import numpy as np +import argparse -if len(sys.argv) > 2: - results = np.load(sys.argv[1]) - exp_results = np.load(sys.argv[2]) -elif len(sys.argv) > 1: - results = np.load(sys.argv[1]) - exp_results_file = "../expected.npy" - exp_results = np.load(exp_results_file) -else: - print('You need to supply an .npy file - aborting') - sys.exit() +desc = "Script to compare libEnsemble history arrays in files" +exmple = '''examples: + + ./compare_npy.py out1.npy out2.npy + ./compare_npy.py out1.npy out2.npy --rtol 1e-03 --atol 1e-06 + ''' exclude_fields = ['gen_worker', 'sim_worker', 'gen_time', 'given_time'] # list of fields to ignore locate_mismatch = True +parser = argparse.ArgumentParser(description=desc, epilog=exmple, + formatter_class=argparse.RawDescriptionHelpFormatter) +parser.add_argument('-r', '--rtol', dest='rtol', type=float, default=1e-05, help='rel. tolerance') +parser.add_argument('-a', '--atol', dest='atol', type=float, default=1e-08, help='abs. tolerance') +parser.add_argument('args', nargs='*', help='*.npy files to compare') +args = parser.parse_args() + +rtol = args.rtol +atol = args.atol +files = args.args + +if len(files) >= 1: + results = np.load(files[0]) + exp_results = np.load(files[1]) if len(files) >= 2 else np.load("../expected.npy") +else: + parser.print_help() + sys.exit() + compare_fields = tuple(filter(lambda x: x not in exclude_fields, exp_results.dtype.names)) +match = all([np.allclose(exp_results[name], results[name], + rtol=rtol, atol=atol, equal_nan=True) + for name in compare_fields]) -match = all([np.allclose(exp_results[name], results[name], equal_nan=True) for name in compare_fields]) print('Compare results: {}\n'.format(match)) if not locate_mismatch: @@ -38,6 +62,7 @@ if not match: for name in compare_fields: for i in range(len(results)): - assert np.isclose(exp_results[name][i], results[name][i], equal_nan=True), \ + assert np.allclose(exp_results[name][i], + results[name][i], rtol=rtol, atol=atol, equal_nan=True), \ 'Mismatch in row ' + str(i) + ' field: ' + name + '. ' \ + str(exp_results[name][i]) + ' ' + str(results[name][i]) diff --git a/postproc_scripts/plot_pareto_3d.py b/postproc_scripts/plot_pareto_3d.py index 0c1fb33b8..6795896db 100755 --- a/postproc_scripts/plot_pareto_3d.py +++ b/postproc_scripts/plot_pareto_3d.py @@ -3,6 +3,11 @@ import sys import matplotlib.pyplot as plt +# The following is not explicitly called but is needed for 3d plotting to work +# with older versions of python/matplotlib. It is not needed for python3.8 with +# matplotlib version 3.2.1. +from mpl_toolkits import mplot3d # noqa + # Loop through objective points in f and extract the Pareto front. # input: f is a list (dimensions n X p) of n p-dimensional objective points. diff --git a/postproc_scripts/readme.rst b/postproc_scripts/readme.rst index e800478e0..a6a447bf9 100644 --- a/postproc_scripts/readme.rst +++ b/postproc_scripts/readme.rst @@ -25,7 +25,7 @@ Results analysis scripts * ``print_npy.py``: Prints to screen from a given ``*.npy`` file containing a NumPy structured array. Use ``done`` to print only the lines containing - ``''returned'`` points. Example:: + ``'returned'`` points. Example:: ./print_npy.py run_libe_forces_results_History_length=1000_evals=8.npy done diff --git a/setup.py b/setup.py index de05e315b..df23f3c5e 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ def run_tests(self): setup( name='libensemble', - version='0.7.0', + version='0.7.1', description='Library to coordinate the concurrent evaluation of dynamic ensembles of calculations', url='https://github.com/Libensemble/libensemble', author='Jeffrey Larson, Stephen Hudson, Stefan M. Wild, David Bindel and John-Luke Navarro', @@ -53,7 +53,7 @@ def run_tests(self): package_data={'libensemble.sim_funcs.branin': ['known_minima_and_func_values']}, - install_requires=['numpy'], + install_requires=['numpy', 'psutil'], # If run tests through setup.py - downloads these but does not install tests_require=['pytest>=3.1', @@ -64,8 +64,8 @@ def run_tests(self): ], extras_require={ - 'extras': ['scipy', 'nlopt', 'mpi4py', 'petsc', 'petsc4py', 'DFO-LS', 'deap'], - 'docs': ['sphinxcontrib.bibtex']}, + 'extras': ['scipy', 'nlopt', 'mpi4py', 'petsc', 'petsc4py', 'DFO-LS', 'deap', 'mpmath'], + 'docs': ['sphinx', 'sphinxcontrib.bibtex', 'sphinx_rtd_theme']}, classifiers=[ 'Development Status :: 4 - Beta',