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
4from pathlib import Path
5
6import numpy as np
7from forces_simf import run_forces # Sim func from current dir
8
9from libensemble import Ensemble
10from libensemble.executors import MPIExecutor
11from libensemble.gen_funcs.persistent_sampling import persistent_uniform as gen_f
12from libensemble.specs import 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 = Path.cwd() / "../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 initial_batch_size=nsim_workers,
52 async_return=False,
53 user={
54 "lb": np.array([1000]), # min particles
55 "ub": np.array([3000]), # max particles
56 },
57 )
58
59 # Starts one persistent generator. Simulated values are returned in batch.
60
61 # Instruct libEnsemble to exit after this many simulations
62 ensemble.exit_criteria = ExitCriteria(sim_max=8)
63
64 # Run ensemble
65 ensemble.run()
66
67 if ensemble.is_manager:
68 # Note, this will change if changing sim_max, nworkers, lb, ub, etc.
69 print(f'Final energy checksum: {np.sum(ensemble.H["energy"])}')
70
71 # To see the input/output for each evaluation
72 # print(ensemble.H[["sim_id", "x", "energy"]])
Also see the Forces tutorial.