From 2254552c1b317207f3a47b02c5cb9f2f6c3cd532 Mon Sep 17 00:00:00 2001 From: jlnav Date: Mon, 14 Feb 2022 12:18:25 -0600 Subject: [PATCH 01/12] initial commit, cleanup start of 1st tutorial --- docs/tutorials/local_sine_tutorial.rst | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/tutorials/local_sine_tutorial.rst b/docs/tutorials/local_sine_tutorial.rst index ed91ca29b..9a3ff24f5 100644 --- a/docs/tutorials/local_sine_tutorial.rst +++ b/docs/tutorials/local_sine_tutorial.rst @@ -6,19 +6,18 @@ This introductory tutorial demonstrates the capability to perform ensembles of calculations in parallel using :doc:`libEnsemble<../introduction>` with Python's multiprocessing. -The foundation of writing libEnsemble routines is accounting for four components: +The foundation of writing libEnsemble routines is accounting for at a minimum three 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 - 4. The calling script, which defines parameters and information about these functions and the libEnsemble task, then begins execution + 1. A :ref:`generator function`, that produces values for simulations + 2. A :ref:`simulator function`, performs simulations based on values from the generator function + 3. A :doc:`calling script<../libe_module>`, for defining settings, fields, and functions, then starting the run libEnsemble initializes a *manager* process and as many *worker* processes as the user requests. The manager coordinates data transfer between workers and assigns -each unit of work, consisting of a ``gen_f`` or ``sim_f`` function to run and -accompanying data. These functions can perform their work in-line with Python or by -launching and controlling user applications with libEnsemble's :ref:`executor`. -Workers then pass results back to the manager. +each units of work, consisting of a function to run and +accompanying data. These functions perform their work in-line with Python and/or +launch and control user applications with libEnsemble's :ref:`Executors`. +Workers pass results back to the manager. For this tutorial, we'll write our ``gen_f`` and ``sim_f`` entirely in Python without other applications. Our ``gen_f`` will produce uniform randomly sampled From 4b3ef09a9360180bae6b13ad6418c278962e3515 Mon Sep 17 00:00:00 2001 From: jlnav Date: Tue, 15 Feb 2022 14:59:46 -0600 Subject: [PATCH 02/12] add css and html for solution blocks, add exercises to sine tutorial --- docs/_static/my_theme.css | 13 ++ docs/_templates/page.html | 14 ++ docs/tutorials/local_sine_tutorial.rst | 182 +++++++++++++++++++------ 3 files changed, 170 insertions(+), 39 deletions(-) create mode 100644 docs/_templates/page.html diff --git a/docs/_static/my_theme.css b/docs/_static/my_theme.css index bc3f00f33..f2c020405 100644 --- a/docs/_static/my_theme.css +++ b/docs/_static/my_theme.css @@ -1,3 +1,16 @@ .wy-nav-content { max-width: 850px !important; } + +.toggle .header { + display: block; + clear: both; +} + +.toggle .header:after { + content: " ▶"; +} + +.toggle .header.open:after { + content: " ▼"; +} diff --git a/docs/_templates/page.html b/docs/_templates/page.html new file mode 100644 index 000000000..b8163bdde --- /dev/null +++ b/docs/_templates/page.html @@ -0,0 +1,14 @@ +{% extends "!page.html" %} + +{% block footer %} + +{% endblock %} diff --git a/docs/tutorials/local_sine_tutorial.rst b/docs/tutorials/local_sine_tutorial.rst index 9a3ff24f5..aae3f1d8e 100644 --- a/docs/tutorials/local_sine_tutorial.rst +++ b/docs/tutorials/local_sine_tutorial.rst @@ -19,9 +19,9 @@ accompanying data. These functions perform their work in-line with Python and/or launch and control user applications with libEnsemble's :ref:`Executors`. Workers pass results back to the manager. -For this tutorial, we'll write our ``gen_f`` and ``sim_f`` entirely in Python -without other applications. Our ``gen_f`` will produce uniform randomly sampled -values, and our ``sim_f`` will calculate the sine of each. By default we don't +For this tutorial, we'll write our generator and simulator functions entirely in Python +without other applications. Our generator will produce uniform randomly sampled +values, and our simulator will calculate the sine of each. By default we don't need to write a new allocation function. All generated and simulated values alongside other parameters are stored in :ref:`H`, the history array. @@ -32,12 +32,9 @@ Getting started libEnsemble and its functions are written entirely in Python_. Let's make sure Python 3 is installed. -Note: If you have a Python version-specific virtual environment set up (e.g., conda), -then ``python`` and ``pip`` will work in place of ``python3`` and ``pip3``. - .. code-block:: bash - $ python3 --version + $ python --version Python 3.7.0 # This should be >= 3.7 .. _Python: https://www.python.org/ @@ -48,9 +45,9 @@ libraries with .. code-block:: bash - $ pip3 install numpy - $ pip3 install libensemble - $ pip3 install matplotlib # Optional + $ pip install numpy + $ pip install libensemble + $ pip install matplotlib # Optional If your system doesn't allow you to perform these installations, try adding ``--user`` to the end of each command. @@ -61,28 +58,28 @@ If your system doesn't allow you to perform these installations, try adding Generator function ------------------ -Let's begin the coding portion of this tutorial by writing our -:ref:`gen_f`, or generator function. +Let's begin the coding portion of this tutorial by writing our generator function, +or :ref:`gen_f`. An available libEnsemble worker will call this generator function with the following parameters: -* :ref:`H`: The history array. Updated by the workers - with ``gen_f`` and ``sim_f`` inputs and outputs, then returned to the user. - libEnsemble passes ``H`` to the generator function in case the user wants to - generate new values based on previous data. +* :ref:`H`: The History array. A NumPy structured array + for storing information about each point generated and processed in the ensemble. + libEnsemble passes a selection of ``H`` to the generator function in case the user + wants to generate new values based on previous data. * :ref:`persis_info`: Dictionary with worker-specific - information. In our case this dictionary contains mechanisms called random - streams for generating random numbers. + information. In our case, this dictionary contains NumPy Random Stream objects + for generating random numbers. -* :ref:`gen_specs`: Dictionary with user-defined and - operational parameters for the ``gen_f``. The user places function-specific - parameters such as boundaries and batch sizes within the nested ``user`` dictionary, - while parameters that libEnsemble depends on to operate the ``gen_f`` are placed - outside ``user``. +* :ref:`gen_specs`: Dictionary with user-defined fields and + parameters for the generator. Customizable parameters such as boundaries and batch + sizes are placed within the nested ``user`` dictionary, while input/output fields + and other specifications that libEnsemble depends on to operate the generator are + placed outside ``user``. -Later on, we'll populate ``gen_specs`` and ``persis_info`` in our calling script. +Later on, we'll populate ``gen_specs`` and ``persis_info`` when we initialize libEnsemble. For now, create a new Python file named ``generator.py``. Write the following: @@ -93,12 +90,12 @@ For now, create a new Python file named ``generator.py``. Write the following: import numpy as np def gen_random_sample(H, persis_info, gen_specs, _): - # underscore parameter for internal/testing arguments + # underscore parameter for advanced arguments - # Pull out user parameters to perform calculations + # Pull out user parameters user_specs = gen_specs['user'] - # Get lower and upper bounds from gen_specs + # Get lower and upper bounds lower = user_specs['lower'] upper = user_specs['upper'] @@ -106,10 +103,10 @@ For now, create a new Python file named ``generator.py``. Write the following: num = len(lower) batch_size = user_specs['gen_batch_size'] - # Create array of 'batch_size' zeros + # Create empty array of 'batch_size' zeros. Array dtype should match 'out' fields out = np.zeros(batch_size, dtype=gen_specs['out']) - # Replace those zeros with the random numbers + # Set the 'x' output field to contain random numbers, using random stream out['x'] = persis_info['rand_stream'].uniform(lower, upper, (batch_size, num)) # Send back our output and persis_info @@ -117,13 +114,44 @@ For now, create a new Python file named ``generator.py``. Write the following: Our function creates ``batch_size`` random numbers uniformly distributed between the ``lower`` and ``upper`` bounds. A random stream -from ``persis_info`` is used to generate these values, where they are placed -into a NumPy array that meets the specifications from ``gen_specs['out']``. +from ``persis_info`` is used to generate these values, which are then placed +into an output NumPy array that meets the specifications from ``gen_specs['out']``. + +Exercise +^^^^^^^^ + +Write a simple generator function that instead produces random integers, using +the ``numpy.random.RandomState.randint(low, high, size)`` function. + +.. container:: toggle + + .. container:: header + + **Click Here for Solution** + + .. code-block:: python + :linenos: + + import numpy as np + + def gen_random_ints(H, persis_info, gen_specs, _): + + user_specs = gen_specs['user'] + lower = user_specs['lower'] + upper = user_specs['upper'] + num = len(lower) + batch_size = user_specs['gen_batch_size'] + + out = np.zeros(batch_size, dtype=gen_specs['out']) + out['x'] = persis_info['rand_stream'].randint(lower, upper, (batch_size, num)) + + return out, persis_info + Simulator function ------------------ -Next, we'll write our :ref:`sim_f` or simulator function. Simulator +Next, we'll write our simulator function or :ref:`sim_f`. Simulator functions perform calculations based on values from the generator function. The only new parameter here is :ref:`sim_specs`, which serves a purpose similar to the ``gen_specs`` dictionary. @@ -136,23 +164,48 @@ Create a new Python file named ``simulator.py``. Write the following: import numpy as np - def sim_find_sine(H, persis_info, sim_specs, _): + def sim_find_sine(H_in, persis_info, sim_specs, _): # underscore for internal/testing arguments # Create an output array of a single zero out = np.zeros(1, dtype=sim_specs['out']) # Set the zero to the sine of the input value stored in H - out['y'] = np.sin(H['x']) + out['y'] = np.sin(H_in['x']) # Send back our output and persis_info return out, persis_info -Our simulator function is called by a worker for every value in its batch from +Our simulator function is called by a worker *for every value in its batch* from the generator function. This function calculates the sine of the passed value, then returns it so a worker can log it into ``H``. -Calling script +Exercise +^^^^^^^^ + +Write a simple simulator function that instead calculates the *cosine* of a received +value, using the ``numpy.cos(x)`` function. + +.. container:: toggle + + .. container:: header + + **Click Here for Solution** + + .. code-block:: python + :linenos: + + import numpy as np + + def sim_find_cosine(H, persis_info, gen_specs, _): + + out = np.zeros(1, dtype=sim_specs['out']) + + out['y'] = np.cos(H_in['x']) + + return out, persis_info + +Calling Script -------------- Now we can write the calling script that configures our generator and simulator @@ -285,7 +338,58 @@ script and run ``python3 calling_script.py`` again plt.legend(loc = 'lower right') plt.savefig('tutorial_sines.png') ---- + +Exercise +^^^^^^^^ + +Write a Calling Script with the following specifications: + + 1. Use the Exercise simulator and generator functions instead + 2. Use 8 workers instead of 4 + 3. Set the generator function's lower and upper bounds to -6 and 6, respectively + 4. Increase the generator batch size to 10 + 5. Set libEnsemble to stop execution after 160 *generations* using the ``gen_max`` key + 6. Print an error message if any errors occurred while libEnsemble was running + +.. container:: toggle + + .. container:: header + + **Click Here for Solution** + + .. code-block:: python + :linenos: + + import numpy as np + from libensemble.libE import libE + from generator import gen_random_ints + from simulator import sim_find_cosine + from libensemble.tools import add_unique_random_streams + + nworkers = 8 + libE_specs = {'nworkers': nworkers, 'comms': 'local'} + + gen_specs = {'gen_f': gen_random_ints, + 'out': [('x', float, (1,))], + 'user': { + 'lower': np.array([-6]), + 'upper': np.array([6]), + 'gen_batch_size': 10 + } + } + + sim_specs = {'sim_f': sim_find_sine, + 'in': ['x'], + 'out': [('y', float)]} + + persis_info = add_unique_random_streams({}, nworkers+1) + exit_criteria = {'gen_max': 160} + + H, persis_info, flag = libE(sim_specs, gen_specs, exit_criteria, persis_info, + libE_specs=libE_specs) + + if flag != 0: + print('Oh no! An error occurred!') Next steps ---------- @@ -293,7 +397,7 @@ Next steps The following is another learning exercise based on the above code. libEnsemble with MPI -"""""""""""""""""""" +^^^^^^^^^^^^^^^^^^^^ MPI_ is a standard interface for parallel computing, implemented in libraries such as MPICH_ and used at extreme scales. MPI potentially allows libEnsemble's @@ -312,7 +416,7 @@ mpi4py_ docs for more information. Verify that MPI has installed correctly with ``mpirun --version``. Modifying the calling script -"""""""""""""""""""""""""""" +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Only a few changes are necessary to make our code MPI-compatible. Modify the top of the calling script as follows: From 303cce0cb35f30c608b073b4a8f5888c81d488f4 Mon Sep 17 00:00:00 2001 From: jlnav Date: Mon, 21 Feb 2022 16:54:14 -0600 Subject: [PATCH 03/12] some rephrasing, reorganization, simplification throughout --- docs/tutorials/executor_forces_tutorial.rst | 84 +++++++++------------ docs/tutorials/local_sine_tutorial.rst | 8 +- 2 files changed, 40 insertions(+), 52 deletions(-) diff --git a/docs/tutorials/executor_forces_tutorial.rst b/docs/tutorials/executor_forces_tutorial.rst index 746f585bb..57e64c3ab 100644 --- a/docs/tutorials/executor_forces_tutorial.rst +++ b/docs/tutorials/executor_forces_tutorial.rst @@ -5,7 +5,7 @@ Executor with Electrostatic Forces This tutorial highlights libEnsemble's capability to execute and monitor external scripts or user applications within simulation or generator functions using the :doc:`executor<../executor/overview>`. In this tutorial, -our calling script registers an external C executable that simulates +our calling script registers a compiled executable that simulates electrostatic forces between a collection of particles. The ``sim_f`` routine then launches and polls this executable. @@ -15,7 +15,7 @@ hard-coding such commands within user scripts isn't portable. Furthermore, many systems like Argonne's :doc:`Theta<../platforms/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. +libEnsemble's Executor was developed to directly address such issues. Getting Started --------------- @@ -47,12 +47,12 @@ generation functions and call libEnsemble. Create a Python file containing: from libensemble.libE import libE from libensemble.gen_funcs.sampling import uniform_random_sample - from libensemble.tools import parse_args, add_unique_random_streams + from libensemble.tools import parse_args from libensemble.executors.mpi_executor import MPIExecutor nworkers, is_manager, libE_specs, _ = parse_args() # Convenience function - # Create executor and register sim to it + # Initialize executor instance exctr = MPIExecutor() # Create empty simulation input directory @@ -61,21 +61,20 @@ generation functions and call libEnsemble. Create a Python file containing: # Register simulation executable with executor sim_app = os.path.join(os.getcwd(), 'forces.x') - exctr.register_app(full_path=sim_app, calc_type='sim') + exctr.register_app(full_path=sim_app, app_name='forces') On line 4 we import our not-yet-written ``sim_f``. We also import necessary libEnsemble components and some :doc:`convenience functions<../utilities>`. -For example, our script can use the number of workers (``nworkers``), a boolean -determining if the process is the manager process (``is_manager``), and a default -:ref:`libE_specs` with a call to the ``parse_args()`` -convenience function. +With a call to the ``parse_args()`` convenience function, our script detects the +number of workers ``nworkers``, a boolean determining if the process is the manager +process ``is_manager``, and a default :ref:`libE_specs` -Next we define our executor class instance. This instance can be customized +Next we define our Executor class instance. This instance can be customized with many of the settings defined :doc:`here<../executor/mpi_executor>`. We'll register our simulation with the executor and use the same -instance within our ``sim_f``. +instance within our simulation function. -libEnsemble can perform and write every simulation (within the ensemble) in a +libEnsemble can perform every simulation instasnce (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. @@ -111,12 +110,12 @@ Next define the :ref:`sim_specs` and } } -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. +These dictionaries configure our generation function and our simulation +function respectively as the ``uniform_random_sample`` and +``run_forces`` functions. Our generation function will generate random seeds to use within +each simulation function call. -After some additions to ``libE_specs`` and defining our ``exit_criteria`` and +After additional settings in ``libE_specs``, defining our ``exit_criteria``, and initializing ``persis_info``, our script calls the main :doc:`libE<../libe_module>` routine: @@ -124,11 +123,11 @@ After some additions to ``libE_specs`` and defining our ``exit_criteria`` and :linenos: libE_specs['save_every_k_gens'] = 1000 # Save every K steps - libE_specs['sim_input_dir'] = './sim' # Sim dir to be copied for each worker + libE_specs['sim_input_dir'] = './sim' # Sim input dir to be copied for each worker exit_criteria = {'sim_max': 8} - persis_info = add_unique_random_streams({}, nworkers + 1) + persis_info = {} H, persis_info, flag = libE(sim_specs, gen_specs, exit_criteria, persis_info=persis_info, libE_specs=libE_specs) @@ -136,12 +135,13 @@ After some additions to ``libE_specs`` and defining our ``exit_criteria`` and Simulation Function ------------------- -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 +Our simulation function is where we'll use libEnsemble's executor to configure and submit +our compiled app for execution. We'll poll this task's state while +it runs, and once we've detected it has finished we'll send any results or exit statuses back to the manager. -Create another Python file named ``forces_simf.py`` containing: +Create another Python file named ``forces_simf.py`` containing some imports +and utility functions for starters: .. code-block:: python :linenos: @@ -173,11 +173,7 @@ Create another Python file named ``forces_simf.py`` containing: line = "" # In case file is empty or not yet created return line -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, +Now we can write the body of the simulation function. We'll write the function definition, extract our parameters from ``sim_specs``, define a random seed, and use ``perturb()`` to randomize our particle counts. @@ -201,40 +197,37 @@ extract our parameters from ``sim_specs``, define a random seed, and use # To give a random variance of work-load sim_particles = perturb(sim_particles, seed, particle_variance) -Next we will instantiate our executor and submit our registered application for +Next we will re-instantiate our executor and submit our registered application for execution. .. code-block:: python :linenos: :emphasize-lines: 2,9,10,12,13 - # Use pre-defined executor object + # Use previously parameterized executor object exctr = Executor.executor # Arguments for our registered simulation args = str(int(sim_particles)) + ' ' + str(sim_timesteps) + ' ' + str(seed) + ' ' + str(kill_rate) # Submit our simulation for execution. - if cores: - task = exctr.submit(calc_type='sim', num_procs=cores, app_args=args, - stdout='out.txt', stderr='err.txt', wait_on_start=True) - else: - task = exctr.submit(calc_type='sim', app_args=args, stdout='out.txt', - stderr='err.txt', wait_on_start=True) - -In each executor ``submit()`` routine, we define the type of calculation being -performed, optionally the number of processors to run the task on, additional + task = exctr.submit(app_name='forces', num_procs=cores, app_args=args, + stdout='out.txt', stderr='err.txt', wait_on_start=True) + +In each executor ``submit()`` routine, we specify the registered application, +optionally the number of MPI processes to run the task with, additional arguments for the simulation code, and files for ``stdout`` and ``stderr`` -output. The ``wait_on_start`` argument pauses ``sim_f`` execution until the task -is confirmed to be running. See the :doc:`docs<../executor/mpi_executor>` +output. The ``wait_on_start`` argument causes this statement to block until the +application is confirmed running. See the :doc:`docs<../executor/mpi_executor>` for more information about these and other options. -The rest of our ``sim_f`` polls the :ref:`task`'s +The rest of our simulation function polls the :ref:`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. -We can poll the task and kill it in certain circumstances: +We can poll the task and kill it if the output file contains some value or if +the task's runtime exceeds the time limit: .. code-block:: python :linenos: @@ -265,15 +258,12 @@ user based on the task's final state: if task.finished: if task.state == 'FINISHED': - print("Task {} completed".format(task.name)) calc_status = WORKER_DONE if read_last_line(filepath) == "kill": print("Warning: Task complete but marked bad (kill flag in forces.stat)") elif task.state == 'FAILED': - print("Warning: Task {} failed: Error code {}".format(task.name, task.errcode)) calc_status = TASK_FAILED elif task.state == 'USER_KILLED': - print("Warning: Task {} has been killed".format(task.name)) calc_status = WORKER_KILL else: print("Warning: Task {} in unknown state {}. Error code {}".format(task.name, task.state, task.errcode)) @@ -305,7 +295,7 @@ This completes our ``sim_f`` and calling script. Run libEnsemble with: 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: +The following *optional* lines parse and display some output: .. code-block:: python :linenos: diff --git a/docs/tutorials/local_sine_tutorial.rst b/docs/tutorials/local_sine_tutorial.rst index aae3f1d8e..03498d199 100644 --- a/docs/tutorials/local_sine_tutorial.rst +++ b/docs/tutorials/local_sine_tutorial.rst @@ -394,8 +394,6 @@ Write a Calling Script with the following specifications: Next steps ---------- -The following is another learning exercise based on the above code. - libEnsemble with MPI ^^^^^^^^^^^^^^^^^^^^ @@ -407,7 +405,7 @@ explore modifying the above code to use MPI instead of multiprocessing. We recommend the MPI distribution MPICH_ for this tutorial, which can be found for a variety of systems here_. You also need mpi4py_, which can be installed -with ``pip3 install mpi4py``. If you'd like to use a specific version or +with ``pip install mpi4py``. If you'd like to use a specific version or distribution of MPI instead of MPICH, configure mpi4py with that MPI at installation with ``MPICC= pip3 install mpi4py`` If this doesn't work, try appending ``--user`` to the end of the command. See the @@ -472,7 +470,7 @@ With these changes in place, our libEnsemble code can be run with MPI by .. code-block:: bash - $ mpirun -n 5 python3 calling_script.py + $ mpirun -n 5 python calling_script.py where ``-n 5`` tells ``mpirun`` to produce five processes, one of which will be the master process with the libEnsemble manager and the other four will run @@ -485,7 +483,7 @@ calculations simultaneously. Please read our :doc:`platform guides <../platforms/platforms_index>` for introductions to using libEnsemble on many such machines. -libEnsemble's can launch non-Python user applications and simulations across +libEnsemble's Executors can launch non-Python user applications and simulations across allocated compute resources. Try out this feature with a more-complicated libEnsemble use-case within our :doc:`Electrostatic Forces tutorial <./executor_forces_tutorial>`. From 1b6d43b3e2e7f2eb6650b2343fe998a01334b96d Mon Sep 17 00:00:00 2001 From: Jeffrey Larson Date: Mon, 21 Feb 2022 21:09:15 -0600 Subject: [PATCH 04/12] Two other pip3's --- docs/advanced_installation.rst | 2 +- docs/tutorials/local_sine_tutorial.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/advanced_installation.rst b/docs/advanced_installation.rst index 5e85db225..4a40809fd 100644 --- a/docs/advanced_installation.rst +++ b/docs/advanced_installation.rst @@ -14,7 +14,7 @@ pip --- We always recommend installing in a virtual environment such as Conda. -If not, then use ``pip3``, ``python3`` below. +If not, then use ``pip``, ``python`` below. To install the latest pip release:: diff --git a/docs/tutorials/local_sine_tutorial.rst b/docs/tutorials/local_sine_tutorial.rst index 03498d199..96d0c7f0d 100644 --- a/docs/tutorials/local_sine_tutorial.rst +++ b/docs/tutorials/local_sine_tutorial.rst @@ -407,7 +407,7 @@ We recommend the MPI distribution MPICH_ for this tutorial, which can be found for a variety of systems here_. You also need mpi4py_, which can be installed with ``pip install mpi4py``. If you'd like to use a specific version or distribution of MPI instead of MPICH, configure mpi4py with that MPI at -installation with ``MPICC= pip3 install mpi4py`` If this +installation with ``MPICC= pip install mpi4py`` If this doesn't work, try appending ``--user`` to the end of the command. See the mpi4py_ docs for more information. From a9884cdd8ea37abe6b104d12ca00c0df1cd14ac8 Mon Sep 17 00:00:00 2001 From: jlnav Date: Tue, 22 Feb 2022 14:29:27 -0600 Subject: [PATCH 05/12] small refactors and labels, additional refactoring or executor tutorial, including Exercises --- docs/executor/overview.rst | 2 +- docs/history_output_logging.rst | 2 + docs/tutorials/executor_forces_tutorial.rst | 176 ++++++++++++++++---- 3 files changed, 150 insertions(+), 30 deletions(-) diff --git a/docs/executor/overview.rst b/docs/executor/overview.rst index 5f591d2c6..c0943c9e0 100644 --- a/docs/executor/overview.rst +++ b/docs/executor/overview.rst @@ -19,7 +19,7 @@ Python's native `concurrent futures`_ interface. Executors feature the ``submit( function for launching apps (detailed below), but currently do not support ``map()`` or ``shutdown()``. Tasks are much like ``futures``, except they correspond to an application instance instead of a callable. They feature the ``cancel()``, -``cancelled()``, ``running()``,``done()``, ``result()``, and ``exception()`` functions +``cancelled()``, ``running()``, ``done()``, ``result()``, and ``exception()`` functions from the standard. The main ``Executor`` class is an abstract class, inherited by the ``MPIExecutor`` diff --git a/docs/history_output_logging.rst b/docs/history_output_logging.rst index 1e76e0bf6..2f8549123 100644 --- a/docs/history_output_logging.rst +++ b/docs/history_output_logging.rst @@ -58,6 +58,8 @@ stderr displaying can be effectively disabled by setting the stderr level to CRI :members: :no-undoc-members: +.. _output_dirs: + Working Directories for User Functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ libEnsemble features configurable output and working directory structuring for diff --git a/docs/tutorials/executor_forces_tutorial.rst b/docs/tutorials/executor_forces_tutorial.rst index 57e64c3ab..e9c687eb1 100644 --- a/docs/tutorials/executor_forces_tutorial.rst +++ b/docs/tutorials/executor_forces_tutorial.rst @@ -74,23 +74,19 @@ with many of the settings defined :doc:`here<../executor/mpi_executor>`. We'll register our simulation with the executor and use the same instance within our simulation function. -libEnsemble can perform every simulation instasnce (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. - Next define the :ref:`sim_specs` and -:ref:`gen_specs` data structures: +:ref:`gen_specs` data structures. Recall that these +are used to specify to libEnsemble what input/output fields to expect, and also +to parameterize function instances without hard-coding: .. code-block:: python :linenos: - # State the sim_f, its arguments, output, and parameters (and their sizes) - sim_specs = {'sim_f': run_forces, # sim_f, imported above - 'in': ['x'], # Name of input for sim_f - 'out': [('energy', float)], # Name, type of output from sim_f - 'user': {'simdir_basename': 'forces', # User parameters for sim_f - 'cores': 2, + # State the simulation function, its arguments, output, and parameters (and their sizes) + sim_specs = {'sim_f': run_forces, # simulation function, imported above + 'in': ['x'], # Name of inputs from History array + 'out': [('energy', float)], # Name, type of output from simulation function + 'user': {'cores': 2, # Additional User parameters 'sim_particles': 1e3, 'sim_timesteps': 5, 'sim_kill_minutes': 10.0, @@ -110,14 +106,17 @@ Next define the :ref:`sim_specs` and } } -These dictionaries configure our generation function and our simulation -function respectively as the ``uniform_random_sample`` and -``run_forces`` functions. Our generation function will generate random seeds to use within +Our generation function will generate random seeds to use within each simulation function call. -After additional settings in ``libE_specs``, defining our ``exit_criteria``, and initializing -``persis_info``, our script calls the main -:doc:`libE<../libe_module>` routine: +libEnsemble can perform every simulation instance (within the ensemble) in a +separate directory for organization and potential I/O benefits. In this example, +libEnsemble copies the source directory ``./sim`` and its contents to create these +simulation directories. These input/output directories are highly customizable, +using many of the settings described :ref:`here`. + +After additional settings and configuring our ``exit_criteria``, we call libEnsemble +using the primary :doc:`libE()<../libe_module>` routine: .. code-block:: python :linenos: @@ -132,11 +131,99 @@ After additional settings in ``libE_specs``, defining our ``exit_criteria``, and H, persis_info, flag = libE(sim_specs, gen_specs, exit_criteria, persis_info=persis_info, libE_specs=libE_specs) +Exercise +^^^^^^^^ + +This may take some additional browsing of the docs to complete. + +Write an alternative Calling Script similar to above, but with the following differences: + + 1. Override the MPIExecutor's detected MPI runner with ``'openmpi'``. + 2. Set the output ensemble directory to be stored in ``/scratch``, then copied back to the starting directory on completion. + 3. Set the libEnsemble logger to print DEBUG messages. + 4. Save the History array and ``persis_info`` to files once libEnsemble completes. + 5. Simplify our Calling Script by using the ``yaml`` interface. + +.. container:: toggle + + .. container:: header + + **Click Here for Solution** + + .. code-block:: python + :linenos: + + #!/usr/bin/env python + import os + import numpy as np + + from libensemble import Ensemble + from libensemble.executors.mpi_executor import MPIExecutor + + sim_app = os.path.join(os.getcwd(), 'forces.x') + + forces = Ensemble() + forces.from_yaml('forces.yaml') + + forces.logger.set_level('DEBUG') + + exctr = MPIExecutor(custom_info={'mpi_runner': 'openmpi'}) + exctr.register_app(full_path=sim_app, app_name='forces') + + forces.gen_specs['user'].update({ + 'lb': np.array([0]), + 'ub': np.array([32767]) + }) + + forces.run() + + if forces.is_manager: + forces.save_output(__file__) + + .. code-block:: yaml + + libE_specs: + save_every_k_gens: 1000 + sim_dirs_make: True + sim_input_dir: ./sim + ensemble_dir_path: /scratch/ensemble + ensemble_copy_back: True + profile: False + exit_criteria: + sim_max: 8 + + sim_specs: + function: forces_simf.run_forces + inputs: + - x + outputs: + energy: + type: float + + user: + keys: + - seed + cores: 2 + sim_particles: 1.e+3 + sim_timesteps: 5 + sim_kill_minutes: 10.0 + particle_variance: 0.2 + kill_rate: 0.5 + + gen_specs: + function: libensemble.gen_funcs.sampling.uniform_random_sample + outputs: + x: + type: float + size: 1 + user: + gen_batch_size: 1000 + Simulation Function ------------------- Our simulation function is where we'll use libEnsemble's executor to configure and submit -our compiled app for execution. We'll poll this task's state while +our application for execution. We'll poll this task's state while it runs, and once we've detected it has finished we'll send any results or exit statuses back to the manager. @@ -173,7 +260,7 @@ and utility functions for starters: line = "" # In case file is empty or not yet created return line -Now we can write the body of the simulation function. We'll write the function definition, +Next let's write the body of the simulation function. We'll write the function definition, extract our parameters from ``sim_specs``, define a random seed, and use ``perturb()`` to randomize our particle counts. @@ -197,14 +284,13 @@ extract our parameters from ``sim_specs``, define a random seed, and use # To give a random variance of work-load sim_particles = perturb(sim_particles, seed, particle_variance) -Next we will re-instantiate our executor and submit our registered application for +Next we will fetch our Executor and submit our registered application for execution. .. code-block:: python :linenos: - :emphasize-lines: 2,9,10,12,13 - # Use previously parameterized executor object + # Fetches *previously parameterized* Executor exctr = Executor.executor # Arguments for our registered simulation @@ -231,7 +317,6 @@ the task's runtime exceeds the time limit: .. code-block:: python :linenos: - :emphasize-lines: 7,10-12,15 # Stat file to check for bad runs statfile = 'forces.stat' @@ -249,12 +334,10 @@ the task's runtime exceeds the time limit: time.sleep(poll_interval) task.poll() # updates the task's attributes -Once our task finishes, adjust ``calc_status`` (our "exit code") and report to the -user based on the task's final state: +Once our task finishes, adjust ``calc_status`` (our "exit code"). .. code-block:: python :linenos: - :emphasize-lines: 1-3,7,8,10,11,14 if task.finished: if task.state == 'FINISHED': @@ -268,7 +351,9 @@ user based on the task's final state: else: print("Warning: Task {} in unknown state {}. Error code {}".format(task.name, task.state, task.errcode)) -Load output data from our task and return to the libEnsemble manager: +Load output data from our task, initialize an output NumPy array with that data, +then return it, ``persis_info`` (unused in our example but required by the interface), +and ``calc_status``: .. code-block:: python :linenos: @@ -286,7 +371,9 @@ 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: +``calc_status`` will be displayed in the ``libE_stats.txt`` log file. + +This completes our calling script and simulation function. Run libEnsemble with: .. code-block:: bash @@ -310,6 +397,37 @@ The following *optional* lines parse and display some output: print(line) print('-'*60) +Exercise +^^^^^^^^ + +This may also take some additional browsing of the docs to complete. + +Write an alternative simulation function similar to above, but with the following differences: + + 1. Submit the forces application to run across two nodes, also using hyperthreads + 2. Replace the ``while`` loop with the ``Executor.polling_loop()`` routine, polling once a second. + +.. container:: toggle + + .. container:: header + + **Click Here for Solution** + + .. code-block:: python + :linenos: + + def run_forces(H, persis_info, sim_specs, libE_info): + + ... + + task = exctr.submit(app_name='forces', num_procs=cores, app_args=args, + stdout='out.txt', stderr='err.txt', wait_on_start=True, + num_nodes=2, hyperthreads=True) + + exctr.polling_loop(task, timeout=time_limit, delay=1) + + ... + Executor Variants ----------------- From 61ec29201b65fbf68ca523c03e1ea672ec5dece0 Mon Sep 17 00:00:00 2001 From: Jeffrey Larson Date: Thu, 24 Feb 2022 13:12:10 -0600 Subject: [PATCH 06/12] Slight wording edits --- docs/tutorials/local_sine_tutorial.rst | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/tutorials/local_sine_tutorial.rst b/docs/tutorials/local_sine_tutorial.rst index 96d0c7f0d..6a73826ef 100644 --- a/docs/tutorials/local_sine_tutorial.rst +++ b/docs/tutorials/local_sine_tutorial.rst @@ -6,7 +6,7 @@ This introductory tutorial demonstrates the capability to perform ensembles of calculations in parallel using :doc:`libEnsemble<../introduction>` with Python's multiprocessing. -The foundation of writing libEnsemble routines is accounting for at a minimum three components: +The foundation of writing libEnsemble routines is accounting for at least three components: 1. A :ref:`generator function`, that produces values for simulations 2. A :ref:`simulator function`, performs simulations based on values from the generator function @@ -14,7 +14,7 @@ The foundation of writing libEnsemble routines is accounting for at a minimum th libEnsemble initializes a *manager* process and as many *worker* processes as the user requests. The manager coordinates data transfer between workers and assigns -each units of work, consisting of a function to run and +units of work to each worker, consisting of a function to run and accompanying data. These functions perform their work in-line with Python and/or launch and control user applications with libEnsemble's :ref:`Executors`. Workers pass results back to the manager. @@ -75,7 +75,7 @@ following parameters: * :ref:`gen_specs`: Dictionary with user-defined fields and parameters for the generator. Customizable parameters such as boundaries and batch - sizes are placed within the nested ``user`` dictionary, while input/output fields + sizes are placed within the ``gen_specs['user']`` dictionary, while input/output fields and other specifications that libEnsemble depends on to operate the generator are placed outside ``user``. @@ -147,7 +147,6 @@ the ``numpy.random.RandomState.randint(low, high, size)`` function. return out, persis_info - Simulator function ------------------ @@ -338,7 +337,6 @@ script and run ``python3 calling_script.py`` again plt.legend(loc = 'lower right') plt.savefig('tutorial_sines.png') - Exercise ^^^^^^^^ From a6433538648a5305949ca7c957fd65d5f429eccc Mon Sep 17 00:00:00 2001 From: jlnav Date: Mon, 28 Feb 2022 13:56:55 -0600 Subject: [PATCH 07/12] literal-include examples block from README in example generators page --- README.rst | 2 ++ docs/examples/gen_funcs.rst | 8 +++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 65f58e8ec..18aff0445 100644 --- a/README.rst +++ b/README.rst @@ -258,6 +258,8 @@ Resources **Example Compatible Packages** +.. before_examples_rst_tag + libEnsemble and the `Community Examples repository`_ include example generator functions for the following libraries: diff --git a/docs/examples/gen_funcs.rst b/docs/examples/gen_funcs.rst index e17f27fe6..a2e9cf3d8 100644 --- a/docs/examples/gen_funcs.rst +++ b/docs/examples/gen_funcs.rst @@ -1,17 +1,15 @@ Generation Functions ==================== -Below are example generation functions available in libEnsemble. - -Additional examples are available in the `libEnsemble Community Repository`_, -with corresponding generator documentation available :doc:`here`. +.. include:: ../../README.rst + :start-after: before_examples_rst_tag .. IMPORTANT:: See the API for generation functions :ref:`here`. .. toctree:: :maxdepth: 2 - :caption: Example Generator Functions: + :caption: Documented Example Generator Functions: sampling aposmm From 757dfea485c157d7dd625c38fa63cdb80698e688 Mon Sep 17 00:00:00 2001 From: jlnav Date: Tue, 1 Mar 2022 15:24:34 -0600 Subject: [PATCH 08/12] add workflows mention --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 18aff0445..ebe080989 100644 --- a/README.rst +++ b/README.rst @@ -25,7 +25,7 @@ Introduction to libEnsemble =========================== -libEnsemble is a Python_ toolkit for coordinating asynchronous and dynamic ensembles of calculations. +libEnsemble is a Python_ toolkit for coordinating workflows of asynchronous and dynamic ensembles of calculations. libEnsemble can help users take advantage of massively parallel resources to solve design, decision, and inference problems and expand the class of problems that can benefit from From 21b146631c66000623941eff352a8ccd711aedfe Mon Sep 17 00:00:00 2001 From: jlnav Date: Mon, 7 Mar 2022 13:16:58 -0600 Subject: [PATCH 09/12] additional allocf mention, add parse_args to exercise task --- docs/tutorials/local_sine_tutorial.rst | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/tutorials/local_sine_tutorial.rst b/docs/tutorials/local_sine_tutorial.rst index 6a73826ef..43467116f 100644 --- a/docs/tutorials/local_sine_tutorial.rst +++ b/docs/tutorials/local_sine_tutorial.rst @@ -9,15 +9,15 @@ multiprocessing. The foundation of writing libEnsemble routines is accounting for at least three components: 1. A :ref:`generator function`, that produces values for simulations - 2. A :ref:`simulator function`, performs simulations based on values from the generator function + 2. A :ref:`simulator function`, that performs simulations based on values from the generator function 3. A :doc:`calling script<../libe_module>`, for defining settings, fields, and functions, then starting the run libEnsemble initializes a *manager* process and as many *worker* processes as the -user requests. The manager coordinates data transfer between workers and assigns -units of work to each worker, consisting of a function to run and -accompanying data. These functions perform their work in-line with Python and/or -launch and control user applications with libEnsemble's :ref:`Executors`. -Workers pass results back to the manager. +user requests. The manager (via the :ref:`allocation function`) +coordinates data transfer between workers and assigns units of work to each worker, +consisting of a function to run and accompanying data. These functions perform their +work in-line with Python and/or launch and control user applications with +libEnsemble's :ref:`Executors`. Workers pass results back to the manager. For this tutorial, we'll write our generator and simulator functions entirely in Python without other applications. Our generator will produce uniform randomly sampled @@ -343,7 +343,7 @@ Exercise Write a Calling Script with the following specifications: 1. Use the Exercise simulator and generator functions instead - 2. Use 8 workers instead of 4 + 2. Use the :meth:`parse_args()` function to detect ``nworkers`` and auto-populate ``libE_specs`` 3. Set the generator function's lower and upper bounds to -6 and 6, respectively 4. Increase the generator batch size to 10 5. Set libEnsemble to stop execution after 160 *generations* using the ``gen_max`` key @@ -364,8 +364,7 @@ Write a Calling Script with the following specifications: from simulator import sim_find_cosine from libensemble.tools import add_unique_random_streams - nworkers = 8 - libE_specs = {'nworkers': nworkers, 'comms': 'local'} + nworkers, is_manager, libE_specs, _ = parse_args() gen_specs = {'gen_f': gen_random_ints, 'out': [('x', float, (1,))], From 5b4e81e34f7338f088ae7da0e9a87f9dbe158248 Mon Sep 17 00:00:00 2001 From: jlnav Date: Mon, 7 Mar 2022 13:35:00 -0600 Subject: [PATCH 10/12] adds links to complete example files in examples/tutorials --- docs/tutorials/aposmm_tutorial.rst | 3 +++ docs/tutorials/executor_forces_tutorial.rst | 3 +++ docs/tutorials/local_sine_tutorial.rst | 3 +++ 3 files changed, 9 insertions(+) diff --git a/docs/tutorials/aposmm_tutorial.rst b/docs/tutorials/aposmm_tutorial.rst index 0af398411..65dd6af0d 100644 --- a/docs/tutorials/aposmm_tutorial.rst +++ b/docs/tutorials/aposmm_tutorial.rst @@ -274,6 +274,8 @@ 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. +Each of these example files can be found in the repository in `examples/tutorials/aposmm`_. + Applications ------------ @@ -289,3 +291,4 @@ can be found in libEnsemble's `WarpX Scaling Test`_. .. _`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 +.. _examples/tutorials/aposmm: https://github.com/Libensemble/libensemble/tree/develop/examples/tutorials diff --git a/docs/tutorials/executor_forces_tutorial.rst b/docs/tutorials/executor_forces_tutorial.rst index e9c687eb1..42a6d7696 100644 --- a/docs/tutorials/executor_forces_tutorial.rst +++ b/docs/tutorials/executor_forces_tutorial.rst @@ -397,6 +397,8 @@ The following *optional* lines parse and display some output: print(line) print('-'*60) +Each of these example files can be found in the repository in `examples/tutorials/forces_with_executor`_. + Exercise ^^^^^^^^ @@ -443,3 +445,4 @@ which executor is imported and called within a calling script. .. _here: https://raw.githubusercontent.com/Libensemble/libensemble/master/libensemble/tests/scaling_tests/forces/forces.c .. _Balsam: https://balsam.readthedocs.io/en/latest/ +.. _examples/tutorials/forces_with_executor: https://github.com/Libensemble/libensemble/tree/develop/examples/tutorials/forces_with_executor diff --git a/docs/tutorials/local_sine_tutorial.rst b/docs/tutorials/local_sine_tutorial.rst index 43467116f..9112f16b7 100644 --- a/docs/tutorials/local_sine_tutorial.rst +++ b/docs/tutorials/local_sine_tutorial.rst @@ -337,6 +337,8 @@ script and run ``python3 calling_script.py`` again plt.legend(loc = 'lower right') plt.savefig('tutorial_sines.png') +Each of these example files can be found in the repository in `examples/tutorials/simple_sine`_. + Exercise ^^^^^^^^ @@ -489,3 +491,4 @@ libEnsemble use-case within our .. _MPICH: https://www.mpich.org/ .. _mpi4py: https://mpi4py.readthedocs.io/en/stable/install.html .. _here: https://www.mpich.org/downloads/ +.. _examples/tutorials/simple_sine: https://github.com/Libensemble/libensemble/tree/develop/examples/tutorials/simple_sine From ed6c859549cbb556dbeeb37be1ff6ff41206dd0c Mon Sep 17 00:00:00 2001 From: Jeffrey Larson Date: Tue, 8 Mar 2022 15:17:53 -0600 Subject: [PATCH 11/12] One-line change --- docs/tutorials/local_sine_tutorial.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorials/local_sine_tutorial.rst b/docs/tutorials/local_sine_tutorial.rst index 9112f16b7..08823f5dc 100644 --- a/docs/tutorials/local_sine_tutorial.rst +++ b/docs/tutorials/local_sine_tutorial.rst @@ -90,7 +90,7 @@ For now, create a new Python file named ``generator.py``. Write the following: import numpy as np def gen_random_sample(H, persis_info, gen_specs, _): - # underscore parameter for advanced arguments + # Underscore ignores advanced arguments # Pull out user parameters user_specs = gen_specs['user'] From febf7e0e42a8bf98d342ee020f15ade1e36d77ff Mon Sep 17 00:00:00 2001 From: jlnav Date: Wed, 9 Mar 2022 10:56:01 -0600 Subject: [PATCH 12/12] implemented suggestions --- docs/tutorials/local_sine_tutorial.rst | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/docs/tutorials/local_sine_tutorial.rst b/docs/tutorials/local_sine_tutorial.rst index 08823f5dc..1c812d39a 100644 --- a/docs/tutorials/local_sine_tutorial.rst +++ b/docs/tutorials/local_sine_tutorial.rst @@ -64,7 +64,7 @@ or :ref:`gen_f`. An available libEnsemble worker will call this generator function with the following parameters: -* :ref:`H`: The History array. A NumPy structured array +* :ref:`H_in`: A selection of the History array, a NumPy structured array for storing information about each point generated and processed in the ensemble. libEnsemble passes a selection of ``H`` to the generator function in case the user wants to generate new values based on previous data. @@ -89,7 +89,7 @@ For now, create a new Python file named ``generator.py``. Write the following: import numpy as np - def gen_random_sample(H, persis_info, gen_specs, _): + def gen_random_sample(H_in, persis_info, gen_specs, _): # Underscore ignores advanced arguments # Pull out user parameters @@ -134,7 +134,7 @@ the ``numpy.random.RandomState.randint(low, high, size)`` function. import numpy as np - def gen_random_ints(H, persis_info, gen_specs, _): + def gen_random_ints(H_in, persis_info, gen_specs, _): user_specs = gen_specs['user'] lower = user_specs['lower'] @@ -175,7 +175,7 @@ Create a new Python file named ``simulator.py``. Write the following: # Send back our output and persis_info return out, persis_info -Our simulator function is called by a worker *for every value in its batch* from +Our simulator function is called by a worker for every work item produced by the generator function. This function calculates the sine of the passed value, then returns it so a worker can log it into ``H``. @@ -196,7 +196,7 @@ value, using the ``numpy.cos(x)`` function. import numpy as np - def sim_find_cosine(H, persis_info, gen_specs, _): + def sim_find_cosine(H_in, persis_info, gen_specs, _): out = np.zeros(1, dtype=sim_specs['out']) @@ -344,12 +344,11 @@ Exercise Write a Calling Script with the following specifications: - 1. Use the Exercise simulator and generator functions instead - 2. Use the :meth:`parse_args()` function to detect ``nworkers`` and auto-populate ``libE_specs`` - 3. Set the generator function's lower and upper bounds to -6 and 6, respectively - 4. Increase the generator batch size to 10 - 5. Set libEnsemble to stop execution after 160 *generations* using the ``gen_max`` key - 6. Print an error message if any errors occurred while libEnsemble was running + 1. Use the :meth:`parse_args()` function to detect ``nworkers`` and auto-populate ``libE_specs`` + 2. Set the generator function's lower and upper bounds to -6 and 6, respectively + 3. Increase the generator batch size to 10 + 4. Set libEnsemble to stop execution after 160 *generations* using the ``gen_max`` key + 5. Print an error message if any errors occurred while libEnsemble was running .. container:: toggle @@ -362,8 +361,8 @@ Write a Calling Script with the following specifications: import numpy as np from libensemble.libE import libE - from generator import gen_random_ints - from simulator import sim_find_cosine + from generator import gen_random_sample + from simulator import sim_find_sine from libensemble.tools import add_unique_random_streams nworkers, is_manager, libE_specs, _ = parse_args()