from __future__ import annotations
from typing import List, Tuple
import numpy as np
import matplotlib.pyplot as plt
import VeraGridEngine.Devices as dev
[docs]
class ProceduralGridDebugger:
"""
Helper class for plotting and validating intermediate results during
procedural grid generation.
This class should only contain debug/inspection utilities and must not
modify the production objects.
"""
__slots__ = ("enabled",)
def __init__(self, enabled: bool = True):
"""
:param enabled: Enable or disable debug actions
"""
self.enabled = enabled
[docs]
def plot_mst_graph(
self,
coords_final_network: np.ndarray,
edges: List[Tuple[int, int]],
n_candidate: int,
n_target: int,
final_steiner_pts: np.ndarray,
title: str = "Procedural Grid Graph",
show_labels: bool = True,
) -> None:
"""
Plot the graph defined by node coordinates and edge list.
:param coords_final_network: Array of shape (n_nodes, 2) with [lon, lat]
:param edges: List of node index pairs
:param n_candidate: Number of candidate buses
:param n_target: Number of target buses
:param final_steiner_pts: Array of Steiner point coordinates
:param title: Plot title
:param show_labels: Whether to label nodes with their indices
"""
if not self.enabled:
return
plt.figure(figsize=(10, 8))
for i_from, i_to in edges:
p1 = coords_final_network[i_from]
p2 = coords_final_network[i_to]
plt.plot(
[p1[0], p2[0]],
[p1[1], p2[1]],
"k-",
linewidth=1.5,
alpha=0.7,
zorder=1,
)
if n_candidate > 0:
plt.scatter(
coords_final_network[:n_candidate, 0],
coords_final_network[:n_candidate, 1],
s=80,
marker="s",
c="green",
label="Candidates",
zorder=2,
)
if n_target > 0:
plt.scatter(
coords_final_network[n_candidate:n_candidate + n_target, 0],
coords_final_network[n_candidate:n_candidate + n_target, 1],
s=80,
marker="s",
c="blue",
label="Targets",
zorder=2,
)
if final_steiner_pts.shape[0] > 0:
plt.scatter(
final_steiner_pts[:, 0],
final_steiner_pts[:, 1],
s=70,
marker="o",
c="gray",
label="Steiner points",
zorder=3,
)
if show_labels:
for i, (x, y) in enumerate(coords_final_network):
plt.text(x, y, str(i), fontsize=8, ha="right", va="bottom")
plt.xlabel("Longitude")
plt.ylabel("Latitude")
plt.title(title)
plt.legend()
plt.grid(True, linestyle="--", alpha=0.5)
plt.axis("equal")
plt.tight_layout()
plt.show()
[docs]
def snapshot_grid_element_names(self, grid: dev.MultiCircuit) -> set[str]:
"""
Take a snapshot of the current element names in the grid.
:param grid: MultiCircuit instance
:return: Set of element names currently present in the grid
"""
names: set[str] = set()
for bus in grid.buses:
names.add(bus.name)
for branch in grid.get_branches(add_vsc=True, add_hvdc=True, add_switch=True):
names.add(branch.name)
for load in grid.get_loads():
names.add(load.name)
for generator in grid.get_generators():
names.add(generator.name)
return names
[docs]
def get_added_element_names(
self,
grid: dev.MultiCircuit,
previous_names: set[str],
) -> list[str]:
"""
Return the names of the elements that were added to the grid
after a previous snapshot.
:param grid: MultiCircuit instance
:param previous_names: Snapshot of names taken before modification
:return: Sorted list of newly added element names
"""
current_names = self.snapshot_grid_element_names(grid=grid)
added_names = current_names - previous_names
return sorted(added_names)