diff --git a/ytopt-libe-openmc/run_ytopt.py b/ytopt-libe-openmc/run_ytopt.py index 216b3d1..9f095bd 100644 --- a/ytopt-libe-openmc/run_ytopt.py +++ b/ytopt-libe-openmc/run_ytopt.py @@ -60,13 +60,6 @@ #libE_specs['sim_dir_copy_files'] = [here + f for f in ['mmp.c', 'Materials.c', 'XSutils.c', 'XSbench_header.h']] libE_specs['sim_dir_symlink_files'] = [here + f for f in ['openmc.sh', 'settings.xml', 'materials.xml', 'geometry.xml', 'exe.pl', 'plopper.py', 'processexe.pl']] -# Declare the sim_f to be optimized, and the input/outputs -sim_specs = { - 'sim_f': init_obj, - 'in': ['p0', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6'], - 'out': [('RUNTIME', float),('elapsed_sec', float)], -} - cs = CS.ConfigurationSpace(seed=1234) # queuing logic type p0 = CSH.CategoricalHyperparameter(name='p0', choices=["openmc", "openmc-queueless"], default_value="openmc-queueless") @@ -84,8 +77,8 @@ p6= CSH.CategoricalHyperparameter(name='p6', choices=['cores','threads','sockets'], default_value='threads') cs.add_hyperparameters([p0, p1, p2, p3, p4, p5, p6]) -#cond = EqualsCondition(p3, p0, "openmc") -#cs.add_conditions([cond]) +cond = EqualsCondition(p3, p0, "openmc") +cs.add_conditions([cond]) ytoptimizer = Optimizer( num_workers=num_sim_workers, @@ -98,11 +91,24 @@ set_NI=10, ) +# Declare the sim_f to be optimized, and the input/outputs +# Add p3_present indicator due to LibEnsemble API -- it is implicit from the ConfigSpace +# but must be explicitly tracked due to current limitations. +sim_specs = { + 'sim_f': init_obj, + 'in': ['p0', 'p1', 'p2', + 'p3_present', + 'p3', 'p4', 'p5', 'p6'], + 'out': [('RUNTIME', float),('elapsed_sec', float)], +} + # Declare the gen_f that will generate points for the sim_f, and the various input/outputs gen_specs = { 'gen_f': persistent_ytopt, 'out': [('p0', "LibEnsemble sentinel adjustment + if not entry[fields.index(field+"_present")]: + field_params[field] = np.nan + continue + else: + if field not in entry.dtype.names: + continue + field_params[field] = float(entry[field][0]) + else: + if field not in entry.dtype.names: + continue + field_params[field] = entry[field][0] results += [(field_params, entry['RUNTIME'])] print('results: ', results) ytoptimizer.tell(results) ytopt_points = ytoptimizer.ask(n_points=batch_size) # Returns a generator that we convert to a list ytopt_points = list(ytopt_points)[0] - + # We must evaluate the presence here, as LibEnsemble will not permit us to assing + # np.nan as an integer parameter value. + # We'll use -1 (invalid from ConfigSpace POV) as a sentinel, and restore the nan-ness after + # LibEnsemble has done its handoff. + # This presence field exists to indicate that such restoration will occur. The sentinel + # does not need to be invalid for the ConfigSpace, but doing so helps validate that this + # wrapper intercepts its own meddling before propagating it to components that should not + # be affected. + for point in ytopt_points: + point['p3_present'] = not np.isnan(point['p3']) # The hand-off of information from ytopt to libE is below. This hand-off may be brittle. H_o = np.zeros(batch_size, dtype=gen_specs['out']) for i, entry in enumerate(ytopt_points): for key, value in entry.items(): - H_o[i][key] = value + # This is the to->LibEnsemble sentinel adjustment. + # String data and many other representations cannot be checked by np.isnan. + # If the data isn't numerical, we're not making a nan-based adjustment so we + # just assign the original value. + try: + if np.isnan(value): + H_o[i][key] = -1 + else: + H_o[i][key] = value + except: + H_o[i][key] = value # This returns the requested points to the libE manager, which will # perform the sim_f evaluations and then give back the values. tag, Work, calc_in = ps.send_recv(H_o) print('received:', calc_in, flush=True) - if calc_in is not None: - if len(calc_in): - b = [] - for entry in calc_in[0]: - try: + if calc_in is not None and len(calc_in): + b = [] + for calc_result in calc_in: + for entry in calc_result: + # Most entries should be 1-length np.ndarrays, however if the top-level implementer + # failed to indicate to LibEnsemble that this would be the case, they'll come back + # as just the value. + try: b += [str(entry[0])] - except: + except: b += [str(entry)] with open('../../results.csv', 'a') as f: if first_write: - f.write(",".join(calc_in.dtype.names)+ "\n") - f.write(",".join(b)+ "\n") + f.write(",".join(calc_result.dtype.names)+ "\n") first_write = False - else: - f.write(",".join(b)+ "\n") + f.write(",".join(b)+ "\n") return H_o, persis_info, FINISHED_PERSISTENT_GEN_TAG + diff --git a/ytopt-libe-openmc/ytopt_obj.py b/ytopt-libe-openmc/ytopt_obj.py index b0ea74a..2ca718d 100644 --- a/ytopt-libe-openmc/ytopt_obj.py +++ b/ytopt-libe-openmc/ytopt_obj.py @@ -27,15 +27,12 @@ def plopper_func(x, params): obj = Plopper('./openmc.sh', './') x = np.asarray_chkfinite(x) value = [point[param] for param in params] - #print(value) os.system("./processexe.pl exe.pl " +str(value[4])+ " " +str(value[5])+ " " +str(value[6])) os.environ["OMP_NUM_THREADS"] = str(value[4]) params = [i.upper() for i in params] - #result = float(obj.findRuntime(value, params, workerID)) result = obj.findRuntime(value, params, workerID) return result - x = np.array([point[f'p{i}'] for i in range(len(point))]) + x = np.array(list(point.values())) results = plopper_func(x, params) - # print('CONFIG and OUTPUT', [point, results], flush=True) return results