Skip to content

Commit

Permalink
Implemented the InterAggregationInfo object as the carrier for inter-…
Browse files Browse the repository at this point in the history
…area information
  • Loading branch information
SanPen committed Sep 13, 2024
1 parent 559ffbd commit 8458d87
Show file tree
Hide file tree
Showing 13 changed files with 207 additions and 126 deletions.
54 changes: 31 additions & 23 deletions .idea/workspace.xml

Large diffs are not rendered by default.

97 changes: 36 additions & 61 deletions src/GridCal/Gui/Main/SubClasses/simulations.py
Original file line number Diff line number Diff line change
Expand Up @@ -585,22 +585,10 @@ def update_available_results(self) -> None:
self.ui.resultsTableView.setModel(None)
self.ui.resultsLogsTreeView.setModel(None)

def get_compatible_from_to_buses_and_inter_branches(self) -> Tuple[
bool,
List[Tuple[int, dev.Bus]],
List[Tuple[int, dev.Bus]],
List[Tuple[int, object, float]],
List[Tuple[int, object, float]],
List[dev.Area | dev.Zone | dev.Country], List[dev.Area | dev.Zone | dev.Country]]:
def get_compatible_from_to_buses_and_inter_branches(self) -> dev.InterAggregationInfo:
"""
Get the lists that help defining the inter area objects
:return: success?,
list of tuples bus idx, Bus in the areas from,
list of tuples bus idx, Bus in the areas to,
List of inter area Branches (branch index, branch object, flow sense w.r.t the area exchange),
List of inter area HVDC (branch index, branch object, flow sense w.r.t the area exchange),
List of areas from,
List of areas to
:return: InterAggregationInfo
"""
dev_tpe_from = self.exchange_places_dict[self.ui.fromComboBox.currentText()]
devs_from = self.circuit.get_elements_by_type(dev_tpe_from)
Expand Down Expand Up @@ -630,12 +618,12 @@ def get_compatible_from_to_buses_and_inter_branches(self) -> Tuple[
dlg.setModal(True)
dlg.exec()

return False, [], [], [], [], [], []
return dev.InterAggregationInfo(False, [], [], [], [], [], [])

lst_br = self.circuit.get_inter_buses_branches(buses_from_set, buses_to_set)
lst_br_hvdc = self.circuit.get_inter_buses_hvdc_branches(buses_from_set, buses_to_set)

return True, lst_from, lst_to, lst_br, lst_br_hvdc, objects_from, objects_to
return dev.InterAggregationInfo(True, lst_from, lst_to, lst_br, lst_br_hvdc, objects_from, objects_to)

def get_selected_power_flow_options(self) -> sim.PowerFlowOptions:
"""
Expand Down Expand Up @@ -1286,20 +1274,19 @@ def run_available_transfer_capacity(self):
threshold = self.ui.atcThresholdSpinBox.value()
max_report_elements = 5 # TODO: self.ui.ntcReportLimitingElementsSpinBox.value()
# available transfer capacity inter areas
(compatible_areas, lst_from, lst_to, lst_br,
lst_hvdc_br, areas_from, areas_to) = self.get_compatible_from_to_buses_and_inter_branches()
info: dev.InterAggregationInfo = self.get_compatible_from_to_buses_and_inter_branches()

if not compatible_areas:
if not info.valid:
return

idx_from = np.array([i for i, bus in lst_from])
idx_to = np.array([i for i, bus in lst_to])
idx_br = np.array([i for i, bus, sense in lst_br])
sense_br = np.array([sense for i, bus, sense in lst_br])
idx_from = info.idx_bus_from
idx_to = info.idx_bus_to
idx_br = info.idx_branches
sense_br = info.sense_branches

# HVDC
idx_hvdc_br = np.array([i for i, bus, sense in lst_hvdc_br])
sense_hvdc_br = np.array([sense for i, bus, sense in lst_hvdc_br])
idx_hvdc_br = info.idx_hvdc
sense_hvdc_br = info.sense_hvdc

if self.ui.usePfValuesForAtcCheckBox.isChecked():
pf_results = self.session.power_flow
Expand Down Expand Up @@ -1401,21 +1388,19 @@ def run_available_transfer_capacity_ts(self, use_clustering=False):
max_report_elements = 5 # TODO: self.ui.ntcReportLimitingElementsSpinBox.value()

# available transfer capacity inter areas
(compatible_areas,
lst_from, lst_to, lst_br,
lst_hvdc_br, areas_from, areas_to) = self.get_compatible_from_to_buses_and_inter_branches()
info: dev.InterAggregationInfo = self.get_compatible_from_to_buses_and_inter_branches()

if not compatible_areas:
if not info.valid:
return

idx_from = np.array([i for i, bus in lst_from])
idx_to = np.array([i for i, bus in lst_to])
idx_br = np.array([i for i, bus, sense in lst_br])
sense_br = np.array([sense for i, bus, sense in lst_br])
idx_from = info.idx_bus_from
idx_to = info.idx_bus_to
idx_br = info.idx_branches
sense_br = info.sense_branches

# HVDC
idx_hvdc_br = np.array([i for i, bus, sense in lst_hvdc_br])
sense_hvdc_br = np.array([sense for i, bus, sense in lst_hvdc_br])
idx_hvdc_br = info.idx_hvdc
sense_hvdc_br = info.sense_hvdc

if self.ui.usePfValuesForAtcCheckBox.isChecked():
pf_results = self.session.power_flow_ts
Expand Down Expand Up @@ -1535,19 +1520,19 @@ def run_continuation_power_flow(self):

if self.ui.atcRadioButton.isChecked():
use_alpha = True
compatible_areas, lst_from, lst_to, lst_br, lst_hvdc_br, areas_from, areas_to = self.get_compatible_from_to_buses_and_inter_branches()
info: dev.InterAggregationInfo = self.get_compatible_from_to_buses_and_inter_branches()

if compatible_areas:
idx_from = [i for i, bus in lst_from]
idx_to = [i for i, bus in lst_to]
if info.valid:
idx_from = info.idx_bus_from
idx_to = info.idx_bus_to

alpha_vec[idx_from] *= 2
alpha_vec[idx_to] *= -2
sel_bus_idx = np.zeros(0, dtype=int) # for completeness

# HVDC
idx_hvdc_br = np.array([i for i, bus, sense in lst_hvdc_br])
sense_hvdc_br = np.array([sense for i, bus, sense in lst_hvdc_br])
idx_hvdc_br = info.idx_hvdc
sense_hvdc_br = info.sense_hvdc
else:
sel_bus_idx = np.zeros(0, dtype=int) # for completeness
# incompatible areas...exit
Expand Down Expand Up @@ -1897,23 +1882,17 @@ def get_opf_options(self) -> Union[None, sim.OptimalPowerFlowOptions]:

# available transfer capacity inter areas
if maximize_flows:
(compatible_areas, lst_from, lst_to,
lst_br, lst_hvdc_br, areas_from, areas_to) = self.get_compatible_from_to_buses_and_inter_branches()
idx_from = np.array([i for i, bus in lst_from])
idx_to = np.array([i for i, bus in lst_to])
inter_aggregation_info: dev.InterAggregationInfo = self.get_compatible_from_to_buses_and_inter_branches()

if len(idx_from) == 0:
if len(inter_aggregation_info.lst_from) == 0:
error_msg('The area "from" has no buses!')
return None

if len(idx_to) == 0:
if len(inter_aggregation_info.lst_to) == 0:
error_msg('The area "to" has no buses!')
return None
else:
idx_from = None
idx_to = None
areas_from = None
areas_to = None
inter_aggregation_info = None

ips_method = self.ips_solvers_dict[self.ui.ips_method_comboBox.currentText()]
ips_tolerance = 1.0 / (10.0 ** self.ui.ips_tolerance_spinBox.value())
Expand All @@ -1932,10 +1911,7 @@ def get_opf_options(self) -> Union[None, sim.OptimalPowerFlowOptions]:
skip_generation_limits=skip_generation_limits,
lodf_tolerance=lodf_tolerance,
maximize_flows=maximize_flows,
area_from_bus_idx=idx_from,
area_to_bus_idx=idx_to,
areas_from=areas_from,
areas_to=areas_to,
inter_aggregation_info=inter_aggregation_info,
unit_commitment=unit_commitment,
export_model_fname=export_model_fname,
generate_report=generate_report,
Expand Down Expand Up @@ -2112,16 +2088,15 @@ def get_opf_ntc_options(self) -> Union[None, sim.OptimalNetTransferCapacityOptio
"""

# available transfer capacity inter areas
(compatible_areas, lst_from, lst_to, lst_br,
lst_hvdc_br, areas_from, areas_to) = self.get_compatible_from_to_buses_and_inter_branches()
info: dev.InterAggregationInfo = self.get_compatible_from_to_buses_and_inter_branches()

if not compatible_areas:
if not info.valid:
error_msg('There are no compatible areas')
return None

idx_from = np.array([i for i, bus in lst_from])
idx_to = np.array([i for i, bus in lst_to])
idx_br = np.array([i for i, bus, sense in lst_br])
idx_from = info.idx_bus_from
idx_to = info.idx_bus_to
idx_br = info.idx_branches

if len(idx_from) == 0:
error_msg('The area "from" has no buses!')
Expand Down
8 changes: 4 additions & 4 deletions src/GridCalEngine/Compilers/circuit_to_newton_pa.py
Original file line number Diff line number Diff line change
Expand Up @@ -1257,11 +1257,11 @@ def get_newton_pa_linear_opf_options(opf_opt: OptimalPowerFlowOptions,
opt.use_ramp_constraints = False
opt.lodf_threshold = opf_opt.lodf_tolerance

if opf_opt.areas_from is not None:
opt.areas_from = [area_dict[e] for e in opf_opt.areas_from]
if opf_opt.aggregations_from is not None:
opt.areas_from = [area_dict[e] for e in opf_opt.aggregations_from]

if opf_opt.areas_to is not None:
opt.areas_to = [area_dict[e] for e in opf_opt.areas_to]
if opf_opt.aggregations_to is not None:
opt.areas_to = [area_dict[e] for e in opf_opt.aggregations_to]

return opt

Expand Down
1 change: 1 addition & 0 deletions src/GridCalEngine/Devices/Aggregation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@
from GridCalEngine.Devices.Aggregation.municipality import Municipality
from GridCalEngine.Devices.Aggregation.branch_group import BranchGroup
from GridCalEngine.Devices.Aggregation.modelling_authority import ModellingAuthority
from GridCalEngine.Devices.Aggregation.inter_aggregation_info import InterAggregationInfo
113 changes: 113 additions & 0 deletions src/GridCalEngine/Devices/Aggregation/inter_aggregation_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# GridCal
# Copyright (C) 2015 - 2024 Santiago Peñate Vera
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 3 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
from __future__ import annotations
from typing import List, Tuple, TYPE_CHECKING
import numpy as np
from GridCalEngine.Devices.Parents.editable_device import EditableDevice, DeviceType
from GridCalEngine.basic_structures import IntVec, Vec

if TYPE_CHECKING:
from GridCalEngine.Devices.Substation.bus import Bus
from GridCalEngine.Devices.Aggregation.area import Area
from GridCalEngine.Devices.Aggregation.zone import Zone
from GridCalEngine.Devices.Aggregation.country import Country


class InterAggregationInfo(EditableDevice):
"""
Class to store information of inter area, inter country, etc
"""

def __init__(self,
valid: bool,
lst_from: List[Tuple[int, Bus]],
lst_to: List[Tuple[int, Bus]],
lst_br: List[Tuple[int, object, float]],
lst_br_hvdc: List[Tuple[int, object, float]],
objects_from: List[Area | Zone | Country],
objects_to: List[Area | Zone | Country]):
"""
:param valid:
:param lst_from: list of tuples bus idx, Bus in the areas from
:param lst_to: list of tuples bus idx, Bus in the areas to
:param lst_br: List of inter area Branches (branch index, branch object, flow sense w.r.t the area exchange),
:param lst_br_hvdc: List of inter area HVDC (branch index, branch object, flow sense w.r.t the area exchange),
:param objects_from: List of areas from
:param objects_to: List of areas to
"""
EditableDevice.__init__(self,
name="",
code="",
idtag=None,
device_type=DeviceType.InterAggregationInfo)

self.valid = valid
self.lst_from = lst_from
self.lst_to = lst_to
self.lst_br = lst_br
self.lst_br_hvdc = lst_br_hvdc
self.objects_from = objects_from
self.objects_to = objects_to

@property
def idx_bus_from(self) -> IntVec:
"""
:return:
"""
return np.array([i for i, bus in self.lst_from])

@property
def idx_bus_to(self) -> IntVec:
"""
:return:
"""
return np.array([i for i, bus in self.lst_to])

@property
def idx_branches(self) -> IntVec:
"""
:return:
"""
return np.array([i for i, bus, sense in self.lst_br])

@property
def sense_branches(self) -> Vec:
"""
:return:
"""
return np.array([sense for i, bus, sense in self.lst_br])

@property
def idx_hvdc(self) -> IntVec:
"""
:return:
"""
return np.array([i for i, bus, sense in self.lst_br_hvdc])

@property
def sense_hvdc(self) -> Vec:
"""
:return:
"""
return np.array([sense for i, bus, sense in self.lst_br_hvdc])
2 changes: 2 additions & 0 deletions src/GridCalEngine/Devices/multi_circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ def get_fused_device_lst(elm_list: List[INJECTION_DEVICE_TYPES], property_names:
return list(), list()




class MultiCircuit(Assets):
"""
The concept of circuit should be easy enough to understand. It represents a set of
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,7 @@ def optimal_linear_contingency_analysis(grid: MultiCircuit,
all_generators_fixed=True,
lodf_threshold=options.lin_options.lodf_threshold,
maximize_inter_area_flow=False,
areas_from=list(),
areas_to=list(),
inter_aggregation_info=None,
energy_0=None,
fluid_level_0=None,
logger=logger,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,7 @@ def linear_opf(self, remote=False, batteries_energy_0=None):
all_generators_fixed=False,
lodf_threshold=self.opf_options.lodf_tolerance,
maximize_inter_area_flow=self.opf_options.maximize_flows,
areas_from=self.opf_options.areas_from,
areas_to=self.opf_options.areas_to,
inter_aggregation_info=self.opf_options.inter_aggregation_info,
energy_0=batteries_energy_0,
optimize_nodal_capacity=True,
nodal_capacity_sign=self.options.nodal_capacity_sign,
Expand Down
14 changes: 7 additions & 7 deletions src/GridCalEngine/Simulations/OPF/linear_opf_ts.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
This file implements a DC-OPF for time series
That means that solves the OPF problem for a complete time series at once
"""
from __future__ import annotations

import numpy as np
from typing import List, Union, Tuple, Callable
from scipy.sparse import csc_matrix

from GridCalEngine.Devices.multi_circuit import MultiCircuit
from GridCalEngine.Devices.Aggregation.area import Area
from GridCalEngine.Devices.Aggregation.inter_aggregation_info import InterAggregationInfo
from GridCalEngine.Devices.Aggregation.contingency_group import ContingencyGroup
from GridCalEngine.DataStructures.numerical_circuit import NumericalCircuit, compile_numerical_circuit_at
from GridCalEngine.DataStructures.generator_data import GeneratorData
Expand Down Expand Up @@ -1542,8 +1544,7 @@ def run_linear_opf_ts(grid: MultiCircuit,
all_generators_fixed: bool = False,
lodf_threshold: float = 0.001,
maximize_inter_area_flow: bool = False,
areas_from: List[Area] = None,
areas_to: List[Area] = None,
inter_aggregation_info: InterAggregationInfo | None = None,
energy_0: Union[Vec, None] = None,
fluid_level_0: Union[Vec, None] = None,
optimize_nodal_capacity: bool = False,
Expand All @@ -1568,8 +1569,7 @@ def run_linear_opf_ts(grid: MultiCircuit,
instead of resorting to dispatcheable status
:param lodf_threshold: LODF threshold value to consider contingencies
:param maximize_inter_area_flow: Maximize the inter-area flow?
:param areas_from: Array of areas "from"
:param areas_to: Array of areas "to"
:param inter_aggregation_info: Inter rea (or country, etc) information
:param energy_0: Vector of initial energy for batteries (size: Number of batteries)
:param fluid_level_0: initial fluid level of the nodes
:param optimize_nodal_capacity: Optimize the nodal capacity? (optional)
Expand Down Expand Up @@ -1617,8 +1617,8 @@ def run_linear_opf_ts(grid: MultiCircuit,
gen_fuel_rates_matrix = grid.get_fuel_rates_sparse_matrix()

if maximize_inter_area_flow:
inter_area_branches = grid.get_inter_areas_branches(a1=areas_from, a2=areas_to)
inter_area_hvdc = grid.get_inter_areas_hvdc_branches(a1=areas_from, a2=areas_to)
inter_area_branches = inter_aggregation_info.lst_br
inter_area_hvdc = inter_aggregation_info.lst_br_hvdc
else:
inter_area_branches = list()
inter_area_hvdc = list()
Expand Down
Loading

0 comments on commit 8458d87

Please sign in to comment.