Solve the LPNLP B&B with all the variable bounds previously tighten: bounds are read from a file (.hdf) """ from instance import Instance import convexrelaxation as rel import lpnlpbb as bb instance = Instance('Simple_Network', 'Profile_5d_30m_smooth', '01/01/2013 00:00', '02/01/2013 00:00', 2) #instance = Instance('Richmond', 'Profile_5d_30m_smooth', '21/05/2013 07:00', '22/05/2013 07:00', 4) #instance = Instance('Anytown', 'Profile_5d_30m_smooth', '01/01/2013 00:00', '02/01/2013 00:00', 2) print('parse:', instance.name, 'horizon start:', instance.periods[0], 'horizon length:', instance.horizon(), 'timestep:', instance.tsinhours()) print(len(instance.pumps), 'pumps', len(instance.valves), 'valves', len(instance.tanks), 'tanks') print("obbt: parse bounds") try: instance.parse_bounds() except UnicodeDecodeError as err: print(f'obbt bounds not read: {err}') print("create model") cvxmodel = rel.build_model(instance) cvxmodel.write('convrel.lp') print("solve model") #cost, costrelax, time, gap = bb.solveconvex(cvxmodel, instance)
def build_model(inst: Instance, pumpvals={}): """Build the convex relaxation gurobi model.""" LP = gp.Model('Pumping_Schedulding') qvar = {} # arc flow svar = {} # pump/valve activity status ivar = {} # pump ignition status hvar = {} # node head for t in inst.horizon(): for (i, j), pipe in inst.pipes.items(): qvar[(i, j), t] = LP.addVar(lb=pipe.qmin, ub=pipe.qmax, name=f'qp({i},{j},{t})') for (i, j), valve in inst.valves.items(): qvar[(i, j), t] = LP.addVar(lb=valve.qmin, ub=valve.qmax, name=f'qv({i},{j},{t})') svar[(i, j), t] = LP.addVar(vtype=GRB.BINARY, name=f'xv({i},{j},{t})') for (i, j), pump in inst.pumps.items(): ivar[(i, j), t] = LP.addVar(vtype=GRB.BINARY, name=f'ik({i},{j},{t})') addQcost = 3.6 * inst.tsinhours( ) * inst.reservoirs[i].drawcost if i in inst.reservoirs else 0 qvar[(i, j), t] = LP.addVar(lb=0, ub=pump.qmax, obj=addQcost + inst.eleccost(t) * pump.power[1], name=f'qk({i},{j},{t})') svar[(i, j), t] = LP.addVar(obj=inst.eleccost(t) * pump.power[0], vtype=GRB.BINARY, name=f'xk({i},{j},{t})') for j in inst.junctions: hvar[j, t] = LP.addVar(name=f'hj({j},{t})') for j, res in inst.reservoirs.items(): hvar[j, t] = LP.addVar(lb=res.head(t), ub=res.head(t), name=f'hr({j},{t})') for j, tank in inst.tanks.items(): hvar[j, t] = LP.addVar(lb=tank.head(tank.vmin), ub=tank.head(tank.vmax), name=f'ht({j},{t})') for j, tank in inst.tanks.items(): hvar[j, inst.nperiods()] = LP.addVar(lb=tank.head(tank.vinit), ub=tank.head(tank.vmax), name=f'ht({j},T)') hvar[j, 0].lb = tank.head(tank.vinit) hvar[j, 0].ub = tank.head(tank.vinit) LP.update() # FLOW CONSERVATION (JUNCTIONS) for j, junc in inst.junctions.items(): for t in inst.horizon(): # !!! original code: round(demand,2) LP.addConstr( gp.quicksum(qvar[a, t] for a in inst.inarcs(j)) - gp.quicksum(qvar[a, t] for a in inst.outarcs(j)) == junc.demand(t)) # FLOW CONSERVATION (TANKS) for j, tank in inst.tanks.items(): for t in inst.horizon(): LP.addConstr(hvar[j, t + 1] - hvar[j, t] == 3.6 * inst.tsinhours() / tank.surface * (gp.quicksum(qvar[a, t] for a in inst.inarcs(j)) - gp.quicksum(qvar[a, t] for a in inst.outarcs(j)))) # MAX WITHDRAWAL AT RESERVOIRS for j, res in inst.reservoirs.items(): if res.drawmax: LP.addConstr(3.6 * inst.tsinhours() * gp.quicksum( qvar(a, t) for a in inst.outarc[j] for t in inst.horizon()) <= res.drawmax) # VALVES for (i, j), valve in inst.valves.items(): if valve.type == 'GV' or valve.type == 'PRV': for t in inst.horizon(): x = svar[(i, j), t] if (valve.type == 'GV') else (1 - svar[(i, j), t]) LP.addConstr(qvar[(i, j), t] <= valve.qmax * svar[(i, j), t]) LP.addConstr(qvar[(i, j), t] >= valve.qmin * x) LP.addConstr(hvar[i, t] - hvar[j, t] >= valve.hlossmin * (1 - svar[(i, j), t])) LP.addConstr(hvar[i, t] - hvar[j, t] <= valve.hlossmax * (1 - x)) # CONVEXIFICATION OF HEAD-FLOW (PIPES) for (i, j), pipe in inst.pipes.items(): coeff = [pipe.hloss[2], pipe.hloss[1]] cutbelow, cutabove = oa.pipecuts(pipe.qmin, pipe.qmax, coeff, (i, j)) # print(f'{pipe}: {len(cutbelow)} cutbelow, {len(cutabove)} cutabove') for t in inst.horizon(): for c in cutbelow: LP.addConstr( hvar[i, t] - hvar[j, t] >= c[0] * qvar[(i, j), t] + c[1]) for c in cutabove: LP.addConstr( hvar[i, t] - hvar[j, t] <= c[0] * qvar[(i, j), t] + c[1]) # ACTIVITY (PUMPS) for a, pump in inst.pumps.items(): for t in inst.horizon(): # !!! original code: integer round ??? LP.addConstr(qvar[a, t] >= svar[a, t] * pump.qmin) LP.addConstr(qvar[a, t] <= svar[a, t] * pump.qmax) # CONVEXIFICATION OF HEAD-FLOW (PUMPS) for (i, j), pump in inst.pumps.items(): cutbelow, cutabove = oa.pumpcuts(pump.qmin, pump.qmax, pump.hgain, (i, j)) #, drawgraph=True) #cutbelow, cutabove = oa.pumpcutsgratien(pump.qmin, pump.qmax, pump.hgain, (i, j)) #, drawgraph=True) for t in inst.horizon(): for c in cutbelow: LP.addConstr( hvar[j, t] - hvar[i, t] >= c[0] * qvar[(i, j), t] + (c[1] - pump.offdhmin) * svar[(i, j), t] + pump.offdhmin) for n, c in enumerate(cutabove): # !!! original code: gapabove = 0 if pump.offdhmax == 1000 else pump.offdhmax - c[1] ??? LP.addConstr( hvar[j, t] - hvar[i, t] <= c[0] * qvar[(i, j), t] + (c[1] - pump.offdhmax) * svar[(i, j), t] + pump.offdhmax) ### PUMP SWITCHING sympumps = inst.symmetries uniquepumps = inst.pumps_without_sym() print('symmetries:', uniquepumps) def getv(vdict, pump, t): return gp.quicksum(vdict[a, t] for a in sympumps) if pump == 'sym' else vdict[pump, t] # !!! check the max ignition constraint for the symmetric group # !!! make ivar[a,0] = svar[a,0] for a in uniquepumps: rhs = 6 * len(sympumps) if a == 'sym' else 6 - svar[a, 0] LP.addConstr( gp.quicksum(getv(ivar, a, t) for t in range(1, inst.nperiods())) <= rhs) for t in range(1, inst.nperiods()): LP.addConstr( getv(ivar, a, t) >= getv(svar, a, t) - getv(svar, a, t - 1)) if inst.tsduration == dt.timedelta( minutes=30) and t < inst.nperiods() - 1: # minimum 1 hour activity LP.addConstr( getv(svar, a, t + 1) + getv(svar, a, t - 1) >= getv(svar, a, t)) ### PUMP DEPENDENCIES if sympumps: for t in inst.horizon(): for i, pump in enumerate(sympumps[:-1]): LP.addConstr(ivar[pump, t] >= ivar[sympumps[i + 1], t]) LP.addConstr(svar[pump, t] >= svar[sympumps[i + 1], t]) if inst.dependencies: for t in inst.horizon(): for s in inst.dependencies['p1 => p0']: LP.addConstr(svar[s[0], t] >= svar[s[1], t]) for s in inst.dependencies['p0 or p1']: LP.addConstr(svar[s[0], t] + svar[s[1], t] >= 1) for s in inst.dependencies['p0 <=> p1 xor p2']: LP.addConstr(svar[s[0], t] == svar[s[1], t] + svar[s[2], t]) for s in inst.dependencies['p1 => not p0']: LP.addConstr(svar[s[0], t] + svar[s[1], t] <= 1) if pumpvals: for pump in inst.pumps: for t in inst.horizon(): LP.addConstr(svar[pump, t] == pumpvals[pump, t]) LP.ModelSense = GRB.MINIMIZE LP.update() LP._svar = svar LP._ivar = ivar LP._qvar = qvar LP._hvar = hvar LP._obj = LP.getObjective() LP.write('modelnew.lp') return LP