Skip to content
116 changes: 77 additions & 39 deletions examples/bioma/fig4.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from concurrent.futures import ProcessPoolExecutor
from pathlib import Path

from plotly.subplots import make_subplots
import matplotlib.pyplot as plt
from problems import ackley4, griewank, schwefel2_26
from utils import IMAGES_DIR, FunctionConfig, build_cmlon

Expand Down Expand Up @@ -31,56 +31,94 @@
N_VAR = 5


def render_3d_cmlons(func_names, cmlons):
standard_camera = dict(
up=dict(x=0, y=0, z=1), center=dict(x=0, y=0, z=0), eye=dict(x=1.55, y=1.55, z=0.4)
)

axis_config = dict(
visible=True,
showgrid=True,
gridcolor="lightgray",
showline=True,
linecolor="black",
showbackground=True,
backgroundcolor="rgb(250, 250, 250)",
zeroline=True,
zerolinecolor="gray",
showticklabels=True,
)

plot_paths = []

for func_name in func_names:
viz = LONVisualizer()
fig = viz.plot_3d(cmlons[func_name])

fig.update_layout(
scene=dict(
xaxis=dict(**axis_config, title="X"),
yaxis=dict(**axis_config, title="Y"),
zaxis=dict(**axis_config, title="Fitness"),
camera=dict(**standard_camera),
aspectmode="cube",
),
title=dict(
text=f"{func_name}",
x=0.5,
y=0.95,
xanchor="center",
font=dict(size=20),
),
showlegend=False,
width=600,
height=600,
margin=dict(l=20, r=20, t=60, b=20),
paper_bgcolor="rgb(255, 255, 255)",
plot_bgcolor="rgb(255, 255, 255)",
)

path = Path(IMAGES_DIR) / f"fig4_{func_name.replace(' ', '_')}.png"
fig.write_image(path, scale=2)
plot_paths.append((func_name, path))
print(f"Saved 3D panel to {path}")

return plot_paths


def merge_3d_plots(plot_paths):
merged_fig, axes = plt.subplots(1, len(plot_paths), figsize=(6 * len(plot_paths), 6))
if len(plot_paths) == 1:
axes = [axes]

for ax, (_, image_path) in zip(axes, plot_paths):
img = plt.imread(image_path)
ax.imshow(img)
ax.axis("off")

merged_fig.tight_layout()

final_path = Path(IMAGES_DIR) / "fig4.png"
merged_fig.savefig(final_path, dpi=200, bbox_inches="tight")
plt.close(merged_fig)
print(f"Successfully saved merged figure to {final_path}")


def main() -> None:
Path(IMAGES_DIR).mkdir(parents=True, exist_ok=True)

viz = LONVisualizer()
func_names = list(FUNCTIONS.keys())

# Build CMLONs in parallel, then create 3D figures
with ProcessPoolExecutor() as executor:
futures = {
func_name: executor.submit(build_cmlon, FUNCTIONS[func_name], N_VAR)
for func_name in func_names
}
cmlons = {name: fut.result() for name, fut in futures.items()}

figures = []
for func_name in func_names:
fig = viz.plot_3d(cmlons[func_name])
figures.append(fig)

# Combine into a single 1x3 subplot figure
combined = make_subplots(
rows=1,
cols=3,
specs=[[{"type": "scene"}, {"type": "scene"}, {"type": "scene"}]],
subplot_titles=[
f"(a) {func_names[0]}",
f"(b) {func_names[1]}",
f"(c) {func_names[2]}",
],
horizontal_spacing=0.02,
)
cmlons = {name: fut.result() for name, fut in futures.items()}

for idx, fig in enumerate(figures, start=1):
scene_name = f"scene{idx}" if idx > 1 else "scene"
for trace in fig.data:
trace.scene = scene_name
combined.add_trace(trace, row=1, col=idx)
# Copy camera / axis settings from original figure
if "scene" in fig.layout:
combined.layout[scene_name].update(fig.layout.scene)

combined.update_layout(
showlegend=False,
width=1800,
height=600,
margin=dict(l=0, r=0, t=60, b=0),
)
plot_paths = render_3d_cmlons(func_names, cmlons)

combined.write_image(f"{IMAGES_DIR}/fig4.png", scale=2)
combined.write_html(f"{IMAGES_DIR}/fig4.html")
merge_3d_plots(plot_paths)


if __name__ == "__main__":
Expand Down
2 changes: 1 addition & 1 deletion examples/bioma/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ def save_metrics_figure(
fontsize=10,
)

