322 lines
14 KiB
Python
322 lines
14 KiB
Python
|
import requests
|
|||
|
import pandas as pd
|
|||
|
import math
|
|||
|
from scipy.optimize import fsolve
|
|||
|
|
|||
|
# 默认文件路径
|
|||
|
PV_EXCEL_PATH = r".\pv_product.xlsx" # 光伏组件数值
|
|||
|
|
|||
|
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 artic_reduction量 (kg)
|
|||
|
- IRR: 内部收益率 (%)
|
|||
|
"""
|
|||
|
|
|||
|
# 1. 计算 PSH(峰值日照小时数)
|
|||
|
def calculate_psh_average(lat, lon, start_year=2010, end_year=2023):
|
|||
|
url = "https://power.larc.nasa.gov/api/temporal/monthly/point"
|
|||
|
params = {
|
|||
|
"parameters": "ALLSKY_SFC_SW_DWN", # 水平面全球辐照(GHI)
|
|||
|
"community": "RE",
|
|||
|
"longitude": lon,
|
|||
|
"latitude": lat,
|
|||
|
"format": "JSON",
|
|||
|
"start": str(start_year),
|
|||
|
"end": str(end_year)
|
|||
|
}
|
|||
|
try:
|
|||
|
response = requests.get(url, params=params)
|
|||
|
response.raise_for_status()
|
|||
|
data = response.json()
|
|||
|
ghi_data = data["properties"]["parameter"]["ALLSKY_SFC_SW_DWN"]
|
|||
|
|
|||
|
# 过滤掉年度平均值(例如 YYYY13)
|
|||
|
ghi_data = {k: v for k, v in ghi_data.items() if not k.endswith("13")}
|
|||
|
|
|||
|
# 创建 DataFrame
|
|||
|
df = pd.DataFrame.from_dict(ghi_data, orient="index", columns=["GHI (kWh/m²/day)"])
|
|||
|
df.index = [f"{k[:4]}-{k[-2:]:02}" for k in df.index]
|
|||
|
|
|||
|
# 计算 PSH(PSH = GHI / 1 kW/m²)
|
|||
|
df["PSH (hours/day)"] = df["GHI (kWh/m²/day)"]
|
|||
|
|
|||
|
# 计算每年的平均 PSH
|
|||
|
df['Year'] = df.index.str[:4]
|
|||
|
annual_avg = df.groupby('Year')['PSH (hours/day)'].mean()
|
|||
|
|
|||
|
# 计算总平均 PSH
|
|||
|
return annual_avg.mean()
|
|||
|
except requests.exceptions.RequestException as e:
|
|||
|
print(f"NASA POWER API Error: {e}")
|
|||
|
return None
|
|||
|
except ValueError as e:
|
|||
|
print(f"Data Error: {e}")
|
|||
|
return None
|
|||
|
|
|||
|
# 2. 计算最佳倾角
|
|||
|
def calculate_optimal_tilt(lat):
|
|||
|
try:
|
|||
|
# 将纬度从角度转换为弧度
|
|||
|
lat_radians = math.radians(lat)
|
|||
|
# 计算最佳倾角:倾角 = 纬度(弧度) × 0.86 + 24
|
|||
|
optimal_tilt = lat_radians * 0.86 + 24
|
|||
|
return optimal_tilt
|
|||
|
except ValueError as e:
|
|||
|
raise Exception(f"Tilt Calculation Error: {e}")
|
|||
|
|
|||
|
# 3. 计算倾角和方位角
|
|||
|
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:
|
|||
|
# 使用公式计算倾角
|
|||
|
tilt = calculate_optimal_tilt(latitude)
|
|||
|
if not (0 <= tilt <= 90):
|
|||
|
raise ValueError(f"计算得到的倾角 {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 = calculate_optimal_tilt(latitude)
|
|||
|
if not (0 <= tilt <= 90):
|
|||
|
raise ValueError(f"计算得到的倾角 {tilt:.2f}° 超出合理范围(0°-90°)")
|
|||
|
else:
|
|||
|
print("倾角:0°(水平)-90°(垂直)")
|
|||
|
tilt = float(input("请输入倾角(度):"))
|
|||
|
if not (0 <= tilt <= 90):
|
|||
|
raise ValueError("倾角需在0-90°")
|
|||
|
return tilt, azimuth
|
|||
|
|
|||
|
# 4. 获取光伏组件信息
|
|||
|
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}")
|
|||
|
|
|||
|
# 5. 计算光伏阵列间距
|
|||
|
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))
|
|||
|
|
|||
|
# 6. 计算等效小时数
|
|||
|
def calculate_equivalent_hours(P, P_r):
|
|||
|
if P_r == 0:
|
|||
|
raise ValueError("额定功率不能为 0")
|
|||
|
h = P / P_r # 单位换算
|
|||
|
return h
|
|||
|
|
|||
|
# 7. 计算装机容量
|
|||
|
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
|
|||
|
|
|||
|
# 8. 计算年发电量
|
|||
|
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("输入参数需满足:辐射量、容量≥0,E_S>0,K∈[0,1]")
|
|||
|
return peak_hours * 365 * (capacity / E_S) * K # 单位:kWh
|
|||
|
|
|||
|
# 9. 计算环境收益
|
|||
|
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
|
|||
|
}
|
|||
|
|
|||
|
# 10. 计算净现值和内部收益率
|
|||
|
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:
|
|||
|
# 获取倾角和方位角
|
|||
|
tilt, azimuth = get_tilt_and_azimuth(
|
|||
|
is_fixed=is_fixed,
|
|||
|
optimize=optimize,
|
|||
|
longitude=longitude,
|
|||
|
latitude=latitude,
|
|||
|
peak_load_hour=peak_load_hour
|
|||
|
)
|
|||
|
|
|||
|
# 获取组件信息
|
|||
|
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
|
|||
|
|
|||
|
# 获取峰值日照小时数(使用 NASA POWER API)
|
|||
|
peak_hours = calculate_psh_average(latitude, longitude)
|
|||
|
|
|||
|
# 计算一天单个组件发电量
|
|||
|
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 单位为 kW,E_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 Exception as e:
|
|||
|
print(f"错误:{e}")
|