Template for CPU executables with input file
Many applications read an input file instead of being given parameters directly on the run line.
In this variant of the forces example, a templated input file is parameterized for each evaluation.
This requires jinja2 to be installed:
pip install jinja2
In the example, the file forces_input contains the following (remember
we are using particles as seed also for simplicity):
num_particles = {{particles}}
num_steps = 10
rand_seed = {{particles}}
libEnsemble will copy this input file to each simulation directory. There, the
simulation function will updates the input file with the particles value for
this simulation.
- forces_simple_with_input_file.forces_simf.run_forces(H, persis_info, sim_specs, libE_info)
Runs the forces MPI application reading input from file
forces_simf.py
1import jinja2
2import numpy as np
3
4# Optional status codes to display in libE_stats.txt for each gen or sim
5from libensemble.message_numbers import TASK_FAILED, WORKER_DONE
6
7
8def set_input_file_params(H, sim_specs, ints=False):
9 """
10 This is a general function to parameterize an input file with any inputs
11 from sim_specs["in"]
12
13 Often sim_specs_in["x"] may be multi-dimensional, where each dimension
14 corresponds to a different input name in sim_specs["user"]["input_names"]).
15 Effectively an unpacking of "x"
16 """
17 input_file = sim_specs["user"]["input_filename"]
18 input_values = {}
19 for i, name in enumerate(sim_specs["user"]["input_names"]):
20 value = int(H["x"][0][i]) if ints else H["x"][0][i]
21 input_values[name] = value
22 with open(input_file, "r") as f:
23 template = jinja2.Template(f.read())
24 with open(input_file, "w") as f:
25 f.write(template.render(input_values))
26
27
28def run_forces(H, persis_info, sim_specs, libE_info):
29 """Runs the forces MPI application reading input from file"""
30
31 calc_status = 0
32
33 set_input_file_params(H, sim_specs, ints=True)
34
35 # Retrieve our MPI Executor
36 exctr = libE_info["executor"]
37
38 # Submit our forces app for execution.
39 task = exctr.submit(app_name="forces") # app_args removed
40
41 # Block until the task finishes
42 task.wait(timeout=60)
43
44 # Stat file to check for bad runs
45 statfile = "forces.stat"
46
47 # Try loading final energy reading, set the sim's status
48 try:
49 data = np.loadtxt(statfile)
50 final_energy = data[-1]
51 calc_status = WORKER_DONE
52 except Exception:
53 final_energy = np.nan
54 calc_status = TASK_FAILED
55
56 # Define our output array, populate with energy reading
57 outspecs = sim_specs["out"]
58 output = np.zeros(1, dtype=outspecs)
59 output["energy"][0] = final_energy
60
61 # Return final information to worker, for reporting to manager
62 return output, persis_info, calc_status
Example usage
1#!/usr/bin/env python
2import os
3import sys
4
5import numpy as np
6from forces_simf import run_forces # Sim func from current dir
7
8from libensemble import Ensemble
9from libensemble.alloc_funcs.start_only_persistent import only_persistent_gens as alloc_f
10from libensemble.executors import MPIExecutor
11from libensemble.gen_funcs.persistent_sampling import persistent_uniform as gen_f
12from libensemble.specs import AllocSpecs, ExitCriteria, GenSpecs, LibeSpecs, SimSpecs
13
14if __name__ == "__main__":
15 # Initialize MPI Executor
16 exctr = MPIExecutor()
17
18 # Register simulation executable with executor
19 sim_app = os.path.join(os.getcwd(), "../forces_app/forces.x")
20
21 if not os.path.isfile(sim_app):
22 sys.exit("forces.x not found - please build first in ../forces_app dir")
23
24 exctr.register_app(full_path=sim_app, app_name="forces")
25
26 # Parse number of workers, comms type, etc. from arguments
27 ensemble = Ensemble(parse_args=True, executor=exctr)
28 nsim_workers = ensemble.nworkers - 1 # One worker is for persistent generator
29
30 input_file = "forces_input"
31
32 # Persistent gen does not need resources
33 ensemble.libE_specs = LibeSpecs(
34 num_resource_sets=nsim_workers,
35 sim_dirs_make=True,
36 sim_dir_copy_files=[input_file],
37 )
38
39 ensemble.sim_specs = SimSpecs(
40 sim_f=run_forces,
41 inputs=["x"],
42 outputs=[("energy", float)],
43 user={"input_filename": input_file, "input_names": ["particles"]},
44 )
45
46 ensemble.gen_specs = GenSpecs(
47 gen_f=gen_f,
48 inputs=[], # No input when start persistent generator
49 persis_in=["sim_id"], # Return sim_ids of evaluated points to generator
50 outputs=[("x", float, (1,))],
51 user={
52 "initial_batch_size": nsim_workers,
53 "lb": np.array([1000]), # min particles
54 "ub": np.array([3000]), # max particles
55 },
56 )
57
58 # Starts one persistent generator. Simulated values are returned in batch.
59 ensemble.alloc_specs = AllocSpecs(
60 alloc_f=alloc_f,
61 user={
62 "async_return": False, # False causes batch returns
63 },
64 )
65
66 # Instruct libEnsemble to exit after this many simulations
67 ensemble.exit_criteria = ExitCriteria(sim_max=8)
68
69 # Seed random streams for each worker, particularly for gen_f
70 ensemble.add_random_streams()
71
72 # Run ensemble
73 ensemble.run()
74
75 if ensemble.is_manager:
76 # Note, this will change if changing sim_max, nworkers, lb, ub, etc.
77 print(f'Final energy checksum: {np.sum(ensemble.H["energy"])}')
78
79 # To see the input/output for each evaluation
80 # print(ensemble.H[["sim_id", "x", "energy"]])
Also see the Forces tutorial.