fig.tight_layout(rect=[0, 0.06, 1, 1])
fig.tight_layout(rect=(0, 0.06, 1, 1))
fig.savefig(str(output_path), dpi=150, bbox_inches="tight", facecolor="white")
plt.close(fig)
print(f"Saved {output_path}")
1 change: 1 addition & 0 deletions examples/number_partitioning/npp_paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
IMAGES_DIR = "images/number_partitioning"
116 changes: 116 additions & 0 deletions examples/number_partitioning/plot_lons.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from pathlib import Path

import matplotlib.pyplot as plt
from npp_paths import IMAGES_DIR

from lonkit import (
CMLON,
ILSSampler,
ILSSamplerConfig,
LONConfig,
LONVisualizer,
NumberPartitioning,
)

N = 20
INSTANCE_SEED = 1
N_RUNS = 100
N_ITER = 500
RANDOM_SEED = 42

K_VALUES = [0.3, 0.7, 0.95]


def render_3d_lons(cmlon_by_k: dict[float, CMLON], output_dir: Path = Path(IMAGES_DIR)) -> None:
output_dir.mkdir(parents=True, exist_ok=True)

standard_camera = dict(
up=dict(x=0, y=0, z=1),
center=dict(x=0, y=0, z=0),
eye=dict(x=1.55, y=1.55, z=0.4),
)

axis_config = dict(
visible=True,
showgrid=True,
gridcolor="lightgray",
showline=True,
linecolor="black",
showbackground=True,
backgroundcolor="rgb(250, 250, 250)",
zeroline=True,
zerolinecolor="gray",
showticklabels=True,
)

for k, cmlon in cmlon_by_k.items():
vis = LONVisualizer(min_edge_width=0.5, max_edge_width=1, min_node_size=2.5, arrow_size=0.1)
fig = vis.plot_3d(cmlon)
fig.update_layout(
scene=dict(
xaxis=dict(**axis_config, title="X"),
yaxis=dict(**axis_config, title="Y"),
zaxis=dict(**axis_config, title="Fitness"),
camera=dict(**standard_camera),
aspectmode="cube",
),
showlegend=False,
width=900,
height=700,
margin=dict(l=20, r=20, t=40, b=20),
)

fig.write_image(output_dir / f"NPP_{k}_3d.png", scale=2)


def render_merged_lon_grid(k_values: list[float], output_dir: Path = Path(IMAGES_DIR)) -> None:
fig, axes = plt.subplots(2, len(k_values), figsize=(5 * len(k_values), 10))
if len(k_values) == 1:
axes = [[axes[0]], [axes[1]]]

for idx, k in enumerate(k_values):
img_2d = plt.imread(output_dir / f"NPP_{k}_2d.png")
img_3d = plt.imread(output_dir / f"NPP_{k}_3d.png")

ax_top = axes[0][idx]
ax_top.imshow(img_2d)
ax_top.set_title(f"2D CMLON (k={k})", fontsize=12)
ax_top.axis("off")

ax_bottom = axes[1][idx]
ax_bottom.imshow(img_3d)
ax_bottom.set_title(f"3D CMLON (k={k})", fontsize=12)
ax_bottom.axis("off")

fig.suptitle("Number Partitioning CMLON Views", fontsize=16, y=0.98)
fig.tight_layout(rect=(0, 0, 1, 0.96))
fig.savefig(output_dir / "NPP_merged_cmlon_views.png", dpi=200)
plt.close(fig)


def main():
Path(IMAGES_DIR).mkdir(parents=True, exist_ok=True)

sampler_config = ILSSamplerConfig(n_runs=N_RUNS, n_iter_no_change=N_ITER, seed=RANDOM_SEED)

lon_config = LONConfig(eq_atol=1e-8)
cmlon_by_k = {}

for k in K_VALUES:
problem = NumberPartitioning(n=N, k=k, instance_seed=INSTANCE_SEED)
sampler = ILSSampler(sampler_config)
result = sampler.sample(problem)

lon = sampler.sample_to_lon(result, lon_config)
cmlon = lon.to_cmlon()
cmlon_by_k[k] = cmlon

vis = LONVisualizer(0.5, 1, arrow_size=0.1)
vis.plot_2d(cmlon, f"{IMAGES_DIR}/NPP_{k}_2d.png")

render_3d_lons(cmlon_by_k)
render_merged_lon_grid(K_VALUES)


if __name__ == "__main__":
main()
Loading
Loading