-
Notifications
You must be signed in to change notification settings - Fork 151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve performance of calc_connected_components #914
Improve performance of calc_connected_components #914
Conversation
@ccoffrin I believe we can also remove the Side note: Graphs.jl has a lot of functionality to do these kind of graph operations, which are likely more efficient (at the cost of yet another dependency). |
src/core/data.jl
Outdated
|
||
# ⚠️ it is important to iterate over _sorted_ bus IDs to ensure that components are labeled correctly | ||
for i in sorted_bus_ids | ||
component_lookup[i] != i0 && continue # bus already flagged; skip |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you remind me how this Julia &&
syntax works? I think it means, if the lhs is true then execute the rhs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is equivalent to
if component_lookup[i] != i0
continue
end
Julia evaluates boolean expressions lazily. Thereby, given A && B
, if A
evaluates to false
, then B
is not evaluated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My rule is that you should always write out the if end
version. It's much simpler to read.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's my preference as well, if you don't mind @mtanneau.
src/core/data.jl
Outdated
while length(V) > 0 | ||
j = pop!(V) | ||
for k in neighbors[j] | ||
component_lookup[k] == i0 || continue # already visited |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you remind me how this Julia ||
syntax works? I am guessing that it means, if the lhs is false then execute the rhs?
Is it also the case that component_lookup[k] == i
if it does not equal i0
, if not, then we have an issue where a previous labeling did not explore the graph correctly, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The syntax is equivalent to
if component_lookup[k] == i0
# do nothing
else
continue
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it also the case that component_lookup[k] == i if it does not equal i0, if not, then we have an issue where a previous labeling did not explore the graph correctly, right?
If component_lookup[k] != i0
, then it should be equal to the index of the bus with smallest index in that connected component. I believe it should then be equal to i
, yes, but only 95% confident.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it not the case 100% of the time then I worry the implementation has a correctness issue.
How about we add an assertion after this check that component_lookup[k] == i
, this will help catch the issue down the road if we run into it.
* Improve performance of calc_connected_components (#914) --------- Co-authored-by: mtanneau3 <[email protected]> * Add in-place conversion to basic data format (#915) * Add in-place conversion to basic data format * Add unit test for in-place make_basic_network --------- Co-authored-by: mtanneau3 <[email protected]> * No deepcopy when renumbering components (#916) * No deepcopy when renumbering components * In-place _renumber_components and unit tests * Fix typo --------- Co-authored-by: mtanneau3 <[email protected]> Co-authored-by: Carleton Coffrin <[email protected]> * add notes to changelog and prep for release * Update psse parser for 3winding transformers (#917) * fix psse parser 3 winding tranformers * update readme * add note to changelog and readme * fix quote counting check in psse parser --------- Co-authored-by: Mathieu Tanneau <[email protected]> Co-authored-by: mtanneau3 <[email protected]> Co-authored-by: Rahmat Heidari <[email protected]>
See #913 and profile therein. This PR focuses on improving the performance of
calc_connected_components
by implementing a more efficient graph search and identification of connected components.In particular, a significant amount of time is (currently) spent in this one line:
PowerModels.jl/src/core/data.jl
Line 2538 in 8e346b4
In the case where there is a single connected component, this ends up computing the union of
N
sets, all of which are identical and contain all the buses in the network. This is because the current implementation stores the entire connected component for every bus in the system, which also comes at a O(n^2) memory cost.I replace the previous DFS search with a more memory-efficient search that indexes each connected component with the smallest bus index in that connected component. There is a O(n log n) cost for sorting bus IDs, and then a O(E) cost for the graph search. The memory cost is O(n). It also saves time because it avoids the costly computation of a set of (large) sets. Note that I kept the last step (converting to a
Set{Set{Int}}
) to comply with the previous function definition.Timings of
make_basic_network
after the changes: times are improved, the impact on memory is marginal1354_pegase
2869_pegase
9241_pegase
13659_pegase
and corresponding profile: