GreenTransPowerCalculate/PV/PV_total3.py

339 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import json
import pandas as pd
import math
from scipy.optimize import fsolve
from shapely.geometry import Point, shape
# 默认文件路径
PV_EXCEL_PATH = r"./pv_product.xlsx" # 光伏组件数值
TILT_EXCEL_PATH = r"./倾角_峰值小时数.xlsx" # 倾角和峰值小时数
GEOJSON_PATH = r"./中国_市.geojson" # GeoJSON 文件
def calculate_pv_metrics(component_name, electricity_price, pv_number, q, longitude, latitude,
is_fixed=True, optimize=True, peak_load_hour=16, cost_per_kw=3.4, E_S=1.0, K=0.8):
"""
计算光伏项目的各项指标包括装机容量、年发电量、等效小时数、环境收益和内部收益率IRR
参数:
component_name (str): 光伏组件名称,例如 "TWMHF-66HD715"
electricity_price (float): 电价(元/kWh
pv_number (int): 光伏组件数量
q (float): 运维成本占初始投资成本的比例(例如 0.02 表示 2%
longitude (float): 经度
latitude (float): 纬度
is_fixed (bool): 是否为固定式支架True 为固定式False 为跟踪式
optimize (bool): 是否优化倾角和方位角
peak_load_hour (int): 峰值负荷小时,默认 16
cost_per_kw (float): 每 kW 投资成本(元/kW默认 3.4 元/kW
E_S (float): 标准辐射量,默认 1.0
K (float): 系统效率,默认 0.8
返回:
dict: 包含以下结果的字典:
- component_name: 组件名称
- tilt: 倾角 (度)
- azimuth: 方位角 (度)
- array_distance: 阵列间距 (米)
- max_power: 单组件最大功率 (Wp)
- capacity: 装机容量 (kW)
- peak_sunshine_hours: 峰值日照小时数 (小时/天)
- single_daily_energy: 一天单个组件发电量 (kWh)
- annual_energy: 年发电量 (kWh)
- equivalent_hours: 等效小时数 (小时)
- coal_reduction: 标准煤减排量 (kg)
- CO2_reduction: CO₂ 减排量 (kg)
- SO2_reduction: SO₂ 减排量 (kg)
- NOX_reduction: NOx 减排量 (kg)
- IRR: 内部收益率 (%)
"""
# 1. 加载 GeoJSON 数据
def load_geojson(file_path):
try:
with open(file_path, 'r', encoding='utf-8') as file:
data = json.load(file)
return data
except FileNotFoundError:
raise FileNotFoundError(f"未找到GeoJSON文件{file_path}")
except Exception as e:
raise Exception(f"加载GeoJSON出错{e}")
# 2. 根据经纬度查找城市
def find_city_by_coordinates(lat, lon, geojson_data):
point = Point(lon, lat)
for feature in geojson_data['features']:
polygon = shape(feature['geometry'])
if polygon.contains(point):
city_name = feature['properties'].get('name', '未知城市')
# 规范化城市名称(去除“市”或“地区”等后缀)
if city_name.endswith('') or city_name.endswith('地区'):
city_name = city_name[:-1]
return city_name
return '未知城市'
# 3. 从 Excel 获取倾角和峰值日照小时数
def get_tilt_and_peak_hours(lat, lon, excel_path=TILT_EXCEL_PATH, geojson_path=GEOJSON_PATH):
try:
# 加载 GeoJSON 数据
geojson_data = load_geojson(geojson_path)
# 查找城市
city = find_city_by_coordinates(lat, lon, geojson_data)
# 读取 Excel 文件
df = pd.read_excel(excel_path)
if len(df.columns) < 5:
raise ValueError("Excel文件需包含至少5列城市、倾角、峰值日照小时数等")
# 匹配城市名称(前两个字)
matched_row = df[df.iloc[:, 2].str.startswith(city, na=False)]
if matched_row.empty:
raise ValueError(f"未找到匹配的城市:{city}")
# 提取并转换为浮点数
tilt = float(matched_row.iloc[0, 3]) # 第四列:倾角
peak_hours = float(matched_row.iloc[0, 4]) # 第五列PSH
return {
"tilt": tilt,
"peak_sunshine_hours": peak_hours
}
except FileNotFoundError:
raise FileNotFoundError(f"未找到Excel文件{excel_path}")
except ValueError as e:
raise ValueError(f"Excel数据转换错误{e}")
except Exception as e:
raise Exception(f"读取Excel或GeoJSON出错{e}")
# 4. 计算倾角和方位角
def get_tilt_and_azimuth(is_fixed=True, optimize=True, longitude=116, latitude=None, peak_load_hour=16):
if optimize and latitude is None:
raise ValueError("优化模式下需提供纬度")
if is_fixed:
if optimize:
# 从 Excel 获取倾角
tilt_data = get_tilt_and_peak_hours(latitude, longitude)
tilt = tilt_data["tilt"]
if not (0 <= tilt <= 90):
raise ValueError(f"Excel中的倾角 {tilt:.2f}° 超出合理范围0°-90°")
azimuth = (peak_load_hour - 12) * 15 + (longitude - 116) # 方位角公式
azimuth = azimuth % 360 if azimuth >= 0 else azimuth + 360
else:
print("倾角0°(水平)-90°(垂直) | 方位角0°(正北)-180°(正南),顺时针")
tilt = float(input("请输入倾角(度)"))
azimuth = float(input("请输入方位角(度)"))
if not (0 <= tilt <= 90) or not (0 <= azimuth <= 360):
raise ValueError("倾角需在0-90°方位角需在0-360°")
else: # 跟踪式
azimuth = 180 # 固定朝南
if optimize:
tilt_data = get_tilt_and_peak_hours(latitude, longitude)
tilt = tilt_data["tilt"]
if not (0 <= tilt <= 90):
raise ValueError(f"Excel中的倾角 {tilt:.2f}° 超出合理范围0°-90°")
else:
print("倾角0°(水平)-90°(垂直)")
tilt = float(input("请输入倾角(度)"))
if not (0 <= tilt <= 90):
raise ValueError("倾角需在0-90°")
return tilt, azimuth
# 5. 获取光伏组件信息
def get_pv_product_info(component_name, excel_path=PV_EXCEL_PATH):
try:
df = pd.read_excel(excel_path)
if len(df.columns) < 10:
raise ValueError("Excel文件需包含至少10列组件名称、尺寸、功率等")
row = df[df.iloc[:, 1] == component_name]
if row.empty:
raise ValueError(f"未找到组件:{component_name}")
return {
"component_name": component_name,
"max_power": row.iloc[0, 5],
"efficiency": row.iloc[0, 9],
"pv_size": row.iloc[0, 3]
}
except FileNotFoundError:
raise FileNotFoundError(f"未找到Excel文件{excel_path}")
except Exception as e:
raise Exception(f"读取Excel出错{e}")
# 6. 计算光伏阵列间距
def calculate_array_distance(L, tilt, latitude):
beta_rad = math.radians(tilt)
phi_rad = math.radians(latitude)
return (L * math.cos(beta_rad) +
L * math.sin(beta_rad) * 0.707 * math.tan(phi_rad) +
0.4338 * math.tan(phi_rad))
# 7. 计算等效小时数
def calculate_equivalent_hours(P, P_r):
if P_r == 0:
raise ValueError("额定功率不能为 0")
h = P / P_r # 单位换算
return h
# 8. 计算装机容量
def calculate_installed_capacity(max_power, num_components):
if max_power < 0 or num_components < 0 or not isinstance(num_components, int):
raise ValueError("功率和数量需为非负数,数量需为整数")
return (max_power * num_components) / 1000 # 单位kW
# 9. 计算年发电量
def calculate_annual_energy(peak_hours, capacity, E_S=E_S, K=K):
if any(x < 0 for x in [peak_hours, capacity]) or E_S <= 0 or not 0 <= K <= 1:
raise ValueError("输入参数需满足辐射量、容量≥0E_S>0K∈[0,1]")
return peak_hours * 365 * (capacity / E_S) * K # 单位kWh
# 10. 计算环境收益
def calculate_environmental_benefits(E_p_million_kwh):
if E_p_million_kwh < 0:
raise ValueError("年发电量需≥0")
return {
"coal_reduction": E_p_million_kwh * 0.404 * 10,
"CO2_reduction": E_p_million_kwh * 0.977 * 10,
"SO2_reduction": E_p_million_kwh * 0.03 * 10,
"NOX_reduction": E_p_million_kwh * 0.015 * 10
}
# 11. 计算净现值和内部收益率
def calculate_reference_yield(E_p, electricity_price, IC, q, n=25):
if E_p < 0 or electricity_price < 0 or IC <= 0 or not 0 <= q <= 1:
raise ValueError("发电量、电价≥0投资成本>0回收比例∈[0,1]")
def npv_equation(irr, p, w, ic, q_val, n=n):
term1 = (1 + irr) ** (-1)
term2 = irr * (1 + irr) ** (-1) if irr != 0 else float('inf')
pv_revenue = p * w * (term1 / term2) * (1 - (1 + irr) ** (-n))
pv_salvage = q_val * ic * (term1 / term2) * (1 - (1 + irr) ** (-n))
return pv_revenue - ic + pv_salvage
irr_guess = 0.1
irr = float(fsolve(npv_equation, irr_guess, args=(E_p, electricity_price, IC, q))[0])
if not 0 <= irr <= 1:
raise ValueError(f"IRR计算结果{irr:.4f}不合理,请检查输入")
npv = npv_equation(irr, E_p, electricity_price, IC, q)
return {"NPV": npv, "IRR": irr * 100}
# 主计算流程
try:
# 验证经纬度
if not (-90 <= latitude <= 90):
raise ValueError("纬度必须在 -90 到 90 之间")
if not (-180 <= longitude <= 180):
raise ValueError("经度必须在 -180 到 180 之间")
# 获取倾角和方位角
tilt, azimuth = get_tilt_and_azimuth(
is_fixed=is_fixed,
optimize=optimize,
longitude=longitude,
latitude=latitude,
peak_load_hour=peak_load_hour
)
# 获取峰值日照小时数
peak_hours = get_tilt_and_peak_hours(latitude, longitude)["peak_sunshine_hours"]
# 获取组件信息
pv_info = get_pv_product_info(component_name)
width_mm = float(pv_info["pv_size"].split("×")[1])
L = (width_mm / 1000) * 4
array_distance = calculate_array_distance(L, tilt, latitude)
# 计算装机容量
max_power = pv_info["max_power"]
capacity = calculate_installed_capacity(max_power, pv_number) # 单位kW
# 计算一天单个组件发电量
single_daily_energy = peak_hours * (capacity / pv_number) * K # 单位kWh
# 计算年发电量
E_p = calculate_annual_energy(peak_hours, capacity, E_S, K) # 单位kWh
# 计算等效小时数
h = calculate_equivalent_hours(E_p, capacity) # P_r 单位为 kWE_p 单位为 kWh
# 计算环境收益(转换为百万 kWh
E_p_million_kwh = E_p / 1000000 # 转换为百万 kWh
env_benefits = calculate_environmental_benefits(E_p_million_kwh)
# 计算初始投资成本
IC = capacity * cost_per_kw * 1000 # 单位:元
# 计算 IRR
ref_yield = calculate_reference_yield(E_p, electricity_price, IC, q)
# 返回结果
return {
"component_name": component_name,
"tilt": tilt,
"azimuth": azimuth,
"array_distance": array_distance,
"max_power": max_power,
"capacity": capacity,
"peak_sunshine_hours": peak_hours,
"single_daily_energy": single_daily_energy,
"annual_energy": E_p,
"equivalent_hours": h,
"coal_reduction": env_benefits["coal_reduction"],
"CO2_reduction": env_benefits["CO2_reduction"],
"SO2_reduction": env_benefits["SO2_reduction"],
"NOX_reduction": env_benefits["NOX_reduction"],
"IRR": ref_yield["IRR"]
}
except Exception as e:
raise Exception(f"计算过程中发生错误: {str(e)}")
# 示例用法
if __name__ == "__main__":
try:
# 输入并验证经纬度
latitude = float(input("请输入纬度(-90 到 90例如 39.9042"))
if not (-90 <= latitude <= 90):
raise ValueError("纬度必须在 -90 到 90 之间")
longitude = float(input("请输入经度(-180 到 180例如 116.4074"))
if not (-180 <= longitude <= 180):
raise ValueError("经度必须在 -180 到 180 之间")
component_name = input("请输入组件名称:")
electricity_price = float(input("请输入电价(元/kWh"))
pv_number = 1200 # 固定组件数量
q = 0.02 # 运维成本占初始投资成本的比例
choice = input("选择光伏支架(固定式/跟踪式):")
optimize = input("是否优化倾角和方位角(是/否):")
# 调用主函数
result = calculate_pv_metrics(
component_name=component_name,
electricity_price=electricity_price,
pv_number=pv_number,
q=q,
is_fixed=(choice == "固定式"),
optimize=(optimize.lower() == ""),
longitude=longitude,
latitude=latitude
)
# 打印结果
print("\n")
print(f"组件名称:{result['component_name']}")
print(f"倾角:{result['tilt']:.2f}° | 方位角:{result['azimuth']:.2f}°")
print(f"阵列间距:{result['array_distance']:.2f}")
print(f"单个组件最大功率Wp{result['max_power']}")
print(f"装机容量:{result['capacity']:.2f} kW")
print(f"峰值日照小时数:{result['peak_sunshine_hours']:.2f} 小时/天")
print(f"一天单个组件发电量:{result['single_daily_energy']:.0f} kWh")
print(f"年发电量:{result['annual_energy']:,.0f} kWh")
print(f"等效小时数:{result['equivalent_hours']:.0f} 小时")
print("环境收益:")
print(f"标准煤减排量:{result['coal_reduction']:,.0f} kg")
print(f"CO₂减排量{result['CO2_reduction']:,.0f} kg")
print(f"SO₂减排量{result['SO2_reduction']:,.0f} kg")
print(f"NOx减排量{result['NOX_reduction']:,.0f} kg")
print(f"内部收益率 IRR{result['IRR']:.2f}%")
except ValueError as e:
print(f"输入错误:{e}")
except Exception as e:
print(f"错误:{e}")