-
Notifications
You must be signed in to change notification settings - Fork 28
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
feat: add local transmission capacity analysis function #279
base: hifld
Are you sure you want to change the base?
Conversation
I like it! Thanks for the exploration with detailed explanation. A few comments from the design perspective which may or may not simplify the graph traversal logic due to complicated return structure:
class TreeNode:
def __init__(self, name, demand, capacity):
self.name = name
self.demand = demand
self.capacity = capacity
self.children = []
def build_tree():
adj = {'b1': ['c1'],
'c1': ['b2', 'b3'],
'b2': [],
'b3': ['c2'],
'c2': ['b4'],
'b4': ['c3', 'c4'],
'c3': ['b5'],
'c4': ['b6', 'b7']
}
root = TreeNode('b1', 1, 0)
queue = [root]
for q in queue:
if q.name in adj and adj[q.name]:
for ch in adj[q.name]:
if 'b' in ch:
node = TreeNode(ch, 1, 0)
q.children.append(node)
queue.append(node)
else:
node = TreeNode(ch, 0, len(adj[ch]))
q.children.append(node)
queue.append(node)
return root
root = build_tree()
def dfs(root):
sub_tree_demand = root.demand
for c in root.children:
sub_tree_demand += dfs(c)
if 'c' in root.name:
root.demand = sub_tree_demand
return sub_tree_demand The code snippet above will give us following results: def helper(root):
print(root.name, root.demand, root.capacity)
for c in root.children:
helper(c)
>>>helper(root)
b1 1 0
c1 6 2
b2 1 0
b3 1 0
c2 4 1
b4 1 0
c3 1 1
b5 1 0
c4 2 2
b6 1 0
b7 1 0 Finally, we can simply go through all articulation points (node name with 'c' in it in the example above) to see whether
|
@BainanXia the I'm not sure whether we can compress all of the graph information into nodes of the
These correspond exactly to the three edges from the node 2 articulation point. The current implementation could definitely be refactored to simplify it, especially if we don't care about keeping track of the set of descendants for each block/articulation point. I had been using that for debugging, and potentially it could be useful downstream of this function when we start to look at the problems and decide what to do about them, but doesn't seem to be strictly necessary just to look for capacity/demand mismatches. |
@danielolsen What does the original structure of In a general case, we should be able to evaluate every parent and child pair regardless of articulation point or block point, i.e. having demand attribute always store the total demand of the downstream subtree and capacity attribute with each edge instead, then checking throughout the graph for each pair: I'm not sure I understand what you mean by |
@BainanXia based on visualizing hifld/ERCOT, the tree can indeed be several layers deep, with downstream branch points as well (not just long radial blocks and articulations caused by long radial transmission lines in series). It probably only gets more complex in the larger interconnects. I agree that we can store demand information for a node and all downstream nodes in any node of the BC Tree, and compare any node against its upstream edge. The current output of |
I see. If the desired info is the list of nodes within a block, probably we can put the keys/values used to construct the BC tree into a list and only use the corresponding index to label the node in the tree. In this way, we don't need to maintain that result data structure during recursion, which might be cleaner. |
The desired info within the |
Aha, I see. I didn't look into the data structure within I think it depends on how we would like to solve the problem after they are identified. If it is solved in a bottom-up way, i.e. starting from the edge of the network, if the violations of those "leaf" biconnected-components are resolved first, then for the upper level bottlenecks, we only need to get local problem solved, i.e. only the total demand behind an edge matters. Is there an example in which case we will need the individual node info within a downstream block (rather than the one directly connected) to fix the edge capacity? Or we are also thinking of modifying the way we distribute load, i.e. |
I haven't figured out the 'what to do with the problems once we identify' step. If you've got ideas, I'm all ears! Like you said, the simplest way is just to expand the line capacities at each constrained bottleneck, but maybe in some cases there's demand/generation that got connected to the wrong part of the network, and needs to be relocated. Once we're thinking about pushing power from a radial section back up to the bulk grid, something like |
I don't know either. One thought other than solutions is once we have load profiles by bus we can look at the peak hour in the downstream network instead of summation of peak demand for all the downstream buses, which will give us less violations and minimize the impact of whatever solution we carry out later. We can run the same code for generation given the graph we build here is undirected 😉. |
4a99b05
to
a24a7c7
Compare
I added the ability for the user to pass a zone demand profile, and get a report of transmission bottlenecks. Right now it's one giant massive printout, but you can get the idea by calling: from prereise.gather.griddata.hifld import create_csvs
create_csvs(
output_folder,
wind_directory,
year,
nrel_email,
nrel_api_key,
zone_demand=YOUR_DEMAND_PROFILE,
) or a little more directly: from prereise.gather.griddata.hifld.data_process.topology import report_bottlenecks
from prereise.gather.griddata.hifld.orchestration import create_grid
full_tables = create_grid(output_folder)
report_bottlenecks(full_tables["branch"], full_tables["bus"], YOUR_DEMAND_PROFILE) Once we generate the demand profile during grid-building, we can do the same sort of analysis and add a better user-facing report, maybe the number of constrained bottlenecks or something like that. |
a24a7c7
to
35d54a0
Compare
35d54a0
to
d91caac
Compare
d1ec341
to
a859620
Compare
a859620
to
b690b2f
Compare
Thinking about this a bit more, maybe the better location for this is somewhere within |
Yeah, it will be useful whenever we build a new grid/update existing grid with new data/resolve infeasibilities of a base case scenario of a future year, etc. |
3e0efc4
to
093ed6c
Compare
Pull Request doc
Purpose
Add a helper function which can identify where local transmission capacity is insufficient to meet local power needs. This was designed around seeing whether local (radial or semi-radial) transmission capacity was sufficient to import enough power from the bulk (meshed) grid, but could easily be extended to ensure that local transmission capacity is sufficient to export power to the bulk grid.
What the code is doing
The user-facing function is
identify_bottlenecks
. It uses a branch dataframe to build a graph representing the transmission network, construct a block-cut tree of the biconnected components (see https://en.wikipedia.org/wiki/Biconnected_component#Block-cut_tree), and identify the largest biconnected component. These data structures are then passed tofind_descendants
, and the return is filtered to output all potential bottlenecks (the"all"
key of the output dict), plus bottlenecks where demand is greater than capacity (the"constrained"
key of the output dict).find_descendants
is a recursive function which starts from aparent
node of a block-cut tree, optionally takes agrandparent
node which indicates which direction is 'upstream', and calculates for each edge of the block-cut tree (between an articulation point and a block) whether the total branch capacity between the articulation point and the block is sufficient to meet all downstream demand.identify_bottlenecks
passes the largest biconnected component as the root, and thenfind_descendants
calculates the total demand downstream of each articulation point, by first calculating the total demand of the downstream block, etc. until it reaches the leaf nodes of the tree where there's nothing downstream, at which point the summation can start going back up the chain. Along the way back up, the set of all downstream nodes is built, and the local capacity between each articulation point and block.Testing
A unit test is added, based around an example from Wikipedia's page on biconnected components, plus some arbitrary demands and capacities.
Usage Example/Visuals
Time estimate
Figuring out the graph traversal logic was a pretty big headache, if I were coming at this fresh I would expect to take more than an hour to understand it. Maybe some of you have more of an intuitive feel for graph traversal algorithms though.
I'm leaving this as a draft PR for now, since this logic is ready for review but we will probably want some higher-level logic that uses the results of the
identify_bottlenecks
to aid in grid-building, besides just throwing warnings. That would require that we have demand profiles during grid-building though, which we currently don't have, so maybe we'll want to merge this now and wait for a follow-up later. I'm open to suggestions.