def tie_bidirectional_link_p_nom(network, snapshots): if not hasattr(n.links, "reversed"): return ext_rev_links = network.links.loc[(network.links.reversed == True) & ( network.links.p_nom_extendable == True)].index if len(ext_rev_links) == 0: return constraints = { lk: [ [ (1, network.model.link_p_nom[lk.split("-")[0]]), (-1, network.model.link_p_nom[lk]), ], "==", 0.0, ] for lk in ext_rev_links } l_constraint(network.model, "bidirectional_link", constraints, list(ext_rev_links)) network.model.bidirectional_link.pprint()
def prepare_model(): # n = pypsa.Network("../../co2-analysis/results/new/postnetwork_heur125.nc") n = pypsa.Network(snakemake.input[0]) # n = pypsa.Network("postnetwork_heur1.0.nc") # n = get_representative_snapshots(n, N_SNAPSHOTS) n.determine_network_topology() sub = n.sub_networks.at["0", "obj"] sub.calculate_PTDF() sub.calculate_BODF() # Define indices snapshots = n.snapshots buses = sub.buses_i() lines = sub.lines_i() outages = [l for l in lines if "outage" in l] ptdf = pd.DataFrame(sub.PTDF, index=lines, columns=buses) lodf = pd.DataFrame(sub.BODF, index=lines, columns=lines) logger.info("define variables") # define model m = ConcreteModel() # Define Variables # P_pos(i) >= 0 m.P_pos = Var(buses, within=NonNegativeReals) # P_neg(i) >= 0 m.P_neg = Var(buses, within=NonNegativeReals) # p_pos(i,l,t) >= 0 m.p_pos = Var(buses, outages, snapshots, within=NonNegativeReals) # p_neg(i,l,t) >= 0 m.p_neg = Var(buses, outages, snapshots, within=NonNegativeReals) logger.info("define constraints") # Define constraints ########### p_pos(i,k,t) <= P_pos(i) ########## c1 = {(i, k, t): [[(1, m.p_pos[i, k, t]), (-1, m.P_pos[i])], "<=", 0.] for i in buses for k in outages for t in snapshots} l_constraint(m, "UpLimitPos", c1, buses, outages, snapshots) ######### p_neg(i,k,t) <= P_neg(i) ######### c2 = {(i, k, t): [[(1, m.p_neg[i, k, t]), (-1, m.P_neg[i])], "<=", 0.] for i in buses for k in outages for t in snapshots} l_constraint(m, "UpLimitNeg", c2, buses, outages, snapshots) ######## sum(i, p_pos(i,k,t) - p_neg(i,k,t)) = 0 ######### c3 = {(k, t): [[*[(1, m.p_pos[i, k, t]) for i in buses], *[(-1, m.p_neg[i, k, t]) for i in buses]], "==", 0.] for k in outages for t in snapshots} l_constraint(m, "FlexBal", c3, outages, snapshots) ######## sum(i, PTDF(l,i) * (p_neg(i,k) - p_pos(i,k)) <= F(l) + f(l) + LODF(l,k) * f(k) ######## c4 = {(l, k, t): [[*[(-ptdf.at[l, i], m.p_pos[i, k, t]) for i in buses], *[(ptdf.at[l, i], m.p_neg[i, k, t]) for i in buses ]], "<=", n.lines_t.p0.at[t, l] + lodf.at[l, k] * n.lines_t.p0.at[t, k] + n.lines.at[l, "s_nom"]] for l in lines for k in outages for t in snapshots} l_constraint(m, "LineDn", c4, lines, outages, snapshots) # sum(i, PTDF(l,i) * (p_pos(i,k) - p_neg(i,k)) <= F(l) - f(l) - LODF(l,k) * f(k) c5 = {(l, k, t): [[ *[(ptdf.at[l, i], m.p_pos[i, k, t]) for i in buses], *[(-ptdf.at[l, i], m.p_neg[i, k, t]) for i in buses ]], "<=", -n.lines_t.p0.at[t, l] - lodf.at[l, k] * n.lines_t.p0.at[t, k] + n.lines.at[l, "s_nom"]] for l in lines for k in outages for t in snapshots} l_constraint(m, "LineUp", c5, lines, outages, snapshots) logger.info("define objective") objective = LExpression() coefficient1 = 1 coefficient2 = 0.0001 objective.variables.extend([(coefficient1, m.P_pos[i]) for i in buses]) objective.variables.extend([(coefficient1, m.P_neg[i]) for i in buses]) objective.variables.extend([(coefficient2, m.p_pos[i, k, t]) for i in buses for k in outages for t in snapshots]) l_objective(m, objective) return m
def build_model_bb(c, params: Dict, deployment_dict: Dict, coordinates_data, criticality_data, tech_points_tuples): """Model build-up. Parameters: ----------- - c: global criticality threshold - deployment_vector: dictionary indicating for each location and technology how many locations must be selected e.g.: deployment_vector: {"BE": {"wind_onshore": 2, "wind_offshore": 3}, "PT": {"pv_utility": 10}} Returns: ----------- """ from pyomo.opt import SolverFactory from pyomo.environ import ConcreteModel, Var, Binary, maximize from pypsa.opt import l_constraint, LConstraint, l_objective, LExpression # Solver options for the MIP problem opt = SolverFactory(params['solver']) opt.options['MIPGap'] = params['mipgap'] opt.options['Threads'] = params['threads'] opt.options['TimeLimit'] = params['timelimit'] """ Comment: - dict_deployment: same as deployment_vector but not nested - partitions: List of names of partitions - indices: dictionary giving for each 'partition' the indices of the sites that falls into that partition """ deployment_dict = {(key1, key2): deployment_dict[key1][key2] for key1 in deployment_dict for key2 in deployment_dict[key1]} partitions = list(deployment_dict.keys()) # Compute indices indices = dict.fromkeys(deployment_dict.keys()) for region, tech in indices: indices[(region, tech)] = [ tuple(site) for site in coordinates_data[(coordinates_data["Technology Name"] == tech) & (coordinates_data[0] == region)] [["Technology Name", "Longitude", "Latitude"]].to_numpy() ] for item in partitions: if item in indices: if deployment_dict[item] > len(indices[item]): raise ValueError( ' More sites required than available for {}'.format(item)) else: indices[item] = [] print( 'Warning! {} not in keys of choice. Make sure there is no requirement here.' .format(item)) model = ConcreteModel() # - Parameters - # no_windows = len(criticality_data.index) model.W = np.arange(1, no_windows + 1) # - Variables - # model.x = Var(model.W, within=Binary) model.y = Var(tech_points_tuples, within=Binary) # - Constraints - # activation_constraint = {} for w in model.W: lhs = LExpression([(criticality_data[site].iloc[w - 1], model.y[site]) for site in tech_points_tuples]) rhs = LExpression([(c, model.x[w])]) activation_constraint[w] = LConstraint(lhs, ">=", rhs) l_constraint(model, "activation_constraint", activation_constraint, list(model.W)) cardinality_constraint = {} for item in partitions: lhs = LExpression([(1, model.y[site]) for site in indices[item]]) rhs = LExpression(constant=deployment_dict[item]) cardinality_constraint[item] = LConstraint(lhs, "==", rhs) l_constraint(model, "cardinality_constraint", cardinality_constraint, partitions) # - Objective - # objective = LExpression([(1, model.x[w]) for w in model.W]) l_objective(model, objective, sense=maximize) return model
def redo_passive_branch_constraints(network, snapshots): model_components_to_delete = [ "flow_upper", "flow_lower", "flow_upper_index", "flow_lower_index", "flow_upper_index_0", "flow_lower_index_0", "flow_upper_index_1", "flow_lower_index_1", ] for model_component in model_components_to_delete: network.model.del_component(model_component) passive_branches = network.passive_branches() extendable_branches = passive_branches[passive_branches.s_nom_extendable] fixed_branches = passive_branches[~passive_branches.s_nom_extendable] s_max_pu = pd.concat( { c: get_switchable_as_dense(network, c, "s_max_pu", snapshots) for c in network.passive_branch_components }, axis=1, sort=False, ) flow_upper = {(b[0], b[1], sn): [ [ (1, network.model.passive_branch_p[b[0], b[1], sn]), (1, network.model.loss[b[0], b[1], sn]), ], "<=", s_max_pu.at[sn, b] * fixed_branches.at[b, "s_nom"], ] for b in fixed_branches.index for sn in snapshots} flow_upper.update({(b[0], b[1], sn): [ [ (1, network.model.passive_branch_p[b[0], b[1], sn]), (1, network.model.loss[b[0], b[1], sn]), ( -s_max_pu.at[sn, b], network.model.passive_branch_s_nom[b[0], b[1]], ), ], "<=", 0, ] for b in extendable_branches.index for sn in snapshots}) l_constraint(network.model, "flow_upper", flow_upper, list(passive_branches.index), snapshots) flow_lower = {(b[0], b[1], sn): [ [ (1, network.model.passive_branch_p[b[0], b[1], sn]), (-1, network.model.loss[b[0], b[1], sn]), ], ">=", -s_max_pu.at[sn, b] * fixed_branches.at[b, "s_nom"], ] for b in fixed_branches.index for sn in snapshots} flow_lower.update({(b[0], b[1], sn): [ [ (1, network.model.passive_branch_p[b[0], b[1], sn]), (-1, network.model.loss[b[0], b[1], sn]), ( s_max_pu.at[sn, b], network.model.passive_branch_s_nom[b[0], b[1]], ), ], ">=", 0, ] for b in extendable_branches.index for sn in snapshots}) l_constraint(network.model, "flow_lower", flow_lower, list(passive_branches.index), snapshots)
def define_loss_constraints(network, snapshots): tangents = network.tangents positions = range(1, tangents + 1) signs = [-1, 1] passive_branches = network.passive_branches() s_max_pus = get_switchable_as_dense(network, "Line", "s_max_pu") network.model.loss = Var(list(passive_branches.index), snapshots, domain=NonNegativeReals) redo_passive_branch_constraints(network, snapshots) loss_upper = {} loss_tangents = {} for branch in passive_branches.index: bus0 = passive_branches.at[branch, "bus0"] bus1 = passive_branches.at[branch, "bus1"] bt = branch[0] bn = branch[1] r_pu_eff = passive_branches.at[branch, "r_pu_eff"] if passive_branches.at[branch, "s_nom_extendable"]: attr = "s_nom_max" elif passive_branches.at[branch, "s_nom_opt"] != 0.0: attr = "s_nom_opt" else: attr = "s_nom" s_nom_max = passive_branches.at[branch, attr] assert np.isfinite(s_nom_max) and not np.isnan( s_nom_max ), f"Infinite or non-existent 's_nom_max' encountered at line {bn}" for sn in snapshots: s_max_pu = s_max_pus.loc[sn, bn] # adjust kcl # use of ._body because of pyomo bug for bus in [bus0, bus1]: network.model.power_balance[bus, sn]._body -= ( network.model.loss[bt, bn, sn] / 2) # upper loss limit lhs = LExpression( [(1, network.model.loss[bt, bn, sn])], -r_pu_eff * (s_max_pu * s_nom_max)**2, ) loss_upper[bt, bn, sn] = LConstraint(lhs, "<=", LExpression()) # loss tangents for k in positions: p_k = k / tangents * s_max_pu * s_nom_max loss_k = r_pu_eff * p_k**2 slope_k = 2 * r_pu_eff * p_k offset_k = loss_k - slope_k * p_k for sign in signs: lhs = LExpression([(1, network.model.loss[bt, bn, sn])]) rhs = LExpression( [(sign * slope_k, network.model.passive_branch_p[bt, bn, sn])], offset_k, ) loss_tangents[sign, k, bt, bn, sn] = LConstraint(lhs, ">=", rhs) l_constraint(network.model, "loss_upper", loss_upper, list(passive_branches.index), snapshots) l_constraint( network.model, "loss_tangents", loss_tangents, signs, list(positions), list(passive_branches.index), snapshots, )