@@ -1374,7 +1374,7 @@ function _resolve_arc_admittance(
13741374 arc_remove_ixs = indexin (removed_arcs, get_arc_axis (new_y_ft))
13751375 arc_keep_ixs = setdiff (collect (1 : length (get_arc_axis (new_y_ft))), arc_remove_ixs)
13761376 # Remap arc endpoint labels to surviving bus numbers. Column data was already merged
1377- # in _merge_arc_admittance_bus_columns! before this call; only the axis labels need
1377+ # in _merge_arc_admittance_bus_columns before this call; only the axis labels need
13781378 # updating so downstream reductions can match arcs by their new bus numbers.
13791379 if ! isempty (merged_bus_pairs)
13801380 for k in eachindex (arc_ax)
@@ -1445,30 +1445,73 @@ function _resolve_arc_admittance(
14451445 return existing_ft, existing_tf
14461446end
14471447
1448- # Transfer arc admittance contributions from the removed bus column to the surviving bus
1449- # column before the removed bus column is sliced out. Without this, to-bus admittance
1450- # entries for arcs that terminate at the removed bus are silently dropped.
1451- function _merge_arc_admittance_bus_columns! (
1448+ # Add every removed bus's column into its surviving bus's column on one arc×bus admittance
1449+ # matrix, in a single O(nnz) relabel-and-sum pass. Each stored entry is emitted at its own
1450+ # column and, when that column is a removed bus, also at its survivor's column; `sparse` sums
1451+ # the collisions. Removed columns keep their own entries, since the bus-removal slice later
1452+ # drops them.
1453+ function _merge_arc_admittance_columns (
1454+ M:: SparseArrays.SparseMatrixCSC{T, Int} ,
1455+ bus_lookup:: Dict{Int, Int} ,
1456+ merged_bus_pairs:: Dict{Int, Int} ,
1457+ ) where {T}
1458+ ncols = size (M, 2 )
1459+ col_survivor = collect (1 : ncols)
1460+ for (removed_bus, surviving_bus) in merged_bus_pairs
1461+ col_survivor[bus_lookup[removed_bus]] = bus_lookup[surviving_bus]
1462+ end
1463+ rows = SparseArrays. rowvals (M)
1464+ vals = SparseArrays. nonzeros (M)
1465+ cap = 2 * length (vals)
1466+ I = Vector {Int} (undef, cap)
1467+ J = Vector {Int} (undef, cap)
1468+ V = Vector {T} (undef, cap)
1469+ n = 0
1470+ for col in 1 : ncols
1471+ s = col_survivor[col]
1472+ for k in SparseArrays. nzrange (M, col)
1473+ r = rows[k]
1474+ v = vals[k]
1475+ n += 1
1476+ I[n] = r
1477+ J[n] = col
1478+ V[n] = v
1479+ if s != col
1480+ n += 1
1481+ I[n] = r
1482+ J[n] = s
1483+ V[n] = v
1484+ end
1485+ end
1486+ end
1487+ return SparseArrays. sparse (
1488+ resize! (I, n), resize! (J, n), resize! (V, n), size (M, 1 ), ncols)
1489+ end
1490+
1491+ # Merge the removed-bus columns into their survivors on both arc admittance matrices, returning
1492+ # new matrices. This transfer keeps the to-bus admittance entries for arcs terminating at a
1493+ # removed bus, which the later bus-removal slice would otherwise drop.
1494+ function _merge_arc_admittance_bus_columns (
14521495 yft:: ArcAdmittanceMatrix ,
14531496 ytf:: ArcAdmittanceMatrix ,
14541497 bus_lookup:: Dict{Int, Int} ,
14551498 merged_bus_pairs:: Dict{Int, Int} ,
14561499)
1457- for (removed_bus, surviving_bus) in merged_bus_pairs
1458- i = bus_lookup[surviving_bus]
1459- j = bus_lookup[removed_bus]
1460- yft . data[:, i] += yft . data[:, j]
1461- ytf. data[:, i] += ytf . data[:, j]
1462- end
1463- return
1500+ new_yft = ArcAdmittanceMatrix (
1501+ _merge_arc_admittance_columns (yft . data, bus_lookup, merged_bus_pairs),
1502+ yft . axes, yft . lookup, yft . network_reduction_data, yft . direction)
1503+ new_ytf = ArcAdmittanceMatrix (
1504+ _merge_arc_admittance_columns ( ytf. data, bus_lookup, merged_bus_pairs),
1505+ ytf . axes, ytf . lookup, ytf . network_reduction_data, ytf . direction)
1506+ return new_yft, new_ytf
14641507end
14651508
1466- _merge_arc_admittance_bus_columns! (
1509+ _merge_arc_admittance_bus_columns (
14671510 :: Nothing ,
14681511 :: Nothing ,
14691512 :: Dict{Int, Int} ,
14701513 :: Dict{Int, Int} ,
1471- ) = nothing
1514+ ) = ( nothing , nothing )
14721515
14731516function _accumulate_csc_row_into! (
14741517 M:: SparseArrays.SparseMatrixCSC ,
@@ -1655,14 +1698,15 @@ function _apply_reduction(ybus::Ybus, nr_new::NetworkReductionData)
16551698 nr = get_network_reduction_data (ybus)
16561699
16571700 # A pure-merge reduction (only ZIBR today) folds removed buses into survivors purely by
1658- # index relabeling, so the merge and the bus-removal slice are fused into one O(nnz)
1659- # rebuild below instead of in-place CSC structural inserts. Arc-admittance columns are
1660- # still merged here (column adds on the arc× bus matrix, not the hot square-matrix path) .
1701+ # index relabeling, so its merge and bus-removal slice are fused into one O(nnz) rebuild
1702+ # below; any other reduction merges the square Ybus in place via _merge_ybus_buses!. In
1703+ # either case the arc-admittance bus columns are merged in _merge_arc_admittance_bus_columns .
16611704 fast_merge = _is_pure_merge_reduction (nr_new)
1705+ yft_merged, ytf_merged = ybus. arc_admittance_from_to, ybus. arc_admittance_to_from
16621706 if ! isempty (nr_new. merged_bus_pairs)
16631707 fast_merge ||
16641708 _merge_ybus_buses! (data, adjacency_data, bus_lookup, nr_new. merged_bus_pairs)
1665- _merge_arc_admittance_bus_columns! (
1709+ yft_merged, ytf_merged = _merge_arc_admittance_bus_columns (
16661710 ybus. arc_admittance_from_to,
16671711 ybus. arc_admittance_to_from,
16681712 bus_lookup,
@@ -1674,8 +1718,8 @@ function _apply_reduction(ybus::Ybus, nr_new::NetworkReductionData)
16741718 new_y_ft, new_y_tf = _add_series_branches_to_ybus! (
16751719 ybus. data,
16761720 get_bus_lookup (ybus),
1677- ybus . arc_admittance_from_to ,
1678- ybus . arc_admittance_to_from ,
1721+ yft_merged ,
1722+ ytf_merged ,
16791723 nr_new. series_branch_map,
16801724 nr,
16811725 )
0 commit comments