绘画系统(06):【类】QPaintPath[官翻]

本文介绍了QPainterPath类的使用方法,包括如何构建复杂的图形形状、获取路径信息及路径转换等。此外,还提供了示例代码和相关函数介绍。

1. QPainterPath

QPainterPath类提供了用于绘画操作的容器,从而可以构造和重用图形形状。

1.2 公共类型

类型
classElement
enumElementType { MoveToElement, LineToElement, CurveToElement, CurveToDataElement }

1.3 公共函数

类名说明
QPainterPath(…)构造函数,多重
~QPainterPath()析构函数
void addEllipse(…)创建一个椭圆,多重,例
void addPath(…)将给定路径添加为该路径作为封闭子路径。
void addPolygon(…)添加多边形路径,例
void addRect(…)添加一个矩形,多重,例
void addRegion(…)将给定区域添加到路径
void addRoundedRect(…)添加一个圆角矩形,多重,例
void addText(…)添加文本,多重,例
qreal angleAtPercent(qreal t) const返回以百分比t表示的路径切线的角度
void arcMoveTo(…)创建一个弧线移动
void arcTo(…)创建一个占据给定矩形的圆弧
QRectF boundingRect() const返回此绘图器路径的边界矩形
int capacity() const返回QPainterPath分配的元素数量。
void clear()清空
void closeSubpath()关闭当前子路径
void connectPath(…)将给定路径连接到当前路径
bool contains(…) const查询是否包含…,多重
QRectF controlPointRect() const返回包含此路径中所有点和控制点的矩形
void cubicTo(…)贝塞尔曲线,多重,例
QPointF currentPosition() const返回当前路径的当前点
QPainterPath::Element elementAt(int index) const返回画家路径中给定索引处的元素
int elementCount() const返回画家路径中路径元素的数量。
Qt::FillRule fillRule() const返回画家路径的当前设置的填充规则。
QPainterPath intersected(…) const返回一个路径的交集
bool intersects(…) const查询是否相交,多重
bool isEmpty() const查询是否为空
qreal length() const当前路径的长度
void lineTo(…)从当前位置到给定的端点添加一条直线
void moveTo(…)将当前点移动到给定点
qreal percentAtLength(qreal len) const返回指定长度len处整个路径的百分比。
QPointF pointAtPercent(qreal t) const返回当前路径的百分比t处的点。 参数t必须在0到1之间。
void quadTo(…)添加一条二次贝塞尔曲线,多重
void reserve(int size)保留给定数量的元素
void setElementPositionAt(…)为index的元素的坐标设置为x和y。
void setFillRule(Qt::FillRule fillRule)将路径的填充规则设置为给定的fillRule
QPainterPath simplified() const返回此路径的简化版本。
qreal slopeAtPercent(qreal t) const以百分比t返回路径的斜率
QPainterPath subtracted(…) const返回一个减去p的填充区域。
void swap(QPainterPath &other)交换画家路径
QPolygonF toFillPolygon(…) const返回QTransform矩阵转换后的多边形
QList< QPolygonF > toFillPolygons(…) const返回QTransform矩阵转换后的多边形列表
QPainterPath toReversed() const返回路径的反向副本。
QList< QPolygonF > toSubpathPolygons(…) const返回QTransform矩阵转换后的多边形列表
void translate(…)转换路径中的所有元素,多重
QPainterPath translated(qreal dx, qreal dy) const返回转换后的路径,多重
QPainterPath united(const QPainterPath &p) const返回并集路径
运算符重载…

1.4. 详述

画家路径是由许多图形构造块(例如矩形,椭圆形,直线和曲线 )组成的对象。可以将构建块连接到闭合的子路径中,例如以矩形或椭圆形。封闭的路径具有一致的起点和终点。或者它们可以作为未封闭的子路径(例如直线和曲线 )独立存在。

QPainterPath对象可用于填充,勾画轮廓和裁剪。要为给定的绘制路径生成可填充的轮廓,请使用QPainterPathStroker类。与常规绘图操作相比,画家路径的主要优势在于,复杂的形状只需要创建一次即可。那么仅使用对QPainter :: drawPath( )函数的调用就可以绘制多次。

QPainterPath提供了一组函数,这些函数可用于获取有关路径及其元素的信息。另外,可以使用toReversed( )函数反转元素的顺序。还有一些函数可以将此绘制器路径对象转换为多边形表示。

1.4.2 组成一个QPainterPath

QPainterPath对象可以构造为具有给定起点的空路径,也可以构造为另一个QPainterPath对象的副本。创建之后,可以使用lineTo( ),arcTo( ),cubicTo( )和quadTo( )函数将直线和曲线添加到路径。直线和曲线从currentPosition( )延伸到作为参数传递的位置。

QPainterPath对象的currentPosition( )始终是最后添加的子路径(或初始起点 )的结束位置。使用moveTo( )函数移动currentPosition( )而不添加组件。 moveTo( )函数隐式启动一个新的子路径,并关闭前一个子路径。启动新子路径的另一种方法是调用closeSubpath( )函数,该函数通过从currentPosition( )回到路径的起始位置添加一条线来关闭当前路径。请注意,新路径的初始currentPosition( )将具有(0,0 )。

QPainterPath类还提供了几个方便的功能,可将封闭的子路径添加到绘制器路径:addEllipse( ),addPath( ),addRect( ),addRegion( )和addText( )。 addPolygon( )函数添加未封闭的子路径。实际上,这些函数都是moveTo( ),lineTo( )和cubicTo( )操作的集合。

另外,可以使用connectPath( )函数将路径添加到当前路径。但是请注意,此函数将通过添加一行将当前路径的最后一个元素连接到给定元素的第一个元素。

以下是显示如何使用QPainterPath对象的代码段:
在这里插入图片描述

QPainterPath path;
path.addRect(20, 20, 60, 60);

path.moveTo(0, 0);
path.cubicTo(99, 0,  50, 50,  99, 99);
path.cubicTo(0, 99,  50, 50,  0, 0);

QPainter painter(this);
painter.fillRect(0, 0, 100, 100, Qt::white);
painter.setPen(QPen(QColor(79, 106, 25), 1, Qt::SolidLine,
                    Qt::FlatCap, Qt::MiterJoin));
painter.setBrush(QColor(122, 163, 39));

painter.drawPath(path);

构造时,画家路径最初是空的。 我们首先添加一个矩形,它是一个封闭的子路径。 然后,我们添加两条贝塞尔曲线,即使它们没有单独闭合,它们也共同形成闭合子路径。 最后,我们绘制了整个路径。 使用默认的填充规则Qt :: OddEvenFill填充路径。 Qt提供了两种填充路径的方法:

Qt::OddEvenFillQt::WindingFill
在这里插入图片描述在这里插入图片描述

有关规则的定义,请参见 Qt::FillRule文档。 可以使用fillRule( ) 函数检索画家路径的当前设置的填充规则,并使用setFillRule() 函数对其进行更改。

