Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions emf/common/helpers/loadflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ def get_network_elements(network: pypowsybl.network,
elements = elements.merge(_voltage_levels, left_on='voltage_level_id', right_index=True, suffixes=(None, '_voltage_level'))
elements = elements.merge(_substations, left_on='substation_id', right_index=True, suffixes=(None, '_substation'))

# Need to ensure that column 'isHvdc' is present if DANGLING_LINE type is requested
if element_type is pypowsybl.network.ElementType.DANGLING_LINE:
# Need to ensure that column 'isHvdc' is present if BOUNDARY_LINE type is requested
if element_type is pypowsybl.network.ElementType.BOUNDARY_LINE:
if 'isHvdc' not in elements.columns:
elements['isHvdc'] = ''

Expand Down Expand Up @@ -121,8 +121,8 @@ def get_model_outages(network: pypowsybl.network):
lines = lines.merge(_substations, left_on='substation_id', right_index=True, suffixes=(None, '_substation'))
lines['element_type'] = 'LINE'

dlines = get_network_elements(network, pypowsybl.network.ElementType.DANGLING_LINE).reset_index(names=['grid_id'])
dlines['element_type'] = 'DANGLING_LINE'
dlines = get_network_elements(network, pypowsybl.network.ElementType.BOUNDARY_LINE).reset_index(names=['grid_id'])
dlines['element_type'] = 'BOUNDARY_LINE'

gens = get_network_elements(network, pypowsybl.network.ElementType.GENERATOR).reset_index(names=['grid_id'])
gens['element_type'] = 'GENERATOR'
Expand Down
62 changes: 31 additions & 31 deletions emf/model_merger/merge_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,50 +300,50 @@ def ensure_paired_equivalent_injection_compatibility(network: pypowsybl.network)
Set P and Q to 0 - so that no additional consumption or production is on tie line
"""
logger.info("Configuring paired boundary points equivalent injections: p0/q0 = 0.0")
dangling_lines = network.get_dangling_lines(all_attributes=True)
paired_dangling_lines = dangling_lines[dangling_lines['paired'] == True]
if paired_dangling_lines.empty:
logger.warning(f"No paired dangling lines found in network model")
boundary_lines = network.get_boundary_lines(all_attributes=True)
paired_boundary_lines = boundary_lines[boundary_lines['paired'] == True]
if paired_boundary_lines.empty:
logger.warning(f"No paired boundary lines found in network model")
return network

# Set p0/q0 to 0 for all paired dangling lines
_updated_p0 = pd.Series(0, index=paired_dangling_lines.index)
_updated_q0 = pd.Series(0, index=paired_dangling_lines.index)
network.update_dangling_lines(id=paired_dangling_lines.index, p0=_updated_p0, q0=_updated_q0)
# Set p0/q0 to 0 for all paired boundary lines
_updated_p0 = pd.Series(0, index=paired_boundary_lines.index)
_updated_q0 = pd.Series(0, index=paired_boundary_lines.index)
network.update_boundary_lines(id=paired_boundary_lines.index, p0=_updated_p0, q0=_updated_q0)

return network


def ensure_paired_boundary_line_connectivity(network: pypowsybl.network):
logger.info("Aligning paired boundary lines connection status")
dangling_lines = network.get_dangling_lines(all_attributes=True)
# Add cim:Tieflow attribute to dangling lines
dangling_lines['isTieflow'] = dangling_lines.index.isin(network.get_areas_boundaries()["element"])
paired_dangling_lines = dangling_lines[dangling_lines['paired'] == True]
if paired_dangling_lines.empty:
logger.warning(f"No paired dangling lines found in network model")
boundary_lines = network.get_boundary_lines(all_attributes=True)
# Add cim:Tieflow attribute to boundary lines
boundary_lines['isTieflow'] = boundary_lines.index.isin(network.get_areas_boundaries()["element"])
paired_boundary_lines = boundary_lines[boundary_lines['paired'] == True]
if paired_boundary_lines.empty:
logger.warning(f"No paired boundary lines found in network model")
return network

# Identify dangling line pairs where the 'connected' status is inconsistent within each pairing_key group
group = paired_dangling_lines.groupby('pairing_key')
# Identify boundary lines pairs where the 'connected' status is inconsistent within each pairing_key group
group = paired_boundary_lines.groupby('pairing_key')
mask_connected = group['connected'].transform(lambda s: s.nunique() > 1)
mask_tieflow = group['isTieflow'].transform( lambda s: s.nunique() > 1)

mismatched_dangling_lines_con = paired_dangling_lines[mask_connected]
logger.info(f"Boundary lines with non-matching connection status: {mismatched_dangling_lines_con['pairing_key'].unique().tolist()}")
mismatched_boundary_lines_con = paired_boundary_lines[mask_connected]
logger.info(f"Boundary lines with non-matching connection status: {mismatched_boundary_lines_con['pairing_key'].unique().tolist()}")

mismatched_dangling_lines_tie = paired_dangling_lines[mask_tieflow]
logger.info(f"Boundary lines with non-matching cim:Tieflow: {mismatched_dangling_lines_tie['pairing_key'].unique().tolist()}")
mismatched_boundary_lines_tie = paired_boundary_lines[mask_tieflow]
logger.info(f"Boundary lines with non-matching cim:Tieflow: {mismatched_boundary_lines_tie['pairing_key'].unique().tolist()}")

mismatched_dangling_lines = pd.concat([mismatched_dangling_lines_con, mismatched_dangling_lines_tie])
mismatched_boundary_lines = pd.concat([mismatched_boundary_lines_con, mismatched_boundary_lines_tie])

# Set all mismatched lines to disconnected (False)
_connected = pd.Series(data=False, index=mismatched_dangling_lines.index)
network.update_dangling_lines(id=mismatched_dangling_lines.index, connected=_connected)
_connected = pd.Series(data=False, index=mismatched_boundary_lines.index)
network.update_boundary_lines(id=mismatched_boundary_lines.index, connected=_connected)

# Log each change
for i, row in mismatched_dangling_lines.iterrows():
logger.info(f"Changed status of dangling line {row['name']}: {row['connected']} -> False")
for i, row in mismatched_boundary_lines.iterrows():
logger.info(f"Changed status of boundary line {row['name']}: {row['connected']} -> False")

return network

Expand Down Expand Up @@ -598,17 +598,17 @@ def update_model_outages(merged_model: object, tso_list: list, scenario_datetime
model_outage_areas = [model_area_map.get(item, item) for item in tso_list]
filtered_model_outages = mapped_model_outages[mapped_model_outages['country'].isin(model_outage_areas)]

# Include cross-border lines for reconnection (both dangling lines)
dangling_lines = get_network_elements(network=merged_model.network,
element_type=pypowsybl.network.ElementType.DANGLING_LINE).reset_index(names=['grid_id'])
border_lines = dangling_lines[dangling_lines['pairing_key'].isin(model_outages['pairing_key'])]
# Include cross-border lines for reconnection (both boundary lines)
boundary_lines = get_network_elements(network=merged_model.network,
element_type=pypowsybl.network.ElementType.BOUNDARY_LINE).reset_index(names=['grid_id'])
border_lines = boundary_lines[boundary_lines['pairing_key'].isin(model_outages['pairing_key'])]
relevant_border_lines = border_lines[border_lines['country'].isin(model_outage_areas)]
# Removing any BRELL lines
relevant_border_lines = relevant_border_lines[~relevant_border_lines['lineEnergyIdentificationCodeEIC'].str.contains('RU')]
additional_dangling_lines = dangling_lines[dangling_lines['pairing_key'].isin(relevant_border_lines['pairing_key'])]
additional_boundary_lines = boundary_lines[boundary_lines['pairing_key'].isin(relevant_border_lines['pairing_key'])]

# Merged dataframe of network elements to be reconnected
filtered_model_outages = pd.concat([filtered_model_outages, additional_dangling_lines]).drop_duplicates(subset='grid_id')
filtered_model_outages = pd.concat([filtered_model_outages, additional_boundary_lines]).drop_duplicates(subset='grid_id')
filtered_model_outages = filtered_model_outages.where(pd.notnull(filtered_model_outages), None)

# rename columns
Expand Down
18 changes: 10 additions & 8 deletions emf/model_merger/model_merger.py
Original file line number Diff line number Diff line change
Expand Up @@ -623,14 +623,12 @@ def handle(self, task_object: dict, properties: dict, **kwargs):
"job_period_start": "2024-05-24T22:00:00+00:00",
"job_period_end": "2024-05-25T06:00:00+00:00",
"task_properties": {
"timestamp_utc": "2026-07-01T15:30:00+00:00",
"timestamp_utc": "2026-07-13T15:30:00+00:00",
"merge_type": "BA",
"merging_entity": "BALTICRCC",
# "included": ["PSE", "LITGRID", "ELERING", "AST"],
"included": ["PSE", "LITGRID", "AST"],
# "included": [],
"included": ["PSE", "LITGRID", "ELERING", "AST"],
"excluded": [],
"local_import": ["ELERING"],
"local_import": [],
"replace_tso": [],
"time_horizon": "1D",
"version": "000",
Expand All @@ -641,11 +639,15 @@ def handle(self, task_object: dict, properties: dict, **kwargs):
"outage_update": "True",
"force_outage_fix": "False",
"upload_to_opdm": "False",
"upload_to_minio": "False",
"send_merge_report": "False",
"upload_to_minio": "True",
"send_merge_report": "True",
"lvl8_reporting": "False"
}
}
class Properties(dict):
pass
properties = Properties()
properties.headers = {}

worker = HandlerMergeModels()
finished_task = worker.handle(sample_task, {})
finished_task = worker.handle(sample_task, {})
Loading
Loading