本文目的:求解上一期所构建的模型,得到发电商在两个市场的均衡策略,在文献或相关资料中,通过代码来实现所构建的模型大多没有细节部分,本文通过笔者的实践,尽量描述清楚这一过程的细节。
注:本文会使用gurobi求解器,gurobi基本的建模方式本文不在陈述,具体可参考Python|Gurobi——零基础学优化建模-NLPPython|Gurobi——零基础学优化建模,另外本文的代码通过python语言编写。
一、合约市场的代码实现
按照之前的分析,合约市场的算法逻辑如下图

从上图分析可知,对于某一个发电商,是在固定其对手的策略下,求解其最优的策略,因此对每一个发电商来讲都为一个小的优化问题,这可通过Gurobi建模求解。进一步,迭代的每一轮中,每一个发电商最优问题的求解都可以在上一轮已经求解出的最优策略的基础上进行,直到迭代满足收敛的标准,即最终迭代出的最优策略会稳定在某一水平,偏差较小。
那么可以为每个发电商的优化模型定义为一个包含优化模型的函数,然后在主程序中不断的调用这个函数,直到迭代满足收敛要求。比如发电商1的优化模型如下:
import gurobipy as gpfrom gurobipy import GRBg_number = 5 #发电商的数量a_gen = [30, 20, 30, 40, 40] #发电商的成本参数b_gen = [0.03, 0.02, 0.03, 0.04, 0.15] #发电商的成本参数A = 20000 #需求函数的参数AB = 0.01 #需求函数的参数B#发电商1的gurobi优化模型def gen1(kgm2,kgm3,kgm4,kgm5,Eqg, Ep_spot): #kgm2等为其余发电商的策略参数,# Eqg为预期的发电商日前市场出清电量,为一个list ,包含5个发电商的数据,Ep_spot为日前市场出清的平均电价。model = gp.Model('foward')model.params.NonConvex = 2 # 模型为非线性规划模型,参数设置为2,具体见gurobi参考手册kgm1 = model.addVar(lb=1,ub=5,vtype=GRB.CONTINUOUS, name="kgm1")r = model.addVar(vtype=GRB.CONTINUOUS, name="r") #出清的合约电价qgc1 = model.addVar(vtype=GRB.CONTINUOUS, name="qgc1") #发电商1出清的合约电量z = model.addVar(vtype=GRB.CONTINUOUS, name="z") #定义额辅助变量y = model.addVar(vtype=GRB.CONTINUOUS, name="y") #定义的辅助变量con_z = model.addConstr(z == (1/(kgm1*b_gen[0])+1/(kgm2*b_gen[1])+1/(kgm3*b_gen[2])+1/(kgm4*b_gen[3])+1/(kgm5*b_gen[4])),name="con_z") #辅助变量约束con_y = model.addConstr(y == gp.quicksum(a_gen[number]/b_gen[number] for number in range(g_number)),name="con_y") #辅助变量约束con_price = model.addConstr(r*(B+z) == (A+y),name="con_price") # 均衡的合约价格约束con_qgc1 = model.addConstr(r == kgm1*(a_gen[0]+b_gen[0]*qgc1),name="con_qgc1") #qgc1约束g1 = r*qgc1 + (Eqg[0]-qgc1)*Ep_spot - 0.5*b_gen[0]*qgc1*qgc1 - a_gen[0]*qgc1 #发电商1的目标函数var = model.getVars()kgm1 = var[0].x #提取计算的结果for v in Var:print(f"{v.varName}:{round(v.x,3)}")#打印变量的结果return kgm1 #返回发电商1的最优策略
其余发电商的优化模型代码同理可写,如下:
def gen2(kgm1,kgm3,kgm4,kgm5,Eqg, Ep_spot):.......return kgm2
def gen3(kgm1,kgm2,kgm4,kgm5,Eqg, Ep_spot):.......return kgm3
def gen4(kgm1,kgm2,kgm3,kgm5,Eqg, Ep_spot):.......return kgm4
def gen5(kgm1,kgm2,kgm3,kgm4,Eqg, Ep_spot):.......return kgm5
在写出5个发电商的优化模型函数之后,就开始写寻找合约市场均衡的代码,程序是不断通过迭代去收敛的,所以写一个for循环,代码如下:
for d in range(1,500):#发电商策略迭代都是在上一轮迭代的基础上进行的,同时赋所有发电商的初值为1kgm1[d] = forward_1.gen1(kgm2[d-1],kgm3[d-1],kgm4[d-1],kgm5[d-1],Eqg,Ep_spot)kgm2[d] = forward_1.gen2(kgm1[d-1],kgm3[d-1],kgm4[d-1],kgm5[d-1],Eqg,Ep_spot)kgm3[d] = forward_1.gen3(kgm1[d-1],kgm2[d-1],kgm4[d-1],kgm5[d-1],Eqg,Ep_spot)kgm4[d] = forward_1.gen4(kgm1[d-1],kgm2[d-1],kgm3[d-1],kgm5[d-1],Eqg,Ep_spot)kgm5[d] = forward_1.gen5(kgm1[d-1],kgm2[d-1],kgm3[d-1],kgm4[d-1],Eqg,Ep_spot)if abs(kgm1[d]-kgm1[d-1])<0.01 and abs(kgm2[d]-kgm2[d-1])<0.0.01 and abs(kgm3[d]-kgm3[d-1])<0.01 and abs(kgm4[d]-kgm4[d-1])<0.01 and abs(kgm5[d]-kgm5[d-1])<0.01:break #当满足迭代条件时跳出循环
注:代码中forward_1为模块名,因为笔者把所有发电商的函数都写在一个模块里面,所以在另外的主程序中需要使用模块名进行调用。运行程序后迭代几次就能找到均衡的策略。
二、日前市场的代码实现
日前市场的算法逻辑如下:

