Skip to content

Commit 2cc6ece

Browse files
committed
Refactor main algorithm into submodules
1 parent e7701cb commit 2cc6ece

1 file changed

Lines changed: 69 additions & 52 deletions

File tree

task_queue/grid_optimizer.py

Lines changed: 69 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -183,23 +183,44 @@ def __init__(self, grid_opt_json):
183183

184184
def optimize(self):
185185
print("Optimizing distribution grid...")
186+
n_grid_consumers = self._setup_coordinates()
187+
print("Determining number of poles...")
188+
self._place_poles(n_grid_consumers)
189+
self._connect_nodes()
190+
print("Determining distribution links...")
191+
self._insert_long_link_poles()
192+
print("Determining power house location...")
193+
self._run_cost_and_shs_loop()
194+
return self._process_results()
195+
196+
def _setup_coordinates(self) -> int:
197+
"""Convert to projected coords, build road graph, clear stale poles.
198+
199+
Returns n_grid_consumers for use by _place_poles.
200+
"""
186201
self.convert_lonlat_xy()
187202
if self.roads is not None:
188203
self._build_road_graph()
189204
# Drop ALL poles from previous runs -> _clear_poles() preserves road-sampled poles
190205
self.nodes = self.nodes[self.nodes["node_type"] != "pole"]
191-
n_total_consumers = len(self.nodes)
192206
n_shs_consumers = len(self.nodes[self.nodes["is_connected"] == False]) # noqa: E712
193-
n_grid_consumers = n_total_consumers - n_shs_consumers
207+
n_grid_consumers = len(self.nodes) - n_shs_consumers
194208
self.nodes = self.nodes.sort_index(key=lambda x: x.astype("int64"))
209+
return n_grid_consumers
210+
211+
def _place_poles(self, n_grid_consumers: int):
212+
"""Place k-means or road-sampled poles. Handles power-house consumer placeholder.
213+
214+
Stores _power_house_idx on self for use by _connect_nodes.
215+
"""
195216
if self.power_house is not None:
196217
power_house_consumers = self._connect_power_house_consumer_manually(
197218
self.connection_cable_max_length,
198219
)
199220
self._placeholder_consumers_for_power_house()
200221
else:
201222
power_house_consumers = None
202-
print("Determining number of poles...")
223+
203224
if self.roads is not None:
204225
self._place_poles_with_roads()
205226
else:
@@ -209,52 +230,44 @@ def optimize(self):
209230

