零幺串

用1和0组成的串为“零幺串”。编写程序实现,任意输入零幺串S,将返回一个值N1和一个值N0,其中N1表示S中最长幺串的长度,N0表示S中最长零串的长度。例如假设S是
00010111001110001111,则返回的N0 = 3,N1 = 4.
代码:

#include <stdio.h>
#include <string.h>
void countNum(char* str,int* N0,int* N1);
int main()
{
	char str[30];
	gets(str);  //gets()其作用是从终端输入一个字符串到字符数组 
	int iNum0=-1;
	int iNum1=-1;
	countNum(str,&iNum0,&iNum1);
	printf("%s: NO=%d,N1=%d.\n",str,iNum0,iNum1);
	return 0;
}
void countNum(char* str,int* N0,int* N1)   //指针变量作为函数参数 
{
	int maxNum0=0;
	int maxNum1=0;
	int length = strlen(str);
	int count=0;
	int i=0;
	for(i=0;i<length;i++)                 //计数0的最长串 
	{
		if(str[i]=='0') {
			count ++;
			if(count > maxNum0) {
 				maxNum0=count;
			}
		}else {                     //每遇到1,count就清零 
			count=0;
		}
	}
	count=0;                        //count清零,准备计数1的最长子串 
	for(i=0;i<length;i++) {
		if(str[i]=='1') {
 			count ++;
			if(count > maxNum1) {
				maxNum1 = count;
			}
		}else {                 //每遇到0,count就清零 
			count=0;
   		}
	}
	(*N0) = maxNum0;
	(*N1) = maxNum1;
}
import os os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE' # 添加到代码开头 import cvxpy as cp import numpy as np import pandas as pd import matplotlib.pyplot as plt import warnings from datetime import datetime, timedelta import os # ============================================================================== # 网络配置文件解析函数,按标签段读取自定义 .e 网络配置文件,把支路参数、光伏/储能/台区节点号以及平衡节点号解析成结构化字典,供主程序直接调用。 # ============================================================================== def load_network_config(config_filepath): config_data = { 'branch': [], 'pv_nodes': [], 'ess_nodes': [], 'district_nodes': [], 'slack_bus_node': None # 初始化 slack_bus_node } current_section = None with open(config_filepath, 'r', encoding='utf-8') as f: for line in f: line = line.strip() if not line or line.startswith('//'): continue if line.startswith('<') and not line.startswith('</'): if line == '<distributed_network>': current_section = 'branch' elif line == '<pv_point>': current_section = 'pv' elif line == '<ess_point>': current_section = 'ess' elif line == '<region_point>': current_section = 'district' elif line == '<slack_bus>': current_section = 'slack' # 新增识别 else: current_section = None continue if line.startswith('</'): current_section = None continue if line.startswith('#'): parts = line.lstrip('#').strip().split() if not parts: continue if current_section == 'branch' and len(parts) >= 5: config_data['branch'].append([int(parts[1]), int(parts[2]), float(parts[3]), float(parts[4])]) elif current_section == 'pv': config_data['pv_nodes'].append(int(parts[0])) elif current_section == 'ess': config_data['ess_nodes'].append(int(parts[0])) elif current_section == 'district': config_data['district_nodes'].append(int(parts[0])) elif current_section == 'slack': # 新增解析 config_data['slack_bus_node'] = int(parts[0]) return config_data # ============================================================================== # 一、数据输入(一定修改为正确的文件读取路径) # ============================================================================== # --- 使用相对路径,请确保数据文件与.py文件在同一目录下,或提供正确绝对路径 --- import argparse parser = argparse.ArgumentParser(description='电压优化算法') parser.add_argument('--data', required=True, help='数据文件路径(.e)') parser.add_argument('--config', required=True, help='配置文件路径(.e)') parser.add_argument('--output', required=True, help='输出目录路径') args = parser.parse_args() data_file_path = args.data config_file_path = args.config hdr_pv, rows_pv = None, [] hdr_load, rows_load = None, [] try: with open(data_file_path, 'r', encoding='utf-8', errors='ignore') as f: current = None for line in f: line = line.rstrip('\n') if line.strip() == '<point_output_pv>': current = 'pv' elif line.strip() == '<load_point>': current = 'load' #通过current状态变量标记当前读取的数据类型(PV或Load)到新的标签段(如<other_section>)时重置状态 elif line.startswith('<') and line.endswith('>'): current = None else: if current == 'pv': if line.startswith('@'): hdr_pv = [h for h in line.lstrip('@').strip().split() if h] elif line.startswith('#'): vals = [v for v in line.lstrip('#').strip().split() if v] if len(vals) == len(hdr_pv): rows_pv.append(vals) elif current == 'load': if line.startswith('@'): hdr_load = [h for h in line.lstrip('@').strip().split() if h] elif line.startswith('#'): vals = [v for v in line.lstrip('#').strip().split() if v] if len(vals) == len(hdr_load): rows_load.append(vals) except FileNotFoundError: print(f"错误:数据文件 '{data_file_path}' 未找到。请检查路径。") exit() df_pv = pd.DataFrame(rows_pv, columns=hdr_pv) df_pv['Time'] = pd.to_datetime(df_pv['Time']) df_pv['Pv'] = pd.to_numeric(df_pv['Pv'], errors='coerce') df_pv['Power_pv_pre'] = pd.to_numeric(df_pv['Power_pv_pre'], errors='coerce') df_load = pd.DataFrame(rows_load, columns=hdr_load) df_load['Time'] = pd.to_datetime(df_load['Time']) # 确保负荷时间也被解析 df_load['Load_Bus_Pre'] = pd.to_numeric(df_load['Load_Bus_Pre'], errors='coerce') #将时间列转换为Pandas的datetime类型,errors='coerce'自动将无效数值转为NaN(如空字符串或非数字字符) # ============================================================================== # 二、主程序 # ============================================================================== plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False warnings.filterwarnings("ignore", category=UserWarning) #设置Matplotlib中文字体显示(避免中文乱码),关闭负数显示警告,忽略UserWarning类警告 #SimHei是Windows系统黑体字库,unicode_minus解决负号显示为方块的问题,警告过滤可避免后续优化计算时的冗余提示 # ------------------- 1. 参数设置 ------------------- T = 96 print(f"正在从 '{config_file_path}' 加载网络配置...") try: network_config = load_network_config(config_file_path) except FileNotFoundError: print(f"错误:配置文件 '{config_file_path}' 未找到。") exit() # --- 1.2 动态定义支路、节点、设备 --- raw_branch_data = network_config['branch'] branch_list = [[row[0], row[1], row[2] + 1j * row[3]] for row in raw_branch_data] branch = np.array(branch_list)#将支路电阻(R)和电抗(X)合并为复数阻抗 # 从拓扑中获取所有节点,确定节点数nb和支路数nl,np.real(branch[:, 0:2])提取所有首末节点编号,np.unique去重后得到全网节点列表 all_node_ids = sorted(np.unique(np.real(branch[:, 0:2]).astype(int))) nb = len(all_node_ids) nl = len(raw_branch_data) # 读取并验证平衡节点,必须有且仅有一个平衡节点(参考节点) slack_bus_id = network_config.get('slack_bus_node') if slack_bus_id is None: raise ValueError("错误:配置文件中未通过 <slack_bus> 指定平衡节点!") # 识别负荷节点(所有非平衡节点) load_node_ids = sorted([nid for nid in all_node_ids if nid != slack_bus_id])#非平衡节点即负荷节点 pv_nodes = network_config['pv_nodes']#光伏数量 ess_nodes = network_config['ess_nodes']#储能数量 district_nodes = network_config['district_nodes']#台区数量 npv = len(pv_nodes) ness = len(ess_nodes) ndistrict = len(district_nodes) print("\n网络配置加载成功:") print(f" - 系统节点: {all_node_ids} (共 {nb} 个)") print(f" - 平衡节点: {slack_bus_id}") print(f" - 负荷节点: {load_node_ids} (共 {len(load_node_ids)} 个)") print(f" - 支路数 (nl): {nl}") print(f" - 主网光伏数量 (npv): {npv}, 接入节点: {pv_nodes}") print(f" - 主网储能数量 (ness): {ness}, 接入节点: {ess_nodes}") print(f" - 台区数量 (ndistrict): {ndistrict}, 接入节点: {district_nodes}\n") # --- 1.3 精确分配负荷和光伏数据 --- df_load['Bus_i'] = pd.to_numeric(df_load['Bus_i']) load_data_dict = { bus_id: group['Load_Bus_Pre'].iloc[0:T].values / 1000#groupby('Bus_i')按节点分组,iloc[0:T]取前T个时间点(T=96对应24小时15分钟间隔),/1000单位转换 for bus_id, group in df_load.groupby('Bus_i') } for bus_id, data in load_data_dict.items(): if len(data) < T: raise ValueError(f"数据文件中负荷组 {bus_id} 的数据点不足 {T} 个。") # 创建 pload 矩阵并按顺序分配 pload = np.zeros((nb, T)) node_to_idx = {node_id: i for i, node_id in enumerate(all_node_ids)} print("\n正在为负荷节点精确分配负荷数据...") load_groups_in_file = sorted(load_data_dict.keys()) if len(load_groups_in_file) < len(load_node_ids): raise ValueError( f"数据文件中有 {len(load_groups_in_file)} 组负荷数据,不足以分配给 {len(load_node_ids)} 个负荷节点。") #pload矩阵维度:(节点数, 时间步长),映射逻辑:将数据文件中的负荷ID映射到网络拓扑节点 for i, target_node_id in enumerate(load_node_ids): source_load_id = load_groups_in_file[i] target_idx = node_to_idx[target_node_id] pload[target_idx, :] = load_data_dict[source_load_id] print(f" - 负荷组 {source_load_id} (来自文件) -> 节点 {target_node_id}") # 动态为每个光伏站分配功率曲线 print(f"\n正在为 {npv} 个光伏站分配功率曲线...") p_pv = np.zeros((npv, T)) for i in range(npv): pv_station_id = (i % 2) + 1 # 示例文件中只有1和2,这里做循环使用 station_df = df_pv[df_pv['Pv'] == pv_station_id] if len(station_df) < T: raise ValueError(f"数据文件 '{data_file_path}' 中光伏站 {pv_station_id} 的数据点不足 {T} 个。") power_curve = station_df['Power_pv_pre'].iloc[0:T].values / 1000 p_pv[i, :] = np.clip(power_curve, a_min=0, a_max=None)#np.clip确保无负功率(夜间可能出现的仪器误差) print("光伏功率曲线分配完成。") # --- 1.4 基于有功功率大小分档设定无功负荷 --- print("\n正在根据功率因数计算无功负荷...") load_thresholds = {'heavy': 0.200, 'light': 0.050} power_factors = {'heavy': 0.85, 'medium': 0.92, 'light': 0.98}#根据有功负荷大小分三档设置功率因数(PF) tan_phi_map = {level: np.tan(np.arccos(pf)) for level, pf in power_factors.items()}#通过tan(arccos(PF))计算无功/有功比 conditions = [ pload > load_thresholds['heavy'], (pload > load_thresholds['light']) & (pload <= load_thresholds['heavy']), pload <= load_thresholds['light'] ]#定义无功计算方式 choices = [pload * tan_phi_map['heavy'], pload * tan_phi_map['medium'], pload * tan_phi_map['light']] qload = np.select(conditions, choices, default=0.0) print("无功负荷计算完成。") # --- 1.5 在优化前进行数据时间对齐 --- print("\n--- 正在进行数据时间对齐 ---") # 1. 计算光伏数据的偏移量 start_time_pv = df_pv['Time'].iloc[0] shift_pv = (start_time_pv.hour * 60 + start_time_pv.minute) // 15#将数据起始时间转换为15分钟间隔的步长数(如08:30→8×4 + 2=34个步长) print(f"光伏数据起始时间: {start_time_pv.strftime('%H:%M')},对应时间步偏移量: {shift_pv}") # 2. 计算负荷数据的偏移量 start_time_load = df_load['Time'].iloc[0] shift_load = (start_time_load.hour * 60 + start_time_load.minute) // 15 print(f"负荷数据起始时间: {start_time_load.strftime('%H:%M')},对应时间步偏移量: {shift_load}") # 3. 使用np.roll进行循环移位,将数据对齐到00:00开始,确保所有数据从00:00开始对齐 p_pv = np.roll(p_pv, shift=shift_pv, axis=1) pload = np.roll(pload, shift=shift_load, axis=1) qload = np.roll(qload, shift=shift_load, axis=1) print("数据已对齐至00:00-23:45时间序列。优化将基于对齐后的数据进行。") print("---------------------------------\n") # --- 1.6 设置其他参数 --- branch[:, 2] = branch[:, 2] / (10 ** 2)#阻抗标幺化 R = np.real(branch[:, 2])#支路电阻 X = np.imag(branch[:, 2])#支路电抗 c_ess = [1] * ness p_essmax = [0.3] * ness s_essmax = [0.3 * 1.1] * ness eta_ch = [0.9] * ness eta_dis = [0.9] * ness p_district_max = [1.0] * ndistrict q_district_max = [0.5] * ndistrict # ------------------- 2. 拓扑结构 ------------------- upstream = np.zeros((nb, nl))#创建 nb×nl 全矩阵,行对应节点,列对应支路;后续标记“支路 i 的首端是哪个节点”。 dnstream = np.zeros((nb, nl)) for i in range(nl):#逐条支路进行处理 from_node_id = int(np.real(branch[i, 0]))#取出第 i 条支路的首端节点编号。 to_node_id = int(np.real(branch[i, 1]))#取出第 i 条支路的末端节点编号。 from_node_idx = node_to_idx[from_node_id]#把首端节点编号映射到 0‥nb-1 的行索引。 to_node_idx = node_to_idx[to_node_id]#把末端节点编号映射到 0‥nb-1 的行索引。 upstream[from_node_idx, i] = 1#在 upstream 矩阵的 (首端节点行, 支路列) 置 1,表示“节点是该支路的起点”。 dnstream[to_node_idx, i] = 1#在 dnstream 矩阵的 (末端节点行, 支路列) 置 1,表示“节点是该支路的终点”。 Vmax = 1.06 * 1.06 * np.ones((nb, T))#给所有节点、所有时段设置电压幅值平方的上限 (1.06 p.u.)²。 Vmin = 0.94 * 0.94 * np.ones((nb, T))#给所有节点、所有时段设置电压幅值平方的下限 (0.94 p.u.)²。 Pgmax = np.zeros((nb, T))#建立四个 nb×T 矩阵,用于后续设定各节点有功/无功出力上下界。 Qgmax = np.zeros((nb, T)) Pgmin = np.zeros((nb, T)) Qgmin = np.zeros((nb, T)) slack_bus_idx = node_to_idx[slack_bus_id]#找到平衡节点在矩阵中的行索引。 Pgmax[slack_bus_idx, :] = 10#仅对平衡节点设定大范围的出力上下界(±10 MW,±10 MVAr),其余节点保持 0,后续再细调。 Pgmin[slack_bus_idx, :] = -10 # (MW) Qgmax[slack_bus_idx, :] = 10 Qgmin[slack_bus_idx, :] = -10 # (MVar) # ------------------- 3. 定义决策变量 ------------------- V = cp.Variable((nb, T))#全网各节点各时段电压幅值平方(标幺)。 I = cp.Variable((nl, T))#各支路各时段电流幅值平方(标幺)。 P = cp.Variable((nl, T))#各支路各时段有功功率。 Q = cp.Variable((nl, T))#各支路各时段无功功率。 Pg = cp.Variable((nb, T))#各节点各时段注入有功功率(含光伏、储能、平衡机等)。 Qg = cp.Variable((nb, T))#各节点各时段注入无功功率。 q_pv = cp.Variable((npv, T))#每个光伏站各时段的无功出力。 p_ch = cp.Variable((ness, T), nonneg=True)#每套储能各时段的充电有功功率(≥0)。 p_dis = cp.Variable((ness, T), nonneg=True) u_ch = cp.Variable((ness, T), boolean=True) u_dis = cp.Variable((ness, T), boolean=True) e_ess = cp.Variable((ness, T))#每套储能各时段的剩余电量(能量)。 q_ess = cp.Variable((ness, T))#每套储能各时段的无功出力。 p_pv_utilized = cp.Variable((nb, T), nonneg=True)#各节点实际被消纳的光伏有功功率(≥0)。 P_district_exchange = cp.Variable((ndistrict, T))#各台区与主网交换的有功功率。 Q_district_exchange = cp.Variable((ndistrict, T))#各台区与主网交换的无功功率。 # ------------------- 4. 设置约束 ------------------- constraints = [] pv_nodes_idx = [node_to_idx[node] for node in pv_nodes] ess_nodes_idx = [node_to_idx[node] for node in ess_nodes] district_nodes_idx = [node_to_idx[node] for node in district_nodes] # 4.1 主网光伏约束 #把光伏无功 q_pv 映射到对应节点,限制无功不超过有功 ±30%,并把实际被利用的光伏有功 p_pv_utilized 约束在 0 与可用光伏之间。 P_pv_matrix = np.zeros((nb, T)) for i, node_idx in enumerate(pv_nodes_idx): P_pv_matrix[node_idx, :] = p_pv[i, :] Q_pv = cp.Variable((nb, T)) for i, node_idx in enumerate(pv_nodes_idx): constraints += [Q_pv[node_idx, :] == q_pv[i, :]] non_pv_nodes_idx = [i for i in range(nb) if i not in pv_nodes_idx] for node_idx in non_pv_nodes_idx: constraints += [Q_pv[node_idx, :] == 0] constraints += [-0.3 * P_pv_matrix[pv_nodes_idx, :] <= q_pv, q_pv <= 0.3 * P_pv_matrix[pv_nodes_idx, :]] constraints += [p_pv_utilized[pv_nodes_idx, :] <= P_pv_matrix[pv_nodes_idx, :]] constraints += [p_pv_utilized[non_pv_nodes_idx, :] == 0, p_pv_utilized >= 0] # 4.2 主网储能约束 #通过 0-1 变量 u_ch / u_dis 保证“充放电互锁”,功率、容量、SOC 均限制在设定范围,且保持日循环(首尾 SOC 相等)。 P_dis_matrix = cp.Constant(np.zeros((nb, T))) P_ch_matrix = cp.Constant(np.zeros((nb, T))) Q_ess_matrix = cp.Constant(np.zeros((nb, T))) for i, node_idx in enumerate(ess_nodes_idx): # 使用辅助矩阵来构建,避免CVXPY的+=性能问题 temp_p_dis = np.zeros((nb, T)) temp_p_dis[node_idx, :] = p_dis[i, :].value if p_dis.value is not None else 0 P_dis_matrix += cp.vstack( [cp.Constant(np.zeros((node_idx, T))), p_dis[i:i + 1, :], cp.Constant(np.zeros((nb - node_idx - 1, T)))]) P_ch_matrix += cp.vstack( [cp.Constant(np.zeros((node_idx, T))), p_ch[i:i + 1, :], cp.Constant(np.zeros((nb - node_idx - 1, T)))]) Q_ess_matrix += cp.vstack( [cp.Constant(np.zeros((node_idx, T))), q_ess[i:i + 1, :], cp.Constant(np.zeros((nb - node_idx - 1, T)))]) constraints += [u_ch + u_dis <= 1] for i in range(ness): constraints += [0 <= p_dis[i, :], p_dis[i, :] <= u_dis[i, :] * p_essmax[i]] constraints += [0 <= p_ch[i, :], p_ch[i, :] <= u_ch[i, :] * p_essmax[i]] for t in range(T): constraints += [cp.norm(cp.vstack([p_dis[i, t] - p_ch[i, t], q_ess[i, t]])) <= s_essmax[i]] for i in range(ness): constraints += [e_ess[i, 0] == c_ess[i] / 2] for t in range(T - 1): constraints += [ e_ess[i, t + 1] == e_ess[i, t] + eta_ch[i] * p_ch[i, t] * 0.25 - (p_dis[i, t] / eta_dis[i]) * 0.25] constraints += [e_ess[i, 0] == e_ess[i, T - 1]] constraints += [c_ess[i] * 0.2 <= e_ess[i, :], e_ess[i, :] <= c_ess[i] * 0.9] # 4.3 台区约束 #将台区与主网的交换功率 P_district_exchange / Q_district_exchange 限制在给定上下界,并映射到对应节点。 P_district_grid = cp.Constant(np.zeros((nb, T))) Q_district_grid = cp.Constant(np.zeros((nb, T))) for d, node_idx in enumerate(district_nodes_idx): constraints += [-p_district_max[d] <= P_district_exchange[d, :], P_district_exchange[d, :] <= p_district_max[d]] constraints += [-q_district_max[d] <= Q_district_exchange[d, :], Q_district_exchange[d, :] <= q_district_max[d]] P_district_grid += cp.vstack([cp.Constant(np.zeros((node_idx, T))), P_district_exchange[d:d + 1, :], cp.Constant(np.zeros((nb - node_idx - 1, T)))]) Q_district_grid += cp.vstack([cp.Constant(np.zeros((node_idx, T))), Q_district_exchange[d:d + 1, :], cp.Constant(np.zeros((nb - node_idx - 1, T)))]) # 4.4 主网潮流约束 (修正了潮流方程中的储能和台区项) #用线性化 DistFlow 描述支路功率平衡、节点功率平衡、电压降落;对每条支路每时段施加二阶锥松弛,使模型可解且保持凸性。 Pin = -upstream @ P + upstream @ cp.multiply(I, R[:, None] * np.ones((1, T))) + dnstream @ P Qin = -upstream @ Q + upstream @ cp.multiply(I, X[:, None] * np.ones((1, T))) + dnstream @ Q # 节点功率平衡方程 constraints += [Pin + pload - Pg - p_pv_utilized - P_dis_matrix + P_ch_matrix - P_district_grid == 0] constraints += [Qin + qload - Qg - Q_pv - Q_ess_matrix - Q_district_grid == 0] # 潮流方程 for i in range(nl): from_node_idx = node_to_idx[int(np.real(branch[i, 0]))] to_node_idx = node_to_idx[int(np.real(branch[i, 1]))] constraints += [V[to_node_idx, :] == V[from_node_idx, :] - 2 * R[i] * P[i, :] - 2 * X[i] * Q[i, :] + ( R[i] ** 2 + X[i] ** 2) * I[i, :]] # 二阶锥约束 for i in range(nl): from_node_idx = node_to_idx[int(np.real(branch[i, 0]))] for t in range(T): constraints += [cp.norm(cp.vstack([2 * P[i, t], 2 * Q[i, t], I[i, t] - V[from_node_idx, t]])) <= I[i, t] + V[ from_node_idx, t]] # 4.5 通用约束 #统一给所有节点加上电压、功率、电流的硬边界,并把平衡节点电压固定为 1 p.u.。 constraints += [Vmin <= V, V <= Vmax] constraints += [Pgmin <= Pg, Pg <= Pgmax] constraints += [Qgmin <= Qg, Qg <= Qgmax] constraints += [0 <= I, I <= 1000] # 放宽电流约束以防无解 constraints += [V[slack_bus_idx, :] == 1.0] # ------------------- 5. 设置目标函数 ------------------- optimization_target = "voltage" objective = cp.Minimize(cp.sum(cp.abs(V - 1)))#电压优化目标:最小化所有节点电压幅值平方与1 p.u.的绝对偏差总和 # ------------------- 6. 求解优化问题 ------------------- #把目标函数和所有约束塞进 cp.Problem,调用 Gurobi 求解器,设置 3 % 的 MIPGap,开始求解。 problem = cp.Problem(objective, constraints) print("\n正在调用求解器求解...") #problem.solve(solver=cp.GUROBI, verbose=True, MIPGap=0.03) # 使用 SCIP 求解器,设置 Gap = 0.03 problem.solve(solver=cp.SCIP, verbose=True, scip_params={"limits/gap": 0.03}) # ------------------- 7. 结果分析 ------------------- print("\n--- 结果分析 ---") if problem.status in [cp.OPTIMAL, cp.OPTIMAL_INACCURATE]:#判断求解器返回的状态是不是“已最优”或“近似最优”。 print("成功求解!") if optimization_target == "voltage": voltage_deviation = cp.sum(cp.abs(V - 1)).value#把目标函数值取出来,即所有节点电压与 1 p.u. 的绝对偏差总和。 print("所有节点的电压偏差总和:", voltage_deviation) elif optimization_target == "pv_utilization": total_pv_available = np.sum(P_pv_matrix)#计算主网光伏理论可发总量。 if total_pv_available > 1e-6:#确保分母不为 0。 pv_utilization_rate_main = np.sum(p_pv_utilized.value[pv_nodes_idx, :]) / total_pv_available#用“实际被利用光伏总量 / 理论可发总量”得出主网光伏消纳率。 print(f"主网光伏消纳率: {pv_utilization_rate_main:.3%}") else: print("主网光伏可用总量为0,无法计算消纳率。") else: print("求解出错:", problem.status) # ------------------- 8. 生成 .e 文件 ------------------- if problem.status in [cp.OPTIMAL, cp.OPTIMAL_INACCURATE]:#仅在 OPTIMAL / OPTIMAL_INACCURATE 状态下才继续; output_dir = args.output#定义输出文件夹的绝对路径。 os.makedirs(output_dir, exist_ok=True)#如果该路径不存在就自动创建,若已存在则静默跳过。 start_time = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)#把当前日期截成“今天 00:00:00”,作为 24 h 时间轴的起点。 time_steps = [start_time + timedelta(minutes=15 * t) for t in range(T)]#以 15 min 为步长,从 00:00 开始生成 T=96 个 datetime 对象,供后续逐时刻写文件使用。 # 台区 .e 文件 region_filename = os.path.join(output_dir, f"region_action_plan_{start_time.strftime('%Y%m%d%H%M')}.e")#把目录、前缀、时间戳拼成完整的台区计划文件路径。 with open(region_filename, 'w', encoding='utf-8') as f:#以 UTF-8 编码、写模式打开文件,准备写入。 f.write("<region_action_plan>\n")#写入段起始标签 <region_action_plan>。 f.write("//\t时间\t台区接入节点号\t台区有功功率(kW)\t台区无功功率(kVar)\n")#写入注释行,说明后面各列含义。 f.write("@\ttime\tregion_node_id\tregion_active_power\tregion_reactive_power\n")#写入列名行(以 @ 开头,后续解析用)。 for i, node_id in enumerate(district_nodes):#遍历每一个台区节点编号。 for t in range(T):#对该节点遍历 0–95 共 96 个时段。 time_str = time_steps[t].strftime('%Y-%m-%dT%H:%M:%S')#把当前时段转成 “YYYY-MM-DDTHH:MM:SS” 字符串格式。 p_exchange = P_district_exchange.value[i, t] * 1000#把求解得到的台区有功交换功率从 MW → kW。 q_exchange = Q_district_exchange.value[i, t] * 1000#把无功交换功率从 MVAr → kVAr。 f.write(f"#\t{time_str}\t{node_id}\t{p_exchange:.4f}\t{q_exchange:.4f}\n")#以 # 开头写一行数据:时间、节点号、有功(kW)、无功(kVAr),均保留 4 位小数。 f.write("</region_action_plan>\n")#写入段结束标签。 print(f".e 文件已生成: {region_filename}")#提示用户文件已成功写出,并显示完整路径。 # 光伏出力计划 .e 文件 pv_filename = os.path.join(output_dir, f"pv_action_plan_{start_time.strftime('%Y%m%d%H%M')}.e") with open(pv_filename, 'w', encoding='utf-8') as f: f.write("<pv_action_plan>\n") f.write("//\t时间\t光伏接入节点号\t光伏输出有功(kW)\t光伏输出无功(kVar)\n") f.write("@\ttime\tpv_node_id\tpv_active_power\tpv_reactive_power\n") for i, node_id in enumerate(pv_nodes): node_idx = pv_nodes_idx[i] for t in range(T): time_str = time_steps[t].strftime('%Y-%m-%dT%H:%M:%S') p_pv_util = p_pv_utilized.value[node_idx, t] * 1000 q_pv_val = q_pv.value[i, t] * 1000 f.write(f"#\t{time_str}\t{node_id}\t{p_pv_util:.4f}\t{q_pv_val:.4f}\n") f.write("</pv_action_plan>\n") print(f".e 文件已生成: {pv_filename}") # 储能计划动作 .e 文件 storage_filename = os.path.join(output_dir, f"storage_action_plan_{start_time.strftime('%Y%m%d%H%M')}.e") with open(storage_filename, 'w', encoding='utf-8') as f: f.write("<storage_action_plan>\n") f.write("//\t时间\t储能接入节点号\t储能输出有功(kW)\t储能输出无功(kVar)\n") f.write("@\ttime\tstorage_node_id\tstorage_active_power\tstorage_reactive_power\n") for i, node_id in enumerate(ess_nodes): for t in range(T): time_str = time_steps[t].strftime('%Y-%m-%dT%H:%M:%S') p_ess = (p_dis.value[i, t] - p_ch.value[i, t]) * 1000 q_ess_val = q_ess.value[i, t] * 1000 f.write(f"#\t{time_str}\t{node_id}\t{p_ess:.4f}\t{q_ess_val:.4f}\n") f.write("</storage_action_plan>\n") print(f".e 文件已生成: {storage_filename}") else: print("因求解失败,无法生成 .e 文件。") # ------------------- 9. 可视化图形 ------------------- if problem.status in [cp.OPTIMAL, cp.OPTIMAL_INACCURATE]: hours = np.linspace(0, 24, T, endpoint=False) major_ticks = np.arange(0, 25, 4) # 图表1&2:台区功率流动 # 移除台区数量条件限制,确保图表生成 fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10), sharex=True) for i in range(ndistrict): ax1.plot(hours, P_district_exchange.value[i, :], marker='o', linestyle='-', label=f'台区 (节点 {district_nodes[i]})', markersize=4) ax2.plot(hours, Q_district_exchange.value[i, :], marker='s', linestyle='--', label=f'台区 (节点 {district_nodes[i]})', markersize=4) ax1.set_title('台区24小时功率流动') ax1.set_ylabel('有功功率 (MW)') ax1.grid(True) ax1.legend() ax2.set_xlabel('时间 (小时)') ax2.set_ylabel('无功功率 (MVar)') ax2.grid(True) ax2.legend() plt.xticks(major_ticks) plt.tight_layout() plt.show() 这是其中的一个优化算法,在调用优化算法进行优化后,如何将优化结果(非图片格式)传送到GUI界面那里,然后在GUI完成数据分析再画图呢?
最新发布
07-31
<think>我们有一个GUI程序(基于PyQt5)和一个优化算法(使用cvxpy求解)。优化算法在单独的文件中,通过命令行参数调用。现在,我们需要将优化算法的结果(非图片格式)传送到GUI界面,然后在GUI中完成数据分析并画图。 解决方案: 1. 优化算法输出结果文件:优化算法已经将结果输出为.e文件(如台区、光伏、储能的动作计划)。但是,我们还需要在优化算法中输出一些中间变量(如电压、电流、功率等)以便在GUI中绘图。 2. 修改优化算法,使其在求解完成后将需要的数据保存为一种便于GUI读取的格式(如pickle、numpy的.npy、或JSON)。考虑到数据量较大且包含多维数组,建议使用pickle或.npy。 3. 在GUI中,当优化计算完成后(即子进程结束后),读取这些数据文件,然后进行绘图。 具体步骤: 步骤1:在优化算法(主程序)中,除了生成.e文件外,还将需要用于绘图的数据保存到一个文件中。 步骤2:在GUI中,当运行优化算法的子进程结束时,检查输出目录,读取该数据文件。 步骤3:在GUI中,使用读取的数据绘制图表。 需要保存的数据(示例): - 时间序列(hours) - 各节点的电压(V) - 各支路的电流(I) - 各支路的有功功率(P)、无功功率(Q) - 光伏消纳情况(p_pv_utilized) - 储能充放电功率(p_ch, p_dis)等 由于优化算法中已经计算了这些变量,我们可以在求解完成后将它们保存起来。 修改优化算法代码(在求解成功后的部分): - 将需要的数据收集到一个字典中,然后使用pickle保存。 例如: if problem.status in [cp.OPTIMAL, cp.OPTIMAL_INACCURATE]: # 保存结果到.e文件(已有代码) ... # 准备绘图数据 plot_data = { 'hours': np.linspace(0, 24, T, endpoint=False), 'V': V.value, 'I': I.value, 'P': P.value, 'Q': Q.value, 'p_pv_utilized': p_pv_utilized.value, 'p_dis': p_dis.value if hasattr(p_dis, 'value') else None, # 储能放电功率 'p_ch': p_ch.value, 'q_ess': q_ess.value, 'P_district_exchange': P_district_exchange.value, 'Q_district_exchange': Q_district_exchange.value, # 其他需要的数据... } # 保存为pickle文件 import pickle data_filename = os.path.join(output_dir, "optimization_results.pkl") with open(data_filename, 'wb') as f: pickle.dump(plot_data, f) 步骤4:在GUI中,当优化子进程结束后(即finished_signal信号发出),读取这个pickle文件,然后进行绘图。 注意:在GUI中,我们是通过子进程调用优化算法脚本的,所以优化脚本的输出目录是已知的(由--output参数指定)。因此,在GUI中,我们可以根据输出目录去查找这个pickle文件。 在GUI的AlgorithmRunner线程中,当finished_signal发出时,我们可以读取结果文件: def on_optimization_finished(return_code): if return_code == 0: # 成功 # 读取输出目录下的optimization_results.pkl output_dir = ... # 这个输出目录在启动优化算法时通过参数传递,GUI需要记录 data_file = os.path.join(output_dir, "optimization_results.pkl") if os.path.exists(data_file): with open(data_file, 'rb') as f: plot_data = pickle.load(f) # 调用绘图函数,使用plot_data绘制图表 self.display_results(plot_data) 步骤5:在GUI中实现display_results方法,根据plot_data绘制各种图表。 注意:由于优化算法可能运行时间较长,保存的数据量可能较大,因此要确保有足够的磁盘空间和内存。 另一种方案:不通过文件,而是通过标准输出传输数据。但是,由于数据量大且包含浮点数数组,通过标准输出传输可能效率低且容易出错(需要设计复杂的编码/解码)。因此,使用文件更可靠。 考虑到我们已经在使用.e文件输出结果,再增加一个pickle文件也是合理的。 但是,请注意,优化算法可能由不同的脚本执行(voltage_opt.py, pvcon_opt.py, mix_opt.py),所以每个脚本都需要修改,在最后保存pickle文件。 在GUI中,我们需要统一读取这个pickle文件(文件名固定,如optimization_results.pkl)来获取数据。 修改优化算法后,在GUI中如何调整? 在GUI中,我们通过AlgorithmRunner线程调用优化算法脚本,这个线程在运行结束后会发出finished_signal信号,并附带返回码。我们在返回码为0(成功)时读取结果文件。 因此,我们需要在启动优化算法时记录输出目录。在OptimizationGUI类的run_optimization方法中,我们在构建args时已经指定了输出目录,所以我们可以将其保存为实例变量。 修改OptimizationGUI类的run_optimization方法: def run_optimization(self): ... output_dir = os.path.join(os.getcwd(), "输出e文件") # 这是当前代码中的输出目录 args = [ "--data", self.pv_file_edit.text(), "--config", self.node_file_edit.text(), "--output", output_dir ] # 保存输出目录,以便后续读取结果 self.current_output_dir = output_dir # 然后启动算法运行器... 然后,在finished_signal信号的槽函数中: def optimization_finished(self, return_code): self.run_button.setEnabled(True) if return_code == 0: # 尝试读取结果文件 data_file = os.path.join(self.current_output_dir, "optimization_results.pkl") if os.path.exists(data_file): try: with open(data_file, 'rb') as f: plot_data = pickle.load(f) # 调用绘图函数 self.display_results(plot_data) except Exception as e: self.update_status(f"加载结果数据失败: {str(e)}") else: self.update_status("未找到结果数据文件。") else: self.update_status(f"优化计算失败,返回码: {return_code}") 步骤6:在GUI中实现display_results方法,绘制各种图表。 例如,绘制电压曲线: def display_results(self, plot_data): # 绘制电压曲线 # 假设plot_data['V']是节点数×时间步的矩阵 # 选择几个关键节点绘制 key_nodes = [0, 5, 10] # 示例节点索引 hours = plot_data['hours'] plt.figure(figsize=(10, 6)) for node_idx in key_nodes: plt.plot(hours, plot_data['V'][node_idx, :], label=f'节点 {node_idx+1}') plt.xlabel('时间(小时)') plt.ylabel('电压(pu)') plt.title('关键节点电压曲线') plt.legend() plt.grid(True) plt.show() 注意:在PyQt5中,我们通常使用FigureCanvas将Matplotlib图形嵌入到GUI中,而不是使用plt.show()弹窗。 我们可以将图表绘制在GUI的结果可视化区域(ResultVisualizer)中。 由于ResultVisualizer之前已经定义,我们可以修改它,使其能够根据传入的数据绘制图表。 因此,我们需要重构ResultVisualizer类,使其包含一个FigureCanvas,并添加绘制各种图表的方法。 例如: class ResultVisualizer(QWidget): def __init__(self, parent=None): super().__init__(parent) self.figure = Figure() self.canvas = FigureCanvas(self.figure) layout = QVBoxLayout() layout.addWidget(self.canvas) self.setLayout(layout) def plot_voltage(self, hours, voltage_data, node_indices): self.figure.clear() ax = self.figure.add_subplot(111) for idx in node_indices: ax.plot(hours, voltage_data[idx, :], label=f'节点 {idx+1}') ax.set_xlabel('时间(小时)') ax.set_ylabel('电压(pu)') ax.set_title('关键节点电压曲线') ax.legend() ax.grid(True) self.canvas.draw() # 类似地,可以定义其他绘图方法 然后在display_results中调用: def display_results(self, plot_data): # 绘制电压曲线 key_node_indices = [0, 5, 10] # 关键节点的索引 self.result_visualizer.plot_voltage(plot_data['hours'], plot_data['V'], key_node_indices) 这样,图表就会显示在GUI的ResultVisualizer部件中。 总结: 1. 修改优化算法脚本,在输出.e文件的同时,将绘图所需的数据保存为pickle文件。 2. 在GUI中,优化算法运行结束后,读取pickle文件。 3. 在GUI的结果可视化部件中,根据数据绘制图表。 注意:由于优化算法脚本可能有多个(电压优化、光伏消纳优化、混合优化),每个脚本都需要在最后添加保存pickle文件的代码。 另外,考虑到优化算法可能运行失败,我们需要在保存pickle文件之前检查求解状态。只有成功求解时才保存。 最后,由于数据量可能很大,如果担心磁盘空间,可以在GUI使用完数据后删除pickle文件,或者考虑使用更紧凑的存储格式(如压缩的npy)。 但是,考虑到数据量通常不会太大(96个时间步,节点数一般不超过100),所以使用pickle是可行的。 现在,我们给出优化算法中保存pickle文件的代码示例(在优化算法脚本的末尾,成功求解后):</think>要解决优化算法结果与GUI界面之间的数据传输问题,并在GUI中完成数据分析和可视化,可以按照以下方案实现: ### 解决方案:建立数据传递通道 需要建立从优化算法到GUI的数据传递机制,主要步骤如下: ```python # 在优化算法中(优化脚本末尾添加) import pickle if problem.status in [cp.OPTIMAL, cp.OPTIMAL_INACCURATE]: # 收集所有需要传输的结果数据 result_data = { 'hours': hours, # 时间轴数据 'voltage': V.value, # 电压矩阵 'pv_utilized': p_pv_utilized.value, # 光伏消纳 'storage': { 'p_dis': p_dis.value, 'p_ch': p_ch.value, 'q_ess': q_ess.value }, 'district': { 'P_exchange': P_district_exchange.value, 'Q_exchange': Q_district_exchange.value }, 'metrics': { 'voltage_deviation': voltage_deviation, 'pv_utilization_rate': pv_utilization_rate_main } } # 序列化数据并保存到临时文件 temp_file = os.path.join(output_dir, "optimization_results.pkl") with open(temp_file, 'wb') as f: pickle.dump(result_data, f) print(f"优化结果已保存到: {temp_file}") ``` ### 在GUI中接收并处理数据 修改GUI代码,添加结果接收和处理逻辑: ```python # 在GUI类中添加以下方法 class OptimizationGUI(QMainWindow): # ... 其他代码不变 ... def optimization_finished(self, return_code): """优化完成后处理结果""" self.run_button.setEnabled(True) if return_code == 0: # 成功完成 # 1. 读取序列化的优化结果 result_file = os.path.join(self.current_output_dir, "optimization_results.pkl") try: with open(result_file, 'rb') as f: self.optimization_data = pickle.load(f) # 2. 在GUI中分析数据 self.analyze_optimization_data() # 3. 更新可视化 self.update_result_visualization() except Exception as e: self.update_status(f"加载结果失败: {str(e)}") else: self.update_status("优化计算失败") def analyze_optimization_data(self): """分析优化结果数据""" data = self.optimization_data # 示例:计算电压合格率 voltage = data['voltage'] v_min, v_max = 0.94**2, 1.06**2 # 电压平方的限值 # 计算每个节点的电压合格率 qualified = np.logical_and(voltage >= v_min, voltage <= v_max) node_qualified_rates = np.mean(qualified, axis=1) * 100 # 存储分析结果 self.analysis_results = { 'voltage_qualified_rates': node_qualified_rates, 'pv_utilization': np.mean(data['pv_utilized']), 'max_voltage_deviation': np.max(np.abs(voltage - 1)) } # 更新状态显示 self.update_status(f"电压平均合格率: {np.mean(node_qualified_rates):.2f}%") self.update_status(f"光伏消纳率: {self.analysis_results['pv_utilization']*100:.2f}%") def update_result_visualization(self): """根据当前选择更新可视化""" # 获取用户选择的图表类型 selected_view = self.result_combo.currentText() # 调用对应的绘图方法 if selected_view == "电压分布": self.plot_voltage_distribution() elif selected_view == "光伏消纳情况": self.plot_pv_utilization() # 其他图表类型... def plot_voltage_distribution(self): """绘制电压分布图""" if not hasattr(self, 'optimization_data'): return data = self.optimization_data fig = self.result_visualizer.figure fig.clear() ax = fig.add_subplot(111) # 绘制所有节点的电压曲线 for node_idx in range(data['voltage'].shape[0]): ax.plot(data['hours'], data['voltage'][node_idx, :], alpha=0.5, linewidth=0.8) # 添加统计信息 ax.set_title(f"节点电压分布 (平均合格率: {self.analysis_results['voltage_qualified_rates'].mean():.2f}%)") ax.set_xlabel("时间 (小时)") ax.set_ylabel("电压 (p.u.)") ax.grid(True) # 添加限值线 ax.axhline(y=1.06**2, color='r', linestyle='--', alpha=0.7, label='上限') ax.axhline(y=0.94**2, color='r', linestyle='--', alpha=0.7, label='下限') ax.legend() self.result_visualizer.canvas.draw() def plot_pv_utilization(self): """绘制光伏消纳情况""" if not hasattr(self, 'optimization_data'): return data = self.optimization_data fig = self.result_visualizer.figure fig.clear() ax = fig.add_subplot(111) # 绘制每个光伏站的消纳曲线 for pv_idx in range(data['pv_utilized'].shape[0]): ax.plot(data['hours'], data['pv_utilized'][pv_idx, :] * 100, label=f'光伏站 {pv_idx+1}') ax.set_title("光伏消纳情况") ax.set_xlabel("时间 (小时)") ax.set_ylabel("消纳率 (%)") ax.grid(True) ax.legend() self.result_visualizer.canvas.draw() ``` ### 关键实现细节说明 1. **数据传输机制**: - 使用Python的`pickle`模块序列化优化结果 - 通过临时文件传递数据(避免进程间通信的复杂性) - 文件路径通过`--output`参数传递,确保GUI知道结果位置 2. **数据封装结构**: ```python result_data = { 'hours': hours, # 时间轴 'voltage': V.value, # 电压矩阵[nodes×time] 'pv_utilized': p_pv_utilized.value, # 光伏消纳 'storage': { # 储能数据 'p_dis': p_dis.value, 'p_ch': p_ch.value, 'q_ess': q_ess.value }, 'district': { # 台区数据 'P_exchange': P_district_exchange.value, 'Q_exchange': Q_district_exchange.value }, 'metrics': { # 关键指标 'voltage_deviation': voltage_deviation, 'pv_utilization_rate': pv_utilization_rate_main } } ``` 3. **GUI端处理流程**: ```mermaid sequenceDiagram participant User participant GUI participant Optimizer User->>GUI: 点击"运行优化" GUI->>Optimizer: 启动优化进程 Optimizer->>Optimizer: 执行优化计算 Optimizer->>Optimizer: 保存结果到.pkl文件 Optimizer->>GUI: 返回退出码 GUI->>GUI: 加载.pkl文件 GUI->>GUI: 分析数据(计算指标) GUI->>GUI: 根据选择更新可视化 GUI->>User: 显示分析结果和图表 ``` ### 架构优势 1. **解耦设计**: - 优化算法独立于GUI,可单独运行和测试 - GUI只负责数据展示,不包含核心算法 2. **高效数据传输**: - 二进制格式传输大型矩阵数据 - 避免重复计算,直接使用优化结果 3. **灵活可视化**: - 按需加载不同图表类型 - 支持动态交互更新 4. **错误处理**: - 完善的异常捕获机制 - 状态反馈和用户提示 ### 注意事项 1. **数据安全**: - 限制.pkl文件只包含必要数据 - 添加数据校验防止恶意文件 2. **大文件处理**: ```python # 添加分块加载支持 CHUNK_SIZE = 96 # 每个时间步的数据 for t in range(0, T, CHUNK_SIZE): chunk = voltage[:, t:t+CHUNK_SIZE] # 处理数据块... ``` 3. **内存管理**: - 及时释放不再使用的数据 - 提供"清除结果"按钮释放内存 4. **跨平台兼容**: - 使用`pathlib`处理路径 - 确保文件编码一致(UTF-8)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值