按照同样的思路,也可以为每个发电商的优化模型写一个函数,比如发电商1的优化模型代码如下:
import numpy as npimport gurobipy as gpfrom gurobipy import GRBn_period = [0.5, 0.3, 0.2]#在每个时段合约电量分配的比例max_gen = [3800, 6000, 5000, 5500, 3000]#发电商的最大出力M = 10000000 #松弛约束线性化处理后的设置的大M辅助变量def gen1(kgt2,kgt3,kgt4,kgt5,r,qgc1,qm,demand_scene):result = [] #定义一个空列表,用于装计算的结果model = gp.Model('gen1')model.params.NonConvex = 2kgt1 = model.addVars(period, lb=1, ub=5,vtype=GRB.CONTINUOUS, name="kgt1")#发电商1的策略变量p_spot = model.addVars(period, lb=0, vtype=GRB.CONTINUOUS, name="p_spot")#日前市场的出清电价qgt = model.addVars(g_number, period, lb=0,vtype=GRB.CONTINUOUS, name="qgt")#日前市场每个发电商的出清电量ugt = model.addVars(g_number, period, lb=0, vtype=GRB.CONTINUOUS,name="ugt")# 出力上限约束额朗格朗日乘子z = model.addVars(g_number, period,vtype=GRB.BINARY,name="z") 松弛约束线性化处理而设置的0,1变量#相应约束条件con_kgt1 = model.addConstrs(kgt1[j]*(a_gen[0]+b_gen[0]*qgt[0,j]) == p_spot[j] - ugt[0,j] for j in range(period))con_kgt2 = model.addConstrs(kgt2[j]*(a_gen[1]+b_gen[1]*qgt[1,j]) == p_spot[j] - ugt[1,j] for j in range(period))con_kgt3 = model.addConstrs(kgt3[j]*(a_gen[2]+b_gen[2]*qgt[2,j]) == p_spot[j] - ugt[2,j] for j in range(period))con_kgt4 = model.addConstrs(kgt4[j]*(a_gen[3]+b_gen[3]*qgt[3,j]) == p_spot[j] - ugt[3,j] for j in range(period))con_kgt5 = model.addConstrs(kgt5[j]*(a_gen[4]+b_gen[4]*qgt[4,j]) == p_spot[j] - ugt[4,j] for j in range(period))con_demand = model.addConstrs(qgt.sum('*',j) == n_period[j]*qm+demand_scene[j] for j in range(period))con_qgt_1 = model.addConstrs(qgt[i,j] <= max_gen[i] for i in range(g_number) for j in range(period))con_ugt = model.addConstrs(ugt[i,j] <= M*z[i,j] for i in range(g_number) for j in range(period))con_qgt_2 = model.addConstrs(max_gen[i] - qgt[i,j] <= M*(1-z[i,j]) for i in range(g_number) for j in range(period))#发电商1的目标函数g1 = (sum(p_spot[j]*qgt[0,j] + n_period[j]*qgc1*(r-p_spot[j]) for j in range(period)) - a_gen[0]*(qgt[0,0]+qgt[0,1]+qgt[0,2]) -0.5*b_gen[0]*(qgt[0,0]+qgt[0,1]+qgt[0,2])**2)model.setObjective(g1, GRB.MAXIMIZE)model.optimize()for v in model.getVars(): #打印出结果print(f"{v.varName}:{round(v.x,3)}")var = model.getVars() #提取出清电量result.append(var[0].x)result.append(var[1].x)result.append(var[2].x)return np.array(result)
注:(1)本程序对松弛约束进行了线性化处理,即上期模型(12)中的松弛约束变为:0<=Ugt<=M*Zgt和 0<=Max_q - qgt<=M*(1-Zgt);(2)qm为总的合约市场的出清电量,由合约模型优化后每个发电商的出清电量加总即可;(3)demand_scene为上期模型(12)中的随机变量,在主程序中设置一定的分布自动生成。(4)模型最后返回一个np.array的结果是为了主程序好计算。
同理可写出每个发电商在日前市场的优化模型函数,同样通过for循环写出均衡结果的迭代程序。如下:
#赋初值kgt1[0] = np.array([1,1,1])kgt2[0] = np.array([1,1,1])kgt3[0] = np.array([1,1,1])kgt4[0] = np.array([1,1,1])kgt5[0] = np.array([1,1,1])e0 = np.array([0.01,0.01,0.01])for d in range(1,500):kgt1[d] = spot_1.gen1(kgt2[d-1],kgt3[d-1],kgt4[d-1],kgt5[d-1],r,qgc1,qm,demand_scene)kgt2[d] = spot_1.gen2(kgt1[d-1],kgt3[d-1],kgt4[d-1],kgt5[d-1],r,qgc2,qm,demand_scene)kgt3[d] = spot_1.gen3(kgt1[d-1],kgt2[d-1],kgt4[d-1],kgt5[d-1],r,qgc3,qm,demand_scene)kgt4[d] = spot_1.gen4(kgt1[d-1],kgt2[d-1],kgt3[d-1],kgt5[d-1],r,qgc4,qm,demand_scene)kgt5[d] = spot_1.gen5(kgt1[d-1],kgt2[d-1],kgt3[d-1],kgt4[d-1],r,qgc5,qm,demand_scene)if d % 2 == 0:e1[d] = abs(kgt1[d]-kgt1[d-2])e2[d] = abs(kgt2[d]-kgt2[d-2])e3[d] = abs(kgt3[d]-kgt3[d-2])e4[d] = abs(kgt4[d]-kgt4[d-2])e5[d] = abs(kgt5[d]-kgt5[d-2])if np.all(e1[d] < e0) and np.all(e2[d] < e0) and np.all(e3[d] < e0) and np.all(e4[d] < e0) and np.all(e5[d] < e0):break
注:(1)由于每个发电商的策略为三个时段的投标组成的,所以把初值设置为array结构;(2)从模型的运行结果来看,存在两个均衡,所以笔者取其给发电商最大利润的均衡,即选择了偶数轮的结果,当然也可以设置程序使得自身可以判断;(3)np.all函数为numpy库里的函数,含义为使得数列里每一个数都小于对应数列的数 即可返回真;(4)模块spot_1同样为了书写日前市场的相关程序而设定的。
三、主程序实现两个市场的均衡
总的算法流程图如下:

