def list( self, grid_type: str = None, base_voltage: Iterable = None, bidding_area: Union[str, List[str]] = None, asset_type: Union[str, List[str]] = None, limit: int = None, **kwargs, ) -> PowerAssetList: """Lists power assets. Supports all parameters as the normal list function in addition to some power specific ones. Args: grid_type (str): filters on Equipment.gridType. Can give "GridTypeKind.regional" or just "regional" etc. base_voltage (Iterable): filters on BaseVoltage_nominalVoltage in the given range or list. bidding_area (Union[str, List[str]]): filters on assets being in the bidding areas with this (case-insensitive) name. asset_type (Union[str, List[str]]): filter on these asset types. Automatically populated for specific APIs kwargs: all other parameters for the normal AssetsAPI.list method Returns: PowerAssetList: List of the requested assets. """ if (base_voltage is not None or isinstance( asset_type, list)) and limit not in [None, -1, float("inf")]: raise ValueError( "Can not set a limit when specifying a base voltage filter or multiple asset types" ) if self.power_type and asset_type: raise ValueError( "Can not filter on asset_types in this API, use client.power_assets instead" ) if isinstance(asset_type, list): asset_lists = [ self.list(asset_type=type, bidding_area=bidding_area, base_voltage=base_voltage, grid_type=grid_type, **kwargs) for type in asset_type ] return PowerAssetList._load_assets( sum(asset_lists, []), asset_type[0] if len(asset_type) == 1 else None, # mixed base_voltage=base_voltage, cognite_client=self._cognite_client, ) filters = self._create_filter(bidding_area=bidding_area, grid_type=grid_type, asset_type=asset_type, **kwargs) assets = super().list(limit=limit, **filters) return PowerAssetList._load_assets( assets, filters.get("metadata", {}).get("type"), base_voltage=base_voltage, cognite_client=self._cognite_client, )
def retrieve_name( self, name: Union[List[str], str], asset_type: str = None, bidding_area: Union[str, List[str]] = None ) -> Union[PowerAsset, PowerAssetList]: """Retrieve one or more assets by exact name match. Fails if not exactly one asset is found. Args: name (Union[List[str], str]): One or more names to search for. asset_type (Union[str, List[str]]): filter on these asset types. Automatically populated for specific APIs bidding_area (Union[str, List[str]]): filters on assets being in the bidding areas with this name. Returns: Union[PowerAsset,PowerAssetList]: The requested asset(s). """ if isinstance(name, str): filters = self._create_filter(asset_type=asset_type, bidding_area=bidding_area) result = assert_single_result(super().list(name=name, **filters)) return PowerAsset._load_from_asset(result, self.power_type or asset_type, self._cognite_client) else: return PowerAssetList( [ self.retrieve_name(name=n, asset_type=asset_type, bidding_area=bidding_area) for n in name ], cognite_client=self._cognite_client, )
def __init__(self, items: List[PowerCorridorComponent], cognite_client=None): """Power Corridor class. When cognite_client is ommitted, it is taken from the first asset given.""" if not cognite_client and items: cognite_client = items[0].asset._cognite_client super().__init__(items, cognite_client=cognite_client) self.assets = PowerAssetList([a.asset for a in items])
def draw_flow( power_area, labels="fixed", position="kamada", height=None, timeseries_type="estimated_value", granularity="1h", date: "np.datetime64" = None, ): """ Draws power flow through the area. Args: labels,position,height: as in `draw` timeseries_type: type of time series to retrieve, i.e. value/estimated_value. granularity: time step at which to average values over, as in the Python SDK `retrieve_dataframe` function. date: datetime object at which to visualize flow, use None for now. """ node_plot_mode = "markers" if labels == "fixed": node_plot_mode += "+text" node_positions = node_layout(power_area, position) substation_plot = create_substation_plot(node_positions, node_plot_mode) lats, lons, center_lats, center_lons, text = edge_locations( power_area, node_positions) ac_line_segment_plots, ac_line_label_point_plot = create_line_segment_plot( lons, lats, center_lons, center_lats, text) terminals = PowerAssetList( list( set( sum([ list(data["terminals"].values()) for f, t, data in power_area._graph.edges(data=True) ], []))), cognite_client=power_area._cognite_client, ) ts = terminals.time_series(measurement_type="ThreePhaseActivePower", timeseries_type=timeseries_type) analogs = power_area._cognite_client.assets.retrieve_multiple( ids=[t.asset_id for t in ts]) terminal_ids: List[int] = [a.parent_id for a in analogs] target_time = np.datetime64(date or datetime.now()) delta = np.timedelta64(5, "D") start = _np_datetime_to_ms((target_time - delta)) end = _np_datetime_to_ms((target_time + delta)) df = power_area._cognite_client.datapoints.retrieve_dataframe( id=[t.id for t in ts], aggregates=["average"], granularity=granularity, start=start, # TODO: split data prep and update end=end, include_aggregate_name=False, ) df.columns = terminal_ids ix = np.searchsorted(df.index, target_time, side="left") flow_values = df.iloc[ix - 1, :] title = f"flow at {df.index[ix - 1]}" distances = [ np.linalg.norm( np.array(node_positions[edge[0]]) - np.array(node_positions[edge[1]])) for edge in power_area._graph.edges ] global_arrow_scale = 0.15 * np.mean( distances) # TODO: what is reasonable here? arrow_traces = [] for f, t, data in power_area._graph.edges(data=True): terminal_map = data["terminals"] terminals = [terminal_map[f], terminal_map[t]] flow_values_t = [] for side in [0, 1]: val = np.nan if terminals[side].id in flow_values.index: val = flow_values[terminals[side].id] if isinstance(val, pd.Series): val = val.dropna() val = val.mean() if not val.empty else np.nan flow_values_t.append(val) from_pos = np.array(node_positions[f]) to_pos = np.array(node_positions[t]) from_to_vec = to_pos - from_pos distance = np.linalg.norm(from_to_vec) arrow_scale = min(global_arrow_scale, 0.3 * distance) from_to_vec /= max(distance, 0.1) if flow_values_t[0] < flow_values_t[1]: flow_vec = -from_to_vec else: flow_vec = from_to_vec orthogonal = np.array([-flow_vec[1], flow_vec[0]]) mid = (from_pos + to_pos) / 2 sign_from = math.copysign( 1, flow_values_t[0]) if not np.isnan(flow_values_t[0]) else 0 arrow_from_mid = mid - 0.5 * arrow_scale * from_to_vec # arrow middle is always closer to from # direction of arrow depends on sign of flow arrow_from_tail = arrow_from_mid - 0.33 * arrow_scale * flow_vec * sign_from arrow_from_head = arrow_from_mid + 0.33 * arrow_scale * flow_vec * sign_from arrow_from_left = arrow_from_tail - orthogonal * global_arrow_scale * 0.5 arrow_from_right = arrow_from_tail + orthogonal * global_arrow_scale * 0.5 sign_to = math.copysign( 1, flow_values_t[1]) if not np.isnan(flow_values_t[1]) else 0 arrow_to_mid = mid + 0.5 * arrow_scale * from_to_vec # arrow middle is always closer to to # direction of arrow depends on sign of flow arrow_to_tail = arrow_to_mid - 0.33 * arrow_scale * flow_vec * ( -sign_to) arrow_to_head = arrow_to_mid + 0.33 * arrow_scale * flow_vec * ( -sign_to) arrow_to_left = arrow_to_tail - orthogonal * global_arrow_scale * 0.5 arrow_to_right = arrow_to_tail + orthogonal * global_arrow_scale * 0.5 arrows = [ [arrow_from_left, arrow_from_head, arrow_from_right], [arrow_to_left, arrow_to_head, arrow_to_right], ] for arrow_points, terminal, flow in zip(arrows, terminals, flow_values_t): arrow_x, arrow_y = zip(*arrow_points) arrow_traces.append( # this makes computers go on fire, but not sure how to fix that. go.Scatter( x=arrow_x, y=arrow_y, text=f"{terminal.name}: {flow:.1f} MW" if not np.isnan(flow) else f"{terminal.name}: NO DATA", line=dict( width=2, color=_flow_color(abs(flow)) if flow and not np.isnan(flow) else "rgb(200,200,200)", ), hoverinfo="text", mode="lines", )) fig = go.Figure( data=ac_line_segment_plots + [substation_plot] + arrow_traces, layout=go.Layout( title=title, height=height, plot_bgcolor="rgb(250,250,250)", titlefont_size=16, showlegend=False, hovermode="closest", margin=dict(b=0, l=0, r=0, t=0), xaxis=dict(showgrid=False, zeroline=False, showticklabels=False, constrain="domain"), yaxis=dict(showgrid=False, zeroline=False, showticklabels=False, scaleanchor="x"), ), ) return fig
def retrieve_multiple(self, *args, **kwargs) -> PowerAssetList: return PowerAssetList._load_assets( super().retrieve_multiple(*args, **kwargs), self.power_type, self._cognite_client)
def search( self, name: str = None, grid_type: str = None, base_voltage: Iterable = None, bidding_area: Union[str, List[str]] = None, asset_type: Union[str, List[str]] = None, limit: int = None, **kwargs, ) -> PowerAssetList: """Search power assets. Supports all parameters as the normal search function in addition to some power specific ones. Args: name (str): Fuzzy search on name. grid_type (str): filters on Equipment.gridType. Can give "GridTypeKind.regional" or just "regional" etc. base_voltage (Iterable): filters on BaseVoltage_nominalVoltage in the given range or list. bidding_area (Union[str, List[str]]): filters on assets being in the bidding areas with this name. asset_type (Union[str, List[str]]): filter on these asset types. Automatically populated for specific APIs kwargs: all other parameters for the normal AssetsAPI.list method Returns: PowerAssetList: List of the requested assets. """ if self.power_type and asset_type: raise ValueError( "Can not filter on asset_types in this API, use client.power_assets instead" ) if isinstance(asset_type, list): asset_lists = [ self.search( name=name, asset_type=type, bidding_area=bidding_area, base_voltage=base_voltage, grid_type=grid_type, **kwargs, ) for type in asset_type ] return PowerAssetList._load_assets( sum(asset_lists, []), asset_type[0] if len(asset_type) == 1 else None, # mixed base_voltage=base_voltage, cognite_client=self._cognite_client, ) filter = self._create_filter( bidding_area=bidding_area, grid_type=grid_type, asset_type=asset_type, wrap_ids=True, **kwargs.get("filter", {}), ) if "filter" in kwargs: del kwargs["filter"] assets = super().search(name=name, limit=limit, filter=filter, **kwargs) return PowerAssetList._load_assets( assets, filter.get("metadata", {}).get("type"), base_voltage=base_voltage, cognite_client=self._cognite_client, )