1.4.3 QPainterPath信息

QPainterPath类提供了一个函数集合,这些函数返回有关路径及其元素的信息。

currentPosition() 函数返回添加的最后一个子路径的终点(或初始起点)。可以使用elementAt() 函数来检索各种子路径元素,可以使用elementCount() 函数来检索元素数量,而isEmpty() 函数可以告诉此QPainterPath对象是否完全包含任何元素。
controlPointRect() 函数返回包含此路径中所有点和控制点的矩形。该函数的计算速度比精确的boundingRect() 更快,后者需要以浮点精度返回此绘制路径的边界矩形。

最后,QPainterPath提供了contains() 函数,可用于确定给定点或矩形是否在路径内,而intersects() 函数可确定给定矩形内的点是否也在该路径内。

1.4.4 QPainterPath转换

出于兼容性原因,可能需要简化绘制路径的表示形式:QPainterPath提供了toFillPolygon() ,toFillPolygons() 和toSubpathPolygons() 函数,这些函数将绘制路径转换为多边形。 toFillPolygon() 以一个单一多边形的形式返回绘制器路径,而后两个函数返回一个多边形列表。

之所以提供toFillPolygons() 和toSubpathPolygons() 函数,是因为尽管绘制的点总数相同,但绘制多个小多边形通常比绘制一个大多边形要快。两者之间的区别在于它们返回的多边形数量:toSubpathPolygons() 为每个子路径创建一个多边形,而不管相交的子路径(即重叠的边界矩形)如何,而toFillPolygons() 函数仅为重叠的子路径创建一个多边形。

toFillPolygon() 和toFillPolygons() 函数首先将所有子路径转换为多边形,然后使用倒带技术来确保可以使用正确的填充规则来填充重叠的子路径。请注意,倒带会在多边形中插入其他线条,因此填充多边形的轮廓与路径的轮廓不匹配。

1.4.5 Examples

Qt提供了位于Qt的示例目录中的Painter Paths示例和Vector Deformation示例。

Painter Paths示例显示了如何使用Painter路径来构建复杂的形状以进行渲染,并允许用户尝试填充和描边。 矢量变形示例显示了如何使用QPainterPath绘制文本。