为方便主程序的书写,同样可以把寻找合约市场和日前市场的均衡封装在一个函数里面,便于调用,即定义forward_clean和spot_clean
def forward_clean(Eqg,Ep_spot):for d in range(1,500):kgm1[d] = forward_1.gen1(kgm2[d-1],kgm3[d-1],kgm4[d-1],kgm5[d-1],Eqg,Ep_spot)kgm2[d] = forward_1.gen2(kgm1[d-1],kgm3[d-1],kgm4[d-1],kgm5[d-1],Eqg,Ep_spot)kgm3[d] = forward_1.gen3(kgm1[d-1],kgm2[d-1],kgm4[d-1],kgm5[d-1],Eqg,Ep_spot)kgm4[d] = forward_1.gen4(kgm1[d-1],kgm2[d-1],kgm3[d-1],kgm5[d-1],Eqg,Ep_spot)kgm5[d] = forward_1.gen5(kgm1[d-1],kgm2[d-1],kgm3[d-1],kgm4[d-1],Eqg,Ep_spot)if abs(kgm1[d]-kgm1[d-1])<0.2 and abs(kgm2[d]-kgm2[d-1])<0.2 and abs(kgm3[d]-kgm3[d-1])<0.2 and abs(kgm4[d]-kgm4[d-1])<0.2 and abs(kgm5[d]-kgm5[d-1])<0.2:............breakreturn r,qgc1,qgc2,qgc3,qgc4,qgc5,qm
def spot_clean(r,qgc1,qgc2,qgc3,qgc4,qgc5,qm):for d in range(1,500):kgt1[d] = spot_1.gen1(kgt2[d-1],kgt3[d-1],kgt4[d-1],kgt5[d-1],r,qgc1,qm,demand_scene)kgt2[d] = spot_1.gen2(kgt1[d-1],kgt3[d-1],kgt4[d-1],kgt5[d-1],r,qgc2,qm,demand_scene)kgt3[d] = spot_1.gen3(kgt1[d-1],kgt2[d-1],kgt4[d-1],kgt5[d-1],r,qgc3,qm,demand_scene)kgt4[d] = spot_1.gen4(kgt1[d-1],kgt2[d-1],kgt3[d-1],kgt5[d-1],r,qgc4,qm,demand_scene)kgt5[d] = spot_1.gen5(kgt1[d-1],kgt2[d-1],kgt3[d-1],kgt4[d-1],r,qgc5,qm,demand_scene)if d % 2 == 0:e1[d] = abs(kgt1[d]-kgt1[d-2])e2[d] = abs(kgt2[d]-kgt2[d-2])e3[d] = abs(kgt3[d]-kgt3[d-2])e4[d] = abs(kgt4[d]-kgt4[d-2])e5[d] = abs(kgt5[d]-kgt5[d-2])if np.all(e1[d] < e0) and np.all(e2[d] < e0) and np.all(e3[d] < e0) and np.all(e4[d] < e0) and np.all(e5[d] < e0):.........return Eqg,Ep_spot #均衡的出清电量额电价的平均水平
注:省略号省略的是模型收敛后计算的相应的需要返回的结果,日前市场收敛后,得到的是每个发电商的均衡投标策略,因此可在pot_1中再写一个ISO的出清函数得到出清电价和每个发电商的出清电量,或者将均衡的策略随便带入某一个发电商的日前优化模型函数,本文采用的是另写一个ISO模型,即:
def ISO(kgt1,kgt2,kgt3,kgt4,kgt5,qm,demand_scene):result_q = {}p_spot={}model = gp.Model('ISO')model.params.NonConvex = 2qgt = model.addVars(g_number, period, vtype=GRB.CONTINUOUS, name="qgt")con_demand = model.addConstrs(qgt.sum('*',j) == n_period[j]*qm+demand_scene[j] for j in range(period))con_qgt = model.addConstrs(qgt[i,j] <= max_gen[i] for i in range(g_number) for j in range(period))#设置目标函数g1 = gp.quicksum(kgt1[j]*qgt[0,j]*(a_gen[0]+0.5*b_gen[0]*qgt[0,j]) for j in range(period))g2 = gp.quicksum(kgt2[j]*qgt[1,j]*(a_gen[1]+0.5*b_gen[1]*qgt[1,j]) for j in range(period))g3 = gp.quicksum(kgt3[j]*qgt[2,j]*(a_gen[2]+0.5*b_gen[2]*qgt[2,j]) for j in range(period))g4 = gp.quicksum(kgt4[j]*qgt[3,j]*(a_gen[3]+0.5*b_gen[3]*qgt[3,j]) for j in range(period))g5 = gp.quicksum(kgt5[j]*qgt[4,j]*(a_gen[4]+0.5*b_gen[4]*qgt[4,j]) for j in range(period))model.setObjective(g1+g2+g3+g4+g5, GRB.MINIMIZE)model.optimize()for v in model.getVars(): #打印出结果print(f"{v.varName}:{round(v.x,3)}")var = model.getVars() #提取出清电量for v in range(15):i = v//3j = v%3result_q[i,j] = round(var[v].x,3)for i in range(5): #计算出清电价if result_q[i,0] < max_gen[i] and result_q[i,1] < max_gen[i] and result_q[i,2] < max_gen[i]:for j in range(3):if i == 0:p_spot[j] = kgt1[j]*(a_gen[0]+b_gen[0]*result_q[i,j])if i == 1:p_spot[j] = kgt2[j]*(a_gen[1]+b_gen[1]*result_q[i,j])if i == 2:p_spot[j] = kgt3[j]*(a_gen[2]+b_gen[2]*result_q[i,j])if i == 3:p_spot[j] = kgt4[j]*(a_gen[3]+b_gen[3]*result_q[i,j])if i == 4:p_spot[j] = kgt5[j]*(a_gen[4]+b_gen[4]*result_q[i,j])return result_q, p_spot
另外,本文的demand_scene为一个随机变量,在主程序中,设置其三个时段的值都服从均匀分布,然后设置多少种场景,就运行多少次日前市场的出清模型spot_clean,最后计算出预期的Eqg,Ep_spot。
demand_scene1 = random.uniform(1000, 2000)demand_scene2 = random.uniform(500, 1000)demand_scene3 = random.uniform(-200,200)demand_scene = [demand_scene1,demand_scene2,demand_scene3]




