def declare_eq_branch_dva(model, index_set, branches): """ Create the equality constraints for the angle difference in the branch """ m = model con_set = decl.declare_set("_con_eq_branch_dva_set", model, index_set) m.eq_dva_branch = pe.Constraint(con_set) for branch_name in con_set: branch = branches[branch_name] from_bus = branch['from_bus'] to_bus = branch['to_bus'] shift = 0.0 if branch['branch_type'] == 'transformer': shift = math.radians(branch['transformer_phase_shift']) m.eq_dva_branch[branch_name] = \ m.dva[branch_name] == \ m.va[from_bus] - m.va[to_bus] + shift
def declare_ineq_p_branch_thermal_lbub_switch(model, index_set, p_thermal_limits): """ Create the inequality constraints for the branch thermal limits based on the power variables or expressions. """ m = model con_set = decl.declare_set('_con_ineq_p_branch_thermal_lbub', model=model, index_set=index_set) m.ineq_pf_branch_thermal_lb = pe.Constraint(con_set) m.ineq_pf_branch_thermal_ub = pe.Constraint(con_set) for branch_name in con_set: if p_thermal_limits[branch_name] is None: continue m.ineq_pf_branch_thermal_lb[branch_name] = \ -p_thermal_limits[branch_name]*m.w[branch_name] <= m.pf[branch_name] m.ineq_pf_branch_thermal_ub[branch_name] = \ m.pf[branch_name] <= p_thermal_limits[branch_name]*m.w[branch_name]
def declare_eq_branch_power_dc_approx( model, index_set, branches, approximation_type=ApproximationType.BTHETA): """ Create the equality constraints for power (from DC approximation) in the branch """ assert (approximation_type == ApproximationType.BTHETA and "Only the B-Theta approximation has been implemented.") m = model con_set = decl.declare_set("_con_eq_branch_power_dc_approx_set", model, index_set) m.eq_pf_branch = pe.Constraint(con_set) for branch_name in con_set: branch = branches[branch_name] if not branch['in_service']: continue from_bus = branch['from_bus'] to_bus = branch['to_bus'] x = branch['reactance'] tau = 1.0 shift = 0.0 if branch['branch_type'] == 'transformer': tau = branch['transformer_tap_ratio'] shift = math.radians(branch['transformer_phase_shift']) b = 1 / (tau * x) m.eq_pf_branch[branch_name] = \ m.pf[branch_name] == \ b * (m.va[from_bus] - m.va[to_bus] - shift)
def declare_ineq_vm_bus_lbub(model, index_set, buses, coordinate_type=CoordinateType.POLAR): """ Create the inequalities for the voltage magnitudes from the voltage variables """ m = model con_set = decl.declare_set('_con_ineq_vm_bus_lbub', model=model, index_set=index_set) m.ineq_vm_bus_lb = pe.Constraint(con_set) m.ineq_vm_bus_ub = pe.Constraint(con_set) if coordinate_type == CoordinateType.POLAR: for bus_name in con_set: m.ineq_vm_bus_lb[bus_name] = \ buses[bus_name]['v_min'] <= m.vm[bus_name] m.ineq_vm_bus_ub[bus_name] = \ m.vm[bus_name] <= buses[bus_name]['v_max'] elif coordinate_type == CoordinateType.RECTANGULAR: for bus_name in con_set: m.ineq_vm_bus_lb[bus_name] = \ buses[bus_name]['v_min']**2 <= m.vr[bus_name]**2 + m.vj[bus_name]**2 m.ineq_vm_bus_ub[bus_name] = \ m.vr[bus_name]**2 + m.vj[bus_name]**2 <= buses[bus_name]['v_max']**2
def declare_expression_pg_operating_cost(model, index_set, p_costs, pw_formulation='delta'): """ Create the Expression objects to represent the operating costs for the real power of each of the generators. """ m = model expr_set = decl.declare_set('_expr_pg_operating_cost', model=model, index_set=index_set) m.pg_operating_cost = pe.Expression(expr_set) for gen_name in expr_set: if gen_name in p_costs: if p_costs[gen_name]['cost_curve_type'] == 'polynomial': m.pg_operating_cost[gen_name] = sum(v*m.pg[gen_name]**i for i, v in p_costs[gen_name]['values'].items()) elif p_costs[gen_name]['cost_curve_type'] == 'piecewise': if pw_formulation == 'delta': p_min = m.pg[gen_name].lb p_max = m.pg[gen_name].ub curve = p_costs[gen_name] cleaned_values = tx_utils.validate_and_clean_cost_curve(curve=curve, curve_type='cost_curve', p_min=p_min, p_max=p_max, gen_name=gen_name) expr = cleaned_values[0][1] if len(cleaned_values) > 1: for ndx, ((o1, c1), (o2, c2)) in enumerate(zip(cleaned_values, cleaned_values[1:])): slope = (c2 - c1) / (o2 - o1) expr += slope * m.delta_pg[gen_name, ndx] m.pg_operating_cost[gen_name] = expr else: m.pg_operating_cost[gen_name] = m.pg_cost[gen_name] else: raise ValueError(f"Unrecognized cost_cureve_type: {p_costs[gen_name]['cost_curve_type']}") else: m.pg_operating_cost[gen_name] = 0
def declare_ineq_p_branch_thermal_lbub(model, index_set, branches, p_thermal_limits, approximation_type=ApproximationType.BTHETA): """ Create the inequality constraints for the branch thermal limits based on the power variables. """ m = model con_set = decl.declare_set('_con_ineq_p_branch_thermal_lbub', model=model, index_set=index_set) m.ineq_pf_branch_thermal_lb = pe.Constraint(con_set) m.ineq_pf_branch_thermal_ub = pe.Constraint(con_set) if approximation_type == ApproximationType.BTHETA: for branch_name in con_set: if p_thermal_limits[branch_name] is None: continue m.ineq_pf_branch_thermal_lb[branch_name] = \ -p_thermal_limits[branch_name] <= m.pf[branch_name] m.ineq_pf_branch_thermal_ub[branch_name] = \ m.pf[branch_name] <= p_thermal_limits[branch_name]
def declare_ineq_angle_diff_branch_lbub_c_s(model, index_set, branches): """ Create the inequality constraints for the angle difference bounds between interconnected buses. """ m = model con_set = decl.declare_set('_con_ineq_angle_diff_branch_lbub', model=model, index_set=index_set) m.ineq_angle_diff_branch_lb = pe.Constraint(con_set) m.ineq_angle_diff_branch_ub = pe.Constraint(con_set) for branch_name in con_set: from_bus = branches[branch_name]['from_bus'] to_bus = branches[branch_name]['to_bus'] m.ineq_angle_diff_branch_lb[branch_name] = ( math.tan(math.radians(branches[branch_name]['angle_diff_min'])) * m.c[(from_bus, to_bus)] <= m.s[(from_bus, to_bus)]) m.ineq_angle_diff_branch_ub[branch_name] = ( m.s[(from_bus, to_bus)] <= math.tan(math.radians(branches[branch_name]['angle_diff_max'])) * m.c[(from_bus, to_bus)])
def declare_eq_branch_power_btheta_approx( model, index_set, branches, approximation_type=ApproximationType.BTHETA): """ Create the equality constraints for power (from BTHETA approximation) in the branch """ m = model con_set = decl.declare_set("_con_eq_branch_power_btheta_approx_set", model, index_set) m.eq_pf_branch = pe.Constraint(con_set) for branch_name in con_set: branch = branches[branch_name] from_bus = branch['from_bus'] to_bus = branch['to_bus'] tau = 1.0 shift = 0.0 if branch['branch_type'] == 'transformer': tau = branch['transformer_tap_ratio'] shift = math.radians(branch['transformer_phase_shift']) if approximation_type == ApproximationType.BTHETA: x = branch['reactance'] b = -1 / (tau * x) elif approximation_type == ApproximationType.BTHETA_LOSSES: b = tx_calc.calculate_susceptance(branch) / tau m.eq_pf_branch[branch_name] = \ m.pf[branch_name] == \ b * (m.va[from_bus] - m.va[to_bus] + shift)
def declare_ineq_angle_diff_branch_lbub(model, index_set, branches, coordinate_type=CoordinateType.POLAR): """ Create the inequality constraints for the angle difference bounds between interconnected buses. """ m = model con_set = decl.declare_set('_con_ineq_angle_diff_branch_lbub', model=model, index_set=index_set) m.ineq_angle_diff_branch_lb = pe.Constraint(con_set) m.ineq_angle_diff_branch_ub = pe.Constraint(con_set) if coordinate_type == CoordinateType.POLAR: for branch_name in con_set: from_bus = branches[branch_name]['from_bus'] to_bus = branches[branch_name]['to_bus'] m.ineq_angle_diff_branch_lb[branch_name] = \ math.radians(branches[branch_name]['angle_diff_min']) <= m.va[from_bus] - m.va[to_bus] m.ineq_angle_diff_branch_ub[branch_name] = \ m.va[from_bus] - m.va[to_bus] <= math.radians(branches[branch_name]['angle_diff_max']) elif coordinate_type == CoordinateType.RECTANGULAR: for branch_name in con_set: from_bus = branches[branch_name]['from_bus'] to_bus = branches[branch_name]['to_bus'] m.ineq_angle_diff_branch_lb[branch_name] = \ math.radians(branches[branch_name]['angle_diff_min']) <= pe.atan(m.vj[from_bus]/m.vr[from_bus]) \ - pe.atan(m.vj[to_bus]/m.vr[to_bus]) m.ineq_angle_diff_branch_ub[branch_name] = \ pe.atan(m.vj[from_bus] / m.vr[from_bus]) \ - pe.atan(m.vj[to_bus] / m.vr[to_bus]) <= math.radians(branches[branch_name]['angle_diff_max'])
def declare_eq_branch_power(model, index_set, branches): """ Create the equality constraints for the real and reactive power in the branch """ m = model con_set = decl.declare_set("_con_eq_branch_power_set", model, index_set) m.eq_pf_branch = pe.Constraint(con_set) m.eq_pt_branch = pe.Constraint(con_set) m.eq_qf_branch = pe.Constraint(con_set) m.eq_qt_branch = pe.Constraint(con_set) for branch_name in con_set: branch = branches[branch_name] from_bus = branch['from_bus'] to_bus = branch['to_bus'] vmsq_from_bus = m.vmsq[from_bus] vmsq_to_bus = m.vmsq[to_bus] g = tx_calc.calculate_conductance(branch) b = tx_calc.calculate_susceptance(branch) bc = branch['charging_susceptance'] tau = 1.0 shift = 0.0 if branch['branch_type'] == 'transformer': tau = branch['transformer_tap_ratio'] shift = math.radians(branch['transformer_phase_shift']) g11 = g / tau**2 g12 = g * math.cos(shift) / tau g21 = g * math.sin(shift) / tau g22 = g b11 = (b + bc / 2) / tau**2 b12 = b * math.cos(shift) / tau b21 = b * math.sin(shift) / tau b22 = b + bc / 2 m.eq_pf_branch[branch_name] = \ m.pf[branch_name] == \ g11 * vmsq_from_bus - \ (g12 * m.c[(from_bus,to_bus)] + g21 * m.s[(from_bus,to_bus)] + b12 * m.s[(from_bus,to_bus)] - b21 * m.c[(from_bus,to_bus)]) m.eq_pt_branch[branch_name] = \ m.pt[branch_name] == \ g22 * vmsq_to_bus - \ (g12 * m.c[(from_bus,to_bus)] + g21 * m.s[(from_bus,to_bus)] - b12 * m.s[(from_bus,to_bus)] + b21 * m.c[(from_bus,to_bus)]) m.eq_qf_branch[branch_name] = \ m.qf[branch_name] == \ -b11 * vmsq_from_bus + \ (b12 * m.c[(from_bus,to_bus)] + b21 * m.s[(from_bus,to_bus)] - g12 * m.s[(from_bus,to_bus)] + g21 * m.c[(from_bus,to_bus)]) m.eq_qt_branch[branch_name] = \ m.qt[branch_name] == \ -b22 * vmsq_to_bus + \ (b12 * m.c[(from_bus,to_bus)] + b21 * m.s[(from_bus,to_bus)] + g12 * m.s[(from_bus,to_bus)] - g21 * m.c[(from_bus,to_bus)])
def declare_eq_branch_power(model, index_set, branches, branch_attrs, coordinate_type=CoordinateType.POLAR): """ Create the equality constraints for the real and reactive power in the branch """ m = model bus_pairs = zip_items(branch_attrs['from_bus'], branch_attrs['to_bus']) unique_bus_pairs = list(set([val for idx, val in bus_pairs.items()])) declare_expr_c(model, unique_bus_pairs, coordinate_type) declare_expr_s(model, unique_bus_pairs, coordinate_type) con_set = decl.declare_set("_con_eq_branch_power_set", model, index_set) m.eq_pf_branch = pe.Constraint(con_set) m.eq_pt_branch = pe.Constraint(con_set) m.eq_qf_branch = pe.Constraint(con_set) m.eq_qt_branch = pe.Constraint(con_set) for branch_name in con_set: branch = branches[branch_name] if not branch['in_service']: continue from_bus = branch['from_bus'] to_bus = branch['to_bus'] if coordinate_type == CoordinateType.POLAR: vmsq_from_bus = m.vm[from_bus]**2 vmsq_to_bus = m.vm[to_bus]**2 elif coordinate_type == CoordinateType.RECTANGULAR: vmsq_from_bus = m.vr[from_bus]**2 + m.vj[from_bus]**2 vmsq_to_bus = m.vr[to_bus]**2 + m.vj[to_bus]**2 g = tx_calc.calculate_conductance(branch) b = tx_calc.calculate_susceptance(branch) bc = branch['charging_susceptance'] tau = 1.0 shift = 0.0 if branch['branch_type'] == 'transformer': tau = branch['transformer_tap_ratio'] shift = math.radians(branch['transformer_phase_shift']) g11 = g / tau**2 g12 = g * math.cos(shift) / tau g21 = g * math.sin(shift) / tau g22 = g b11 = (b + bc / 2) / tau**2 b12 = b * math.cos(shift) / tau b21 = b * math.sin(shift) / tau b22 = b + bc / 2 m.eq_pf_branch[branch_name] = \ m.pf[branch_name] == \ g11 * vmsq_from_bus - \ (g12 * m.c[(from_bus,to_bus)] + g21 * m.s[(from_bus,to_bus)] + b12 * m.s[(from_bus,to_bus)] - b21 * m.c[(from_bus,to_bus)]) m.eq_pt_branch[branch_name] = \ m.pt[branch_name] == \ g22 * vmsq_to_bus - \ (g12 * m.c[(from_bus,to_bus)] + g21 * m.s[(from_bus,to_bus)] - b12 * m.s[(from_bus,to_bus)] + b21 * m.c[(from_bus,to_bus)]) m.eq_qf_branch[branch_name] = \ m.qf[branch_name] == \ -b11 * vmsq_from_bus + \ (b12 * m.c[(from_bus,to_bus)] + b21 * m.s[(from_bus,to_bus)] - g12 * m.s[(from_bus,to_bus)] + g21 * m.c[(from_bus,to_bus)]) m.eq_qt_branch[branch_name] = \ m.qt[branch_name] == \ -b22 * vmsq_to_bus + \ (b12 * m.c[(from_bus,to_bus)] + b21 * m.s[(from_bus,to_bus)] + g12 * m.s[(from_bus,to_bus)] - g21 * m.c[(from_bus,to_bus)])
def declare_eq_branch_current(model, index_set, branches, coordinate_type=CoordinateType.RECTANGULAR): """ Create the equality constraints for the real and imaginary current in the branch """ assert (coordinate_type != CoordinateType.POLAR and "Branch current in polar coordinates not implemented.") m = model con_set = decl.declare_set("_con_eq_branch_current_set", model, index_set) m.eq_ifr_branch = pe.Constraint(con_set) m.eq_ifj_branch = pe.Constraint(con_set) m.eq_itr_branch = pe.Constraint(con_set) m.eq_itj_branch = pe.Constraint(con_set) for branch_name in con_set: branch = branches[branch_name] if not branch['in_service']: continue from_bus = branch['from_bus'] to_bus = branch['to_bus'] g = tx_calc.calculate_conductance(branch) b = tx_calc.calculate_susceptance(branch) bc = branch['charging_susceptance'] tau = 1.0 shift = 0.0 if branch['branch_type'] == 'transformer': tau = branch['transformer_tap_ratio'] shift = math.radians(branch['transformer_phase_shift']) g11 = g / tau**2 g12 = (g * math.cos(shift) - b * math.sin(shift)) / tau g21 = (g * math.cos(shift) + b * math.sin(shift)) / tau g22 = g b11 = (b + bc / 2) / tau**2 b12 = (b * math.cos(shift) + g * math.sin(shift)) / tau b21 = (b * math.cos(shift) - g * math.sin(shift)) / tau b22 = b + bc / 2 m.eq_ifr_branch[branch_name] = \ m.ifr[branch_name] == \ g11 * m.vr[from_bus] - g12 * m.vr[to_bus] - (b11 * m.vj[from_bus] - b12 * m.vj[to_bus]) m.eq_ifj_branch[branch_name] = \ m.ifj[branch_name] == \ g11 * m.vj[from_bus] - g12 * m.vj[to_bus] + (b11 * m.vr[from_bus] - b12 * m.vr[to_bus]) m.eq_itr_branch[branch_name] = \ m.itr[branch_name] == \ -(g21 * m.vr[from_bus] - g22 * m.vr[to_bus] - (b21 * m.vj[from_bus] - b22 * m.vj[to_bus])) m.eq_itj_branch[branch_name] = \ m.itj[branch_name] == \ -(g21 * m.vj[from_bus] - g22 * m.vj[to_bus] + (b21 * m.vr[from_bus] - b22 * m.vr[to_bus]))
def create_cold_start_lpac_model(model_data, cosine_segment_count=20, lower_bound=-pi / 3, upper_bound=pi / 3, include_feasibility_slack=False, mode="uniform"): """ The cold start LPAC model assumes that no target voltages are available and that all voltages are initially approximated as 1 pu. """ ###Grid data md = model_data.clone_in_service() tx_utils.scale_ModelData_to_pu(md, inplace=True) gens = dict(md.elements(element_type='generator')) buses = dict(md.elements(element_type='bus')) branches = dict(md.elements(element_type='branch')) loads = dict(md.elements(element_type='load')) shunts = dict(md.elements(element_type='shunt')) gen_attrs = md.attributes(element_type='generator') bus_attrs = md.attributes(element_type='bus') branch_attrs = md.attributes(element_type='branch') inlet_branches_by_bus, outlet_branches_by_bus = \ tx_utils.inlet_outlet_branches_by_bus(branches, buses) gens_by_bus = tx_utils.gens_by_bus(buses, gens) model = pe.ConcreteModel() ### declare the polar voltages libbus.declare_var_va(model, bus_attrs['names'], initialize=bus_attrs['va']) libbus.declare_var_vmsq( model=model, index_set=bus_attrs['names'], initialize={k: v**2 for k, v in bus_attrs['vm'].items()}, bounds=zip_items({k: v**2 for k, v in bus_attrs['v_min'].items()}, {k: v**2 for k, v in bus_attrs['v_max'].items()})) ### declare the voltage change variables decl.declare_var('phi', model, bus_attrs['names']) ### declare the cosine approximation variables cos_hat_bounds = {k: (0, 1) for k in branch_attrs['names']} decl.declare_var('cos_hat', model, branch_attrs['names'], bounds=cos_hat_bounds) ### fix the reference bus ref_bus = md.data['system']['reference_bus'] #ref_angle = md.data['system']['reference_bus_angle'] model.va[ref_bus].fix(radians(0.0)) ### declare (and fix) the loads at the buses bus_p_loads, bus_q_loads = tx_utils.dict_of_bus_loads(buses, loads) libbus.declare_var_pl(model, bus_attrs['names'], initialize=bus_p_loads) libbus.declare_var_ql(model, bus_attrs['names'], initialize=bus_q_loads) model.pl.fix() model.ql.fix() ### declare the fixed shunts at the buses bus_bs_fixed_shunts, bus_gs_fixed_shunts = tx_utils.dict_of_bus_fixed_shunts( buses, shunts) ### include the feasibility slack for the bus balances p_rhs_kwargs = {} q_rhs_kwargs = {} if include_feasibility_slack: p_rhs_kwargs, q_rhs_kwargs, penalty_expr = _include_feasibility_slack( model, bus_attrs, gen_attrs, bus_p_loads, bus_q_loads) ### declare the generator real and reactive power pg_init = { k: (gen_attrs['p_min'][k] + gen_attrs['p_max'][k]) / 2.0 for k in gen_attrs['pg'] } libgen.declare_var_pg(model, gen_attrs['names'], initialize=pg_init, bounds=zip_items(gen_attrs['p_min'], gen_attrs['p_max'])) qg_init = { k: (gen_attrs['q_min'][k] + gen_attrs['q_max'][k]) / 2.0 for k in gen_attrs['qg'] } libgen.declare_var_qg(model, gen_attrs['names'], initialize=qg_init, bounds=zip_items(gen_attrs['q_min'], gen_attrs['q_max'])) ### declare the current flows in the branches vr_init = { k: bus_attrs['vm'][k] * pe.cos(bus_attrs['va'][k]) for k in bus_attrs['vm'] } vj_init = { k: bus_attrs['vm'][k] * pe.sin(bus_attrs['va'][k]) for k in bus_attrs['vm'] } s_max = {k: branches[k]['rating_long_term'] for k in branches.keys()} s_lbub = dict() for k in branches.keys(): if s_max[k] is None: s_lbub[k] = (None, None) else: s_lbub[k] = (-s_max[k], s_max[k]) pf_bounds = s_lbub pt_bounds = s_lbub qf_bounds = s_lbub qt_bounds = s_lbub pf_init = dict() pt_init = dict() qf_init = dict() qt_init = dict() for branch_name, branch in branches.items(): from_bus = branch['from_bus'] to_bus = branch['to_bus'] y_matrix = tx_calc.calculate_y_matrix_from_branch(branch) ifr_init = tx_calc.calculate_ifr(vr_init[from_bus], vj_init[from_bus], vr_init[to_bus], vj_init[to_bus], y_matrix) ifj_init = tx_calc.calculate_ifj(vr_init[from_bus], vj_init[from_bus], vr_init[to_bus], vj_init[to_bus], y_matrix) itr_init = tx_calc.calculate_itr(vr_init[from_bus], vj_init[from_bus], vr_init[to_bus], vj_init[to_bus], y_matrix) itj_init = tx_calc.calculate_itj(vr_init[from_bus], vj_init[from_bus], vr_init[to_bus], vj_init[to_bus], y_matrix) pf_init[branch_name] = tx_calc.calculate_p(ifr_init, ifj_init, vr_init[from_bus], vj_init[from_bus]) pt_init[branch_name] = tx_calc.calculate_p(itr_init, itj_init, vr_init[to_bus], vj_init[to_bus]) qf_init[branch_name] = tx_calc.calculate_q(ifr_init, ifj_init, vr_init[from_bus], vj_init[from_bus]) qt_init[branch_name] = tx_calc.calculate_q(itr_init, itj_init, vr_init[to_bus], vj_init[to_bus]) libbranch.declare_var_pf(model=model, index_set=branch_attrs['names'], initialize=pf_init, bounds=pf_bounds) libbranch.declare_var_pt(model=model, index_set=branch_attrs['names'], initialize=pt_init, bounds=pt_bounds) libbranch.declare_var_qf(model=model, index_set=branch_attrs['names'], initialize=qf_init, bounds=qf_bounds) libbranch.declare_var_qt(model=model, index_set=branch_attrs['names'], initialize=qt_init, bounds=qt_bounds) ################################ #Constraints ################################ ### Balance equations at a bus (based on Kirchhoff Current Law) #Should be able to just use DC OPF approximation of B-theta type? ### declare the p balance libbus.declare_eq_p_balance(model=model, index_set=bus_attrs['names'], bus_p_loads=bus_p_loads, gens_by_bus=gens_by_bus, bus_gs_fixed_shunts=bus_gs_fixed_shunts, inlet_branches_by_bus=inlet_branches_by_bus, outlet_branches_by_bus=outlet_branches_by_bus, approximation_type=ApproximationType.BTHETA, **p_rhs_kwargs) #Need one also for q balance libbus.declare_eq_q_balance(model=model, index_set=bus_attrs['names'], bus_q_loads=bus_q_loads, gens_by_bus=gens_by_bus, bus_bs_fixed_shunts=bus_bs_fixed_shunts, inlet_branches_by_bus=inlet_branches_by_bus, outlet_branches_by_bus=outlet_branches_by_bus, **q_rhs_kwargs) ### Constraints for power in a branch branch_con_set = decl.declare_set('_con_eq_p_q_lpac_branch_power', model, branch_attrs['names']) model.eq_pf_branch_t = pe.Constraint(branch_con_set) model.eq_pt_branch_t = pe.Constraint(branch_con_set) model.eq_qf_branch_t = pe.Constraint(branch_con_set) model.eq_qt_branch_t = pe.Constraint(branch_con_set) for branch_name in branch_con_set: branch = branches[branch_name] from_bus = branch['from_bus'] to_bus = branch['to_bus'] g = tx_calc.calculate_conductance(branch) b = tx_calc.calculate_susceptance(branch) model.eq_pf_branch_t[branch_name] = \ model.pf[branch_name] == \ g - g * model.cos_hat[branch_name] - b * (model.va[from_bus] - model.va[to_bus]) model.eq_pt_branch_t[branch_name] = \ model.pt[branch_name] == \ g - g * model.cos_hat[branch_name] - b * (model.va[to_bus] - model.va[from_bus]) model.eq_qf_branch_t[branch_name] = \ model.qf[branch_name] == \ -b - g*(model.va[from_bus] - model.va[to_bus]) + b*model.cos_hat[branch_name] - b*(model.phi[from_bus] - model.phi[to_bus]) model.eq_qt_branch_t[branch_name] = \ model.qt[branch_name] == \ -b - g*(model.va[to_bus] - model.va[from_bus]) +b*model.cos_hat[branch_name] - b*(model.phi[to_bus] - model.phi[from_bus]) ### Piecewise linear cosine constraints model.N = pe.Set(initialize=list(range(cosine_segment_count + 1))) declare_pwl_cosine_bounds(model=model, index_set=branch_attrs['names'], branches=branches, lower_bound=lower_bound, upper_bound=upper_bound, cosine_segment_count=cosine_segment_count, mode=mode) ### Objective is to maximize cosine hat variables # obj_expr = sum(model.cos_hat[branch_name] for branch_name in branch_attrs['names']) # if include_feasibility_slack: # obj_expr += penalty_expr # model.obj = pe.Objective(expr=obj_expr) ###Objective to match with acopf.py ### declare the generator cost objective libgen.declare_expression_pgqg_operating_cost(model=model, index_set=gen_attrs['names'], p_costs=gen_attrs['p_cost'], q_costs=gen_attrs.get( 'q_cost', None)) obj_expr = sum(model.pg_operating_cost[gen_name] for gen_name in model.pg_operating_cost) if include_feasibility_slack: obj_expr += penalty_expr if hasattr(model, 'qg_operating_cost'): obj_expr += sum(model.qg_operating_cost[gen_name] for gen_name in model.qg_operating_cost) model.obj = pe.Objective(expr=obj_expr) return model, md
def declare_eq_p_balance_dc_approx(model, index_set, bus_p_loads, gens_by_bus, bus_gs_fixed_shunts, inlet_branches_by_bus, outlet_branches_by_bus, approximation_type=ApproximationType.BTHETA, **rhs_kwargs): """ Create the equality constraints for the real power balance at a bus using the variables for real power flows, respectively. NOTE: Equation build orientates constants to the RHS in order to compute the correct dual variable sign """ m = model con_set = decl.declare_set('_con_eq_p_balance', model, index_set) m.eq_p_balance = pe.Constraint(con_set) for bus_name in con_set: if approximation_type == ApproximationType.BTHETA: p_expr = -sum([ m.pf[branch_name] for branch_name in outlet_branches_by_bus[bus_name] ]) p_expr += sum([ m.pf[branch_name] for branch_name in inlet_branches_by_bus[bus_name] ]) elif approximation_type == ApproximationType.BTHETA_LOSSES: p_expr = -0.5 * sum([ m.pfl[branch_name] for branch_name in inlet_branches_by_bus[bus_name] ]) p_expr -= 0.5 * sum([ m.pfl[branch_name] for branch_name in outlet_branches_by_bus[bus_name] ]) p_expr -= sum([ m.pf[branch_name] for branch_name in outlet_branches_by_bus[bus_name] ]) p_expr += sum([ m.pf[branch_name] for branch_name in inlet_branches_by_bus[bus_name] ]) if bus_gs_fixed_shunts[bus_name] != 0.0: p_expr -= bus_gs_fixed_shunts[bus_name] if bus_p_loads[ bus_name] != 0.0: # only applies to fixed loads, otherwise may cause an error p_expr -= m.pl[bus_name] if rhs_kwargs: for idx, val in rhs_kwargs.items(): if idx == 'include_feasibility_slack_pos': p_expr -= eval("m." + val)[bus_name] if idx == 'include_feasibility_slack_neg': p_expr += eval("m." + val)[bus_name] for gen_name in gens_by_bus[bus_name]: p_expr += m.pg[gen_name] m.eq_p_balance[bus_name] = \ p_expr == 0.0
def declare_ineq_p_branch_thermal_lbub( model, index_set, branches, p_thermal_limits, approximation_type=ApproximationType.BTHETA, slacks=False): """ Create the inequality constraints for the branch thermal limits based on the power variables or expressions. """ m = model con_set = decl.declare_set('_con_ineq_p_branch_thermal_lbub', model=model, index_set=index_set) # flag for if slacks are on the model if slacks: if not hasattr(model, 'pf_slack_pos'): raise Exception( 'No positive slack branch variables on model, but slacks=True') if not hasattr(model, 'pf_slack_neg'): raise Exception( 'No negative slack branch variables on model, but slacks=True') m.ineq_pf_branch_thermal_lb = pe.Constraint(con_set) m.ineq_pf_branch_thermal_ub = pe.Constraint(con_set) if approximation_type == ApproximationType.BTHETA or \ approximation_type == ApproximationType.PTDF: for branch_name in con_set: if p_thermal_limits[branch_name] is None: continue if slacks and branch_name in m.pf_slack_neg: pf_bn = m.pf[branch_name] if hasattr(pf_bn, 'expr') and isinstance( pf_bn.expr, LinearExpression): ## create a copy old_expr = pf_bn.expr expr = LinearExpression( constant=old_expr.constant, linear_vars=old_expr.linear_vars[:] + [m.pf_slack_neg[branch_name]], linear_coefs=old_expr.linear_coefs[:] + [1], ) else: expr = m.pf[branch_name] + m.pf_slack_neg[branch_name] m.ineq_pf_branch_thermal_lb[branch_name] = \ (-p_thermal_limits[branch_name], expr, None) else: m.ineq_pf_branch_thermal_lb[branch_name] = \ (-p_thermal_limits[branch_name], m.pf[branch_name], None) if slacks and branch_name in m.pf_slack_pos: pf_bn = m.pf[branch_name] if hasattr(pf_bn, 'expr') and isinstance( pf_bn.expr, LinearExpression): ## create a copy old_expr = pf_bn.expr expr = LinearExpression( constant=old_expr.constant, linear_vars=old_expr.linear_vars[:] + [m.pf_slack_pos[branch_name]], linear_coefs=old_expr.linear_coefs[:] + [-1], ) else: expr = m.pf[branch_name] - m.pf_slack_pos[branch_name] m.ineq_pf_branch_thermal_lb[branch_name] = \ (None, expr, p_thermal_limits[branch_name]) else: m.ineq_pf_branch_thermal_ub[branch_name] = \ (None, m.pf[branch_name], p_thermal_limits[branch_name])
def create_hot_start_lpac_model(model_data, voltages, lower_bound=-pi / 3, upper_bound=pi / 3, cosine_segment_count=20, include_feasibility_slack=False, mode="uniform"): """ The hot start LPAC model assumes that voltages are known, e.g. from an AC base point solution. """ ###Grid data md = model_data.clone_in_service() tx_utils.scale_ModelData_to_pu(md, inplace=True) gens = dict(md.elements(element_type='generator')) buses = dict(md.elements(element_type='bus')) branches = dict(md.elements(element_type='branch')) loads = dict(md.elements(element_type='load')) shunts = dict(md.elements(element_type='shunt')) gen_attrs = md.attributes(element_type='generator') bus_attrs = md.attributes(element_type='bus') branch_attrs = md.attributes(element_type='branch') inlet_branches_by_bus, outlet_branches_by_bus = \ tx_utils.inlet_outlet_branches_by_bus(branches, buses) gens_by_bus = tx_utils.gens_by_bus(buses, gens) model = pe.ConcreteModel() ###declare (and fix) the voltage magnitudes and squares of voltage magnitudes bus_voltage_magnitudes = voltages #Assumes voltages is given as a dictionary libbus.declare_var_vm(model, bus_attrs['names'], initialize=bus_voltage_magnitudes) model.vm.fix() libbus.declare_var_vmsq( model=model, index_set=bus_attrs['names'], initialize={k: v**2 for k, v in bus_attrs['vm'].items()}, bounds=zip_items({k: v**2 for k, v in bus_attrs['v_min'].items()}, {k: v**2 for k, v in bus_attrs['v_max'].items()})) ### declare the polar voltages libbus.declare_var_va(model, bus_attrs['names'], initialize=bus_attrs['va']) ### declare the cosine approximation variables cos_hat_bounds = {k: (0, 1) for k in branch_attrs['names']} decl.declare_var('cos_hat', model, branch_attrs['names'], bounds=cos_hat_bounds) ### fix the reference bus ref_bus = md.data['system']['reference_bus'] #ref_angle = md.data['system']['reference_bus_angle'] model.va[ref_bus].fix(radians(0.0)) ### declare the fixed shunts at the buses bus_bs_fixed_shunts, bus_gs_fixed_shunts = tx_utils.dict_of_bus_fixed_shunts( buses, shunts) ### declare (and fix) the loads at the buses bus_p_loads, bus_q_loads = tx_utils.dict_of_bus_loads(buses, loads) libbus.declare_var_pl(model, bus_attrs['names'], initialize=bus_p_loads) libbus.declare_var_ql(model, bus_attrs['names'], initialize=bus_q_loads) model.pl.fix() model.ql.fix() ### include the feasibility slack for the bus balances p_rhs_kwargs = {} q_rhs_kwargs = {} if include_feasibility_slack: p_rhs_kwargs, q_rhs_kwargs, penalty_expr = _include_feasibility_slack( model, bus_attrs, gen_attrs, bus_p_loads, bus_q_loads) ### declare the generator real and reactive power pg_init = { k: (gen_attrs['p_min'][k] + gen_attrs['p_max'][k]) / 2.0 for k in gen_attrs['pg'] } libgen.declare_var_pg(model, gen_attrs['names'], initialize=pg_init, bounds=zip_items(gen_attrs['p_min'], gen_attrs['p_max'])) qg_init = { k: (gen_attrs['q_min'][k] + gen_attrs['q_max'][k]) / 2.0 for k in gen_attrs['qg'] } libgen.declare_var_qg(model, gen_attrs['names'], initialize=qg_init, bounds=zip_items(gen_attrs['q_min'], gen_attrs['q_max'])) ### declare the current flows in the branches vr_init = { k: bus_attrs['vm'][k] * pe.cos(bus_attrs['va'][k]) for k in bus_attrs['vm'] } vj_init = { k: bus_attrs['vm'][k] * pe.sin(bus_attrs['va'][k]) for k in bus_attrs['vm'] } s_max = {k: branches[k]['rating_long_term'] for k in branches.keys()} s_lbub = dict() for k in branches.keys(): if s_max[k] is None: s_lbub[k] = (None, None) else: s_lbub[k] = (-s_max[k], s_max[k]) pf_bounds = s_lbub pt_bounds = s_lbub qf_bounds = s_lbub qt_bounds = s_lbub pf_init = dict() pt_init = dict() qf_init = dict() qt_init = dict() for branch_name, branch in branches.items(): from_bus = branch['from_bus'] to_bus = branch['to_bus'] y_matrix = tx_calc.calculate_y_matrix_from_branch(branch) ifr_init = tx_calc.calculate_ifr(vr_init[from_bus], vj_init[from_bus], vr_init[to_bus], vj_init[to_bus], y_matrix) ifj_init = tx_calc.calculate_ifj(vr_init[from_bus], vj_init[from_bus], vr_init[to_bus], vj_init[to_bus], y_matrix) itr_init = tx_calc.calculate_itr(vr_init[from_bus], vj_init[from_bus], vr_init[to_bus], vj_init[to_bus], y_matrix) itj_init = tx_calc.calculate_itj(vr_init[from_bus], vj_init[from_bus], vr_init[to_bus], vj_init[to_bus], y_matrix) pf_init[branch_name] = tx_calc.calculate_p(ifr_init, ifj_init, vr_init[from_bus], vj_init[from_bus]) pt_init[branch_name] = tx_calc.calculate_p(itr_init, itj_init, vr_init[to_bus], vj_init[to_bus]) qf_init[branch_name] = tx_calc.calculate_q(ifr_init, ifj_init, vr_init[from_bus], vj_init[from_bus]) qt_init[branch_name] = tx_calc.calculate_q(itr_init, itj_init, vr_init[to_bus], vj_init[to_bus]) libbranch.declare_var_pf(model=model, index_set=branch_attrs['names'], initialize=pf_init, bounds=pf_bounds) libbranch.declare_var_pt(model=model, index_set=branch_attrs['names'], initialize=pt_init, bounds=pt_bounds) libbranch.declare_var_qf(model=model, index_set=branch_attrs['names'], initialize=qf_init, bounds=qf_bounds) libbranch.declare_var_qt(model=model, index_set=branch_attrs['names'], initialize=qt_init, bounds=qt_bounds) #################### #Constraints #################### ###Balance equations in a bus #p balance libbus.declare_eq_p_balance(model=model, index_set=bus_attrs['names'], bus_p_loads=bus_p_loads, gens_by_bus=gens_by_bus, bus_gs_fixed_shunts=bus_gs_fixed_shunts, inlet_branches_by_bus=inlet_branches_by_bus, outlet_branches_by_bus=outlet_branches_by_bus, **p_rhs_kwargs) #q balance libbus.declare_eq_q_balance(model=model, index_set=bus_attrs['names'], bus_q_loads=bus_q_loads, gens_by_bus=gens_by_bus, bus_bs_fixed_shunts=bus_bs_fixed_shunts, inlet_branches_by_bus=inlet_branches_by_bus, outlet_branches_by_bus=outlet_branches_by_bus, **q_rhs_kwargs) ### Power in a branch branch_con_set = decl.declare_set('_con_eq_p_q_lpac_branch_power', model, branch_attrs['names']) model.eq_pf_branch_t = pe.Constraint(branch_con_set) model.eq_pt_branch_t = pe.Constraint(branch_con_set) model.eq_qf_branch_t = pe.Constraint(branch_con_set) model.eq_qt_branch_t = pe.Constraint(branch_con_set) for branch_name in branch_con_set: branch = branches[branch_name] from_bus = branch['from_bus'] to_bus = branch['to_bus'] g = tx_calc.calculate_conductance(branch) b = tx_calc.calculate_susceptance(branch) model.eq_pf_branch_t[branch_name] = \ model.pf[branch_name] == \ g*model.vmsq[from_bus] - model.vm[from_bus]*model.vm[to_bus]*(g * model.cos_hat[branch_name] + b * (model.va[from_bus] - model.va[to_bus])) model.eq_pt_branch_t[branch_name] = \ model.pt[branch_name] == \ g*model.vmsq[to_bus] - model.vm[from_bus]*model.vm[to_bus]*(g * model.cos_hat[branch_name] + b * (model.va[to_bus] - model.va[from_bus])) model.eq_qf_branch_t[branch_name] = \ model.qf[branch_name] == \ -b*model.vmsq[from_bus] - model.vm[from_bus]*model.vm[to_bus]*(g*(model.va[from_bus] - model.va[to_bus]) - b*model.cos_hat[branch_name]) model.eq_qt_branch_t[branch_name] = \ model.qt[branch_name] == \ -b*model.vmsq[to_bus] - model.vm[from_bus]*model.vm[to_bus]*(g*(model.va[to_bus] - model.va[from_bus]) - b*model.cos_hat[branch_name]) ### Piecewise linear cosine constraints model.N = pe.Set(initialize=list(range(cosine_segment_count + 1))) declare_pwl_cosine_bounds(model=model, index_set=branch_attrs['names'], branches=branches, lower_bound=lower_bound, upper_bound=upper_bound, cosine_segment_count=cosine_segment_count, mode=mode) ### Objective is to maximize cosine hat variables obj_expr = sum(model.cos_hat[branch_name] for branch_name in branch_attrs['names']) if include_feasibility_slack: obj_expr += penalty_expr model.obj = pe.Objective(expr=obj_expr) return model, md
def _create_base_ac_with_pwl_approx_model(model_data, branch_dict, Q, include_feasibility_slack=False): md = model_data.clone_in_service() tx_utils.scale_ModelData_to_pu(md, inplace = True) gens = dict(md.elements(element_type='generator')) buses = dict(md.elements(element_type='bus')) branches = dict(md.elements(element_type='branch')) loads = dict(md.elements(element_type='load')) shunts = dict(md.elements(element_type='shunt')) gen_attrs = md.attributes(element_type='generator') bus_attrs = md.attributes(element_type='bus') branch_attrs = md.attributes(element_type='branch') inlet_branches_by_bus, outlet_branches_by_bus = \ tx_utils.inlet_outlet_branches_by_bus(branches, buses) gens_by_bus = tx_utils.gens_by_bus(buses, gens) bus_pairs = zip_items(branch_attrs['from_bus'], branch_attrs['to_bus']) unique_bus_pairs = list(OrderedDict((val, None) for idx, val in bus_pairs.items())) model = pe.ConcreteModel() ### declare (and fix) the loads at the buses bus_p_loads, bus_q_loads = tx_utils.dict_of_bus_loads(buses, loads) libbus.declare_var_pl(model, bus_attrs['names'], initialize=bus_p_loads) libbus.declare_var_ql(model, bus_attrs['names'], initialize=bus_q_loads) model.pl.fix() model.ql.fix() ### declare the fixed shunts at the buses bus_bs_fixed_shunts, bus_gs_fixed_shunts = tx_utils.dict_of_bus_fixed_shunts(buses, shunts) libbus.declare_var_vm(model=model, index_set=bus_attrs['names'], initialize=bus_attrs['vm'], bounds=zip_items(bus_attrs['v_min'], bus_attrs['v_max'])) libbus.declare_var_vmsq(model=model, index_set=bus_attrs['names'], initialize={k: v**2 for k, v in bus_attrs['vm'].items()}, bounds=zip_items({k: v**2 for k, v in bus_attrs['v_min'].items()}, {k: v**2 for k, v in bus_attrs['v_max'].items()})) # libbranch.declare_var_c(model=model, index_set=unique_bus_pairs) # libbranch.declare_var_s(model=model, index_set=unique_bus_pairs) ### declare the polar voltages va_bounds = {k: (-math.pi, math.pi) for k in bus_attrs['va']} libbus.declare_var_va(model, bus_attrs['names'], initialize=bus_attrs['va'], bounds=va_bounds ) ###declare the phase angle differences in each branch libbranch.declare_var_dva(model, index_set=unique_bus_pairs) libbranch.declare_eq_delta_va(model, index_set=unique_bus_pairs) ### include the feasibility slack for the bus balances p_rhs_kwargs = {} q_rhs_kwargs = {} if include_feasibility_slack: p_rhs_kwargs, q_rhs_kwargs, penalty_expr = _include_feasibility_slack(model, bus_attrs, gen_attrs, bus_p_loads, bus_q_loads) ### declare the generator real and reactive power pg_init = {k: (gen_attrs['p_min'][k] + gen_attrs['p_max'][k]) / 2.0 for k in gen_attrs['pg']} libgen.declare_var_pg(model, gen_attrs['names'], initialize=pg_init, bounds=zip_items(gen_attrs['p_min'], gen_attrs['p_max']) ) qg_init = {k: (gen_attrs['q_min'][k] + gen_attrs['q_max'][k]) / 2.0 for k in gen_attrs['qg']} libgen.declare_var_qg(model, gen_attrs['names'], initialize=qg_init, bounds=zip_items(gen_attrs['q_min'], gen_attrs['q_max']) ) ### declare the current flows in the branches vr_init = {k: bus_attrs['vm'][k] * pe.cos(bus_attrs['va'][k]) for k in bus_attrs['vm']} vj_init = {k: bus_attrs['vm'][k] * pe.sin(bus_attrs['va'][k]) for k in bus_attrs['vm']} s_max = {k: branches[k]['rating_long_term'] for k in branches.keys()} s_lbub = dict() for k in branches.keys(): if s_max[k] is None: s_lbub[k] = (None, None) else: s_lbub[k] = (-s_max[k],s_max[k]) pf_bounds = s_lbub pt_bounds = s_lbub qf_bounds = s_lbub qt_bounds = s_lbub pf_init = dict() pt_init = dict() qf_init = dict() qt_init = dict() for branch_name, branch in branches.items(): from_bus = branch['from_bus'] to_bus = branch['to_bus'] y_matrix = tx_calc.calculate_y_matrix_from_branch(branch) ifr_init = tx_calc.calculate_ifr(vr_init[from_bus], vj_init[from_bus], vr_init[to_bus], vj_init[to_bus], y_matrix) ifj_init = tx_calc.calculate_ifj(vr_init[from_bus], vj_init[from_bus], vr_init[to_bus], vj_init[to_bus], y_matrix) itr_init = tx_calc.calculate_itr(vr_init[from_bus], vj_init[from_bus], vr_init[to_bus], vj_init[to_bus], y_matrix) itj_init = tx_calc.calculate_itj(vr_init[from_bus], vj_init[from_bus], vr_init[to_bus], vj_init[to_bus], y_matrix) pf_init[branch_name] = tx_calc.calculate_p(ifr_init, ifj_init, vr_init[from_bus], vj_init[from_bus]) pt_init[branch_name] = tx_calc.calculate_p(itr_init, itj_init, vr_init[to_bus], vj_init[to_bus]) qf_init[branch_name] = tx_calc.calculate_q(ifr_init, ifj_init, vr_init[from_bus], vj_init[from_bus]) qt_init[branch_name] = tx_calc.calculate_q(itr_init, itj_init, vr_init[to_bus], vj_init[to_bus]) libbranch.declare_var_pf(model=model, index_set=branch_attrs['names'], initialize=pf_init, bounds=pf_bounds ) libbranch.declare_var_pt(model=model, index_set=branch_attrs['names'], initialize=pt_init, bounds=pt_bounds ) libbranch.declare_var_qf(model=model, index_set=branch_attrs['names'], initialize=qf_init, bounds=qf_bounds ) libbranch.declare_var_qt(model=model, index_set=branch_attrs['names'], initialize=qt_init, bounds=qt_bounds ) ### declare the branch power flow constraints ### declare a binary on/off variable for deenergizing a given branch decl.declare_var('u', model=model, index_set=branch_attrs['names'], within=pe.Binary) model.u.fix(1) branch_name_set = decl.declare_set('branch_name', model=model, index_set=branch_attrs['names']) model.box_index_set = pe.RangeSet(Q) model.power_type_set = pe.Set(initialize=[0,1]) #Note: 0 is for power_type == "Active"; 1 is for power_type=="Reactive" #For active power energization/deenergization model.u_branch = pe.Var(branch_name_set, model.box_index_set, model.power_type_set, within=pe.Binary) #For selecting the appropriate interval of the PWL approximation model.dva_branch = pe.Var(branch_name_set, model.box_index_set, model.power_type_set) #(5) - Constraints for the on/off variable u def u_sum_rule(model, branch_name, j): return model.u[branch_name] == sum(model.u_branch[branch_name, i, j] for i in model.box_index_set) model.u_sum_Constr = pe.Constraint(branch_name_set, model.power_type_set, rule=u_sum_rule) #(6) - Constraints that sum of dva variables should be equal to total dva #Upper bound constraints def delta_branch_ub_rule(model, branch_name, j): branch = branches[branch_name] from_bus = branch['from_bus'] to_bus = branch['to_bus'] return -model.dva[(from_bus, to_bus)] + sum(model.dva_branch[branch_name, i, j] for i in model.box_index_set) <= math.pi*(1-model.u[branch_name]) model.delta_branch_ub_Constr = pe.Constraint(branch_name_set, model.power_type_set, rule=delta_branch_ub_rule) #Lower bound constraints def delta_branch_lb_rule(model, branch_name, j): branch = branches[branch_name] from_bus = branch['from_bus'] to_bus = branch['to_bus'] return -model.dva[(from_bus, to_bus)] + sum(model.dva_branch[branch_name, i, j] for i in model.box_index_set) >= -math.pi*(1-model.u[branch_name]) model.delta_branch_lb_Constr = pe.Constraint(branch_name_set, model.power_type_set, rule=delta_branch_lb_rule) #(7) - Constraints that force dva variable to be in only one interval #Upper bound def delta_branch_box_ub_rule(model, branch_name, i, j): if j==0: delta_ub = branch_dict["Active_from_bus"][branch_name]['boxes']['coords'][i-1][7][2] else: delta_ub = branch_dict["Reactive_from_bus"][branch_name]['boxes']['coords'][i-1][7][2] return model.dva_branch[branch_name, i, j] <= delta_ub*model.u_branch[branch_name, i, j] model.delta_branch_box_ub_Constr = pe.Constraint(branch_name_set, model.box_index_set, model.power_type_set, rule=delta_branch_box_ub_rule) def delta_branch_box_lb_rule(model, branch_name, i, j): if j==0: delta_lb = branch_dict["Active_from_bus"][branch_name]['boxes']['coords'][i-1][0][2] else: delta_lb = branch_dict["Reactive_from_bus"][branch_name]['boxes']['coords'][i-1][0][2] return model.dva_branch[branch_name, i, j] >= delta_lb*model.u_branch[branch_name, i, j] model.delta_branch_box_lb_Constr = pe.Constraint(branch_name_set, model.box_index_set, model.power_type_set, rule=delta_branch_box_lb_rule) #(8) - Approximating power flow equation by PWL approximation #Active_from_bus def pwl_active_from_ub_rule(model, branch_name, i): branch = branches[branch_name] from_bus = branch['from_bus'] to_bus = branch['to_bus'] g = tx_calc.calculate_conductance(branch) b = tx_calc.calculate_susceptance(branch) coeffs = branch_dict["Active_from_bus"][branch_name]['boxes']['coefficients'][i-1] #M = 10*(g+b) + 4*(coeffs[0]+coeffs[1]+coeffs[2]+coeffs[3]) M = 2*s_max[branch_name] + 10*(np.abs(coeffs[0])+np.abs(coeffs[1])+np.abs(coeffs[2])+np.abs(coeffs[3])) return -model.pf[branch_name] + coeffs[0]*model.vm[from_bus] + coeffs[1]*model.vm[to_bus] + coeffs[2]*model.dva_branch[branch_name, i, 0] + coeffs[3] <= M*(1-model.u_branch[branch_name, i, 0]) model.pwl_active_from_ub_Constr = pe.Constraint(branch_name_set, model.box_index_set, rule=pwl_active_from_ub_rule) def pwl_active_from_lb_rule(model, branch_name, i): branch = branches[branch_name] from_bus = branch['from_bus'] to_bus = branch['to_bus'] g = tx_calc.calculate_conductance(branch) b = tx_calc.calculate_susceptance(branch) coeffs = branch_dict["Active_from_bus"][branch_name]['boxes']['coefficients'][i-1] #M = -(10*(g+b) + 4*(coeffs[0]+coeffs[1]+coeffs[2]+coeffs[3])) M = -2*s_max[branch_name] - 10*(np.abs(coeffs[0])+np.abs(coeffs[1])+np.abs(coeffs[2])+np.abs(coeffs[3])) return -model.pf[branch_name] + coeffs[0]*model.vm[from_bus] + coeffs[1]*model.vm[to_bus] + coeffs[2]*model.dva_branch[branch_name, i, 0] + coeffs[3] >= M*(1-model.u_branch[branch_name, i, 0]) model.pwl_active_from_lb_Constr = pe.Constraint(branch_name_set, model.box_index_set, rule=pwl_active_from_lb_rule) #Active_to_bus def pwl_active_to_ub_rule(model, branch_name, i): branch = branches[branch_name] from_bus = branch['from_bus'] to_bus = branch['to_bus'] g = tx_calc.calculate_conductance(branch) b = tx_calc.calculate_susceptance(branch) coeffs = branch_dict["Active_to_bus"][branch_name]['boxes']['coefficients'][i-1] #M = 10*(g+b) + 4*(coeffs[0]+coeffs[1]+coeffs[2]+coeffs[3]) M = 2*s_max[branch_name] + 10*(np.abs(coeffs[0])+np.abs(coeffs[1])+np.abs(coeffs[2])+np.abs(coeffs[3])) return -model.pt[branch_name] + coeffs[0]*model.vm[from_bus] + coeffs[1]*model.vm[to_bus] + coeffs[2]*model.dva_branch[branch_name, i, 0] + coeffs[3] <= M*(1-model.u_branch[branch_name, i, 0]) model.pwl_active_to_ub_Constr = pe.Constraint(branch_name_set, model.box_index_set, rule=pwl_active_to_ub_rule) def pwl_active_to_lb_rule(model, branch_name, i): branch = branches[branch_name] from_bus = branch['from_bus'] to_bus = branch['to_bus'] g = tx_calc.calculate_conductance(branch) b = tx_calc.calculate_susceptance(branch) coeffs = branch_dict["Active_to_bus"][branch_name]['boxes']['coefficients'][i-1] #M = -(10*(g+b) + 4*(coeffs[0]+coeffs[1]+coeffs[2]+coeffs[3])) M = -2*s_max[branch_name] - 10*(np.abs(coeffs[0])+np.abs(coeffs[1])+np.abs(coeffs[2])+np.abs(coeffs[3])) return -model.pt[branch_name] + coeffs[0]*model.vm[from_bus] + coeffs[1]*model.vm[to_bus] + coeffs[2]*model.dva_branch[branch_name, i, 0] + coeffs[3] >= M*(1-model.u_branch[branch_name, i, 0]) model.pwl_active_to_lb_Constr = pe.Constraint(branch_name_set, model.box_index_set, rule=pwl_active_to_lb_rule) #Reactive_from_bus def pwl_reactive_from_ub_rule(model, branch_name, i): branch = branches[branch_name] from_bus = branch['from_bus'] to_bus = branch['to_bus'] g = tx_calc.calculate_conductance(branch) b = tx_calc.calculate_susceptance(branch) coeffs = branch_dict["Reactive_from_bus"][branch_name]['boxes']['coefficients'][i-1] #M = 10*(g+b) + 4*(coeffs[0]+coeffs[1]+coeffs[2]+coeffs[3]) M = 2*s_max[branch_name] + 10*(np.abs(coeffs[0])+np.abs(coeffs[1])+np.abs(coeffs[2])+np.abs(coeffs[3])) return -model.qf[branch_name] + coeffs[0]*model.vm[from_bus] + coeffs[1]*model.vm[to_bus] + coeffs[2]*model.dva_branch[branch_name, i, 1] + coeffs[3] <= M*(1-model.u_branch[branch_name, i, 1]) model.pwl_reactive_from_ub_Constr = pe.Constraint(branch_name_set, model.box_index_set, rule=pwl_reactive_from_ub_rule) def pwl_reactive_from_lb_rule(model, branch_name, i): branch = branches[branch_name] from_bus = branch['from_bus'] to_bus = branch['to_bus'] g = tx_calc.calculate_conductance(branch) b = tx_calc.calculate_susceptance(branch) coeffs = branch_dict["Reactive_from_bus"][branch_name]['boxes']['coefficients'][i-1] #M = -(10*(g+b) + 4*(coeffs[0]+coeffs[1]+coeffs[2]+coeffs[3])) M = -2*s_max[branch_name] - 10*(np.abs(coeffs[0])+np.abs(coeffs[1])+np.abs(coeffs[2])+np.abs(coeffs[3])) return -model.qf[branch_name] + coeffs[0]*model.vm[from_bus] + coeffs[1]*model.vm[to_bus] + coeffs[2]*model.dva_branch[branch_name, i, 1] + coeffs[3] >= M*(1-model.u_branch[branch_name, i, 1]) model.pwl_reactive_from_lb_Constr = pe.Constraint(branch_name_set, model.box_index_set, rule=pwl_reactive_from_lb_rule) #Reactive_to_bus def pwl_reactive_to_ub_rule(model, branch_name, i): branch = branches[branch_name] from_bus = branch['from_bus'] to_bus = branch['to_bus'] g = tx_calc.calculate_conductance(branch) b = tx_calc.calculate_susceptance(branch) coeffs = branch_dict["Reactive_to_bus"][branch_name]['boxes']['coefficients'][i-1] #M = 10*(g+b) + 4*(coeffs[0]+coeffs[1]+coeffs[2]+coeffs[3]) M = 2*s_max[branch_name] + 10*(np.abs(coeffs[0])+np.abs(coeffs[1])+np.abs(coeffs[2])+np.abs(coeffs[3])) return -model.qt[branch_name] + coeffs[0]*model.vm[from_bus] + coeffs[1]*model.vm[to_bus] + coeffs[2]*model.dva_branch[branch_name, i, 1] + coeffs[3] <= M*(1-model.u_branch[branch_name, i, 1]) model.pwl_reactive_to_ub_Constr = pe.Constraint(branch_name_set, model.box_index_set, rule=pwl_reactive_to_ub_rule) def pwl_reactive_to_lb_rule(model, branch_name, i): branch = branches[branch_name] from_bus = branch['from_bus'] to_bus = branch['to_bus'] g = tx_calc.calculate_conductance(branch) b = tx_calc.calculate_susceptance(branch) coeffs = branch_dict["Reactive_to_bus"][branch_name]['boxes']['coefficients'][i-1] #M = -(10*(g+b) + 4*(coeffs[0]+coeffs[1]+coeffs[2]+coeffs[3])) M = -2*s_max[branch_name] - 10*(np.abs(coeffs[0])+np.abs(coeffs[1])+np.abs(coeffs[2])+np.abs(coeffs[3])) return -model.qt[branch_name] + coeffs[0]*model.vm[from_bus] + coeffs[1]*model.vm[to_bus] + coeffs[2]*model.dva_branch[branch_name, i, 1] + coeffs[3] >= M*(1-model.u_branch[branch_name, i, 1]) model.pwl_reactive_to_lb_Constr = pe.Constraint(branch_name_set, model.box_index_set, rule=pwl_reactive_to_lb_rule) ### declare the pq balances libbus.declare_eq_p_balance(model=model, index_set=bus_attrs['names'], bus_p_loads=bus_p_loads, gens_by_bus=gens_by_bus, bus_gs_fixed_shunts=bus_gs_fixed_shunts, inlet_branches_by_bus=inlet_branches_by_bus, outlet_branches_by_bus=outlet_branches_by_bus, **p_rhs_kwargs ) libbus.declare_eq_q_balance(model=model, index_set=bus_attrs['names'], bus_q_loads=bus_q_loads, gens_by_bus=gens_by_bus, bus_bs_fixed_shunts=bus_bs_fixed_shunts, inlet_branches_by_bus=inlet_branches_by_bus, outlet_branches_by_bus=outlet_branches_by_bus, **q_rhs_kwargs ) ### declare the thermal limits libbranch.declare_ineq_s_branch_thermal_limit(model=model, index_set=branch_attrs['names'], branches=branches, s_thermal_limits=s_max, flow_type=FlowType.POWER ) # declare angle difference limits on interconnected buses # libbranch.declare_ineq_angle_diff_branch_lbub_c_s(model=model, # index_set=branch_attrs['names'], # branches=branches # ) ### declare the generator cost objective libgen.declare_expression_pgqg_operating_cost(model=model, index_set=gen_attrs['names'], p_costs=gen_attrs['p_cost'], q_costs=gen_attrs.get('q_cost', None) ) obj_expr = sum(model.pg_operating_cost[gen_name] for gen_name in model.pg_operating_cost) if include_feasibility_slack: obj_expr += penalty_expr if hasattr(model, 'qg_operating_cost'): obj_expr += sum(model.qg_operating_cost[gen_name] for gen_name in model.qg_operating_cost) model.obj = pe.Objective(expr=obj_expr) return model, md