# -*- coding: utf-8 -*- import pandas as pd import numpy as np import copy import datetime class PvOptimizationTool(object): """光伏优化工具""" """ "pv_system":{ "user_type": "工商业", #建筑类型 "install_space":10000, #安装面积m2 "efficiency":0.8, #光伏发电效率 "first_year_decay_rate":2.5, #首年衰减率 "first10_year_decay_rate":0.8, #第2-10年衰减率 "other_year_decay_rate":0.7, #第11-25年衰减率 "evaluate_year": 25, #评估年限 "self_use_ratio":0.7, #自发自用比例 "peak_sunshine_hours": 1347.945 #年峰值日照小数数 }, "price":{ "rmb_per_wp":7.5元/Wp, #建设单价 "self_use_price_discout":100.0, #自发自用电价折扣 "sel_use_per_kwh": 1元/kWh, #自发自用电价 "state_subsidy": 0.42 元/kWh, #国家补贴 "local_subsidy": 0.25 元/kWh, #地方补贴 "local_subsidy_year":5, #地方补贴年限 "first_install_subsidy": 0.0 元/Wp, #初始安装补贴 "coal_in_grid": 0.43 元/kWh, #脱硫电价 "spfv_price":{ "section_s":{"price":0, "time_range": ""}, "section_p":{"price":0.8888, "time_range": "14:00-17:00;19:00-22:00"}, "section_f":{"price":0.5503, "time_range": "8:00-14:00;17:00-19:00;22:00-24:00"}, "section_v":{"price":0.2900, "time_range":"00:00-8:00"} } #分时电价及对应时段小时数 } env_benifit_param = { "one_family_kwh": 3600, #一户家庭一年用3600度电 "CO2":0.822, #二氧化碳排放kg/kWh "SO2": 0.00039, #二氧化硫排放kg/kWh "NOx": 0.00036 , #氮氧化合物kg/kWh "smoke": 0.00008 , #烟尘kg/kWh "coal": 0.312, #煤耗kg/kWh "h2o": 0.0040, #纯净水m3/kWh "tree": 18.3 #=0.822/18.3相当于植树棵/kWh 1棵树1年可吸收18.3千克CO2 } inline_var:{ "inline_capacity":进线容量,kVA, "peak_kwh":峰时段电量kwh, "peak_charge":峰时段电费元, "flat_kwh":平时段电量kwh, "flat_charge":平时段电费元 } df_load :pandas.DataFrame column_name:["quarter_time", "load_curve"] df_pv: pandas.DataFrame column_name:["quarter_time", "pv_curve"] """ def __init__(self, pv_system, price, env_benifit_param, df_load, df_pv, inline_var): self.pv_system = pv_system self.price = price self.env_benifit_param = env_benifit_param self.curve = self._merge_curve(df_load, df_pv) self.inline_var = inline_var self.self_use_price = self._cal_mean_price() self.time_section = self._check_time_section() self.income_table = 0 self.invest_capacity = 0 self.invest_evaluate = 0 self.opt_analysis = 0 self.opt_curve = 0 self.msg = '' def _merge_curve(self, df_load, df_pv): pv_kWp = self.pv_system["install_space"] * 100 / 1000.0 df_load1 = copy.deepcopy(df_load) df_pv1 = copy.deepcopy(df_pv) df_load1.set_index("quarter_time", inplace=True) df_load1 = df_load1.resample("1H", closed="left", label="left").mean() df_load1.reset_index(inplace=True) df_load1["time_point"] = df_load1["quarter_time"].apply( lambda x: datetime.datetime.strftime(x, "%H:%M:%S")) df_pv1["time_point"] = df_pv1["quarter_time"].apply( lambda x: datetime.datetime.strftime(x, "%H:%M:%S")) df_pv1["pv_curve"] = df_pv1["pv_curve"] * pv_kWp / 1000.0 * \ self.pv_system["efficiency"] df_new = pd.merge(df_load1[["time_point", "load_curve"]], df_pv1[["time_point", "pv_curve"]], how="left", on="time_point") return df_new def cal_invest_capacity(self): rst = {} rst["install_space"] = self.pv_system["install_space"] rst["capacity"] = self.pv_system["install_space"] * 100 / 1000.0 rst["ttl_invest"] = rst["capacity"] * ( self.price["rmb_per_wp"] - self.price[ "first_install_subsidy"]) * 1000.0 # 元 rst["first_year_ttl_kwh"] = rst["capacity"] * self.pv_system[ "efficiency"] * self.pv_system["peak_sunshine_hours"] # 度 return rst def time_15min_parse(self, time_range): time_sections = time_range.split(";") sections = [] for section in time_sections: section_range = section.split("-") start_time = pd.Timestamp(section_range[0]) if section_range[1] == "24:00": section_range[1] = "00:00" stop_time = pd.Timestamp(section_range[1]) + pd.Timedelta( days=1) else: stop_time = pd.Timestamp(section_range[1]) current_time = start_time # + pd.Timedelta(seconds=900) while True: if current_time < stop_time: sections.append(current_time) current_time += pd.Timedelta(seconds=900) else: break return sections def time_1hour_parse(self): time_index_series = self.time_section.copy(deep=True) time_index_series = time_index_series.set_index("quarter_time") time_section_resample = time_index_series.resample("1H", closed="left", label="left").max() # , loffset="-1H" time_section_resample.reset_index(inplace=True) time_section_resample["time_point"] = time_section_resample[ "quarter_time"].apply( lambda x: datetime.datetime.strftime(x, "%H:%M:%S")) df_curve = copy.deepcopy(self.curve) # df_curve["time_point"] = df_curve["quarter_time"].apply(lambda x:datetime.datetime.strftime(x, "%H:%M:%S")) df_new = pd.merge(time_section_resample[["time_point", "spfv_flag"]], df_curve[["time_point", "load_curve", "pv_curve"]], how="left", on="time_point") df_new["load_pv_curve"] = df_new["load_curve"] - df_new["pv_curve"] df_new.set_index("time_point", inplace=True) # time_section_resample = time_section_resample.reset_index() # df = pd.concat([time_section_resample, self.curve], axis=1, ignore_index=False) return df_new def _check_time_section(self): param = {"section_s": 3, "section_p": 2, "section_f": 1, "section_v": 0} hour = [] spfv_flag = [] for key in self.price["spfv_price"].keys(): time_point = self.time_15min_parse( self.price["spfv_price"][key]["time_range"]) hour.extend(time_point) num_15mins = len(time_point) spfv_flag.extend([param[key]] * num_15mins) time_section = pd.DataFrame( {"quarter_time": hour, "spfv_flag": spfv_flag}) time_section = time_section.sort_values(by="quarter_time") time_section.index = range(96) return time_section def _cal_mean_price(self): mean_price = 0.0 if not self.price["sel_use_per_kwh"]: mean_price = (self.inline_var["peak_charge"] + self.inline_var[ "flat_charge"]) / (self.inline_var["peak_kwh"] + self.inline_var["flat_kwh"] + 0.0001) else: mean_price = self.price["sel_use_per_kwh"] return mean_price * self.price["self_use_price_discout"] / 10.0 def _cal_income_table(self): if self.pv_system["evaluate_year"] < 20: evaluate_year = 20 else: evaluate_year = self.pv_system["evaluate_year"] if self.price["local_subsidy_year"] >= evaluate_year: self.price["local_subsidy_year"] = evaluate_year decay_rate = np.array([0, self.pv_system["first_year_decay_rate"]] + [self.pv_system["first10_year_decay_rate"]] * 9 + [self.pv_system["other_year_decay_rate"]] * ( evaluate_year - 10 - 1)) / 100.0 end_year_percent = 1 - decay_rate.cumsum() year_gen_kwh = self.invest_capacity[ "first_year_ttl_kwh"] * end_year_percent state_subsidy = np.array( [self.price["state_subsidy"]] * 20 + [0] * (evaluate_year - 20)) local_subsidy = np.array( [self.price["local_subsidy"]] * self.price["local_subsidy_year"] + [ 0] * (evaluate_year - self.price["local_subsidy_year"])) self_use_price = np.array([ self.self_use_price] * evaluate_year + state_subsidy + local_subsidy) in_grid_price = np.array([self.price[ "coal_in_grid"]] * evaluate_year + state_subsidy + local_subsidy) income_table = pd.DataFrame( { "year": range(1, evaluate_year + 1), "decay_rate": decay_rate, "end_year_percent": end_year_percent, "year_gen_kwh": year_gen_kwh, "self_use_price": self_use_price, "in_grid_price": in_grid_price } ) income_table["year_benifit"] = ( income_table["year_gen_kwh"] * self.pv_system[ "self_use_ratio"] * income_table["self_use_price"] + income_table["year_gen_kwh"] * ( 1.0 - self.pv_system["self_use_ratio"]) * income_table["in_grid_price"]) income_table["cumsum_benifit"] = income_table["year_benifit"].cumsum() - \ self.invest_capacity["ttl_invest"] income_table["year_benifit_ratio"] = income_table["year_benifit"] / ( self.invest_capacity["ttl_invest"] + 0.001) income_table = income_table.set_index("year") n_years = (income_table["cumsum_benifit"] < 0).sum() if n_years < 1: self.invest_cycle = 0 elif n_years >= len(income_table["cumsum_benifit"]): self.invest_cycle = evaluate_year else: self.invest_cycle = n_years - income_table["cumsum_benifit"][ n_years] / (income_table["year_benifit"][n_years + 1] + 0.0001) return income_table def cal_invest_evaluate(self): rst = {} rst["invest_income"] = self._cal_invest_income() rst["env_benifit_per_year"] = self._cal_env_benifit() return rst def _cal_invest_income(self): rst = {} self.income_table = self._cal_income_table() rst["first_year_income"] = self.income_table["year_benifit"][1] rst["first_year_income_rate"] = self.income_table["year_benifit_ratio"][ 1] rst["first_year_month_income"] = rst["first_year_income"] / 12.0 rst["invest_income_year"] = self.invest_cycle return rst def _cal_env_benifit(self): env_benifit = {} for key in self.env_benifit_param.keys(): if key == "tree": env_benifit[key] = self.invest_capacity["first_year_ttl_kwh"] * \ self.env_benifit_param["CO2"] / ( self.env_benifit_param[ key] + 0.001) elif key == "one_family_kwh": env_benifit[key] = self.invest_capacity[ "first_year_ttl_kwh"] / ( self.env_benifit_param[ key] + 0.00001) else: env_benifit[key] = self.invest_capacity["first_year_ttl_kwh"] * \ self.env_benifit_param[key] return env_benifit def cal_opt_analysis(self): rst = {} rst["peak_clip"] = self._peak_clip() rst["max_demand"] = self._max_demand_control() rst["economic_operation"] = self._economic_operation() return rst def _peak_clip(self): rst = {} df = self.time_1hour_parse() pv_not_null = df[df["pv_curve"] > 0] pv_peak = pv_not_null[pv_not_null["spfv_flag"] >= 2] if pv_peak["pv_curve"].sum() > 0: peak_clip_flag = True peak_clip_kwh = pv_peak["pv_curve"].sum() * 30 else: peak_clip_flag = False peak_clip_kwh = 0.0 rst["peak_clip_flag"] = peak_clip_flag rst["peak_clip_kwh"] = self.fix_decimal_points(peak_clip_kwh, decimal_num=2) opt_curve = df[["load_curve", "pv_curve", "load_pv_curve"]] opt_curve.index.name = "quarter_time" self.opt_curve = opt_curve return rst def _max_demand_control(self): rst = {} old_max_demand = self.opt_curve["load_curve"].max() new_max_demand = self.opt_curve["load_pv_curve"].max() old_new = old_max_demand - new_max_demand try: if self.inline_var["inline_capacity"]: percent_flag = (old_new / self.inline_var[ "inline_capacity"] >= 0.01) else: percent_flag = (old_new >= 10) if percent_flag: max_demand_flag = True max_demand_benifit = old_new * self.price["max_demand"] else: max_demand_flag = False max_demand_benifit = 0.0 except Exception as e: max_demand_flag = False max_demand_benifit = 0.0 rst["max_demand_flag"] = max_demand_flag rst["max_demand_benifit"] = self.fix_decimal_points(max_demand_benifit, decimal_num=2) return rst def _economic_operation(self): rst = {} old_max_load = self.opt_curve["load_curve"].max() new_max_load = self.opt_curve["load_pv_curve"].max() old_new = old_max_load - new_max_load try: if self.inline_var["inline_capacity"]: percent_flag = (old_new / self.inline_var[ "inline_capacity"] >= 0.01) else: percent_flag = (old_new >= 10) if percent_flag: economic_flag = True reduce_peak = old_new reduce_load_factor = new_max_load / ( self.inline_var["inline_capacity"] + 0.001) else: economic_flag = False reduce_peak = 0.0 reduce_load_factor = 0.0 except Exception as e: economic_flag = False reduce_peak = 0.0 reduce_load_factor = 0.0 rst["economic_operation_flag"] = economic_flag rst["reduce_peak"] = self.fix_decimal_points(reduce_peak, decimal_num=2) rst["reduce_load_factor"] = self.fix_decimal_points(reduce_load_factor, decimal_num=4) return rst @staticmethod def fix_decimal_points(v, decimal_num=4): """ 将浮点型的值固定小数位, 默认保留4位小数位 :param v: 浮点型的值 """ rlt = v fmt = "%.df" fmt = fmt.replace('d', str(decimal_num)) if isinstance(v, float): s = fmt % v rlt = float(s) return rlt def fix_output_point(self): for key in self.invest_capacity.keys(): self.invest_capacity[key] = self.fix_decimal_points( self.invest_capacity[key], decimal_num=2) for key_top in self.invest_evaluate.keys(): for key in self.invest_evaluate[key_top].keys(): self.invest_evaluate[key_top][key] = self.fix_decimal_points( self.invest_evaluate[key_top][key], decimal_num=2) def output(self): self.input_param_process() self.invest_capacity = self.cal_invest_capacity() self.invest_evaluate = self.cal_invest_evaluate() self.opt_analysis = self.cal_opt_analysis() self.fix_output_point() def input_param_process(self): if self.pv_system["efficiency"] <= 0: self.pv_system["efficiency"] = 0.1 elif self.pv_system["efficiency"] >= 1: self.pv_system["efficiency"] = 1 if self.pv_system["self_use_ratio"] <= 0: self.pv_system["self_use_ratio"] = 0.0 elif self.pv_system["self_use_ratio"] >= 1: self.pv_system["self_use_ratio"] = 1 if self.price["self_use_price_discout"] <= 0: self.price["self_use_price_discout"] = 0.0 elif self.price["self_use_price_discout"] >= 10: self.price["self_use_price_discout"] = 10 if __name__ == "__main__": pv_system = { "user_type": "工商业", # 建筑类型 "install_space": 1200, # 装机容量m2 "efficiency": 0.8, # 光伏发电效率 "first_year_decay_rate": 2.5, # 首年衰减率 "first10_year_decay_rate": 0.8, # 2-10年衰减率 "other_year_decay_rate": 0.7, # 11-25年衰减率 "evaluate_year": 25, # 评估年限 "self_use_ratio": 0.7, # 自发自用比例 "peak_sunshine_hours": 1347.945 # 年峰值日照小数数 } price = { "rmb_per_wp": 7.5, # 建设单价 "self_use_price_discout": 10.0, # 自发自用电价折扣 "sel_use_per_kwh": None, # 自发自用电价 # "state_subsidy": 0.42, # 国家补贴 "state_subsidy": 0.0, # 国家补贴 "local_subsidy": 0.25, # 地方补贴 "local_subsidy_year": 5, # 地方补贴年限 "first_install_subsidy": 0.0, # 初始安装补贴 "coal_in_grid": 0.43, # 脱硫电价 "max_demand": 32.0, "spfv_price": { "section_p": {"price": 0.8888, "time_range": "14:00-17:00;19:00-22:00"}, "section_f": {"price": 0.5503, "time_range": "8:00-14:00;17:00-19:00;22:00-24:00"}, "section_v": {"price": 0.2900, "time_range": "00:00-8:00"} } # 分时电价及对应时段小时数 } # 节能减排环境效益 env_benifit_param = { "one_family_kwh": 3600, # kwh on year "CO2": 0.822, # 二氧化碳排放kg/kWh "SO2": 0.00039, # 二氧化硫排放kg/kWh "NOx": 0.00036, # 氮氧化合物kg/kWh "Smoke": 0.00008, # 烟尘kg/kWh "Coal": 0.312, # 煤耗kg/kWh "H2O": 0.0040, # 纯净水m3/kWh "tree": 18.3 # =0.822/18.3相当于植树棵/kWh 1棵树1年可吸收18.3千克CO2 } env_benifit_param = { "one_family_kwh": 3600, # kwh on year "CO2": 0.822, # 二氧化碳排放kg/kWh "SO2": 0.00039, # 二氧化硫排放kg/kWh "NOx": 0.00036, # 氮氧化合物kg/kWh "Smoke": 0.00008, # 烟尘kg/kWh "Coal": 0.307, # 煤耗kg/kWh "H2O": 0.0040, # 纯净水m3/kWh "tree": 18.3 # =0.822/18.3相当于植树棵/kWh 1棵树1年可吸收18.3千克CO2 } inline_var = { "inline_capacity": 315, "peak_kwh": 71.724 * 10000, "peak_charge": 71.724 * 10000 * 0.8888, "flat_kwh": 92.302 * 10000, "flat_charge": 92.302 * 10000 * 0.5503 } # 月 stat_time = pd.date_range("2019-01-01", "2019-01-02", freq="1H")[:-1] stat_time1 = pd.date_range("2019-01-01", "2019-01-02", freq="0.25H")[:-1] # 光伏小时出力曲线 pv_curve = pd.Series( [0.00, 0.00, 0.00, 0.00, 0.00, 0.83, 7.24, 18.94, 31.94, 43.13, 63.16, 68.65, 80.03, 76.91, 70.50, 53.72, 34.43, 16.74, 5.01, 0.00, 0.00, 0.00, 0.00, 0.00], index=range(24)) pv_curve = pd.Series( [0, 0, 0, 0, 0, 7.526, 47.126, 126.118, 242.868, 360.416, 457.112, 502.836, 513.488, 486.449, 405.981, 297.523, 175.011, 76.375, 21.156, 0.781, 0, 0, 0, 0], index=range(24)) # 负荷小时出力曲线 load_curve = pd.Series( [3.03, 3.04, 3.03, 3.03, 3.03, 3.03, 3.18, 74.07, 89.27, 145.34, 141.61, 141.78, 143.44, 147.09, 156.70, 129.52, 127.47, 128.37, 95.19, 47.12, 46.36, 45.73, 44.61, 8.64], index=range(24)) load_curve = 0.03 * pd.Series( [2000.0] * 24 + [2050.0, 2100.0, 2150.0, 2200.0] + [2650.0, 3100.0, 3550.0, 4000.0] + [ 4500.0, 5200.0, 6000.0] + [6800.0, 7900.0, 8600.0, 8800.0] + [ 9200.0, 8500.0, 7700.0, 7200.0] + [7000.0, 6900.0, 6800.0, 6700.0] + list( np.arange(6500.0, 7501.0, 125.0)) + list( np.arange(7500.0, 7001.0, -62.5)) + list( np.arange(7000.0, 4001.0, -375.0)) + list( np.arange(4000.0, 2001.0, -83.3))) df_load = {} df_pv = {} df_load["quarter_time"] = stat_time1 df_load["load_curve"] = load_curve df_pv["quarter_time"] = stat_time df_pv["pv_curve"] = pv_curve df_load = pd.DataFrame(df_load) df_pv = pd.DataFrame(df_pv) # df_curve["load_pv_curve"] = df_curve["load_curve"] - df_curve["pv_curve"] obj = PvOptimizationTool(pv_system, price, env_benifit_param, df_load, df_pv, inline_var) obj.output() try: import matplotlib.pyplot as plt obj.opt_curve.plot() plt.show() except Exception: pass print( "**********************************************************************************************************") print("输入参数") print() print( "**********************************************************************************************************") print("接入规模") print( "总装机容量:{capacity}kWp 安装面积:{install_space}m2 总投资:{ttl_invest}元 首年发电量:{first_year_ttl_kwh}kwh".format( **obj.invest_capacity)) print( "**********************************************************************************************************") print("投资与测算") print("建筑类型:{user_type}".format(**obj.pv_system)) print("投资成本:%0.2f元 = %0.2f kWp装机容量 x %0.2f元每瓦成本" % ( obj.invest_capacity["ttl_invest"], obj.invest_capacity["capacity"], (obj.price["rmb_per_wp"] - obj.price["first_install_subsidy"]))) print("安装面积:{install_space}m2".format(**obj.pv_system)) print("辐照参数:{peak_sunshine_hours}kWh/kWp/年".format(**obj.pv_system)) print("首年发电量:{first_year_ttl_kwh}度".format(**obj.invest_capacity)) print("国家补贴:{state_subsidy}元/度".format(**obj.price)) print("地方补贴:{local_subsidy}元/度".format(**obj.price)) print("自发自用电价折扣{self_use_price_discout}折".format(**obj.price)) print( "投资与收益\n建筑类型:工商业\n首年月平均收益:{first_year_month_income}元\n首年总收益:{first_year_income}元\n首年收益率:{first_year_income_rate}\n回本年限:{invest_income_year}\n".format( **obj.invest_evaluate["invest_income"])) print( "**********************************************************************************************************") print( "碳足迹/年\n提供电力:{one_family_kwh}户\n相当植树:{tree}棵\n节省燃煤:{Coal}kg\n减少CO2:{CO2}kg\n减少SO2:{SO2}kg\n减节省水:{H2O}m3\n".format( **obj.invest_evaluate["env_benifit_per_year"])) print( "**********************************************************************************************************") print("优化分析") print("经济运行 存在空间,可降低峰荷{reduce_peak}kW,最高负载率降低为{reduce_load_factor:}".format( **obj.opt_analysis["economic_operation"])) print("需量控制 存在空间,可降低用电成本{max_demand_benifit}元/月".format( **obj.opt_analysis["max_demand"])) print("移峰填谷 可减少峰段电网用电量{peak_clip_kwh}度/月,消纳光伏用电费用需根据用户与光伏电站协商电价确定".format( **obj.opt_analysis["peak_clip"])) print(obj.opt_curve)