Inspect and Improve Convergence¶
This guide covers how to read convergence metrics, visualize area errors and generator displacement, and tune parameters to reach a satisfactory result faster.
See also: Tutorial · Explanation: CVT Algorithm · Analyze and Fix Topology
import matplotlib.pyplot as plt
import carto_flow.data as examples
import carto_flow.voronoi_cartogram as vor
from carto_flow.voronoi_cartogram.visualization import plot_convergence
us_states = examples.load_us_census(population=True)
Run With History and Progress Logging¶
Pass show_progress=True to print per-iteration diagnostics and record_history=5 to snapshot every 5 iterations.
result = vor.create_voronoi_cartogram(
us_states,
weights="Population (Millions)",
options=vor.VoronoiOptions(
n_iter=30,
show_progress=True,
record_history=5,
),
)
iter 0 max_disp=1.895e+05 area_cv=1.4179 factor=1.900 iter 1 max_disp=8.302e+04 area_cv=1.3549 factor=1.862 iter 2 max_disp=6.322e+04 area_cv=1.3536 factor=1.825 iter 3 max_disp=5.712e+04 area_cv=1.3276 factor=1.788 iter 4 max_disp=5.026e+04 area_cv=1.2980 factor=1.752 iter 5 max_disp=5.852e+04 area_cv=1.2487 factor=1.717 iter 6 max_disp=6.821e+04 area_cv=1.1861 factor=1.683 iter 7 max_disp=6.91e+04 area_cv=1.1019 factor=1.649 iter 8 max_disp=6.738e+04 area_cv=1.0075 factor=1.616 iter 9 max_disp=7.681e+04 area_cv=0.8923 factor=1.584 iter 10 max_disp=8.289e+04 area_cv=0.6834 factor=1.552 iter 11 max_disp=5.484e+04 area_cv=0.5962 factor=1.521 iter 12 max_disp=3.911e+04 area_cv=0.5473 factor=1.491 iter 13 max_disp=3.801e+04 area_cv=0.5087 factor=1.461 iter 14 max_disp=3.798e+04 area_cv=0.4766 factor=1.432 iter 15 max_disp=3.486e+04 area_cv=0.4545 factor=1.403 iter 16 max_disp=3.526e+04 area_cv=0.4357 factor=1.375 iter 17 max_disp=4.108e+04 area_cv=0.4202 factor=1.348 iter 18 max_disp=4.75e+04 area_cv=0.4054 factor=1.321 iter 19 max_disp=4.635e+04 area_cv=0.3907 factor=1.294 iter 20 max_disp=4.519e+04 area_cv=0.3770 factor=1.268
iter 21 max_disp=1.479e+05 area_cv=0.3664 factor=1.243 iter 22 max_disp=3.753e+04 area_cv=0.3520 factor=1.218 iter 23 max_disp=2.246e+04 area_cv=0.3425 factor=1.194 iter 24 max_disp=2.164e+04 area_cv=0.3345 factor=1.170 iter 25 max_disp=1.932e+04 area_cv=0.3289 factor=1.147 iter 26 max_disp=1.858e+04 area_cv=0.3245 factor=1.124 iter 27 max_disp=1.952e+04 area_cv=0.3214 factor=1.101 iter 28 max_disp=1.743e+04 area_cv=0.3163 factor=1.079 iter 29 max_disp=1.968e+04 area_cv=0.3122 factor=1.058
Read the Metrics¶
The metrics dict is the first place to look after a run.
| Key | Meaning |
|---|---|
converged |
True if a tolerance was set and reached |
n_iterations |
Total iterations run |
initial_area_cv |
Area coefficient of variation before relaxation |
final_area_cv |
Area CV after relaxation; lower is better |
mean_area_error_pct |
Average absolute % deviation from target area |
max_area_error_pct |
Worst-case % deviation |
for k, v in result.metrics.items():
print(f" {k:<25} {v}")
n_iterations 30 converged False initial_area_cv 0.7664003738127129 final_area_cv 0.3121765092995689 mean_area_error_pct 82.81724752324067 max_area_error_pct 369.6119219906679
Plot the Convergence Curve¶
fig, ax = plt.subplots(figsize=(4, 5))
plot_convergence(result, ax=ax)
ax.set_ylim(0, None)
plt.tight_layout();
A still-decreasing curve means more iterations will help. A flat curve means the algorithm has stalled and parameter changes are needed.
Per-Region Area Errors¶
area_errors gives the signed % deviation from each region's target area.
fig, ax = plt.subplots(figsize=(10, 6))
result.plot(
column="area_error_pct",
ax=ax,
cmap="RdYlGn_r",
vmin=-70,
vmax=70,
legend=True,
legend_kwds={"shrink": 0.6, "label": "Area error (%)"},
)
for cell, label in zip(result.cells, us_states["State Abbreviation"], strict=False):
ax.text(cell.centroid.x, cell.centroid.y, label, fontsize=7, ha="center", va="center")
ax.set(title="Per-region area error")
ax.axis("off")
plt.tight_layout();
Displacement Plot¶
plot_displacement() draws arrows from each region's original centroid to its final generator position, showing which regions moved most during relaxation.
fig, ax = plt.subplots(figsize=(10, 6))
result.plot_displacement(ax=ax, show_adjacency=False)
ax.set(title="Generator displacement")
ax.axis("off")
plt.tight_layout();
result_more = vor.create_voronoi_cartogram(
us_states,
weights="Population (Millions)",
options=vor.VoronoiOptions(n_iter=100, area_cv_tol=0.05),
)
print(
f"Converged: {result_more.metrics['converged']} "
f"after {result_more.metrics['n_iterations']} iterations "
f"(area CV: {result_more.metrics['final_area_cv']:.4f})"
)
Converged: False after 100 iterations (area CV: 0.1793)
Over-relaxation schedule¶
The default "overrelax" schedule applies a decaying over-relaxation factor that overshoots each step slightly and anneals toward standard Lloyd. You can customise start, decay, and minimum.
# Default equivalent — shown explicitly for reference
# schedule = vor.RelaxationSchedule(start=1.9, decay=0.98, minimum=1.0)
# slower decay of over-relaxation
schedule = vor.RelaxationSchedule(start=1.9, decay=0.999, minimum=1.0)
result_relax = vor.create_voronoi_cartogram(
us_states,
weights="Population (Millions)",
backend=vor.RasterBackend(relaxation=schedule),
options=vor.VoronoiOptions(n_iter=30),
)
print(f"Final area CV: {result_relax.metrics['final_area_cv']:.4f}")
Final area CV: 0.2680
Higher resolution¶
A larger pixel grid (RasterBackend(resolution=500)) yields smoother cells and more accurate centroids, at the cost of longer iteration times. Convergence may improve if the initial resolution was too low for the number of input geometries.
result_hires = vor.create_voronoi_cartogram(
us_states,
weights="Population (Millions)",
backend=vor.RasterBackend(resolution=500),
options=vor.VoronoiOptions(n_iter=30),
)
print(f"Final area CV: {result_hires.metrics['final_area_cv']:.4f}")
Final area CV: 0.3116
Elastic boundaries¶
By making the outer boundary elastic and move to accomodate shtinking/growing cells, convergence can usually be improved considerably.
result_elastic = vor.create_voronoi_cartogram(
us_states,
weights="Population (Millions)",
backend=vor.RasterBackend(boundary=vor.ElasticBoundary(strength=0.05)),
options=vor.VoronoiOptions(n_iter=30),
)
print(f"Final area CV: {result_elastic.metrics['final_area_cv']:.4f}")
Final area CV: 0.0637
See Also¶
- Tutorial
- Explanation: CVT Algorithm
- Analyze and Fix Topology —
plot_displacementalso helps diagnose topology issues