def _add_interface_violations(lazy_violations, flows, mb, md, solver, ptdf_options, PTDF, model, baseMVA, persistent_solver, rel_ptdf_tol, abs_ptdf_tol, time, prepend_str, obj_multi): ## in case there's no interfaces if not hasattr(mb, 'ineq_pf_interface_bounds'): return constr = mb.ineq_pf_interface_bounds int_viol_in_mb = mb._interfaces_monitored for i, i_n in _iter_over_int_viol_set( lazy_violations.interface_lazy_violations, mb, PTDF, abs_ptdf_tol, rel_ptdf_tol): minimum_limit = PTDF.interface_min_limits[i] maximum_limit = PTDF.interface_max_limits[i] if flows.PFV_I is None: logger.debug( prepend_str + _generate_flow_monitor_message('interface', i_n, time=time)) else: logger.debug(prepend_str + _generate_flow_monitor_message( 'interface', i_n, flows.PFV_I[i], minimum_limit, maximum_limit, baseMVA, time)) constr[i_n], new_slacks = _generate_interface_bounds( mb, i_n, minimum_limit, maximum_limit) int_viol_in_mb.append(i) if new_slacks: m = model obj_coef = m.TimePeriodLengthHours * m.InterfaceLimitPenalty[i_n] if persistent_solver: m_model = m.model() if m is not m_model and obj_multi is None: raise RuntimeError( "Cannot add lazy var for interface slacks if part of a larger model" ) if obj_multi is not None: obj_coef = obj_multi * obj_coef ## update the objective through the add_column method solver.add_column(m_model, mb.pfi_slack_pos[i_n], obj_coef, [], []) solver.add_column(m_model, mb.pfi_slack_neg[i_n], obj_coef, [], []) else: m.InterfaceViolationCost[time].expr += (obj_coef*mb.pfi_slack_pos[i_n] + \ obj_coef*mb.pfi_slack_neg[i_n] ) if persistent_solver: solver.add_constraint(constr[i_n])
def _add_contingency_violations(lazy_violations, flows, mb, md, solver, ptdf_options, PTDF, model, baseMVA, persistent_solver, rel_ptdf_tol, abs_ptdf_tol, time, prepend_str, obj_multi): ## in case there's no contingencies if not hasattr(mb, 'ineq_pf_contingency_branch_thermal_bounds'): return constr = mb.ineq_pf_contingency_branch_thermal_bounds contingencies_monitored = mb._contingencies_monitored for cn, bn, i_b in _iter_over_cont_viol_set( lazy_violations.contingency_lazy_violations, mb, PTDF, abs_ptdf_tol, rel_ptdf_tol): emergency_thermal_limit = PTDF.contingency_limits_array_masked[i_b] logger.debug( prepend_str + _generate_flow_monitor_message('contingency', (cn, bn), time=time)) constr[cn, bn], new_slacks = _generate_contingency_bounds( mb, (cn, bn), -emergency_thermal_limit, emergency_thermal_limit) contingencies_monitored.append((cn, i_b)) if new_slacks: m = model obj_coef = m.TimePeriodLengthHours * m.SystemContingencyLimitPenalty if persistent_solver: m_model = m.model() if m is not m_model and obj_multi is None: raise RuntimeError( "Cannot add lazy var for branch contingency slacks if part of a larger model" ) if obj_multi is not None: obj_coef = obj_multi * obj_coef ## update the objective through the add_column method solver.add_column(m_model, mb.pfc_slack_pos[cn, bn], obj_coef, [], []) solver.add_column(m_model, mb.pfc_slack_neg[cn, bn], obj_coef, [], []) else: m.ContingencyViolationCost[time].expr += (obj_coef*mb.pfc_slack_pos[cn,bn] + \ obj_coef*mb.pfc_slack_neg[cn,bn] ) if persistent_solver: solver.add_constraint(constr[cn, bn])
def remove_inactive(mb, solver, time=None, prepend_str=""): model = mb.model() PTDF = mb._PTDF ptdf_options = model._ptdf_options baseMVA = model.model_data.data['system']['baseMVA'] slack_tol = ptdf_options['active_flow_tol'] persistent_solver = isinstance(solver, PersistentSolver) ## get the lines we're monitoring idx_monitored = mb._idx_monitored interfaces_monitored = mb._interfaces_monitored ## get the branchnname to index map branchname_index_map = PTDF.branchname_to_index_masked_map interfacename_index_map = PTDF.interfacename_to_index_map ## branches branches = model.model_data.data['elements']['branch'] interfaces = model.model_data.data['elements']['interface'] constr_to_remove = list() for bn, constr in mb.ineq_pf_branch_thermal_bounds.items(): ## don't take out branches we were told to monitor if 'lazy' in branches[bn] and not branches[bn]['lazy']: continue slack = constr.slack() if slack_tol <= abs(slack): logger.debug(prepend_str + _generate_flow_monitor_remove_message( 'branch', bn, abs(slack), baseMVA, time)) constr_to_remove.append(constr) ## remove the index from the lines we're monitoring idx_monitored.remove(branchname_index_map[bn]) for i_n, constr in mb.ineq_pf_interface_bounds.items(): ## don't take out branches we were told to monitor if 'lazy' in interfaces[i_n] and not interfaces[i_n]['lazy']: continue slack = constr.slack() if slack_tol <= abs(slack): logger.debug(prepend_str + _generate_flow_monitor_remove_message( 'interface', i_n, abs(slack), baseMVA, time)) constr_to_remove.append(constr) ## remove the index from the lines we're monitoring interfaces_monitored.remove(interfacename_index_map[i_n]) msg = prepend_str + "removing {} inactive transmission constraint(s)".format( len(constr_to_remove)) if time is not None: msg += " at time {}".format(time) logger.debug(msg) for constr in constr_to_remove: if persistent_solver: solver.remove_constraint(constr) del constr return len(constr_to_remove)
def remove_inactive(mb, solver, time=None, prepend_str=""): model = mb.model() PTDF = mb._PTDF ptdf_options = model._ptdf_options baseMVA = model.model_data.data['system']['baseMVA'] slack_tol = ptdf_options['active_flow_tol'] persistent_solver = isinstance(solver, PersistentSolver) ## get the lines we're monitoring gt_idx_monitored = mb._gt_idx_monitored lt_idx_monitored = mb._lt_idx_monitored ## get the branchnname to index map branchname_index_map = PTDF.branchname_to_index_masked_map constr_to_remove = list() for bn, constr in mb.ineq_pf_branch_thermal_lb.items(): slack = constr.slack() if slack_tol <= abs(slack): logger.debug(prepend_str + _generate_branch_remove_message( 'LB', bn, abs(slack), baseMVA, time)) constr_to_remove.append(constr) ## remove the index from the lines we're monitoring lt_idx_monitored.remove(branchname_index_map[bn]) for bn, constr in mb.ineq_pf_branch_thermal_ub.items(): slack = constr.slack() if slack_tol <= abs(slack): logger.debug(prepend_str + _generate_branch_remove_message( 'UB', bn, abs(slack), baseMVA, time)) constr_to_remove.append(constr) ## remove the index from the lines we're monitoring gt_idx_monitored.remove(branchname_index_map[bn]) msg = prepend_str + "removing {} inactive transmission constraint(s)".format( len(constr_to_remove)) if time is not None: msg += " at time {}".format(time) logger.debug(msg) for constr in constr_to_remove: if persistent_solver: solver.remove_constraint(constr) del constr return len(constr_to_remove)
def add_violations(lazy_violations, flows, mb, md, solver, ptdf_options, PTDF, time=None, prepend_str=""): if time is None: model = mb else: model = mb.parent_block() baseMVA = md.data['system']['baseMVA'] persistent_solver = isinstance(solver, PersistentSolver) ## static information between runs rel_ptdf_tol = ptdf_options['rel_ptdf_tol'] abs_ptdf_tol = ptdf_options['abs_ptdf_tol'] constr = mb.ineq_pf_branch_thermal_bounds viol_in_mb = mb._idx_monitored for i, bn in _iter_over_viol_set(lazy_violations.branch_lazy_violations, mb, PTDF, abs_ptdf_tol, rel_ptdf_tol): thermal_limit = PTDF.branch_limits_array_masked[i] if flows.PFV is None: logger.debug( prepend_str + _generate_flow_monitor_message('branch', bn, time=time)) else: logger.debug(prepend_str + _generate_flow_monitor_message( 'branch', bn, flows.PFV[i], -thermal_limit, thermal_limit, baseMVA, time)) constr[bn], new_slacks = _generate_branch_thermal_bounds( mb, bn, thermal_limit) viol_in_mb.append(i) if new_slacks: m = model obj_coef = m.TimePeriodLengthHours * m.BranchLimitPenalty[bn] if persistent_solver: if m is not m.model(): raise RuntimeError( "Cannot add lazy var for branch slacks if part of a larger model" ) ## update the objective through the add_column method solver.add_column(m, mb.pf_slack_pos[bn], obj_coef, [], []) solver.add_column(m, mb.pf_slack_neg[bn], obj_coef, [], []) else: m.BranchViolationCost[time].expr += ( obj_coef*mb.pf_slack_pos[bn] + \ obj_coef*mb.pf_slack_neg[bn] ) if persistent_solver: solver.add_constraint(constr[bn]) _add_interface_violations(lazy_violations, flows, mb, md, solver, ptdf_options, PTDF, model, baseMVA, persistent_solver, rel_ptdf_tol, abs_ptdf_tol, time, prepend_str) _add_contingency_violations(lazy_violations, flows, mb, md, solver, ptdf_options, PTDF, model, baseMVA, persistent_solver, rel_ptdf_tol, abs_ptdf_tol, time, prepend_str)
def check_violations(mb, md, PTDF, max_viol_add, time=None, prepend_str=""): if time is None: # DCOPF active_slack_tol = mb._ptdf_options['active_flow_tol'] else: # Unit Commitment active_slack_tol = mb.parent_block()._ptdf_options['active_flow_tol'] ## PFV -- power flow vector ## PFV_I -- interface power flow vector ## VA -- bus voltage angle vector PFV, PFV_I, VA = PTDF.calculate_masked_PFV(mb) violations_store = _MaximalViolationsStore(max_viol_add=max_viol_add, md=md, time=time, prepend_str=prepend_str) if len(PTDF.branches_keys_masked) > 0: violations_store.check_and_add_violations( 'branch', PFV, mb.pf, PTDF.lazy_branch_limits, PTDF.enforced_branch_limits, -PTDF.lazy_branch_limits, -PTDF.enforced_branch_limits, mb._idx_monitored, PTDF.branches_keys_masked) if len(PTDF.interface_keys) > 0: violations_store.check_and_add_violations( 'interface', PFV_I, mb.pfi, PTDF.lazy_interface_max_limits, PTDF.enforced_interface_max_limits, PTDF.lazy_interface_min_limits, PTDF.enforced_interface_min_limits, mb._interfaces_monitored, PTDF.interface_keys) if PTDF.contingencies and \ violations_store.total_violations == 0: ## NOTE: checking contingency constraints in general could be very expensive ## we probably want to delay doing so until we have a nearly transmission feasible ## solution ## For each contingency, we'll only calculate the difference in flow, ## and check this against the difference in bounds, i.e., ## power_flow_contingency == PFV + PFV_delta_c ## -rate_c <= power_flow_contingency <= +rate_c ## <===> ## -rate_c - PFV <= PFV_delta_c <= +rate_c - PFV ## <===> ## contingency_limits_lower <= PFV_delta_c <= contingency_limits_upper ## and ## contingency_limits_lower == -rate_c - PFV; contingency_limits_upper == rate_c - PFV ## In this way, we avoid (number of contingenies) adds PFV+PFV_delta_c logger.debug("Checking contingency flows...") lazy_contingency_limits_upper = PTDF.lazy_contingency_limits - PFV lazy_contingency_limits_lower = -PTDF.lazy_contingency_limits - PFV enforced_contingency_limits_upper = PTDF.enforced_contingency_limits - PFV enforced_contingency_limits_lower = -PTDF.enforced_contingency_limits - PFV for cn in PTDF.contingency_compensators: PFV_delta = PTDF.calculate_masked_PFV_delta(cn, PFV, VA) violations_store.check_and_add_violations( 'contingency', PFV_delta, mb.pfc, lazy_contingency_limits_upper, enforced_contingency_limits_upper, lazy_contingency_limits_lower, enforced_contingency_limits_lower, mb._contingencies_monitored, PTDF.branches_keys_masked, outer_name=cn, PFV=PFV) logger.debug( f"branches_monitored: {mb._idx_monitored}\n" f"interfaces_monitored: {mb._interfaces_monitored}\n" f"contingencies_monitored: {mb._contingencies_monitored}\n" f"Violations being added: {violations_store.violations_store}\n" f"Violations in model: {violations_store.monitored_violations}\n") viol_lazy = _LazyViolations( branch_lazy_violations=set( violations_store.get_violations_named('branch')), interface_lazy_violations=set( violations_store.get_violations_named('interface')), contingency_lazy_violations=set( violations_store.get_violations_named('contingency'))) flows = _CalculatedFlows(PFV=PFV, PFV_I=PFV_I) return flows, violations_store.total_violations, violations_store.monitored_violations, viol_lazy
def add_violations(viol_lazy, int_viol_lazy, PFV, PFV_I, mb, md, solver, ptdf_options, PTDF, time=None, prepend_str=""): model = mb.model() baseMVA = md.data['system']['baseMVA'] persistent_solver = isinstance(solver, PersistentSolver) ## static information between runs rel_ptdf_tol = ptdf_options['rel_ptdf_tol'] abs_ptdf_tol = ptdf_options['abs_ptdf_tol'] constr = mb.ineq_pf_branch_thermal_bounds viol_in_mb = mb._idx_monitored for i, bn in _iter_over_viol_set(viol_lazy, mb, PTDF, abs_ptdf_tol, rel_ptdf_tol): thermal_limit = PTDF.branch_limits_array_masked[i] if PFV is None: logger.debug( prepend_str + _generate_flow_monitor_message('branch', bn, time=time)) else: logger.debug(prepend_str + _generate_flow_monitor_message( 'branch', bn, PFV[i], -thermal_limit, thermal_limit, baseMVA, time)) constr[bn] = (-thermal_limit, mb.pf[bn], thermal_limit) viol_in_mb.append(i) if persistent_solver: solver.add_constraint(constr[bn]) ## in case there's no interfaces if not hasattr(mb, 'ineq_pf_interface_bounds'): return constr = mb.ineq_pf_interface_bounds int_viol_in_mb = mb._interfaces_monitored for i, i_n in _iter_over_int_viol_set(int_viol_lazy, mb, PTDF, abs_ptdf_tol, rel_ptdf_tol): minimum_limit = PTDF.interface_min_limits[i] maximum_limit = PTDF.interface_max_limits[i] if PFV_I is None: logger.debug( prepend_str + _generate_flow_monitor_message('interface', i_n, time=time)) else: logger.debug(prepend_str + _generate_flow_monitor_message( 'interface', i_n, PFV_I[i], minimum_limit, maximum_limit, baseMVA, time)) constr[i_n] = (minimum_limit, mb.pfi[i_n], maximum_limit) int_viol_in_mb.append(i) if persistent_solver: solver.add_constraint(constr[i_n])
def add_violations(gt_viol_lazy, lt_viol_lazy, PFV, mb, md, solver, ptdf_options, PTDF, time=None, prepend_str=""): model = mb.model() baseMVA = md.data['system']['baseMVA'] persistent_solver = isinstance(solver, PersistentSolver) ## static information between runs rel_ptdf_tol = ptdf_options['rel_ptdf_tol'] abs_ptdf_tol = ptdf_options['abs_ptdf_tol'] ## helper for generating pf def _iter_over_viol_set(viol_set): for i in viol_set: bn = PTDF.branches_keys_masked[i] if mb.pf[bn].expr is None: expr = libbranch.get_power_flow_expr_ptdf_approx( mb, bn, PTDF, abs_ptdf_tol=abs_ptdf_tol, rel_ptdf_tol=rel_ptdf_tol) mb.pf[bn] = expr yield i, bn constr = mb.ineq_pf_branch_thermal_lb lt_viol_in_mb = mb._lt_idx_monitored for i, bn in _iter_over_viol_set(lt_viol_lazy): thermal_limit = PTDF.branch_limits_array_masked[i] if PFV is None: logger.debug(prepend_str + _generate_flow_monitor_message('LB', bn, time=time)) else: logger.debug(prepend_str + _generate_flow_monitor_message( 'LB', bn, PFV[i], -thermal_limit, baseMVA, time)) constr[bn] = (-thermal_limit, mb.pf[bn], None) lt_viol_in_mb.append(i) if persistent_solver: solver.add_constraint(constr[bn]) constr = mb.ineq_pf_branch_thermal_ub gt_viol_in_mb = mb._gt_idx_monitored for i, bn in _iter_over_viol_set(gt_viol_lazy): thermal_limit = PTDF.branch_limits_array_masked[i] if PFV is None: logger.debug(prepend_str + _generate_flow_monitor_message('UB', bn, time=time)) else: logger.debug(prepend_str + _generate_flow_monitor_message( 'UB', bn, PFV[i], thermal_limit, baseMVA, time)) constr[bn] = (None, mb.pf[bn], thermal_limit) gt_viol_in_mb.append(i) if persistent_solver: solver.add_constraint(constr[bn])
def remove_inactive(mb, solver, time=None, prepend_str=""): if time is None: # DCOPF model = mb else: # UC model = mb.parent_block() PTDF = mb._PTDF ptdf_options = model._ptdf_options baseMVA = model.model_data.data['system']['baseMVA'] slack_tol = ptdf_options['active_flow_tol'] persistent_solver = isinstance(solver, PersistentSolver) ## get the lines we're monitoring idx_monitored = mb._idx_monitored interfaces_monitored = mb._interfaces_monitored contingencies_monitored = mb._contingencies_monitored ## get the branchnname to index map branchname_index_map = PTDF.branchname_to_index_masked_map interfacename_index_map = PTDF.interfacename_to_index_map ## branches branches = model.model_data.data['elements']['branch'] interfaces = model.model_data.data['elements']['interface'] # list of tuples -- each tuple is ( key, indexed_constraint, constraint_data ) constr_to_remove = list() for bn, constr in mb.ineq_pf_branch_thermal_bounds.items(): ## don't take out branches we were told to monitor if 'lazy' in branches[bn] and not branches[bn]['lazy']: continue slack = constr.slack() if slack_tol <= abs(slack): logger.debug(prepend_str + _generate_flow_monitor_remove_message( 'branch', bn, abs(slack), baseMVA, time)) constr_to_remove.append( (bn, mb.ineq_pf_branch_thermal_bounds, constr)) ## remove the index from the lines we're monitoring idx_monitored.remove(branchname_index_map[bn]) for i_n, constr in mb.ineq_pf_interface_bounds.items(): ## don't take out branches we were told to monitor if 'lazy' in interfaces[i_n] and not interfaces[i_n]['lazy']: continue slack = constr.slack() if slack_tol <= abs(slack): logger.debug(prepend_str + _generate_flow_monitor_remove_message( 'interface', i_n, abs(slack), baseMVA, time)) constr_to_remove.append((i_n, mb.ineq_pf_interface_bounds, constr)) ## remove the index from the lines we're monitoring interfaces_monitored.remove(interfacename_index_map[i_n]) for name, constr in mb.ineq_pf_contingency_branch_thermal_bounds.items(): slack = constr.slack() if slack_tol <= abs(slack): logger.debug(prepend_str + _generate_flow_monitor_remove_message( 'contingeny', name, abs(slack), baseMVA, time)) constr_to_remove.append( (name, mb.ineq_pf_contingency_branch_thermal_bounds, constr)) ## remove the index from the lines we're monitoring contingencies_monitored.remove( (name[0], branchname_index_map[name[1]])) ## TODO: name? msg = prepend_str + "removing {} inactive transmission constraint(s)".format( len(constr_to_remove)) if time is not None: msg += " at time {}".format(time) logger.debug(msg) for key, indexed_constraint, constr_data in constr_to_remove: if persistent_solver: solver.remove_constraint(constr_data) del indexed_constraint[key] return len(constr_to_remove)