210231
if self.power_house is not None:
211232
cluster_label = self.nodes.loc["100000", "cluster_label"]
212-
power_house_idx = self.nodes[
233+
self._power_house_idx = self.nodes[
213234
(self.nodes["node_type"] == "pole")
214235
& (self.nodes["cluster_label"] == cluster_label)
215236
].index
216237
power_house_consumers["cluster_label"] = cluster_label
217238
power_house_consumers["consumer_type"] = np.nan
218239
self.nodes = pd.concat([self.nodes, power_house_consumers])
219240
self._placeholder_consumers_for_power_house(remove=True)
220-
# Drop old power house index to avoid duplicates
221241
self.nodes = self.nodes.drop(index=self.power_house.index)
242+
else:
243+
self._power_house_idx = None
222244

245+
def _connect_nodes(self):
246+
"""Build MST distribution backbone and connect consumers to their poles."""
223247
self.create_minimum_spanning_tree()
224248
self.connect_grid_consumers()
249+
if self.power_house is not None and self._power_house_idx is not None:
250+
ph = self._power_house_idx[0]
251+
self.nodes.loc[self.nodes.index == ph, "node_type"] = "power-house"
252+
self.nodes.loc[self.nodes.index == ph, "how_added"] = "manual"
225253

226-
if self.power_house is not None:
227-
self.nodes.loc[self.nodes.index == power_house_idx[0], "node_type"] = "power-house"
228-
self.nodes.loc[self.nodes.index == power_house_idx[0], "how_added"] = "manual"
229-
230-
# Populate self.links with distribution links so find_index_longest_distribution_link
231-
# can detect links that exceed max_length. A second call follows after intermediate
232-
# poles have been inserted to rebuild distribution links with long links broken.
254+
def _insert_long_link_poles(self):
255+
"""Find over-length distribution links, insert intermediate poles, and reconnect."""
233256
self.connect_grid_poles()
234-
# Find the connection links_df in the network with lengths greater than the
235-
# maximum allowed length for `connection` cables, specified by the user.
236257
long_links = self.find_index_longest_distribution_link()
237-
# Add poles to the identified long `distribution` links_df, so that the
238-
# distance between all poles remains below the maximum allowed distance.
239258
self._add_fixed_poles_on_long_links(long_links=long_links)
240-
# Update the (lon,lat) coordinates based on the newly inserted poles
241-
# which only have (x,y) coordinates.
242259
self.convert_lonlat_xy(inverse=True)
243-
# Connect all poles together using the minimum spanning tree algorithm.
244-
print("Determining distribution links...")
245260
self.connect_grid_poles(long_links=long_links)
246-
# Calculate distances of all poles from the load centroid.
247-
# Find the location of the power house.
248261
self.add_number_of_distribution_and_connection_cables()
262+
263+
def _run_cost_and_shs_loop(self):
264+
"""Iteratively place power house, compute costs, and determine SHS consumers."""
249265
n_iter = 2 if self.power_house is None else 1
250-
print("Determining power house location...")
251266
for i in range(n_iter):
252267
if self.power_house is None and i == 0:
253268
self._select_location_of_power_house()
254269
self._set_direction_of_links()
255-
self.allocate_poles_to_branches()
256-
self.allocate_subbranches_to_branches()
257-
self.label_branch_of_consumers()
270+
self._build_branch_hierarchy()
258271
self.determine_cost_per_pole()
259272
self._connection_cost_per_consumer()
260273
self.determine_costs_per_branch()
@@ -276,8 +289,6 @@ def optimize(self):
276289
else:
277290
break
278291

279-
return self._process_results()
280-
281292
def _process_results(self):
282293
"""
283294
Returns a json object with processed nodes and links
@@ -911,7 +922,14 @@ def add_number_of_distribution_and_connection_cables(self):
911922
self.nodes["n_connection_links"].fillna(0).astype(int)
912923
)
913924

914-
def allocate_poles_to_branches(self):
925+
def _build_branch_hierarchy(self):
926+
"""Assign branch and parent_branch labels to all poles and consumers.
927+
928+
Runs the three allocation steps in the required order:
929+
1. assign branch labels to poles (allocate_poles_to_branches)
930+
2. assign parent_branch labels to poles (allocate_subbranches_to_branches)
931+
3. propagate branch labels to consumers (label_branch_of_consumers)
932+
"""
915933
poles = self._poles().copy()
916934
leaf_poles = pd.Series(
917935
poles[poles["n_distribution_links"] == 1].index
@@ -957,7 +975,27 @@ def allocate_poles_to_branches(self):
957975
"branch",
958976
] = power_house
959977

960-
def label_branch_of_consumers(self):
978+
# --- parent_branch assignment ---
979+
poles = self._poles().copy()
980+
self.nodes["parent_branch"] = None
981+
982+
if len(poles["branch"].unique()) > 1:
983+
leaf_poles_idx = poles[poles["n_distribution_links"] == 1].index
984+
self._determine_parent_branches(leaf_poles_idx)
985+
poles_excl_ph = poles[poles["node_type"] != "power-house"]
986+
split_poles_idx = poles_excl_ph[
987+
poles_excl_ph["n_distribution_links"] > 2 # noqa: PLR2004
988+
].index
989+
if len(split_poles_idx) > 0:
990+
self._determine_parent_branches(split_poles_idx)
991+
992+
self.nodes.loc[
993+
(self.nodes["parent_branch"].isna())
994+
& (self.nodes["node_type"].isin(["pole", "power-house"])),
995+
"parent_branch",
996+
] = power_house
997+
998+
# --- propagate branch to consumers ---
961999
branch_map = self.nodes.loc[
9621000
self.nodes.node_type.isin(["pole", "power-house"]),
9631001
"branch",
@@ -967,7 +1005,7 @@ def label_branch_of_consumers(self):
9671005
"parent",
9681006
].map(branch_map)
9691007

970-
def determine_parent_branches(self, start_poles):
1008+
def _determine_parent_branches(self, start_poles):
9711009
poles = self._poles().copy()
9721010
for pole in start_poles:
9731011
branch_start_pole = poles[poles.index == pole]["branch"].iloc[0]
@@ -980,27 +1018,6 @@ def determine_parent_branches(self, start_poles):
9801018
"parent_branch",
9811019
] = parent_branch
9821020

983-
def allocate_subbranches_to_branches(self):
984-
poles = self._poles().copy()
985-
self.nodes["parent_branch"] = None
986-
power_house = poles[poles["node_type"] == "power-house"].index[0]
987-
988-
if len(poles["branch"].unique()) > 1:
989-
leaf_poles = poles[poles["n_distribution_links"] == 1].index
990-
self.determine_parent_branches(leaf_poles)
991-
poles_expect_power_house = poles[poles["node_type"] != "power-house"]
992-
split_poles = poles_expect_power_house[
993-
poles_expect_power_house["n_distribution_links"] > 2 # noqa: PLR2004
994-
].index
995-
if len(split_poles) > 0:
996-
self.determine_parent_branches(split_poles)
997-
998-
self.nodes.loc[
999-
(self.nodes["parent_branch"].isna())
1000-
& (self.nodes["node_type"].isin(["pole", "power-house"])),
1001-
"parent_branch",
1002-
] = power_house
1003-
10041021
def determine_cost_per_pole(self):
10051022
poles = self._poles().copy()
10061023
links = self.get_links().copy()

0 commit comments

Comments
 (0)