Esempio n. 1
0
    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,
        )
Esempio n. 2
0
    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,
            )
Esempio n. 3
0
 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])
Esempio n. 4
0
    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
Esempio n. 5
0
 def retrieve_multiple(self, *args, **kwargs) -> PowerAssetList:
     return PowerAssetList._load_assets(
         super().retrieve_multiple(*args, **kwargs), self.power_type,
         self._cognite_client)
Esempio n. 6
0
    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,
        )