Multi-Fidelity Surrogate based Optimization
Multi-Fidelity surrogate based optimization (MF-SBO) also is a common practice in aerodynamic shape optimization. In comparison to the standard SBO, the idea in this case is to build multiple initial design of experiments (DOE) with different fidelities (e.g. RANS vs LES, increasing mesh fineness, etc.). Then, these multi-fidelity datasets are used to train a multi-fidelity surrogate model of the problem's quantities of interest (QoIs). In the end, the multi-fidelity surrogate model, which is expected to be more precised than a mono-fidelity model, is used in the optimization loop in place of the CFD solver.
This tutorial illustrates the steps to do so with Aero-Optim:
1) a non-penalized low-fidelity DOE is built with a single generation optimization execution,
2) the best candidates amongst low-fidelity ones are used as candidates for the high-fidelity DOE that is then built with a single generation optimization execution,
3) the low-fidelity and high-fidelity results are used to train the multi-fidelity surrogate model of the user's choosing,
4) a full optimization based on a CustomSimulator
and a CustomOptimizer
is performed by evaluating candidates with the trained multi-fidelity surrogate model.
Illustration
The NACA12/naca_mf_smt
example shows how this can be done with two fidelities: the naca_base
use-case with its coarse mesh for the first one, the naca_adap
use-case with its adapted mesh for the second fidelity. Once again, the smt
toolbox is used to build and train the multi-fidelity surrogates.
First, the naca_lf_doe.json
configuration file is built with max_generations
set to 1 (the DOE is generated with pymoo
):
subprocess.run(["optim", "-c", "naca_lf_doe.json", "--pymoo"],
env=os.environ,
stdin=subprocess.DEVNULL,
check=True)
Then, the results are loaded and the best candidates are selected to become those of the high-fidelity DOE:
# lf data loading
X_lf = np.loadtxt(os.path.join(lf_outdir, "candidates.txt"))
Y_lf = []
with open(os.path.join(lf_outdir, "df_dict.pkl"), "rb") as handle:
df_dict = pickle.load(handle)
for gid in range(len(df_dict)):
for cid in range(len(df_dict[gid])):
Y_lf.append([df_dict[gid][cid]["CD"].iloc[-1], df_dict[gid][cid]["CL"].iloc[-1]])
Y_lf = np.array(Y_lf)
del df_dict
# hf candidates selection
best_candidates_idx = np.argsort(Y_lf[:, 0])
np.savetxt(
os.path.join(hf_outdir, "custom_doe.txt"), X_lf[best_candidates_idx][:hf_doe_size]
)
# hf data generation
subprocess.run(["optim", "-c", f"naca_hf_doe.json", "--pymoo"],
env=os.environ,
stdin=subprocess.DEVNULL,
check=True)
naca_hf_doe.json
is based on the naca_adp.json
configuration file with an additional parameter enabling the Generator
to fetch the newly selected candidates:
- "custom_doe": "output_hf_doe/custom_doe.txt"
in the "optim"
entry.
The high-fidelity results are next loaded and used to train the multi-fidelity surrogate models for both Cd
and Cl
:
X_hf = np.loadtxt(os.path.join(hf_outdir, "candidates.txt"))
Y_hf = []
with open(os.path.join(hf_outdir, "df_dict.pkl"), "rb") as handle:
df_dict = pickle.load(handle)
for gid in range(len(df_dict)):
for cid in range(len(df_dict[gid])):
Y_hf.append([df_dict[gid][cid]["CD"].iloc[-1], df_dict[gid][cid]["CL"].iloc[-1]])
Y_hf = np.array(Y_hf)
del df_dict
# Cd
mfsm_cd = MFK(theta0=X_lf.shape[1] * [1.0])
mfsm_cd.set_training_values(X_lf, Y_lf[:, 0], name=0)
mfsm_cd.set_training_values(X_hf, Y_hf[:, 0])
mfsm_cd.train()
# Cl
mfsm_cl = MFK(theta0=X_lf.shape[1] * [1.0])
mfsm_cl.set_training_values(X_lf, Y_lf[:, 1], name=0)
mfsm_cl.set_training_values(X_hf, Y_hf[:, 1])
mfsm_cl.train()
The exact same CustomSM
class as the one introduced in the SBO example is used to combine both models and emulate what would have been extracted from a simulation run:
custom_mf_sm = CustomSM([sm_cd, sm_cl])
with open(os.path.join(outdir, "model.pkl"), "wb") as handle:
pickle.dump(custom_mf_sm, handle)
Finally, a full optimization using this multi-fidelity surrogate model is performed with the same naca_smt.json
configuration file and two additional parameters:
"custom_file": "../naca_smt/custom_sm.py"
in the"study"
entry,"model_file": "output_hf_doe/model.pkl"
in the"simulator"
entry.
subprocess.run(["optim", "-c", "naca_smt.json", "--pymoo"],
env=os.environ,
stdin=subprocess.DEVNULL,
check=True)
Quick Experiments
In order to run this examples, the smt
library must first be added to the virtual environment:
pip install smt
main_mf_sm.py
script in the NACA12/naca_mf_smt
example folder can be used to perform all three steps at once:
# from aero-optim to naca_mf_smt
cd examples/NACA12/naca_mf_smt
python3 main_mf_sm.py -clf naca_lf_doe.json -chf naca_hf_doe.json -cmfsm naca_smt.json
It will produce an output_lf_doe
folder with the results of the low-fidelity DOE generation, an output_hf_doe
folder with the high-fidelity results and output_smt
with the results of the multi-fidelity surrogate based optimization.
At that point, the optimal profile properties obtained with the surrogate can be compared to its corresponding CFD simulation:
# deformed profile generation
ffd -f ../data/naca12.dat -nc 4 -d "<displacement of the optimal profile>" -o output_optim
# deformed profile meshing
mesh -c naca_hf_doe.json -f output_optim/naca12_g0_c0.dat -o output_optim
# simulation execution
simulator -c naca_hf_doe.json -f output_optim/naca_base.mesh -o optim_profile