Painter Paths ExampleVector Deformation Example
在这里插入图片描述在这里插入图片描述
import pandas as pd import networkx as nx import numpy as np import itertools # --- Constants and Assumptions --- # These should be clearly stated and can be modified. VOLTAGE_KV = 10.0 # Line voltage in kV ROOT_3 = np.sqrt(3) BASE_DG_CAPACITY_KW = 300.0 # Initial capacity for each DG N_DG = 8 # Failure rates from problem description FAILURE_RATE_DG_PERCENT = 0.5 / 100.0 # FAILURE_RATE_USER_PERCENT = 0.5 / 100.0 # Not directly used in this simplified line-fault model for widespread outages # FAILURE_RATE_SWITCH_PERCENT = 0.2 / 100.0 # Assuming switch failures manifest as line failures or inability to operate tie FAILURE_RATE_LINE_PER_KM = 0.002 # Per km per year (assuming rates are annual) # Costs (placeholders - these are critical for actual risk values) # Value of Lost Load (VoLL) in monetary units per kW per hour. # For risk = P * C, if P is annual probability, C should be impact of one event. # Let's define C_loss as total kW unserved * a severity factor. # Or, if we want an annual risk cost: P_annual_fault * kW_unserved * hours_outage * cost_per_kWh # For simplicity, using $/kW of unserved load for the consequence C. COST_VOLL_PER_KW = 10.0 # Example: $10 per kW of unserved load AVG_OUTAGE_DURATION_H = 4 # Example: average hours for an outage, if converting to energy # Cost of Overload (Consequence C_over) # This can be complex: accelerated aging, tripping, damage. # Simplified: A penalty if any line is overloaded in a given state. COST_PENALTY_FOR_ANY_OVERLOAD = 1000.0 # Example: $1000 penalty if system is in an overloaded state # Or, a cost per MWh of overloaded energy, or per overloaded line. # Line and Feeder Capacities # Main feeder rated current from problem: 220A. # P_rated_feeder_kW = ROOT_3 * VOLTAGE_KV * FEEDER_RATED_CURRENT_A * 1.0 (pf=1) # = 1.732 * 10 * 220 = 3810.4 kW (approx 3.8 MW, problem says 2.2MW for 220A, implies lower pf or different basis) # Let's use current as the primary limit. FEEDER_RATED_CURRENT_A = 220.0 # Assumption for individual line segments: For this model, we'll assume all lines # have a rated current equal to the main feeder. This is a strong simplification. # A more detailed model would assign ratings based on conductor types or downstream load. LINE_RATED_CURRENT_A = 100.0 # More conservative assumption for individual segments than 220A. Needs proper engineering values. # For lines directly from substation, perhaps 220A is more appropriate. # Let's use a dictionary for specific line ratings if known, else default. DEFAULT_LINE_RATED_CURRENT_A = 100.0 # Tie Line Capacity TIE_LINE_RATED_CURRENT_A = 150.0 # Assumption, should be based on tie switch/line capacity # DG Locations (Node IDs from 1 to 62) - Based on Figure 1 interpretation DG_LOCATIONS_KW = { 6: BASE_DG_CAPACITY_KW, 10: BASE_DG_CAPACITY_KW, 15: BASE_DG_CAPACITY_KW, 27: BASE_DG_CAPACITY_KW, 31: BASE_DG_CAPACITY_KW, 37: BASE_DG_CAPACITY_KW, 50: BASE_DG_CAPACITY_KW, 58: BASE_DG_CAPACITY_KW } # Tie Switches: (node1, node2, switch_id_text) - normally open # Interpretation based on careful review of Figure 1: # S13-1: (13, 22) - Intra-Feeder 1 (Connects two branches of Feeder 1) # S29-2: (29, 42) - Intra-Feeder 2 (Connects two branches of Feeder 2) # S62-3: (62, 19) - Inter-Feeder (Connects Feeder 3 (node 62) to Feeder 1 (node 19)) TIE_SWITCHES_INFO = [ {'nodes': (13, 22), 'id': 'S13-1', 'type': 'intra-F1', 'capacity_A': TIE_LINE_RATED_CURRENT_A}, {'nodes': (29, 42), 'id': 'S29-2', 'type': 'intra-F2', 'capacity_A': TIE_LINE_RATED_CURRENT_A}, {'nodes': (62, 19), 'id': 'S62-3', 'type': 'inter-F3_F1', 'capacity_A': TIE_LINE_RATED_CURRENT_A} ] # This interpretation means Feeder 2 cannot directly receive support from F1 or F3. # If problem implies all feeders can support each other, TIE_SWITCHES_INFO would need redefinition. # Substation connection points (source nodes for feeders) # CB1 -> Node 1, CB2 -> Node 23, CB3 -> Node 43 # Node 0 will represent the main grid / infinite source. SOURCE_NODE = 0 SUBSTATION_CONNECTIONS = { 'CB1': (SOURCE_NODE, 1), 'CB2': (SOURCE_NODE, 23), 'CB3': (SOURCE_NODE, 43) } # Capacity of connection from source to substation nodes (effectively feeder capacity) SUBSTATION_LINE_CAPACITY_A = FEEDER_RATED_CURRENT_A # --- Data Loading Functions --- def load_load_data(filename="C题附件:有源配电网62节点系统基本参数.xlsx - 表1 有源配电网62节点系统负荷参数.csv"): df = pd.read_csv(filename) df.columns = ['node_id', 'load_kw'] # Convert node_id to int if it's not already df['node_id'] = df['node_id'].astype(int) return df.set_index('node_id')['load_kw'].to_dict() def load_topology_data(filename="C题附件:有源配电网62节点系统基本参数.xlsx - 表2 有源配电网62节点系统拓扑参数.csv"): df = pd.read_csv(filename) # Rename columns for easier access (assuming standard Chinese headers) df.columns = ['line_num', 'from_node', 'to_node', 'length_km', 'resistance_ohm', 'reactance_ohm'] # Convert relevant columns to numeric for col in ['from_node', 'to_node', 'length_km', 'resistance_ohm', 'reactance_ohm']: df[col] = pd.to_numeric(df[col], errors='coerce') return df # --- Core Power Grid Model Class --- class PowerGridModel: def __init__(self, load_data, topology_data, dg_locations_kw, tie_switches_info, substation_connections): self.loads_kw = load_data self.topology_df = topology_data self.dg_kw = dg_locations_kw.copy() # Allow modification for different scenarios self.tie_switches_info = tie_switches_info self.substation_connections = substation_connections self.graph = self._build_graph() self.feeder_info = self._identify_feeders() def _build_graph(self): G = nx.Graph() # Use Graph for undirected, or DiGraph if flow direction is fixed by sources # Add nodes with load and DG info all_nodes = set(self.topology_df['from_node']) | set(self.topology_df['to_node']) for node_id in all_nodes: node_id = int(node_id) # Ensure int G.add_node(node_id, load_kw=self.loads_kw.get(node_id, 0), dg_kw=self.dg_kw.get(node_id, 0)) # Add lines from topology data for _, row in self.topology_df.iterrows(): u, v = int(row['from_node']), int(row['to_node']) G.add_edge(u, v, id=row['line_num'], length_km=row['length_km'], resistance_ohm=row['resistance_ohm'], # reactance_ohm=row['reactance_ohm'], # Ignoring reactance as per problem rated_current_a=DEFAULT_LINE_RATED_CURRENT_A, # Default, can be refined failed=False) # Add substation connections (virtual lines from a common source) # These represent the main feeder lines from CBs G.add_node(SOURCE_NODE, type='source') for cb_id, (src, dest_node) in self.substation_connections.items(): G.add_edge(src, dest_node, id=cb_id, length_km=0.01, resistance_ohm=0.001, # Minimal impedance rated_current_a=SUBSTATION_LINE_CAPACITY_A, type='substation_link', failed=False) return G def _get_subgraph_with_operational_lines(self, graph_to_copy, faulty_line_edge=None): """Creates a subgraph considering only non-failed lines and open tie switches.""" g_op = graph_to_copy.copy() # Remove failed lines lines_to_remove = [] if faulty_line_edge: # faulty_line_edge is (u,v) if g_op.has_edge(*faulty_line_edge): lines_to_remove.append(faulty_line_edge) for u, v, data in list(g_op.edges(data=True)): if data.get('failed', False): lines_to_remove.append((u,v)) g_op.remove_edges_from(lines_to_remove) # Normally, tie switches are open. For restoration, specific ones might be closed. # This base function assumes they are open unless explicitly handled by restoration logic. return g_op def _identify_feeders(self): """Identifies nodes belonging to each feeder under normal operation (tie switches open).""" g_normal = self._get_subgraph_with_operational_lines(self.graph) feeder_info = {} # {'CB1': {nodes}, 'CB2': {nodes}, ...} for cb_id, (src_node, start_node) in self.substation_connections.items(): if g_normal.has_node(start_node) and g_normal.has_node(src_node) and nx.has_path(g_normal, src_node, start_node): # Find all nodes reachable from start_node without passing through another substation's start_node # or the main source node again, after removing other substation links. temp_g = g_normal.copy() other_cb_links = [] for other_cb, (s,d) in self.substation_connections.items(): if other_cb != cb_id and temp_g.has_edge(s,d): other_cb_links.append((s,d)) temp_g.remove_edges_from(other_cb_links) if nx.has_path(temp_g, src_node, start_node): # All nodes in the component connected to start_node, excluding the source itself component_nodes = nx.node_connected_component(temp_g.subgraph( [n for n in temp_g.nodes if n != src_node or n == start_node] # Consider start_node part of feeder ), start_node) feeder_info[cb_id] = component_nodes else: # Should not happen if graph is built correctly feeder_info[cb_id] = {start_node} if start_node in g_normal else set() else: feeder_info[cb_id] = {start_node} if start_node in g_normal else set() return feeder_info def _calculate_line_current_kw(self, power_kw): """Calculates current (A) given power (kW) at VOLTAGE_KV (line-to-line).""" if VOLTAGE_KV <= 0: return float('inf') return abs(power_kw) / (ROOT_3 * VOLTAGE_KV * 1.0) # Assumed PF=1 for current calculation from P def _get_downstream_info(self, G, line_u, line_v, source_nodes_for_feeder): """ Calculates total load and DG power downstream of a directed line (u,v), assuming v is further from the source_node for this path. G: graph to operate on (can be a faulted graph) source_nodes_for_feeder: list of possible source nodes for the current connected component. """ # Temporarily make graph directed from source to loads to find downstream nodes # This is tricky if the graph is not purely radial or has loops after closing ties. # A simpler approach for radial sections: # Check connectivity from sources to line_v, if line_u is removed. # If line_v is disconnected from all sources when (u,v) is cut, then everything # in the component of line_v is downstream. # Create a copy of G to modify temp_g = G.copy() if not temp_g.has_edge(line_u, line_v): return [], 0, 0 # Line doesn't exist temp_g.remove_edge(line_u, line_v) downstream_nodes = set() # Check which side (u or v) is disconnected from the source(s) # Assume u is closer to source, v is further. # If v is still connected to a source, then (u,v) might be part of a loop or fed from elsewhere. # A robust way: find path from source to v. If (u,v) is on all paths, then v is downstream of u via this line. # For radial feeders (normal operation): # If we consider (u,v) where u is parent of v: component_of_v = set() q = [line_v] visited = {line_u, line_v} # Start by marking u as visited (as if coming from u) # If line_v is connected to any source_node without passing through line_u v_connected_to_source_alt_path = False for src in source_nodes_for_feeder: if nx.has_path(temp_g, src, line_v): v_connected_to_source_alt_path = True break if v_connected_to_source_alt_path: # (u,v) is part of a loop or v is fed from elsewhere # This simple downstream logic is insufficient for meshed networks. # For now, assume radial for this part of flow calculation. # A more complex flow calculation (Newton-Raphson) would be needed for meshed. # Given problem constraints, assume feeders are normally radial. pass # This line might not have a clear "downstream" if looped. # If v is disconnected from source when (u,v) is cut, then its component is downstream. # Check connectivity for v in temp_g (where (u,v) is removed) v_still_connected = any(nx.has_path(temp_g, src, line_v) for src in source_nodes_for_feeder if src in temp_g) if not v_still_connected: # v is now isolated from source, so its component is downstream of (u,v) component_of_v = nx.node_connected_component(temp_g, line_v) else: # v is still connected, means (u,v) might be redundant or complex. # Try to determine direction based on distance from source dist_u = float('inf') dist_v = float('inf') for src in source_nodes_for_feeder: if src not in G: continue if nx.has_path(G, src, line_u): dist_u = min(dist_u, nx.shortest_path_length(G, src, line_u)) if nx.has_path(G, src, line_v): dist_v = min(dist_v, nx.shortest_path_length(G, src, line_v)) if dist_v > dist_u : # v is downstream of u # Find component of v if (u,v) is removed and v is not connected to source g_temp_removed_edge = G.copy() g_temp_removed_edge.remove_edge(line_u,line_v) is_v_conn_to_src = False for src_node_feeder in source_nodes_for_feeder: if src_node_feeder in g_temp_removed_edge and nx.has_path(g_temp_removed_edge, src_node_feeder, line_v): is_v_conn_to_src = True break if not is_v_conn_to_src: component_of_v = nx.node_connected_component(g_temp_removed_edge, line_v) else: # v is still connected, (u,v) is likely a loop closing line. Flow is complex. # For simplicity, this function will return 0 flow for loop lines if direction is ambiguous. return [], 0, 0 elif dist_u > dist_v: # u is downstream of v (swap them) # similar logic for u g_temp_removed_edge = G.copy() g_temp_removed_edge.remove_edge(line_u,line_v) is_u_conn_to_src = False for src_node_feeder in source_nodes_for_feeder: if src_node_feeder in g_temp_removed_edge and nx.has_path(g_temp_removed_edge, src_node_feeder, line_u): is_u_conn_to_src = True break if not is_u_conn_to_src: component_of_v = nx.node_connected_component(g_temp_removed_edge, line_u) # component_of_v is actually comp of u else: return [], 0, 0 else: # Equidistant or complex, cannot determine simple downstream for this line return [], 0, 0 downstream_nodes = component_of_v total_downstream_load_kw = sum(G.nodes[n]['load_kw'] for n in downstream_nodes) total_downstream_dg_kw = sum(G.nodes[n]['dg_kw'] for n in downstream_nodes if G.nodes[n]['dg_kw'] > 0) return list(downstream_nodes), total_downstream_load_kw, total_downstream_dg_kw def calculate_power_flows_and_currents(self, current_graph_state, active_dgs_kw): """ Simplified power flow for radial networks or parts of networks. Returns dict of line flows and currents, and substation powers. Flows: {(u,v): power_kw} where power_kw > 0 means u to v. Currents: {(u,v): current_A} Substation_powers: {'CB1': power_kw_drawn_from_substation} """ line_flows_kw = {} line_currents_a = {} substation_powers_kw = {} # Update DG outputs in the graph state for node_id, dg_val in active_dgs_kw.items(): if node_id in current_graph_state: current_graph_state.nodes[node_id]['dg_kw'] = dg_val for node_id in current_graph_state.nodes(): # Reset others if not in active_dgs_kw if node_id not in active_dgs_kw and 'dg_kw' in current_graph_state.nodes[node_id]: if current_graph_state.nodes[node_id].get('type') != 'source': # Don't zero out if it was never a DG current_graph_state.nodes[node_id]['dg_kw'] = 0 # Determine connected components and their sources # This is a very simplified load flow. It assumes power flows from sources (substations) # down to loads. DG power reduces the load seen by upstream sections. # It does not handle loops well without iterative methods (e.g. Hardy Cross or Newton-Raphson). # For each feeder, calculate flows assuming radial structure # This is an approximation. A full AC or DC power flow is more accurate. # Initialize all line flows to 0 for u, v in current_graph_state.edges(): line_flows_kw[(u,v)] = 0 line_flows_kw[(v,u)] = 0 # For bi-directional calculation needs line_currents_a[(u,v)] = 0 # Iterate multiple times for flow distribution in case of ties or complex paths # This is a placeholder for a proper iterative flow solution. # For now, a topological sort based flow for radial parts. processed_nodes_for_flow_calc = set() for cb_id, (src_node, start_node) in self.substation_connections.items(): if not current_graph_state.has_node(start_node) or not nx.is_connected(current_graph_state.subgraph([n for n in current_graph_state.nodes() if n != SOURCE_NODE])): # This feeder might be entirely down if start_node is disconnected from actual nodes substation_powers_kw[cb_id] = 0 continue # Get nodes for this feeder (dynamic based on current_graph_state) # Nodes connected to start_node, excluding SOURCE_NODE, if start_node is connected to SOURCE_NODE feeder_nodes_component = set() if current_graph_state.has_edge(src_node, start_node): temp_g_for_feeder = current_graph_state.copy() # Remove other substation links to isolate this feeder's component other_links_to_remove = [] for other_cb, (s,d) in self.substation_connections.items(): if other_cb != cb_id and temp_g_for_feeder.has_edge(s,d): other_links_to_remove.append((s,d)) temp_g_for_feeder.remove_edges_from(other_links_to_remove) if nx.has_path(temp_g_for_feeder, src_node, start_node): try: # Consider only the part of the graph reachable from start_node, not crossing back to SOURCE_NODE # except via the designated start_node path. search_nodes = [n for n in temp_g_for_feeder.nodes if n != src_node] sub_graph_feeder = temp_g_for_feeder.subgraph(search_nodes) if start_node in sub_graph_feeder: feeder_nodes_component = nx.node_connected_component(sub_graph_feeder, start_node) except nx.NetworkXError: # if start_node not in subgraph feeder_nodes_component = set() if not feeder_nodes_component: substation_powers_kw[cb_id] = 0 continue # Order nodes from furthest to closest to substation for power accumulation # This is for radial feeders. If loops exist, this is not sufficient. # Using BFS layers from start_node # Net load at each node (Load - DG) node_net_power_kw = {} for node in feeder_nodes_component: node_net_power_kw[node] = current_graph_state.nodes[node]['load_kw'] - current_graph_state.nodes[node]['dg_kw'] # Accumulate power up towards the substation # This requires a tree traversal (e.g., DFS post-order traversal) from leaves to root. # For simplicity, if the feeder is a tree rooted at start_node: if nx.is_tree(current_graph_state.subgraph(feeder_nodes_component | {start_node})): # Check if it's a tree # Create a directed tree towards the source for easier traversal # This part is complex if graph is not a tree. # For now, sum all net loads on the feeder as the substation power (approximation) total_feeder_net_load = sum(node_net_power_kw[n] for n in feeder_nodes_component) substation_powers_kw[cb_id] = total_feeder_net_load # Distribute this flow down the lines (highly simplified) # A proper method: for each line, sum net_power of all nodes in subtree rooted by that line. # This simplified flow calculation is a major placeholder. # For overload risk, we need per-line flows. # Simplified: assume current_graph_state is a tree rooted at start_node for this feeder # Use BFS to assign flow from start_node downwards # This is not a full power flow, but an estimation for line loading. # Build a directed graph for this feeder based on BFS from start_node # This is just for flow assignment direction. # Actual flow needs to sum up demands from downstream. # For each edge (u,v) in the feeder: # Determine parent (closer to start_node) and child # Power on (parent, child) = sum of net loads in subtree rooted at child. # This is complex to implement robustly here without a full flow algorithm. # Fallback: Use the _get_downstream_info logic if possible, iterate edges # This needs to be called carefully to avoid double counting or misdirection in non-radial. # For now, this function will primarily return substation_powers_kw and # leave detailed line_flows_kw and line_currents_a for a more robust implementation # or accept its high level of approximation. # Let's try a slightly better approximation for line flows on a tree: # For each edge (u,v) in the feeder tree (rooted at start_node) # Assume u is parent of v. Power(u,v) = sum of net_power for all nodes in subtree of v. if start_node in feeder_nodes_component: # Should be try: dfs_edges = list(nx.dfs_edges(nx.bfs_tree(current_graph_state.subgraph(feeder_nodes_component), start_node), source=start_node)) # Calculate power for each node including its children's power node_total_subtree_power = node_net_power_kw.copy() for u, v in reversed(dfs_edges): # From leaves up to root if u in node_total_subtree_power and v in node_total_subtree_power: node_total_subtree_power[u] += node_total_subtree_power[v] for u,v in dfs_edges: # From root down if v in node_total_subtree_power: flow = node_total_subtree_power[v] line_flows_kw[(u,v)] = flow line_flows_kw[(v,u)] = -flow # Convention for direction line_currents_a[(u,v)] = self._calculate_line_current_kw(flow) except Exception as e: # print(f"Warning: Could not perform tree-based flow for {cb_id} due to {e}") pass # Keep substation power as sum, line flows might be inaccurate else: # Not a tree, flow calculation is more complex. # print(f"Warning: Feeder {cb_id} is not a tree. Simplified flow may be inaccurate.") total_feeder_net_load = sum(node_net_power_kw[n] for n in feeder_nodes_component if n in node_net_power_kw) substation_powers_kw[cb_id] = total_feeder_net_load # Line flows in meshed networks require iterative solvers. # For now, this part will be very approximate for meshed sections. # Handle reverse power flow and inter-feeder DG adjustment # "分布式能源不得向上级电网倒送功率" # "可以在相邻馈线间进行调节" for cb_id in list(substation_powers_kw.keys()): if substation_powers_kw[cb_id] < 0: # Reverse power flow excess_dg_on_feeder = -substation_powers_kw[cb_id] # Try to transfer to other feeders via inter-feeder tie lines # This logic is complex and needs careful state management. # For Q1, a simpler approach might be DG curtailment on that feeder. # Find inter-feeder tie switches connected to this feeder # Example: S62-3 connects Feeder 3 (node 62) to Feeder 1 (node 19) # If Feeder 1 has excess_dg, it might try to send to Feeder 3 via (19,62) # This is an advanced feature. For now, assume DG curtailment if倒送. # To implement curtailment: identify DGs on this feeder, reduce their output # proportionally until substation_powers_kw[cb_id] >= 0. # This would require re-calculating flows. # For now, just flag it. # print(f"Warning: Reverse power flow on {cb_id} of {substation_powers_kw[cb_id]} kW. DG curtailment or transfer needed.") # A simple curtailment: dG_on_feeder_nodes = [n for n in self.feeder_info.get(cb_id, set()) if n in active_dgs_kw and active_dgs_kw[n] > 0] total_dg_cap_on_feeder = sum(active_dgs_kw[n] for n in dG_on_feeder_nodes) if total_dg_cap_on_feeder > 0: curtail_ratio = min(1.0, excess_dg_on_feeder / total_dg_cap_on_feeder) if total_dg_cap_on_feeder >0 else 0 for dg_node in dG_on_feeder_nodes: active_dgs_kw[dg_node] *= (1 - curtail_ratio) # Flows need to be recalculated after curtailment. This suggests an iterative solution. # For this submission, we'll assume this check is done *before* final flow calc, # or simply note the violation. # To avoid recursion here, this function should ideally take DGs as fixed input. # The adjustment logic should be outside or iterative. pass return line_flows_kw, line_currents_a, substation_powers_kw def calculate_overload_risk(self): """ Calculates overload risk for the current DG setup. Assumes DGs are at their BASE_DG_CAPACITY_KW. """ # Get current operational graph (no faults, ties normally open) g_op = self._get_subgraph_with_operational_lines(self.graph) # Calculate power flows and currents # Need to handle DG outputs properly. current_dg_outputs = self.dg_kw.copy() # Use the model's current DG settings # Iterative step for DG curtailment if reverse power flow: # This is a simplified loop. A more robust solution uses optimization or better heuristics. for _iter in range(3): # Max 3 iterations for adjustment line_flows, line_currents, substation_powers = self.calculate_power_flows_and_currents(g_op, current_dg_outputs) reverse_power_detected = False for cb_id, power_kw in substation_powers.items(): if power_kw < -1e-3: # Small threshold for倒送 reverse_power_detected = True # print(f"Info: Reverse power on {cb_id} ({power_kw:.2f} kW). Attempting curtailment.") excess_dg_on_feeder = abs(power_kw) feeder_nodes_for_cb = self.feeder_info.get(cb_id, set()) dG_on_feeder_nodes = [n for n in feeder_nodes_for_cb if n in current_dg_outputs and current_dg_outputs[n] > 0] total_dg_cap_on_feeder = sum(current_dg_outputs[n] for n in dG_on_feeder_nodes) if total_dg_cap_on_feeder > 1e-3 : # Avoid division by zero curtail_amount_total = excess_dg_on_feeder for dg_node in dG_on_feeder_nodes: # Proportional curtailment proportion = current_dg_outputs[dg_node] / total_dg_cap_on_feeder curtail_this_dg = proportion * curtail_amount_total current_dg_outputs[dg_node] = max(0, current_dg_outputs[dg_node] - curtail_this_dg) else: # No DG to curtail, reverse power might be from other sources or model issue pass if not reverse_power_detected: break # Final flows after potential curtailment line_flows, line_currents, substation_powers = self.calculate_power_flows_and_currents(g_op, current_dg_outputs) overloaded_lines = [] for u, v, data in g_op.edges(data=True): if data.get('type') == 'substation_link': continue # Don't check substation links themselves for overload here current = line_currents.get((u,v), 0) # If flow is from v to u, current might be stored as current_uv = -current_vu # Take absolute value of flow for current calculation, or ensure current is always positive. # The _calculate_line_current_kw uses abs(power_kw) so current should be positive. rated_current = data.get('rated_current_a', DEFAULT_LINE_RATED_CURRENT_A) if current > 1.1 * rated_current: overloaded_lines.append({'edge': (u,v), 'current': current, 'rated': rated_current, 'over_by_%': (current/(1.1*rated_current)-1)*100 if rated_current>0 else float('inf')}) if overloaded_lines: # print(f"System Overload Detected. Overloaded lines: {overloaded_lines}") # P_over = 1 (for this deterministic scenario) # C_over = fixed penalty or sum of penalties risk_overload = 1.0 * COST_PENALTY_FOR_ANY_OVERLOAD # Or, sum of consequences for each overloaded line, if C_over is per line. # risk_overload = sum(some_cost_function(ol['over_by_%']) for ol in overloaded_lines) else: # print("System is NOT overloaded in the base case.") risk_overload = 0.0 return risk_overload, overloaded_lines, substation_powers, current_dg_outputs def calculate_load_loss_risk(self): """ Calculates total load loss risk by considering single line faults. R_loss = sum(P_fault_i * C_loss_i) P_fault_i = annual probability of fault i C_loss_i = consequence of fault i (e.g., unserved_load_kw * COST_VOLL_PER_KW) """ total_load_loss_risk = 0.0 detailed_fault_impacts = [] # Iterate through all operational lines (excluding substation virtual links for fault simulation) original_edges = [ (u,v,data) for u,v,data in self.graph.edges(data=True) if data.get('type') != 'substation_link' and not data.get('is_tie', False)] for u_fault, v_fault, line_data_faulted in original_edges: faulty_edge = (u_fault, v_fault) line_length_km = line_data_faulted.get('length_km', 0) # Probability of this specific line failing (annual) # Assuming failure rates are independent and this is the probability of this line being the one to fail. prob_line_fault = line_length_km * FAILURE_RATE_LINE_PER_KM if prob_line_fault == 0: continue # --- Simulate fault --- g_faulted = self.graph.copy() if not g_faulted.has_edge(*faulty_edge): continue g_faulted.edges[faulty_edge]['failed'] = True # Mark as failed g_after_fault_isolation = self._get_subgraph_with_operational_lines(g_faulted, faulty_edge=faulty_edge) # --- Identify initial load loss --- initial_unserved_load_kw = 0 disconnected_load_nodes = {} # {node: load_kw} # Check connectivity for all load nodes for node_id, load_kw in self.loads_kw.items(): if load_kw <= 0: continue is_connected_to_source = False for cb_id, (src_node, start_node) in self.substation_connections.items(): if nx.has_path(g_after_fault_isolation, src_node, node_id): is_connected_to_source = True break if not is_connected_to_source: initial_unserved_load_kw += load_kw disconnected_load_nodes[node_id] = load_kw if initial_unserved_load_kw == 0: # Fault does not cause load loss (e.g. redundant line) detailed_fault_impacts.append({'fault': faulty_edge, 'unserved_kw_initial': 0, 'unserved_kw_final':0, 'restored_kw':0, 'risk_contrib':0}) continue # --- Attempt restoration via tie lines --- # This is a complex part. Needs to: # 1. Identify disconnected areas and loads. # 2. Identify available tie switches that can connect these areas to healthy feeders. # 3. Check capacity of tie lines and the supporting feeder. # 4. Prioritize restoration (e.g., maximize load restored). # For this model, a simplified restoration: # Iterate over available tie switches. If closing one helps, simulate it. # This should be greedy or more optimized. restored_load_kw_total_for_this_fault = 0 # Create a graph state for restoration attempts g_for_restoration = g_after_fault_isolation.copy() # Sort disconnected loads by size (optional, for prioritization) sorted_disconnected_loads = sorted(disconnected_load_nodes.items(), key=lambda item: item[1], reverse=True) # Try closing tie switches one by one (if they connect a live part to a dead part) # This is a very simplified greedy approach. # A proper approach would evaluate all combinations or use optimization. # Identify current live sources/feeders live_feeder_sources = [] # (cb_id, start_node_of_live_feeder) for cb_id, (src,start) in self.substation_connections.items(): if nx.has_path(g_for_restoration, src, start): # Check if substation itself is connected live_feeder_sources.append(start) for tie in self.tie_switches_info: tie_n1, tie_n2 = tie['nodes'] tie_capacity_a = tie['capacity_A'] if not g_for_restoration.has_node(tie_n1) or not g_for_restoration.has_node(tie_n2): continue if g_for_restoration.has_edge(tie_n1, tie_n2): continue # Already closed or part of main graph (should not be for ties) # Check if one end is live and other is dead (or part of the disconnected component) tie_n1_is_live = any(nx.has_path(g_for_restoration, src, tie_n1) for src in live_feeder_sources) tie_n2_is_live = any(nx.has_path(g_for_restoration, src, tie_n2) for src in live_feeder_sources) if tie_n1_is_live == tie_n2_is_live: continue # Both live or both dead, closing doesn't bridge outage for now live_tie_node, dead_tie_node = (tie_n1, tie_n2) if tie_n1_is_live else (tie_n2, tie_n1) # Check if dead_tie_node is part of the current outage we are trying to fix # This requires knowing which component dead_tie_node belongs to. # For now, assume if it's not live, it's part of some outage. # Simulate closing this tie switch g_for_restoration.add_edge(live_tie_node, dead_tie_node, id=tie['id'], type='tie_closed', rated_current_a=tie_capacity_a, resistance_ohm=0.001, length_km=0.01) # Check how much load can be restored through this tie without overloading tie or new path # This requires a flow calculation on g_for_restoration. # Simplified: Check loads now connected. newly_restored_load_kw_this_tie = 0 temp_restored_nodes_this_tie = [] for node_id, load_val in disconnected_load_nodes.items(): if node_id not in g_for_restoration: continue # Should not happen # Check if this node is now connected to ANY source is_now_connected = any(nx.has_path(g_for_restoration, src, node_id) for src in live_feeder_sources) if is_now_connected and node_id not in temp_restored_nodes_this_tie: # And not already counted as restored by previous ties # More checks needed: # 1. Tie line capacity: Power through (live_tie_node, dead_tie_node) <= tie_capacity_a # 2. Path capacity on the live feeder. # This is where the simplified flow becomes a bottleneck. # For now, assume if connected, it can be restored up to a certain limit. # This is a MAJOR simplification. newly_restored_load_kw_this_tie += load_val temp_restored_nodes_this_tie.append(node_id) # Here, we'd need to check if adding newly_restored_load_kw_this_tie overloads the tie or feeder. # If current_through_tie > tie_capacity_a, then not all of this load can be restored. # This part needs a proper constrained flow allocation. # For now, let's assume a fraction can be restored if connected, or all if small. # This is a placeholder for a more robust restoration algorithm. # Let's assume, for now, if connected, it's restored. If this overloads things, # the overload risk model should capture it (but that's for normal state). # Here, the goal is to minimize unserved load. # If this tie leads to overload, we shouldn't use it or only partially. # For now, naively accept all newly connected load. if newly_restored_load_kw_this_tie > 0: restored_load_kw_total_for_this_fault += newly_restored_load_kw_this_tie # Update disconnected_load_nodes: for r_node in temp_restored_nodes_this_tie: if r_node in disconnected_load_nodes: del disconnected_load_nodes[r_node] # No longer disconnected else: # Closing this tie didn't help, revert if g_for_restoration.has_edge(live_tie_node, dead_tie_node): g_for_restoration.remove_edge(live_tie_node, dead_tie_node) # Final unserved load for this fault scenario final_unserved_load_kw = initial_unserved_load_kw - restored_load_kw_total_for_this_fault final_unserved_load_kw = max(0, final_unserved_load_kw) # Cannot be negative consequence_c_loss = final_unserved_load_kw * COST_VOLL_PER_KW risk_contribution = prob_line_fault * consequence_c_loss total_load_loss_risk += risk_contribution detailed_fault_impacts.append({ 'fault_type': 'line', 'component_id': faulty_edge, 'prob_fault': prob_line_fault, 'unserved_kw_initial': initial_unserved_load_kw, 'restored_kw': restored_load_kw_total_for_this_fault, 'unserved_kw_final': final_unserved_load_kw, 'consequence_c_loss': consequence_c_loss, 'risk_contribution': risk_contribution }) # TODO: Add DG faults, Switch faults, User faults if they cause wider outages. # For DG faults: prob_dg_fault = FAILURE_RATE_DG_PERCENT # A DG fault primarily impacts system's ability to meet load or avoid overload. # It doesn't directly cause load loss unless it's islanded and the DG is the only source. # The problem implies grid-connected DGs. return total_load_loss_risk, detailed_fault_impacts # --- Main Execution --- if __name__ == '__main__': print("--- 配电网风险评估模型 Q1 ---") # 1. Load Data print("\n1. 加载数据...") loads = load_load_data() topology = load_topology_data() # print(f"负荷数据: {len(loads)} 点") # print(f"拓扑数据: {len(topology)} 条线路") # 2. Initialize Power Grid Model print("\n2. 初始化电网模型...") grid = PowerGridModel(loads, topology, DG_LOCATIONS_KW, TIE_SWITCHES_INFO, SUBSTATION_CONNECTIONS) # print(f"电网图: {grid.graph.number_of_nodes()} 个节点, {grid.graph.number_of_edges()} 条边") # print(f"馈线信息: {grid.feeder_info}") # --- 问题1: 失负荷风险和过负荷风险计算模型 --- print("\n--- 问题1: 风险计算 ---") # A. 过负荷风险模型 (R_over = P_over * C_over) # For Q1, DGs are at BASE_DG_CAPACITY_KW. This is a deterministic check for this state. # P_over = 1 if overload occurs, 0 otherwise. C_over is the penalty. print("\nA. 计算过负荷风险...") # Note: The calculate_power_flows_and_currents is highly simplified. # Results for overload depend heavily on its accuracy and line ratings. try: risk_overload, overloaded_lines_details, substation_p, final_dg_out = grid.calculate_overload_risk() print(f" 计算得到的过负荷风险 (R_over): ${risk_overload:.2f}") if overloaded_lines_details: print(f" 检测到过负荷线路 ({len(overloaded_lines_details)} 条):") # for ol in overloaded_lines_details[:3]: # Print first 3 # print(f" - 线路 {ol['edge']}, 电流: {ol['current']:.2f}A, 额定: {ol['rated']:.2f}A, 超出: {ol['over_by_%']:.2f}%") else: print(" 在当前DG配置下,未检测到线路过负荷。") # print(f" 变电站出口功率 (kW): {substation_p}") # print(f" 最终DG出力 (kW) (可能经过削减): {final_dg_out}") except Exception as e: print(f" 计算过负荷风险时发生错误: {e}") risk_overload = -1 # Indicate error # B. 失负荷风险模型 (R_loss = sum(P_fault_i * C_loss_i)) print("\nB. 计算失负荷风险...") # Note: Restoration logic is simplified. try: total_r_loss, fault_details = grid.calculate_load_loss_risk() print(f" 计算得到的总失负荷风险 (R_loss): ${total_r_loss:.2f} (基于所选成本)") # print("\n 部分故障场景详情:") # for fd in fault_details[:3]: # Print first 3 # print(f" - 故障线路: {fd.get('component_id')}, " # f"初始失负荷: {fd.get('unserved_kw_initial'):.2f} kW, " # f"最终失负荷: {fd.get('unserved_kw_final'):.2f} kW, " # f"风险贡献: ${fd.get('risk_contribution'):.2f}") except Exception as e: print(f" 计算失负荷风险时发生错误: {e}") total_r_loss = -1 # Indicate error print("\n--- 模型执行完毕 ---") print("注意: 此模型包含多项简化和假设 (如线路额定电流, 成本参数, 潮流计算简化, 恢复逻辑简化).") print("结果的准确性取决于这些假设的合理性和参数的精确性。") 根据此代码分布式能源接入配电网的风险分析 背景知识: 随着我国双碳目标的推进,可再生分布式能源在配电网中的大规模应用不可避免,这对传统配电网运行提出挑战。为了量化分析配电网中接入分布式能源的风险,需要对其进行建模与分析。 配电网发生故障后失负荷,可以通过联络线实现部分复电,供电恢复的目标是在系统拓扑结构发生变化时,将系统的经济损失降至最小,行业分将消费者分为居民住宅、商业、政府机构和办公建筑等别,按照这种分,供电中断危害可依据部门客户危害度函数进行计算。 分布式能源接入配电网,由于其发电出力的波动性与不确定性,对馈线的失负荷和过负荷带来影响,为了提高配电网就地消纳能力,本竞赛题目要求分布式能源不得向上级电网倒送功率。 风险评估的通用计算公式为: (1) 式中:代表系统风险;代表系统失负荷的发生概率;代表由系统失负荷造成的危害程度;代表系统过负荷的发生概率;代表由系统过负荷造成的危害程度。 由式(1)可知,风险评估的量化计算为该场景下各事件概率与其危害的乘积之和,为了实现风险评估,需要从事件概率计算及事件造成后果的危害度函数构建两方面入手进行分析与建模。 名称解释 失负荷:因故障导致负荷供电中断 。 过负荷:线路电流超过额定载流量10%以上。 馈线:从变电站出线开关到终端负荷的配电线路。 联络线:通过联络开关连接不同馈线的线路,正常运行时联络开关处于断开状态,根据运行方式调整的需要可以调整联络开关的状态,实现馈线间的功率转移。 配电网的运行方式: 在同一时刻, 馈线上的每个负荷与各变电站之间只有一条通路(只由一个变电站出线开关CB供电),每个分布式电源DG都可接入馈线供电,不同用户型的停电损失会造成不同的危害度。 计算配电系统风险的约束条件如下: (1)各个型故障是独立发生的,同一时间同一型只发生一个故障; (2)不考虑无功功率和电压越限的影响,风险计算分析仅考虑有功功率和电流的影响; (3)联络开关不考虑故障恢复的自愈系统对失负荷的影响,但是需考虑联络开关的负荷转移能力。 问题: (1)请分别建立分布式能源接入配电网后,配电系统的失负荷风险和过负荷风险的计算模型,要求:失负荷风险模型要记及本馈线故障造成的负荷损失可以从其他相邻馈线通过联络线转供实现复电的情况;过负荷风险模型要记及本馈线的有功功率不得向上级变电站倒送的情况,但是可以在相邻馈线间进行调节;
最新发布
05-12
<think>好的,我现在需要分析用户提供的代码,并回答关于分布式能源接入配电网风险分析的问题。用户希望了解这段代码如何解决失负荷和过负荷风险的计算模型,特别是模型建立的步骤。首先,我需要理解代码的结构和各个部分的功能。首先,用户提供的代码使用Python编写,主要依赖pandas、networkx和numpy库。代码分为几个部分:常量定义、数据加载函数、电网模型以及主执行部分。电网模型PowerGridModel负责构建配电网的图结构,计算潮流、过载风险和失负荷风险。 接下来,我需要将代码与问题描述的数学模型对应起来。用户的问题(1)要求建立失负荷和过负荷风险模型,考虑联络线转供和禁止倒送功率。根据代码,过负荷风险通过calculate_overload_risk方法计算,其中处理了反向功率流,通过削减DG出力来避免倒送。失负荷风险则在calculate_load_loss_risk中模拟线路故障,尝试通过闭合联络开关恢复供电。我需要解释这两个方法的具体步骤。比如,过载风险部分,代码如何检测线路电流超过额定值,并计算风险值。对于失负荷风险,如何模拟线路故障后的负荷损失,并通过联络线恢复部分负荷,从而计算风险。同时,用户提到数学模型的具体步骤。我需要将这些步骤与代码中的实现对应起来,例如概率计算、后果评估、风险聚合等。此外,需要指出代码中的简化假设,比如忽略无功功率、使用简化的潮流计算等,以及这些假设对结果的影响。 在解释过程中,要确保术语准确,如馈线、联络线、DG等,并引用代码中的常量、函数和方法来说明实现细节。最后,根据用户的要求,提出相关问题,涵盖模型优化、参数影响、其他故障型和对比分析等方面。 总结来说,我需要将代码的各个部分与问题中的数学模型对应,详细解释实现步骤,同时指出代码的局限性和可能的改进方向。确保回答结构清晰,步骤分明,符合用户的要求。</think>分布式能源接入配电网的风险分析模型的详细解释,基于提供的代码实现: ### 一、过负荷风险计算模型(R_over) ```python def calculate_overload_risk(self): # 实现步骤 # 1. 构建正常运行的电网拓扑 # 2. 计算线路潮流和电流 # 3. 检测超过额定值110%的线路 # 4. 计算风险值 = 1(必然事件概率) * 罚款金额 # 5. 处理反向功率流(通过DG出力削减) # ... if current > 1.1 * rated_current: overloaded_lines.append({'edge': (u,v), 'current': current...}) risk_overload = 1.0 * COST_PENALTY_FOR_ANY_OVERLOAD ``` #### 数学模型建立步骤: 1. **过负荷概率计算**: - 采用确定性分析:P_over = 1(只要存在过载线路即视为必然发生) - 基于线路电流检测:I_line > 1.1*I_rated 2. **后果评估**: - 使用固定惩罚成本:C_over = COST_PENALTY_FOR_ANY_OVERLOAD - 或按过载程度计算:C_over = Σ(过载比例 * 线路成本系数) 3. **反向功率处理**: ```python for cb_id in list(substation_powers_kw.keys()): if substation_powers_kw[cb_id] < 0: # 检测反向功率流 # 按比例削减本馈线DG出力 curtail_ratio = excess_dg / total_dg_cap active_dgs_kw[dg_node] *= (1 - curtail_ratio) ``` ### 二、失负荷风险计算模型(R_loss) ```python def calculate_load_loss_risk(self): # 实现步骤 # 1. 遍历所有线路模拟单点故障 # 2. 计算故障隔离后的孤岛区域 # 3. 尝试通过联络开关恢复供电 # 4. 计算未恢复负荷的损失成本 # 5. 累计所有故障场景的期望风险 # ... prob_line_fault = line_length_km * FAILURE_RATE_LINE_PER_KM consequence = final_unserved_load_kw * COST_VOLL_PER_KW total_risk += prob_line_fault * consequence ``` #### 数学模型建立步骤: 1. **故障概率计算**: - 线路故障率:P_fault_i = length_km × 0.002/km - 假设单故障场景,独立事件 2. **负荷损失计算**: ```python # 故障隔离后检测连通性 if not nx.has_path(g_after_fault, src, node): initial_unserved_load += load ``` 3. **供电恢复机制**: ```python # 尝试闭合联络开关 for tie in self.tie_switches_info: if tie两端连通性不同: g_temp.add_edge(tie节点) # 检测新连通负荷 if nx.has_path(新拓扑, source, load_node): restored_load += load ``` 4. **风险聚合**: ```math R_loss = Σ(P_fault_i × (未恢复负荷 × COST_VOLL_PER_KW)) ``` ### 三、模型实现特点 1. **网络建模**: - 使用NetworkX构建拓扑结构 - 节点属性包含负荷/DG信息 ```python G.add_node(node_id, load_kw=..., dg_kw=...) ``` 2. **潮流计算**: - 基于拓扑排序的简化算法 - 从末端负荷向变电站聚合功率 ```python for u,v in dfs_edges: line_flows[(u,v)] = subtree_power[v] ``` 3. **约束处理**: - 通过虚拟节点(SOURCE_NODE)实现多馈线建模 - 使用rated_current_a参数存储线路容量 ```python G.add_edge(u, v, rated_current_a=100.0) ``` ### 四、模型局限与改进方向 1. **简化假设**: - 忽略电压和无功功率影响 - 使用恒定功率因数(PF=1) - 联络开关操作不考虑容量约束 2. **可扩展性**: - 可添加概率分布模型(如DG出力波动) - 可集成更精确的潮流算法(牛顿拉夫逊法) - 可增加用户型差异化成本系数
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值