风电光伏+电池+废弃矿井抽蓄:Python互补调度优化实战

发布时间:2026/10/7 12:14:51
风电光伏+电池+废弃矿井抽蓄:Python互补调度优化实战 先说一个我在实际项目里经常看到的误区很多人一听到风电、光伏配储能第一反应就是多装电池把电存起来。但真正跑过调度模型之后你会发现储能不是配得越多越好关键是储能类型和出力特性的匹配。电池响应快但容量成本高抽水蓄能容量大但响应慢两者如果只是简单并联很多时候反而是浪费。这篇内容我结合自己做过的一个Python调度项目讲讲风电、光伏、电池储能和废弃矿井改造的小型抽水蓄能怎么在一个优化模型里互补运行以及代码层面到底怎么落地。这个方向特别适合正在做新能源消纳、微电网调度、储能容量配置研究的同学参考。项目本身的设定很实际风电场和光伏电站共用一条并网通道配了电池储能和一座由废弃矿井改造的小型抽水蓄能电站目标是在满足负荷需求的前提下让整个系统的运行成本最低、弃风弃光最少。我会把建模思路、约束体系、Python求解代码和经验教训一次性讲清楚。1. 为什么要把电池与废弃矿井抽蓄放在一个调度模型里1.1 互补调度的本质电源出力波动与储能调节能力的匹配先想清楚一个问题风电和光伏为什么要互补调度不是因为它们能互相补出力曲线——风电夜间大发、光伏午间大发严格来说它们是错峰甚至反调的。真正的互补点在于通过储能这个中转站让两者的随机波动在时间轴上被搬移到负荷需要的时候。风电出力的特点是波动幅度大、持续性长一个大风过程可能持续十几个小时光伏出力的特点是规律性强但受天气影响剧烈云天和晴天的出力曲线完全两个样。如果只配电池遇到连续两天大风天气电池容量很快就会被打满剩下的风只能弃掉。如果只配抽蓄遇到午后光伏出力骤降这样的快速变化抽蓄机组从收到指令到满负荷发电可能需要几分钟中间这段功率缺口没人补。这就是电池和抽蓄必须同时存在的原因电池管短时功率波动抽蓄管长时间能量搬移。调度模型里的核心工作就是把这两种时间尺度的调节能力分配到各自最擅长的工作上。1.2 废弃矿井小型抽水蓄能被忽视的存量资源小型抽水蓄能这个词可能有些读者不熟悉简单说一下原理常规抽蓄需要新建上水库和下水库投资大、建设周期长、选址要求高。废弃矿井改造的小型抽蓄本质是把矿井已有的巷道、竖井和采空区利用起来作为上库或下库再配置水泵水轮机组。这个思路的优势在工程上非常明显矿井的巷道系统本身就是现成的储水空间地质结构经过多年开采验证渗漏风险相对可控征地移民成本几乎为零。劣势也明确单机容量小通常一个矿井改造项目的装机也就是几兆瓦到几十兆瓦的级别远比不上常规抽蓄的百万千瓦级。另外矿井巷道的水力条件复杂水头变化范围大机组效率一般比常规抽蓄低几个百分点。放到调度模型里我们需要抓住它的核心特征容量上限由矿井可用空间和上下库水位决定功率上限由水泵水轮机组决定效率取一个综合值通常75%到80%低于常规抽蓄的85%左右还有一条很关键的约束——抽水和发电不能同时进行这在数学上必须用整数变量表达。1.3 电池与抽蓄的特性互补响应速度与容量成本的权衡把两种储能的关键参数放在一张表里看比任何文字描述都直观特性电池储能废弃矿井小型抽蓄响应时间毫秒级到秒级分钟级启动到满出力约25分钟能量容量成本较高且随循环次数衰减低依赖矿井空间使用寿命长循环寿命一般30005000次数万次机械磨损为主单位容量投资约150250万元/MWh约50100万元/MWh改造项目充放电效率85%95%70%80%适用场景调频、快速削峰、短时填谷日循环调峰、连续多日储能从这张表能直接推导出调度策略的底层逻辑电池适宜频繁动作但每次电量别太深抽蓄适合一天一两次的大容量吞吐。建模的时候电池的SOC可以按小时甚至15分钟粒度动态变化抽蓄则更适合设定为固定的日调节模式——夜间抽水蓄能白天顶峰放水。后面我会展示怎么把这些差异写成约束。2. 调度优化问题的数学模型目标函数与约束体系怎么定2.1 目标函数从单一经济性到多目标权衡大多数风电光伏储能调度论文会把目标函数设定为系统运行成本最小或弃风弃光率最小。实际做下来你会发现纯成本目标容易导致模型为了省钱而牺牲消纳纯弃风弃光率目标又可能不计代价地调用储能。我的做法是主目标加惩罚项运行成本最小化弃风弃光以惩罚系数计入目标。运行成本包括三块从主网购电的成本、储能设备的运行维护成本、弃风弃光的惩罚成本。写成数学形式是$$ \min \sum_{t1}^{T} \left( C_{buy,t} P_{buy,t} C_{om}^{bat} (P_{ch,t}P_{dis,t}) C_{om}^{psh} (P_{pump,t}P_{gen,t}) C_{curtail} (P_{wind,t}^{curt}P_{pv,t}^{curt}) \right) $$其中 $P_{buy,t}$ 是t时段从电网购入的功率$P_{ch,t}$、$P_{dis,t}$是电池充放电功率$P_{pump,t}$、$P_{gen,t}$是抽蓄抽水和发电功率$P_{wind,t}^{curt}$、$P_{pv,t}^{curt}$是弃风弃光功率。$C_{buy,t}$是分时电价$C_{om}$是运维成本系数$C_{curtail}$是弃能惩罚系数。这样设定之后模型会自动权衡储能动作太频繁导致运维成本上升模型就会减少不必要的充放电弃风弃光惩罚设置得足够高模型就会优先调用储能消纳。我这里把 $C_{curtail}$ 设为上网电价的1.5倍效果比较合理。2.2 核心约束功率平衡、运行边界与储能动态约束体系是整个模型的主体我按类别梳理一下功率平衡约束是最基本的一条每个时段系统内所有电源和负荷必须相等$$ P_{wind,t} P_{pv,t} P_{dis,t} P_{gen,t} P_{buy,t} P_{load,t} P_{ch,t} P_{pump,t} P_{sell,t} $$这里 $P_{wind,t}$、$P_{pv,t}$ 是实际消纳的风光出力等于预测出力减去弃电量。$P_{sell,t}$ 是向电网卖出的功率如果模型设定不允许倒送电直接把这一项置零即可。电池储能的动态约束包括SOC递推、充放电功率上下限、以及循环次数限制如果用日循环次数约束的话。SOC递推公式是$$ SOC_{t1} SOC_t \eta_{ch} P_{ch,t} \Delta t - \frac{P_{dis,t}}{\eta_{dis}} \Delta t $$$\eta_{ch}$、$\eta_{dis}$ 分别是充放电效率$\Delta t$ 是调度步长我用的1小时。另外要约束SOC首末相等否则模型会把初始能量当作免费资源用掉结果失真。抽水蓄能的建模比电池复杂一些关键是要区分水量和功率两个维度。电量层面上下库的水量动态方程类似SOC功率层面抽水状态和发电状态互斥必须引入二进制变量 $u_{pump,t}$ 和 $u_{gen,t}$$$ 0 \leq P_{pump,t} \leq u_{pump,t} P_{pump}^{max}, \quad 0 \leq P_{gen,t} \leq u_{gen,t} P_{gen}^{max}, \quad u_{pump,t} u_{gen,t} \leq 1 $$最后一条不等式保证了同一时段不能既抽水又发电。很多初学者在第一步建模时漏掉这个互斥约束导致结果里抽蓄在同一时段既当电动机又当发电机白白消耗能量模型还觉得挺划算——这是最典型的建模错误之一。2.3 时间尺度的选择日前调度与日内滚动修正调度时间尺度直接决定模型规模和实用性。我建议分两层第一层是日前调度以1小时为步长优化未来24小时的出力计划。这一步确定电池和抽蓄的大致充放电安排求解速度快模型对输入数据的敏感性可控。第二层是日内滚动修正以15分钟为步长每15分钟重新求解一次窗口4小时。因为超短期风电光伏预测精度比日前预测高一截滚动修正可以及时调整储能出力弥补日前计划的偏差。两层配合下来既保证了计算效率又兼顾了应对出力波动的能力。3. Python代码实现从数据准备到求解器调用3.1 输入数据风光出力序列与负荷曲线的获取与清洗做调度仿真最耗时间的往往不是建模而是数据准备。风电光伏出力数据的来源有三类实测历史数据最理想、气象再分析数据反演、用工具生成模拟序列。没有现成实测数据时我常用pvlib生成光伏出力曲线风功率则根据风速数据套用功率曲线计算。数据拿到手先做三件事缺测值处理、异常值剔除、时间对齐。缺测值我习惯用前后线性插值异常值主要看有没有超过装机容量或深夜出现光伏出力这类明显错误时间对齐是要确保风电、光伏、负荷三条序列的时间戳完全一致时区统一夏令时问题在调度仿真空最容易翻车——我有一次就因为UTC和本地时间没对齐光伏出力整体偏移了一小时调度结果完全没法看。数据清洗完成后我建议把序列归一化到标幺值除以各自装机容量这样模型数值稳定性更好后面改装机容量也不需要重新预处理。3.2 建模工具选型PuLP还是ortools为什么Python里做线性规划求解可选方案不少PuLP、ortools、scipy.optimize.linprog、Pyomo等。我的经验是如果模型规模小几百个变量scipy.optimize.linprog够用零依赖如果模型包含整数变量且希望代码简单直观选PuLP内置CBC求解器如果模型规模大或求解速度要求高选ortools它对HiGHS等多线程求解器支持更好如果要做复杂的建模或多目标分析上Pyomo但学习曲线陡一些我这个项目用了PuLP原因很简单模型里包含二进制变量抽蓄的抽水/发电互斥需要混合整数线性规划求解器而PuLP的语法最接近数学表达式的写法别人读代码也容易懂。3.3 核心代码框架目标函数与约束的落地写法下面是梳理后的核心代码按实际项目的简化版本给出。完整代码包括数据读取、参数设置、模型求解和结果导出这里重点展示决策变量定义和约束实现。import pulp as lp import numpy as np import pandas as pd # 时段数24小时 T 24 # 读取风电、光伏预测出力序列单位MW负荷曲线单位MW wind_forecast np.array([...]) # 长度T pv_forecast np.array([...]) # 长度T load_forecast np.array([...]) # 长度T # 价格参数与储能参数 price_buy np.array([...]) # 分时购电价长度T P_wind_max 50.0 # 风电场装机 P_pv_max 30.0 # 光伏装机 P_load_max 60.0 # 电池参数 E_bat_cap 40.0 # MWh P_bat_max 10.0 # MW eta_ch, eta_dis 0.95, 0.95 soc_min, soc_max 0.1, 0.9 # 抽蓄参数 P_pump_max 8.0 # 抽水功率上限 MW P_gen_max 8.0 # 发电功率上限 MW E_psh_cap 100.0 # 等效能量容量 MWh eta_pump, eta_gen 0.78, 0.78 soc_psh_min, soc_psh_max 0.15, 0.95 # 创建问题 prob lp.LpProblem(Wind_PV_Storage_Dispatch, lp.LpMinimize) # 决策变量 wind_use lp.LpVariable.dicts(wind_use, range(T), 0, P_wind_max) pv_use lp.LpVariable.dicts(pv_use, range(T), 0, P_pv_max) wind_curt lp.LpVariable.dicts(wind_curt, range(T), 0, P_wind_max) pv_curt lp.LpVariable.dicts(pv_curt, range(T), 0, P_pv_max) buy_power lp.LpVariable.dicts(buy_power, range(T), 0, P_load_max) # 电池变量 ch_power lp.LpVariable.dicts(ch_power, range(T), 0, P_bat_max) dis_power lp.LpVariable.dicts(dis_power, range(T), 0, P_bat_max) soc_bat lp.LpVariable.dicts(soc_bat, range(T), soc_min, soc_max) u_ch lp.LpVariable.dicts(u_ch, range(T), catBinary) u_dis lp.LpVariable.dicts(u_dis, range(T), catBinary) # 抽蓄变量 pump_power lp.LpVariable.dicts(pump_power, range(T), 0, P_pump_max) gen_power lp.LpVariable.dicts(gen_power, range(T), 0, P_gen_max) soc_psh lp.LpVariable.dicts(soc_psh, range(T), soc_psh_min, soc_psh_max) u_pump lp.LpVariable.dicts(u_pump, range(T), catBinary) u_gen lp.LpVariable.dicts(u_gen, range(T), catBinary) # 目标函数购电成本 储能运维成本 弃能惩罚 C_buy 0.8 # 单位购电成本均价后面用分时序列替换 C_om_bat 0.02 # 电池运维 元/kWh 折算系数 C_om_psh 0.005 # 抽蓄运维 C_curtail 1.5 # 弃能惩罚系数高于上网电价 prob ( lp.lpSum(price_buy[t] * buy_power[t] for t in range(T)) lp.lpSum(C_om_bat * (ch_power[t] dis_power[t]) for t in range(T)) lp.lpSum(C_om_psh * (pump_power[t] gen_power[t]) for t in range(T)) lp.lpSum(C_curtail * (wind_curt[t] pv_curt[t]) for t in range(T)) ) # 约束1功率平衡假设不允许倒送电 for t in range(T): prob ( wind_use[t] pv_use[t] dis_power[t] gen_power[t] buy_power[t] load_forecast[t] ch_power[t] pump_power[t] ) # 约束2风光消纳与预测上限 for t in range(T): prob wind_use[t] wind_curt[t] wind_forecast[t] prob pv_use[t] pv_curt[t] pv_forecast[t] # 约束3电池SOC递推与充放电互斥 prob soc_bat[0] 0.5 * E_bat_cap for t in range(1, T): prob soc_bat[t] soc_bat[t-1] eta_ch * ch_power[t-1] * 1 - dis_power[t-1] / eta_dis # 首末SOC相等约束 prob soc_bat[T-1] 0.5 * E_bat_cap for t in range(T): prob ch_power[t] u_ch[t] * P_bat_max prob dis_power[t] u_dis[t] * P_bat_max prob u_ch[t] u_dis[t] 1 prob soc_bat[t] soc_min * E_bat_cap prob soc_bat[t] soc_max * E_bat_cap # 约束4抽蓄水量/能量递推、抽发互斥、状态约束 prob soc_psh[0] 0.5 * E_psh_cap for t in range(1, T): prob soc_psh[t] soc_psh[t-1] eta_pump * pump_power[t-1] * 1 - gen_power[t-1] / eta_gen prob soc_psh[T-1] 0.5 * E_psh_cap for t in range(T): prob pump_power[t] u_pump[t] * P_pump_max prob gen_power[t] u_gen[t] * P_gen_max prob u_pump[t] u_gen[t] 1 prob soc_psh[t] soc_psh_min * E_psh_cap prob soc_psh[t] soc_psh_max * E_psh_cap # 求解 solver lp.PULP_CBC_CMD(msgTrue, timeLimit60) status prob.solve(solver) # 结果输出 print(Status:, lp.LpStatus[status]) result_columns {} for t in range(T): result_columns[fwind_use_{t}] wind_use[t].varValue # ... 同理收集其他变量这段代码跑通以后输出结果直接转成DataFrame下一步就是画图分析。3.4 结果可视化调度曲线的绘制与评价指标我习惯用matplotlib画三张图第一张是风光储联合出力与负荷曲线的堆叠图第二张是电池SOC和抽蓄SOC的对比曲线第三张是弃风弃光电量的柱状图。前两张能直观看出调度策略是否合理第三张用于评估消纳效果。评价指标主要算三个弃电率弃风弃光总量占可用资源总量的比例、储能利用率实际吞吐量占额定吞吐量的比例、系统运行成本。这些指标整理成表格方便对比不同参数配置下的效果。4. 典型场景实测调度结果解读与储能配合分析4.1 典型夏季日场景光伏主导下的储能充放电策略先看一个夏季典型日的场景光伏预测出力中午前后达到峰值约28MW负荷呈现早高峰和晚高峰两个峰值风电出力平稳偏低约15MW左右分时电价为峰谷两段制。求解结果呈现清晰的光伏充电池、电池补晚峰的模式上午时段光伏出力大于负荷电池开始充电SOC从50%逐步爬升到午后光伏出力回落后电池停止充电晚高峰时段负荷攀升电池放电支撑一直持续到负荷下降。抽蓄在这个场景里只在凌晨低谷时段抽水傍晚高峰发一次电充放电各一次符合日循环调峰的典型特征。这个结果验证了电池和抽蓄的分工逻辑电池在午间到晚间这段几小时的跨度内完成一次充放循环响应频率高抽蓄则抓住凌晨和傍晚两个时间点做的是大容量慢节奏的搬移。4.2 冬季大风场景风电高发期的弃风控制再换一个冬季场景风电连续大风夜间出力达到40MW以上同期负荷偏低凌晨3点到6点只有25MW左右光伏出力几乎为零。这种工况是最考验系统调节能力的。模型给出的策略是这样夜间风电大发、负荷低谷时电池和抽蓄同时充电吸收多余风电把弃风压到很低的水平。但电池容量40MWh、最大充电功率10MW吸收能力有限真正承担主要吸纳任务的是抽蓄它从凌晨1点开始抽水一直持续到早上7点累计吸收了约48MWh的弃风电量。到白天负荷上升后抽蓄分时段放水发电把夜间的风电搬移到白天使用。这组结果说明一个很现实的问题如果只配电池冬季大风的连续多日场景几乎必然出现大量弃风因为电池的容量和功率都不足以覆盖整夜的风电盈余。抽蓄虽然响应慢但长时间大容量的特性让它成为风电消纳的主力。4.3 电池与抽蓄的协同动作谁负责调峰谁负责填谷做完多个场景的统计之后我把两类储能的实际动作规律总结如下时段特征电池动作抽蓄动作协同逻辑光伏大发午间快速充电SOC上升静默或低功率抽水电池快速响应光伏波动晚高峰负荷上升放电支撑12小时放电支撑整个晚峰电池补瞬时缺口抽蓄顶持续高峰凌晨风电大发充电至SOC上限后停持续抽水数小时电池快速填满后抽蓄接力出力骤变云层遮挡快速调整充放电保持不变电池负责秒级到分钟级平衡两条核心结论第一电池的SOC波动幅度远大于抽蓄说明它在承担削峰的精细调节任务第二抽蓄的单日充放电量通常是电池的2到3倍承担的是填谷的大容量任务。这个协同规律在不同季节不同天气场景下都稳定成立。5. 避坑实录建模与求解中容易翻车的细节5.1 抽蓄电站的连续变量陷阱为什么必须引入整数状态我做第一版模型的时候偷过懒想着抽蓄的抽水功率和发电功率都是连续变量不加互斥约束靠目标函数自动调节。结果跑出来一看抽蓄同一时段既在抽水又在发电两股功率在模型里互相抵消凭空消耗了能量目标函数反而优化得更好了——因为这两项功率都没带来收益但也没增加成本模型就随意填上了。这个问题的本质是没有物理约束的数学优化一定会走极端。必须在模型里显式加入二进制变量强制抽水状态和发电状态互斥。代码里就是那一条prob u_pump[t] u_gen[t] 1再加上功率上限约束分别乘以对应的状态变量prob pump_power[t] u_pump[t] * P_pump_max prob gen_power[t] u_gen[t] * P_gen_max这样就能保证抽蓄在同一时刻要么抽水、要么发电、要么停机三选一。这个坑特别容易踩因为电池储能也有类似的充放电互斥问题但电池的互斥约束在很多简化模型里可以省略——充电和放电同时发生意味着净功率是两者之差如果效率相同结果不会错但抽蓄的抽水和发电涉及两个完全不同的物理过程效率不同且水流方向决然相反不排除就必然出错。5.2 SOC初值、终值约束与滚动调度的衔接储能SOC的初值和终值设置是第二个高频翻车点。最常见的问题是初值设成0模型把储能当成免费的能量源第一天疯狂放电之后每天都充不满放不尽结果曲线失真。解决办法是加首末SOC相等的约束。在日前调度里我强制终值SOC不低于初始值这样储能只起搬移当天电量的作用不会凭空产生能量。在日内滚动调度里问题更隐蔽一些每个滚动周期的初始SOC应该取上一周期最后一个时段的求解结果而不是固定值。如果每次都重置为50%储能的实际运行状态就会和日前计划脱节。我建议在代码里维护一个状态变量字典滚动求解时把上一轮的SOC结果传给下一轮。这个衔接逻辑虽然只差几行代码但直接影响调度结果的真实性。5.3 求解器遇到不可行解时怎么快速定位约束冲突MILP模型跑出infeasible不可行是家常便饭。新手第一反应是加放宽条件我的做法是先系统排查第一步检查数据单位是否统一。功率用MW、能量用MWh、效率无量纲这三者混用是常识性错误。我踩过最典型的坑是把光伏出力单位从kW当成MW用导致功率平衡约束直接差了一个数量级怎么调都是不可行。第二步检查初始条件和递推关系是否矛盾。如果SOC递推公式里充电效率放在乘的地方放电效率放在除的地方效率数值填错了递推出来的SOC就会漂移进而触碰SOC上下限约束。第三步用逐步松弛法定位冲突约束。我会先把SOC上下限放宽到0到1再看有没有解有了解之后逐步收紧每收紧一个约束就重新求解直到找到真正导致不可行的约束。这个过程虽然土但在MILP模型里比任何调试器都有效。6. 扩展方向与我的实操体会6.1 从确定性到随机优化场景法与鲁棒优化的区别上面介绍的是确定性优化——风电光伏出力取预测值。实际运行中预测误差必然存在所以学术研究和工程应用更常用随机优化。场景法是对预测误差的概率分布进行采样生成多个出力场景然后做一个两阶段优化第一阶段决定储能的充放电计划第二阶段在不同场景下做功率平衡调整。鲁棒优化则反过来找一个在最差出力场景下仍然可行的调度方案。代码层面场景法可以用scipy.stats生成误差样本然后把单场景目标函数扩展为多场景加权求和。模型规模会成倍增长求解时间也随之上升但换来的决策稳健性值得这个代价。6.2 加入碳交易或辅助服务市场如果想要让模型更贴近实际市场环境可以进一步引入碳交易成本或辅助服务收益。碳交易思路简单——给化石能源购电加一个碳价系数购电成本变成电量成本加碳成本模型会自动减少电网购电、增加储能放电。辅助服务市场更复杂需要把调频、备用等产品的容量和价格纳入优化这要求模型从单时段功率平衡扩展到多时段联合优化代码规模会上一个台阶。6.3 网格化精细建模抽蓄的水力约束最后的扩展方向是抽蓄本身的精细建模。我上面的模型用的是等效能量容量隐含假设是抽蓄的水头恒定、效率恒定。实际矿井抽蓄的水头变化范围大效率随水头变化更精确的做法是把水库水位、流量、功率三者耦合起来建模——水位差决定当前效率流量决定功率水量平衡方程连接水位变化。这需要在模型里增加一组非线性约束通常要线性化或分段逼近复杂度明显上升。我个人的实践体会是这个项目里最有价值的不是某个模型多精妙而是电池加抽蓄这种互补配置思路。纯电池方案在长周期消纳场景下几乎必然力不从心纯抽蓄方案在快速调节场景下又不够灵活两者的配合是储能调度研究里非常实用的方向。如果要从头做这个课题我建议先把确定性模型跑通理解两种储能的动作规律再逐步加随机因素和市场机制。最后提一个小技巧目标函数中的弃电惩罚系数最好做几组敏感性分析看它对调度结果的影响这个系数设置是否合理直接决定模型偏向多花钱买消纳还是宁可弃电保经济。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询