Visualizing Optimization Runs¶
This library focuses on the optimisation logic itself and does not ship with built-in
plotting routines. That said, the data recorded by the
HistoryTracker works seamlessly with
standard data-science tools, so you can create any visual you need with just a few
lines of code.
The examples on this page use seaborn,
matplotlib and plotly,
available as optional dependencies by installing metaheuristic_designer[examples]—
but you are free to choose whatever plotting library you prefer.
All the following plots are generated directly in the documentation. You can check the source code that produced them.
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
sns.set_theme(style="whitegrid")
All examples assume algo is a completed Algorithm run
and df = algo.history_tracker.to_pandas().
Convergence of the Best Objective¶
The simplest diagnostic: how does the best solution improve over time?
fig, ax = plt.subplots(figsize=(8, 5))
sns.lineplot(data=df, x="iteration", y="best_objective", linewidth=2, ax=ax)
ax.set_xlabel("Generation")
ax.set_ylabel("Objective")
ax.set_title("Convergence Plot")
plt.show()
(Source code, png, hires.png, pdf)
For problems where the objective spans orders of magnitude, use a logarithmic y-axis:
ax.set_yscale("log")
(Source code, png, hires.png, pdf)
Best, Median, and Worst Objectives¶
If you enabled track_median and track_worst, you can visualise the spread of
the population.
fig, ax = plt.subplots(figsize=(8, 5))
sns.lineplot(data=df, x="iteration", y="best_objective", label="Best", linewidth=2, ax=ax)
sns.lineplot(data=df, x="iteration", y="median_objective", label="Median", linewidth=1.5, ax=ax)
sns.lineplot(data=df, x="iteration", y="worst_objective", label="Worst", linewidth=1, ax=ax)
ax.set_xlabel("Generation")
ax.set_ylabel("Objective")
ax.set_title("Best, Median, and Worst")
ax.legend()
plt.show()
(Source code, png, hires.png, pdf)
To highlight the gap between best and worst, shade it:
ax.fill_between(df["iteration"], df["worst_objective"], df["best_objective"],
alpha=0.1, color="steelblue", label="Range")
ax.legend()
(Source code, png, hires.png, pdf)
Comparing Multiple Algorithms¶
Combine the DataFrames of several algorithms and use hue to differentiate them.
ga_df["algorithm"] = "GA"
de_df["algorithm"] = "DE"
pso_df["algorithm"] = "PSO"
combined = pd.concat([ga_df, de_df, pso_df], ignore_index=True)
fig, ax = plt.subplots(figsize=(8, 5))
sns.lineplot(data=combined, x="iteration", y="best_objective",
hue="algorithm", linewidth=2, ax=ax)
ax.set_xlabel("Generation")
ax.set_ylabel("Best objective")
ax.set_title("Algorithm Comparison")
plt.show()
(Source code, png, hires.png, pdf)
Fitness Distribution Over Generations¶
When track_full_objective is enabled, the tracker stores the entire vector of raw
objectives at every generation. The method to_pandas_full_objective()
returns a wide-format DataFrame where each column corresponds to one individual
(Individual_0, Individual_1, …). To create a boxplot you first melt this
table into long format:
wide_df = algo.history_tracker.to_pandas_full_objective()
# Melt to long format for seaborn
long_df = wide_df.melt(id_vars="iteration",
var_name="individual",
value_name="objective")
fig, ax = plt.subplots(figsize=(12, 5))
# Plot every 5th generation to keep the plot readable
plot_data = long_df[long_df["iteration"] % 5 == 0]
sns.boxplot(data=plot_data, x="iteration", y="objective", ax=ax,
palette="viridis", width=0.6)
ax.set_xlabel("Generation")
ax.set_ylabel("Objective")
ax.set_title("Fitness Distribution")
plt.show()
(Source code, png, hires.png, pdf)
Diversity Over Generations¶
If you enabled track_diversity, the DataFrame contains a diversity column.
You can plot it alongside the best objective to see how exploration evolves.
A dual-axis plot is often the clearest:
fig, ax1 = plt.subplots(figsize=(8, 5))
ax2 = ax1.twinx()
sns.lineplot(data=df, x="iteration", y="best_objective", ax=ax1,
linewidth=2, color="tab:blue", label="Objective")
sns.lineplot(data=df, x="iteration", y="diversity", ax=ax2,
linewidth=2, color="tab:red", label="Diversity")
ax1.set_xlabel("Generation")
ax1.set_ylabel("Objective", color="tab:blue")
ax2.set_ylabel("Diversity", color="tab:red")
ax1.set_title("Convergence and Diversity")
# Combine legends
lines1, labels1 = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax2.legend(lines1 + lines2, labels1 + labels2, loc="upper right")
plt.show()
(Source code, png, hires.png, pdf)
Evolution of Schedulable Parameters¶
When you use parameter schedules (see Welcome to the Metaheuristic Designer API), the tracker
automatically records the current value of each scheduled parameter at every generation
if track_parameters is enabled. The parameter names are built from the component
hierarchy, e.g. "mutation.gaussian_mutation.F" for the mutation strength
or "BranchOperator.p" for a branch probability. These columns appear directly
in the DataFrame returned by to_pandas().
Plot them alongside convergence to understand how the search adapts over time:
param_cols = ["DE/rand/1.F", "DE/rand/1.Cr"]
fig, ax = plt.subplots(figsize=(8, 6))
for col in param_cols:
sns.lineplot(data=df, x="iteration", y=col, ax=ax, label=col)
ax.set_xlabel("Generation")
ax.set_ylabel("Parameter value")
ax.set_title("Scheduled Parameter Evolution")
ax.legend()
ax.axvline(0, color="grey")
ax.axhline(0, color="grey")
plt.tight_layout()
plt.show()
(Source code, png, hires.png, pdf)
If you have many parameters, consider using separate subplots or a dual-axis plot to avoid clutter.
Combining Multiple Metrics in a Dashboard¶
Put several views into one figure to get a comprehensive picture:
fig = plt.figure(figsize=(14, 10))
# Convergence
ax1 = fig.add_subplot(2, 2, 1)
sns.lineplot(data=df, x="iteration", y="best_objective", ax=ax1)
ax1.set_title("Best Objective")
# Fitness distribution (every 10th generation)
ax2 = fig.add_subplot(2, 2, 2)
wide_df = algo.history_tracker.to_pandas_full_objective()
long_df = wide_df.melt(id_vars="iteration", var_name="individual", value_name="objective")
plot_data = long_df[long_df["iteration"] % 10 == 0]
sns.boxplot(data=plot_data, x="iteration", y="objective", ax=ax2, width=0.6)
ax2.set_title("Fitness Distribution")
# Parameter 1
ax3 = fig.add_subplot(2, 2, 3)
sns.lineplot(data=df, x="iteration", y="mutation.gaussian_mutation.F",
ax=ax3, color="tab:orange")
ax3.set_title("Mutation Strength")
# Parameter 2
ax4 = fig.add_subplot(2, 2, 4)
sns.lineplot(data=df, x="iteration", y="BranchOperator.p",
ax=ax4, color="tab:red")
ax4.set_title("Branch Probability")
plt.tight_layout()
plt.show()
(Source code, png, hires.png, pdf)
Real-Time Visualization¶
For interactive exploration, you can step the algorithm manually and update a plot
live. The core logic is independent of the plotting library; here is a sketch using
Plotly’s FigureWidget:
import plotly.graph_objects as go
fig = go.FigureWidget()
fig.add_trace(go.Scatter(x=[], y=[], mode="lines", name="Best"))
algo.initialize()
for gen in range(max_generations):
algo.step()
algo.history_tracker.step(algo)
df = algo.history_tracker.to_pandas()
fig.data[0].x = df["iteration"]
fig.data[0].y = df["best_objective"]
Note
No plot is shown for this example, we encourage you to check out the real_time_plotting_tutorial.ipynb file in the tutorials/ directory.
This approach keeps the entire history; you can also stream only the latest data.
Customising and Exporting¶
All plots are standard matplotlib/seaborn objects. You can:
Add a horizontal target line:
ax.axhline(y=target, color="red", linestyle="--")Save to file:
plt.savefig("convergence.pdf", dpi=300)Annotate the best solution:
ax.annotate(...)Use the DataFrame for further analysis in Jupyter notebooks.