From 7dc33968ead340832c67a5e8ca1155a220f56982 Mon Sep 17 00:00:00 2001 From: Leif Warland Date: Tue, 21 Jun 2022 07:43:30 +0200 Subject: [PATCH] Add function to combine common subjects with ';' --- cimsparql/cim.py | 1 + cimsparql/line_queries.py | 42 +++--- cimsparql/queries.py | 226 +++++++++++++++++++----------- cimsparql/query_support.py | 94 +++++++------ cimsparql/transformer_windings.py | 21 +-- tests/test_query_support.py | 26 +--- 6 files changed, 230 insertions(+), 180 deletions(-) diff --git a/cimsparql/cim.py b/cimsparql/cim.py index 7c342f25..cf4d1a45 100644 --- a/cimsparql/cim.py +++ b/cimsparql/cim.py @@ -12,5 +12,6 @@ SUBSTATION = "cim:VoltageLevel.Substation" SYNC_MACH = "cim:SynchronousMachine" TC_EQUIPMENT = "cim:Terminal.ConductingEquipment" +T_SEQUENCE = "cim:Terminal.sequenceNumber" TC_NODE = "cim:Terminal.ConnectivityNode" TR_WINDING = "cim:PowerTransformerEnd" diff --git a/cimsparql/line_queries.py b/cimsparql/line_queries.py index 1d2c15de..84f1a56e 100644 --- a/cimsparql/line_queries.py +++ b/cimsparql/line_queries.py @@ -1,6 +1,4 @@ -from functools import reduce -from operator import iconcat -from typing import Iterable, List, Literal, Optional, Tuple, Union +from typing import Iterable, List, Optional, Tuple, Union import cimsparql.query_support as sup from cimsparql.cim import ACLINE, CNODE_CONTAINER, EQUIP_CONTAINER, ID_OBJ, SUBSTATION @@ -10,12 +8,7 @@ def _sv_terminal_injection(nr: int) -> str: - return "\n".join( - [ - f"?sv_t_{nr} cim:SvPowerFlow.Terminal ?_t_mrid_{nr}.", - f"?sv_t_{nr} cim:SvPowerFlow.p ?sv_p_{nr}", - ] - ) + return f"?sv_t_{nr} cim:SvPowerFlow.Terminal ?_t_mrid_{nr};cim:SvPowerFlow.p ?sv_p_{nr}" def _line_query( @@ -24,10 +17,10 @@ def _line_query( connectivity: Optional[str], nodes: Optional[str], with_loss: bool, - rates: Iterable[Literal["Normal", "Warning", "Overload"]], + rates: Iterable[Rates], network_analysis: bool, with_market: bool, - impedance: Iterable[str], + impedance: Iterable[Impedance], ) -> Tuple[List[str], List[str], str]: mrid_subject = "?_mrid" name = "?name" @@ -38,14 +31,18 @@ def _line_query( if connectivity: variables.extend(sup.sequence_variables(connectivity)) - impedance_properties = {z: f"?{z}" for z in impedance} where_list = [ - f"{mrid_subject} {ID_OBJ}.mRID ?mrid", - sup.rdf_type_tripler(mrid_subject, line_type), - sup.get_name(mrid_subject, name), - sup.base_voltage(mrid_subject, "?un"), + sup.common_subject( + mrid_subject, + [ + f"{ID_OBJ}.mRID ?mrid", + sup.rdf_type_tripler("", line_type), + sup.get_name("", name), + sup.base_voltage("", "?un"), + *sup.predicate_list("", line_type, {z: f"?{z}" for z in impedance}), + ], + ), *sup.terminal_sequence_query(cim_version, connectivity, nodes, mrid_subject), - *sup.predicate_list(mrid_subject, line_type, impedance_properties), ] sup.include_market(with_market, variables, where_list) @@ -62,9 +59,7 @@ def _line_query( if rates: limit_type = "ActivePowerLimit" if line_type == ACLINE else "CurrentLimit" variables.extend([f"?rate{rate}" for rate in rates]) - where_rate: List[str] = reduce( - iconcat, [sup.operational_limit(mrid_subject, rate, limit_type) for rate in rates], [] - ) + where_rate = [sup.operational_limit(mrid_subject, rate, limit_type) for rate in rates] where_list.append(sup.group_query(where_rate, command="OPTIONAL")) return variables, where_list, mrid_subject @@ -173,9 +168,10 @@ def borders_query( border_filter = sup.border_filter(region, *areas) where_list = [ - f"{mrid_subject} {ID_OBJ}.mRID ?mrid", - sup.get_name(mrid_subject, name), - sup.rdf_type_tripler(mrid_subject, ACLINE), + sup.common_subject( + mrid_subject, + [f"{ID_OBJ}.mRID ?mrid", sup.get_name("", name), sup.rdf_type_tripler("", ACLINE)], + ), *sup.terminal_sequence_query(cim_version, "con", nodes, mrid_subject), sup.combine_statements(*border_filter, group=True, split=union_split), ] diff --git a/cimsparql/queries.py b/cimsparql/queries.py index 169e0993..6f2a7206 100644 --- a/cimsparql/queries.py +++ b/cimsparql/queries.py @@ -8,6 +8,7 @@ ID_OBJ, SUBSTATION, SYNC_MACH, + T_SEQUENCE, TC_EQUIPMENT, TC_NODE, TR_WINDING, @@ -22,6 +23,7 @@ SyncVars, TapChangerObjects, ) +from cimsparql.query_support import common_subject from cimsparql.transformer_windings import number_of_windings, terminal, transformer_common from cimsparql.typehints import Region @@ -30,10 +32,15 @@ def version_date() -> str: name: str = "?name" variables = ["?mrid", name, "?activationDate"] where_list = [ - sup.rdf_type_tripler("?marketDefinitionSet", "SN:MarketDefinitionSet"), - f"?marketDefinitionSet {ID_OBJ}.mRID ?mrid", - sup.get_name("?marketDefinitionSet", name), - "?marketDefinitionSet SN:MarketDefinitionSet.activationDate ?activationDate", + common_subject( + "?marketDefinitionSet", + [ + sup.rdf_type_tripler("", "SN:MarketDefinitionSet"), + f"{ID_OBJ}.mRID ?mrid", + sup.get_name("", name), + "SN:MarketDefinitionSet.activationDate ?activationDate", + ], + ), f"FILTER regex({name}, 'ScheduleResource')", ] return sup.combine_statements(sup.select_statement(variables), sup.group_query(where_list)) @@ -44,10 +51,15 @@ def regions_query() -> str: variables = ["?mrid", "?shortName"] region_variable = "?subgeoreg" where_list = [ - f"{mrid_subject} {ID_OBJ}.mRID ?mrid", - sup.rdf_type_tripler(mrid_subject, GEO_REG), - f"{mrid_subject} SN:IdentifiedObject.shortName ?shortName", - f"{mrid_subject} {GEO_REG}.Region {region_variable}", + common_subject( + mrid_subject, + [ + f"{ID_OBJ}.mRID ?mrid", + sup.rdf_type_tripler("", GEO_REG), + "SN:IdentifiedObject.shortName ?shortName", + f"{GEO_REG}.Region {region_variable}", + ], + ) ] names = {mrid_subject: ["?name", "?alias_name"], region_variable: ["?region", "?region_name"]} for name_mrid, (name, alias_name) in names.items(): @@ -68,40 +80,59 @@ def phase_tap_changer_query( variables = ["?mrid", "?w_mrid_1", "?w_mrid_2", "?t_mrid_1", "?t_mrid_2"] tap = "?tap" where_list = [ - f"{mrid_subject} {ID_OBJ}.mRID ?mrid", - sup.rdf_type_tripler(mrid_subject, TR_WINDING), - f"{mrid_subject} cim:TransformerEnd.PhaseTapChanger {tap}", - f"{mrid_subject} {TR_WINDING}.PowerTransformer ?pt", + common_subject( + mrid_subject, + [ + f"{ID_OBJ}.mRID ?mrid", + sup.rdf_type_tripler("", TR_WINDING), + "cim:TransformerEnd.PhaseTapChanger ?_tap", + f"{TR_WINDING}.PowerTransformer ?pt", + ], + ) ] if with_tap_changer_values: variables.extend([tap, "?phase_incr"] + sup.to_variables(tap_changer_objects)) properties = {f"{obj}Step": f"?{obj}" for obj in tap_changer_objects} - where_list.extend( - [ - *sup.predicate_list(tap, "cim:TapChanger", properties), - "?tap cim:PhaseTapChangerLinear.stepPhaseShiftIncrement ?phase_incr", - ] + where_list.append( + common_subject( + "?_tap", + [ + *sup.predicate_list("", "cim:TapChanger", properties), + "cim:PhaseTapChangerLinear.stepPhaseShiftIncrement ?phase_incr", + f"cim:IdentifiedObject.mRID {tap}", + ], + ) ) if impedance: variables.extend(sup.to_variables(impedance)) - where_list.extend([f"?w_mrid_1 {TR_WINDING}.{imp} ?{imp}" for imp in impedance]) + where_list.append( + common_subject("?_w_mrid_1", [f"{TR_WINDING}.{imp} ?{imp}" for imp in impedance]) + ) if region: where_list.extend([f"?pt {EQUIP_CONTAINER} ?Substation"]) where_list.extend(sup.region_query(region, sub_region, "Substation")) for i in sequence_numbers: - where_list.extend( + w_mrid = common_subject( + f"?_w_mrid_{i}", [ - sup.rdf_type_tripler(f"?term_{i}", "cim:Terminal"), - f"?w_mrid_{i} {TR_WINDING}.PowerTransformer ?pt", - f"?w_mrid_{i} cim:TransformerEnd.Terminal ?term_{i}", - f"?term_{i} cim:Terminal.sequenceNumber {i}", - f"?term_{i} {ID_OBJ}.mRID ?t_mrid_{i}", - ] + f"{TR_WINDING}.PowerTransformer ?pt", + f"cim:TransformerEnd.Terminal ?term_{i}", + f"cim:IdentifiedObject.mRID ?w_mrid_{i}", + ], + ) + t_mrid = common_subject( + f"?term_{i}", + [ + sup.rdf_type_tripler("", "cim:Terminal"), + f"cim:Terminal.sequenceNumber {i}", + f"{ID_OBJ}.mRID ?t_mrid_{i}", + ], ) + where_list.extend([w_mrid, t_mrid]) return sup.combine_statements(sup.select_statement(variables), sup.group_query(where_list)) @@ -118,13 +149,18 @@ def connectivity_names(mrid_subject: str, name: str = "?name") -> str: def full_model() -> str: variables = ["?model", "?time", "?profile", "?description", "?version", "?created", "?dependon"] where_list = [ - sup.rdf_type_tripler("?model", "md:FullModel"), - "?model md:Model.profile ?profile", - "?model md:Model.scenarioTime ?time", - "?model md:Model.description ?description", - "?model md:Model.version ?version", - "?model md:Model.created ?created", - "?model md:Model.DependentOn ?dependon", + common_subject( + "?model", + [ + sup.rdf_type_tripler("", "md:FullModel"), + "md:Model.profile ?profile", + "md:Model.scenarioTime ?time", + "md:Model.description ?description", + "md:Model.version ?version", + "md:Model.created ?created", + "md:Model.DependentOn ?dependon", + ], + ), "?dependon rdf:type md:FullModel", ] return sup.combine_statements(sup.select_statement(variables), sup.group_query(where_list)) @@ -136,12 +172,16 @@ def bus_data(region: Region, sub_region: bool, with_market: bool = True) -> str: variables = [f"({mrid_subject} as ?mrid)", "?name", bus_name, "?un"] where_list = [ - sup.rdf_type_tripler(mrid_subject, "cim:TopologicalNode"), - sup.get_name(mrid_subject, bus_name), - f"{mrid_subject} cim:TopologicalNode.BaseVoltage/cim:BaseVoltage.nominalVoltage ?un", - f"{mrid_subject} cim:TopologicalNode.ConnectivityNodeContainer ?cont", - f"?cont {ID_OBJ}.aliasName ?name", - f"?cont {SUBSTATION} ?Substation", + common_subject( + mrid_subject, + [ + sup.rdf_type_tripler("", "cim:TopologicalNode"), + sup.get_name("", bus_name), + "cim:TopologicalNode.BaseVoltage/cim:BaseVoltage.nominalVoltage ?un", + "cim:TopologicalNode.ConnectivityNodeContainer ?cont", + ], + ), + common_subject("?cont", [f"{ID_OBJ}.aliasName ?name", f"{SUBSTATION} ?Substation"]), ] if with_market: variables.append("?bidzone") @@ -157,11 +197,15 @@ def three_winding_dummy_bus(region: Region, sub_region: bool) -> str: name = "?name" variables = ["?mrid", name, f"({name} as ?busname)", "?un"] where_list = [ - f"?p_mrid {ID_OBJ}.mRID ?mrid", - "?w_mrid cim:TransformerEnd.endNumber 1", - f"?w_mrid {TR_WINDING}.ratedU ?un", - f"?w_mrid {TR_WINDING}.PowerTransformer ?p_mrid", - sup.get_name("?p_mrid", name), + common_subject("?p_mrid", [f"{ID_OBJ}.mRID ?mrid", sup.get_name("", name)]), + common_subject( + "?w_mrid", + [ + "cim:TransformerEnd.endNumber 1", + f"{TR_WINDING}.ratedU ?un", + f"{TR_WINDING}.PowerTransformer ?p_mrid", + ], + ), number_of_windings("?p_mrid", 3), ] if region: @@ -204,7 +248,7 @@ def load_query( f"{mrid_subject} {ID_OBJ}.aliasName ?name", f"?_t_mrid {ID_OBJ}.mRID ?t_mrid", sup.combine_statements(*cim_types, group=len(cim_types) > 1, split=union_split), - *sup.terminal_where_query( + sup.terminal_where_query( cim_version, connectivity, nodes, mrid_subject, with_sequence_number ), *sup.bid_market_code_query(mrid_subject), @@ -301,7 +345,7 @@ def _power(mrid: str, sync_vars: Iterable[str], op: Callable[[str, str], bool]) sup.get_name(mrid_subject, name), sup.rdf_type_tripler(mrid_subject, SYNC_MACH), *sup.bid_market_code_query(mrid_subject), - *sup.terminal_where_query( + sup.terminal_where_query( cim_version, connectivity, nodes, mrid_subject, with_sequence_number ), *[ @@ -320,15 +364,25 @@ def _power(mrid: str, sync_vars: Iterable[str], op: Callable[[str, str], bool]) station_group = [ f"{mrid_subject} cim:SynchronousMachine.GeneratingUnit ?gu", - "?gu SN:GeneratingUnit.marketCode ?market_code", - "?gu cim:GeneratingUnit.maxOperatingP ?maxP", - "?gu cim:GeneratingUnit.minOperatingP ?minP", - "?gu SN:GeneratingUnit.groupAllocationMax ?apctmax", + common_subject( + "?gu", + [ + "SN:GeneratingUnit.marketCode ?market_code", + "cim:GeneratingUnit.maxOperatingP ?maxP", + "cim:GeneratingUnit.minOperatingP ?minP", + "SN:GeneratingUnit.groupAllocationMax ?apctmax", + "SN:GeneratingUnit.groupAllocationWeight ?allocationWeight", + "SN:GeneratingUnit.ScheduleResource ?ScheduleResource", + ], + ), + common_subject( + "?ScheduleResource", + [ + "SN:ScheduleResource.marketCode ?station_group", + sup.get_name("", "?st_gr_n", alias=True), + ], + ), "bind(xsd:float(str(?apctmax))*xsd:float(str(?maxP)) / 100.0 as ?allocationmax)", - "?gu SN:GeneratingUnit.groupAllocationWeight ?allocationWeight", - "?gu SN:GeneratingUnit.ScheduleResource ?ScheduleResource", - "?ScheduleResource SN:ScheduleResource.marketCode ?station_group", - sup.get_name("?ScheduleResource", "?st_gr_n", alias=True), ] if station_group_optional: @@ -364,18 +418,22 @@ def wind_generating_unit_query(network_analysis: bool) -> str: "?plant_mrid", ] where_list = [ - f"{mrid_subject} {ID_OBJ}.mRID ?mrid", - sup.rdf_type_tripler(mrid_subject, "cim:WindGeneratingUnit"), - f"{mrid_subject} cim:GeneratingUnit.maxOperatingP ?maxP", - f"{mrid_subject} SN:GeneratingUnit.marketCode ?market_code", - f"{mrid_subject} cim:GeneratingUnit.minOperatingP ?minP", - sup.get_name(mrid_subject, name), - f"{mrid_subject} SN:WindGeneratingUnit.WindPowerPlant ?plant_mrid", - f"{mrid_subject} SN:GeneratingUnit.groupAllocationMax ?apctmax", + common_subject( + mrid_subject, + [ + f"{ID_OBJ}.mRID ?mrid", + sup.rdf_type_tripler("", "cim:WindGeneratingUnit"), + "cim:GeneratingUnit.maxOperatingP ?maxP", + "SN:GeneratingUnit.marketCode ?market_code", + "cim:GeneratingUnit.minOperatingP ?minP", + sup.get_name("", name), + "SN:WindGeneratingUnit.WindPowerPlant ?plant_mrid", + "SN:GeneratingUnit.groupAllocationMax ?apctmax", + "SN:GeneratingUnit.groupAllocationWeight ?allocationWeight", + "SN:GeneratingUnit.ScheduleResource/SN:ScheduleResource.marketCode ?station_group", + ], + ), "bind(xsd:float(str(?apctmax))*xsd:float(str(?maxP)) / 100.0 as ?allocationmax)", - f"{mrid_subject} SN:GeneratingUnit.groupAllocationWeight ?allocationWeight", - f"{mrid_subject} SN:GeneratingUnit.ScheduleResource ?sr", - "?sr SN:ScheduleResource.marketCode ?station_group", ] if network_analysis: @@ -478,17 +536,23 @@ def transformer_query( variables = [name, "?p_mrid", "?w_mrid", "?endNumber", "?un", "?t_mrid"] variables.extend(sup.to_variables(impedance)) + w_mrid = common_subject( + "?_w_mrid", + [ + f"{ID_OBJ}.mRID ?w_mrid", + f"{ID_OBJ}.name {name}", + f"{TR_WINDING}.PowerTransformer {mrid_subject}", + f"{TR_WINDING}.ratedU ?un", + "cim:TransformerEnd.endNumber ?endNumber", + "cim:TransformerEnd.Terminal ?_t_mrid", + *sup.predicate_list("", TR_WINDING, {z: f"?{z}" for z in impedance}), + ], + ) where_list = [ f"{mrid_subject} {ID_OBJ}.mRID ?p_mrid", - f"?_w_mrid {ID_OBJ}.mRID ?w_mrid", - f"?_t_mrid {ID_OBJ}.mRID ?t_mrid", sup.rdf_type_tripler(mrid_subject, "cim:PowerTransformer"), - f"?_w_mrid {TR_WINDING}.PowerTransformer {mrid_subject}", - f"?_w_mrid {TR_WINDING}.ratedU ?un", - "?_w_mrid cim:TransformerEnd.endNumber ?endNumber", - "?_w_mrid cim:TransformerEnd.Terminal ?_t_mrid", - f"?_w_mrid {ID_OBJ}.name {name}", - *sup.predicate_list("?_w_mrid", TR_WINDING, {z: f"?{z}" for z in impedance}), + f"?_t_mrid {ID_OBJ}.mRID ?t_mrid", + w_mrid, ] if with_market: variables.append("?bidzone") @@ -509,7 +573,7 @@ def transformer_query( where_rate = [] for rate in rates: variables.append(f"?rate{rate}") - where_rate.extend(sup.operational_limit(mrid_subject, rate)) + where_rate.append(sup.operational_limit(mrid_subject, rate)) where_list.append(sup.group_query(where_rate, command="OPTIONAL")) return sup.combine_statements(sup.select_statement(variables), sup.group_query(where_list)) @@ -558,19 +622,17 @@ def converters( if sequence_numbers: for num in sequence_numbers: + t_mrid = f"?t_mrid_{num}" + t_mrid_ref = f"?_t_mrid_{num}" + sequence_where = [f"{TC_EQUIPMENT} {mrid_subject}", f"{T_SEQUENCE} {num}"] if nodes: node = f"?{nodes}" + (f"_{num}" if len(sequence_numbers) > 1 else "") - sup.node_list(node, where_list, cim_version=16, mrid=f"?_t_mrid_{num}") + sup.node_list(node, where_list, cim_version=16, mrid=t_mrid_ref) variables.append(node) else: - variables.append(f"?t_mrid_{num}") - where_list.extend( - [ - f"?_t_mrid_{num} {TC_EQUIPMENT} {mrid_subject}", - f"?_t_mrid_{num} cim:Terminal.sequenceNumber {num}", - f"?_t_mrid_{num} {ID_OBJ}.mRID ?t_mrid_{num}", - ] - ) + variables.append(t_mrid) + sequence_where.append(f"{ID_OBJ}.mRID {t_mrid}") + where_list.append(common_subject(t_mrid_ref, sequence_where)) if region: container = "Substation" diff --git a/cimsparql/query_support.py b/cimsparql/query_support.py index f2a99bb2..55f107cf 100644 --- a/cimsparql/query_support.py +++ b/cimsparql/query_support.py @@ -26,34 +26,37 @@ def base_voltage(mrid: str, var: str) -> str: def node_list(node: str, query_list: List[str], cim_version: int, mrid: str) -> None: - query_list.extend( - [ - f"{mrid} cim:{acdc_terminal(cim_version)}.connected 'true'", - f"{mrid} cim:Terminal.TopologicalNode {node}", - ] + query_list.append( + common_subject( + mrid, + [ + f"cim:{acdc_terminal(cim_version)}.connected 'true'", + f"cim:Terminal.TopologicalNode {node}", + ], + ) ) def terminal_sequence_query( cim_version: int, con: Optional[str], nodes: Optional[str], mrid_subject: str ) -> List[str]: - query_list = [] - for nr in sequence_numbers: - t_sequence_mrid = f"?_t_mrid_{nr}" - query_list.extend( - [ - f"{t_sequence_mrid} {ID_OBJ}.mRID ?t_mrid_{nr}", - rdf_type_tripler(t_sequence_mrid, "cim:Terminal"), - f"{t_sequence_mrid} {TC_EQUIPMENT} {mrid_subject}", - f"{t_sequence_mrid} cim:{acdc_terminal(15)}.sequenceNumber {nr}", - ] - ) + def _term_seq_nr( + cim_version: int, con: Optional[str], nodes: Optional[str], mrid_subject: str, nr: int + ) -> str: + where_list = [ + f"{ID_OBJ}.mRID ?t_mrid_{nr}", + rdf_type_tripler("", "cim:Terminal"), + f"{TC_EQUIPMENT} {mrid_subject}", + f"cim:{acdc_terminal(15)}.sequenceNumber {nr}", + ] if con: - query_list.append(f"{t_sequence_mrid} {TC_NODE} ?{con}_{nr}") + where_list.append(f"{TC_NODE} ?{con}_{nr}") if nodes: - node_list(f"?{nodes}_{nr}", query_list, cim_version, t_sequence_mrid) - query_list.append(f"{t_sequence_mrid} cim:ACDCTerminal.connected 'true'") - return query_list + node_list(f"?{nodes}_{nr}", where_list, cim_version, "") + where_list.append("cim:ACDCTerminal.connected 'true'") + return common_subject(f"?_t_mrid_{nr}", where_list) + + return [_term_seq_nr(cim_version, con, nodes, mrid_subject, nr) for nr in sequence_numbers] def operational_limit( @@ -61,14 +64,16 @@ def operational_limit( rate: str, limit_type: Literal["ActivePowerLimit", "CurrentLimit"] = "ActivePowerLimit", limit_set: Literal["Terminal", "Equipment"] = "Equipment", -) -> List[str]: - equip_predicate = f"{OPERATIONAL_LIMIT_SET}/cim:OperationalLimitSet.{limit_set}" - return [ - f"?p_lim{rate} {equip_predicate} {mrid}", - rdf_type_tripler(f"?p_lim{rate}", f"cim:{limit_type}"), - f"?p_lim{rate} {ID_OBJ}.name '{rate}@20'", - f"?p_lim{rate} cim:{limit_type}.value ?rate{rate}", - ] +) -> str: + return common_subject( + f"?p_lim{rate}", + [ + f"{OPERATIONAL_LIMIT_SET}/cim:OperationalLimitSet.{limit_set} {mrid}", + rdf_type_tripler("", f"cim:{limit_type}"), + f"{ID_OBJ}.name '{rate}@20'", + f"cim:{limit_type}.value ?rate{rate}", + ], + ) def region_name_query(region: str, sub_region: bool, geographical_region: str) -> str: @@ -132,26 +137,21 @@ def terminal_where_query( node: Optional[str], mrid_subject: str, with_sequence_number: bool = False, -) -> List[str]: - t_mrid_subject: str = "?_t_mrid" - query_list = [ - rdf_type_tripler(t_mrid_subject, "cim:Terminal"), - f"{t_mrid_subject} {TC_EQUIPMENT} {mrid_subject}", - ] +) -> str: + + query_list = [rdf_type_tripler("", "cim:Terminal"), f"{TC_EQUIPMENT} {mrid_subject}"] if con: - query_list.append(f"{t_mrid_subject} {TC_NODE} ?{con}") + query_list.append(f"{TC_NODE} ?{con}") if node: query_list.extend( [ - f"{t_mrid_subject} cim:{acdc_terminal(cim_version)}.connected 'true'", - f"{t_mrid_subject} cim:Terminal.TopologicalNode ?{node}", + f"cim:{acdc_terminal(cim_version)}.connected 'true'", + f"cim:Terminal.TopologicalNode ?{node}", ] ) if with_sequence_number: - query_list.append( - f"{t_mrid_subject} cim:{acdc_terminal(cim_version)}.sequenceNumber ?sequenceNumber" - ) - return query_list + query_list.append(f"cim:{acdc_terminal(cim_version)}.sequenceNumber ?sequenceNumber") + return common_subject("?_t_mrid", query_list) def _temperature_list(temperature: float, xsd: str, curve: str) -> List[str]: @@ -189,6 +189,18 @@ def to_variables(vars: Iterable[str]) -> List[str]: return [f"?{var}" for var in vars] +def common_subject(subject: str, predicates_and_objects: List[str]) -> str: + """Combine list of predicates and objects with common subject + + Example: + >>> common_subject("?s", ["rdf:type ?type", "cim:ACDCTerminal.connected ?connected"]) + + extracts the rdf:type predicate and the cim:ACDCTerminal.connected predicate for all subjects + where both predicates are present. + """ + return f"{subject} {';'.join(predicates_and_objects)}" + + def combine_statements(*args, group: bool = False, split: str = "\n") -> str: """Join *args diff --git a/cimsparql/transformer_windings.py b/cimsparql/transformer_windings.py index c81dc5fc..a5a1ca30 100644 --- a/cimsparql/transformer_windings.py +++ b/cimsparql/transformer_windings.py @@ -25,8 +25,10 @@ def terminal(mrid: str, nr: int, lock_end_number: bool = True) -> List[str]: """Where statements for transformer terminals""" return [ *_end_number(nr, lock_end_number), - f"?w_mrid_{nr} cim:TransformerEnd.Terminal ?_t_mrid_{nr}", - f"?w_mrid_{nr} {TR_WINDING}.PowerTransformer {mrid}", + sup.common_subject( + f"?w_mrid_{nr}", + [f"cim:TransformerEnd.Terminal ?_t_mrid_{nr}", f"{TR_WINDING}.PowerTransformer {mrid}"], + ), ] @@ -35,16 +37,18 @@ def number_of_windings(mrid: str, winding_count: int, with_loss: bool = False) - variables = [mrid, "(count(distinct ?nr) as ?winding_count)"] where_list = [ f"{mrid} rdf:type cim:PowerTransformer", - f"?wwmrid {TR_WINDING}.PowerTransformer {mrid}", - "?wwmrid cim:TransformerEnd.endNumber ?nr", + sup.common_subject( + "?wwmrid", [f"{TR_WINDING}.PowerTransformer {mrid}", "cim:TransformerEnd.endNumber ?nr"] + ), ] if with_loss: variables.append("(sum(xsd:float(?sv_p)) as ?pl)") where_list.extend( [ "?wwmrid cim:TransformerEnd.Terminal ?p_t_mrid", - "?sv_t cim:SvPowerFlow.Terminal ?p_t_mrid", - "?sv_t cim:SvPowerFlow.p ?sv_p", + sup.common_subject( + "?sv_t", ["cim:SvPowerFlow.Terminal ?p_t_mrid", "cim:SvPowerFlow.p ?sv_p"] + ), ] ) select = sup.combine_statements(sup.select_statement(variables), sup.group_query(where_list)) @@ -92,8 +96,7 @@ def transformer_common( where_list.extend( [ sup.get_name("?p_mrid", name), - f"?w_mrid_1 {TR_WINDING}.ratedU ?un", - f"?w_mrid_1 {ID_OBJ}.mRID ?mrid", + sup.common_subject("?w_mrid_1", [f"{TR_WINDING}.ratedU ?un", f"{ID_OBJ}.mRID ?mrid"]), number_of_windings("?p_mrid", winding_count, with_loss), ] ) @@ -111,5 +114,5 @@ def transformer_common( where_rate = [] for rate in rates: variables.append(f"?rate{rate}") - where_rate.extend(sup.operational_limit("?_t_mrid_1", rate, limit_set="Terminal")) + where_rate.append(sup.operational_limit("?_t_mrid_1", rate, limit_set="Terminal")) where_list.append(sup.group_query(where_rate, command="OPTIONAL")) diff --git a/tests/test_query_support.py b/tests/test_query_support.py index f98af257..701fe374 100644 --- a/tests/test_query_support.py +++ b/tests/test_query_support.py @@ -1,4 +1,4 @@ -from typing import Dict, Optional, Union +from typing import Dict, Union import pytest @@ -19,27 +19,3 @@ def test_region_query_empty(region_kwargs: Dict[str, Union[bool, str]]): def test_region_query(region_kwargs: Dict[str, Union[bool, str]]): regions = qs.region_query(region="NO", **region_kwargs) assert len(regions) == 3 - - -@pytest.fixture(scope="module") -def terminal_kwargs() -> Dict[str, Optional[Union[str]]]: - return {"node": None, "mrid_subject": "?_mrid"} - - -def test_default_terminal_where_query(terminal_kwargs: Dict[str, Optional[str]]): - assert len(qs.terminal_where_query(cim_version, con="con", **terminal_kwargs)) == 3 - - -def test_terminal_where_query_no_var(terminal_kwargs: Dict[str, Optional[str]]): - assert len(qs.terminal_where_query(cim_version, con=None, **terminal_kwargs)) == 2 - - -def test_terminal_where_query_no_var_with_sequence(terminal_kwargs: Dict[str, Optional[str]]): - assert ( - len( - qs.terminal_where_query( - cim_version, con=None, with_sequence_number=True, **terminal_kwargs - ) - ) - == 3 - )