From 55babc63e30925996646b4c65fd7741ae4e9a4b6 Mon Sep 17 00:00:00 2001 From: omiralles Date: Mon, 2 Dec 2024 09:12:17 +0100 Subject: [PATCH 01/10] Add possibility to create a graph from txt file --- src/anemoi/graphs/nodes/__init__.py | 2 ++ src/anemoi/graphs/nodes/builders/from_file.py | 20 +++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/anemoi/graphs/nodes/__init__.py b/src/anemoi/graphs/nodes/__init__.py index 228a282..15d2768 100644 --- a/src/anemoi/graphs/nodes/__init__.py +++ b/src/anemoi/graphs/nodes/__init__.py @@ -10,6 +10,7 @@ from .builders.from_file import LimitedAreaNPZFileNodes from .builders.from_file import NPZFileNodes from .builders.from_file import ZarrDatasetNodes +from .builders.from_file import TextNodes from .builders.from_healpix import HEALPixNodes from .builders.from_healpix import LimitedAreaHEALPixNodes from .builders.from_icon import ICONCellGridNodes @@ -35,4 +36,5 @@ "ICONMultimeshNodes", "ICONCellGridNodes", "ICONNodes", + "TextNodes", ] diff --git a/src/anemoi/graphs/nodes/builders/from_file.py b/src/anemoi/graphs/nodes/builders/from_file.py index c6bdc88..139ac37 100644 --- a/src/anemoi/graphs/nodes/builders/from_file.py +++ b/src/anemoi/graphs/nodes/builders/from_file.py @@ -41,7 +41,7 @@ class ZarrDatasetNodes(BaseNodeBuilder): Register the nodes in the graph. register_attributes(graph, name, config) Register the attributes in the nodes of the graph specified. - update_graph(graph, name, attrs_config) + update_graph(graph, name, attr_config) Update the graph with new nodes and attributes. """ @@ -62,6 +62,22 @@ def get_coordinates(self) -> torch.Tensor: dataset = open_dataset(self.dataset) return self.reshape_coords(dataset.latitudes, dataset.longitudes) +class TextNodes(BaseNodeBuilder): + def __init__(self, dataset, name: str) -> None: + LOGGER.info("Reading the dataset from %s.", dataset) + self.dataset = np.loadtxt(dataset) + super().__init__(name) + self.hidden_attributes = BaseNodeBuilder.hidden_attributes | {"dataset"} + + def get_coordinates(self) -> torch.Tensor: + """Get the coordinates of the nodes. + + Returns + ------- + torch.Tensor of shape (num_nodes, 2) + A 2D tensor with the coordinates, in radians. + """ + return self.reshape_coords(self.dataset[1, :], self.dataset[0, :]) class NPZFileNodes(BaseNodeBuilder): """Nodes from NPZ defined grids. @@ -83,7 +99,7 @@ class NPZFileNodes(BaseNodeBuilder): Register the nodes in the graph. register_attributes(graph, name, config) Register the attributes in the nodes of the graph specified. - update_graph(graph, name, attrs_config) + update_graph(graph, name, attr_config) Update the graph with new nodes and attributes. """ From 95b0490e7c4c9d9b35c06c102d89b42f76eb5716 Mon Sep 17 00:00:00 2001 From: omiralles Date: Tue, 3 Dec 2024 21:48:34 +0100 Subject: [PATCH 02/10] Add flat option for 2D voronoi --- src/anemoi/graphs/nodes/attributes.py | 45 +++++++++++++++++---------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/src/anemoi/graphs/nodes/attributes.py b/src/anemoi/graphs/nodes/attributes.py index 13ec00b..1d01b5e 100644 --- a/src/anemoi/graphs/nodes/attributes.py +++ b/src/anemoi/graphs/nodes/attributes.py @@ -18,7 +18,7 @@ import numpy as np import torch from anemoi.datasets import open_dataset -from scipy.spatial import SphericalVoronoi +from scipy.spatial import SphericalVoronoi, Voronoi, ConvexHull from torch_geometric.data import HeteroData from torch_geometric.data.storage import NodeStorage @@ -125,11 +125,13 @@ def __init__( centre: np.ndarray = np.array([0, 0, 0]), fill_value: float = 0.0, dtype: str = "float32", + flat: bool = False, ) -> None: super().__init__(norm, dtype) self.radius = radius self.centre = centre self.fill_value = fill_value + self.flat = flat def get_raw_values(self, nodes: NodeStorage, **kwargs) -> np.ndarray: """Compute the area associated to each node. @@ -149,22 +151,31 @@ def get_raw_values(self, nodes: NodeStorage, **kwargs) -> np.ndarray: Attributes. """ latitudes, longitudes = nodes.x[:, 0], nodes.x[:, 1] - points = latlon_rad_to_cartesian((np.asarray(latitudes), np.asarray(longitudes))) - sv = SphericalVoronoi(points, self.radius, self.centre) - mask = np.array([bool(i) for i in sv.regions]) - sv.regions = [region for region in sv.regions if region] - # compute the area weight without empty regions - area_weights = sv.calculate_areas() - if (null_nodes := (~mask).sum()) > 0: - LOGGER.warning( - "%s is filling %d (%.2f%%) nodes with value %f", - self.__class__.__name__, - null_nodes, - 100 * null_nodes / len(mask), - self.fill_value, - ) - result = np.ones(points.shape[0]) * self.fill_value - result[mask] = area_weights + if self.flat: + points = np.stack([latitudes, longitudes], -1) + v = Voronoi(points, qhull_options="QJ Pp") + areas = [] + for r in v.regions: + area = ConvexHull(v.vertices[r, :]).volume + areas.append(area) + result = np.asarray(areas) + else: + points = latlon_rad_to_cartesian((np.asarray(latitudes), np.asarray(longitudes))) + sv = SphericalVoronoi(points, self.radius, self.centre) + mask = np.array([bool(i) for i in sv.regions]) + sv.regions = [region for region in sv.regions if region] + # compute the area weight without empty regions + area_weights = sv.calculate_areas() + if (null_nodes := (~mask).sum()) > 0: + LOGGER.warning( + "%s is filling %d (%.2f%%) nodes with value %f", + self.__class__.__name__, + null_nodes, + 100 * null_nodes / len(mask), + self.fill_value, + ) + result = np.ones(points.shape[0]) * self.fill_value + result[mask] = area_weights LOGGER.debug( "There are %d of weights, which (unscaled) add up a total weight of %.2f.", len(result), From 2277d6e970515f6ee1a851f6e6b8bf2686f66241 Mon Sep 17 00:00:00 2001 From: omiralles Date: Thu, 5 Dec 2024 09:27:38 +0100 Subject: [PATCH 03/10] revert doc change --- src/anemoi/graphs/nodes/builders/from_file.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/anemoi/graphs/nodes/builders/from_file.py b/src/anemoi/graphs/nodes/builders/from_file.py index 139ac37..86e7ec9 100644 --- a/src/anemoi/graphs/nodes/builders/from_file.py +++ b/src/anemoi/graphs/nodes/builders/from_file.py @@ -41,7 +41,7 @@ class ZarrDatasetNodes(BaseNodeBuilder): Register the nodes in the graph. register_attributes(graph, name, config) Register the attributes in the nodes of the graph specified. - update_graph(graph, name, attr_config) + update_graph(graph, name, attrs_config) Update the graph with new nodes and attributes. """ @@ -99,7 +99,7 @@ class NPZFileNodes(BaseNodeBuilder): Register the nodes in the graph. register_attributes(graph, name, config) Register the attributes in the nodes of the graph specified. - update_graph(graph, name, attr_config) + update_graph(graph, name, attrs_config) Update the graph with new nodes and attributes. """ From 65ba51ac42ab00e77a6ff357d78b9201ad83e80d Mon Sep 17 00:00:00 2001 From: omiralles Date: Fri, 6 Dec 2024 14:11:58 +0100 Subject: [PATCH 04/10] Separate area weights in 2 classes and add idx lon/lat in TxtNodes --- src/anemoi/graphs/nodes/__init__.py | 2 +- src/anemoi/graphs/nodes/attributes.py | 120 +++++++++++------- src/anemoi/graphs/nodes/builders/from_file.py | 26 +++- 3 files changed, 99 insertions(+), 49 deletions(-) diff --git a/src/anemoi/graphs/nodes/__init__.py b/src/anemoi/graphs/nodes/__init__.py index 15d2768..6fc429b 100644 --- a/src/anemoi/graphs/nodes/__init__.py +++ b/src/anemoi/graphs/nodes/__init__.py @@ -9,8 +9,8 @@ from .builders.from_file import LimitedAreaNPZFileNodes from .builders.from_file import NPZFileNodes -from .builders.from_file import ZarrDatasetNodes from .builders.from_file import TextNodes +from .builders.from_file import ZarrDatasetNodes from .builders.from_healpix import HEALPixNodes from .builders.from_healpix import LimitedAreaHEALPixNodes from .builders.from_icon import ICONCellGridNodes diff --git a/src/anemoi/graphs/nodes/attributes.py b/src/anemoi/graphs/nodes/attributes.py index 1d01b5e..88e2087 100644 --- a/src/anemoi/graphs/nodes/attributes.py +++ b/src/anemoi/graphs/nodes/attributes.py @@ -12,13 +12,16 @@ import logging from abc import ABC from abc import abstractmethod +from typing import Self from typing import Type from typing import Union import numpy as np import torch from anemoi.datasets import open_dataset -from scipy.spatial import SphericalVoronoi, Voronoi, ConvexHull +from scipy.spatial import ConvexHull +from scipy.spatial import SphericalVoronoi +from scipy.spatial import Voronoi from torch_geometric.data import HeteroData from torch_geometric.data.storage import NodeStorage @@ -101,6 +104,62 @@ def get_raw_values(self, nodes: NodeStorage, **kwargs) -> np.ndarray: class AreaWeights(BaseNodeAttribute): """Implements the area of the nodes as the weights. + Attributes + ---------- + flat: bool + If True, the area is computed in 2D, otherwise in 3D. + **other: Any + Additional keyword arguments, see PlanarAreaWeights and SphericalAreaWeights + for details. + + Methods + ------- + compute(self, graph, nodes_name) + Compute the area attributes for each node. + """ + + def __new__(cls, flat: bool = True, **kwargs) -> Self: + if flat: + return PlanarAreaWeights(**kwargs) + return SphericalAreaWeights(**kwargs) + + +class PlanarAreaWeights(BaseNodeAttribute): + """Implements the 2D area of the nodes as the weights. + + Attributes + ---------- + norm : str + Normalisation of the weights. + + Methods + ------- + compute(self, graph, nodes_name) + Compute the area attributes for each node. + """ + + def __init__( + self, + norm: str | None = None, + dtype: str = "float32", + ) -> None: + super().__init__(norm, dtype) + + def get_raw_values(self, nodes: NodeStorage, **kwargs) -> np.ndarray: + latitudes, longitudes = nodes.x[:, 0], nodes.x[:, 1] + points = np.stack([latitudes, longitudes], -1) + v = Voronoi(points, qhull_options="QJ Pp") + areas = [] + for r in v.regions: + area = ConvexHull(v.vertices[r, :]).volume + areas.append(area) + result = np.asarray(areas) + return result + + +class SphericalAreaWeights(BaseNodeAttribute): + """Implements the 3D area of the nodes as the weights. + Attributes ---------- norm : str @@ -125,57 +184,30 @@ def __init__( centre: np.ndarray = np.array([0, 0, 0]), fill_value: float = 0.0, dtype: str = "float32", - flat: bool = False, ) -> None: super().__init__(norm, dtype) self.radius = radius self.centre = centre self.fill_value = fill_value - self.flat = flat def get_raw_values(self, nodes: NodeStorage, **kwargs) -> np.ndarray: - """Compute the area associated to each node. - - It uses Voronoi diagrams to compute the area of each node. - - Parameters - ---------- - nodes : NodeStorage - Nodes of the graph. - kwargs : dict - Additional keyword arguments. - - Returns - ------- - np.ndarray - Attributes. - """ latitudes, longitudes = nodes.x[:, 0], nodes.x[:, 1] - if self.flat: - points = np.stack([latitudes, longitudes], -1) - v = Voronoi(points, qhull_options="QJ Pp") - areas = [] - for r in v.regions: - area = ConvexHull(v.vertices[r, :]).volume - areas.append(area) - result = np.asarray(areas) - else: - points = latlon_rad_to_cartesian((np.asarray(latitudes), np.asarray(longitudes))) - sv = SphericalVoronoi(points, self.radius, self.centre) - mask = np.array([bool(i) for i in sv.regions]) - sv.regions = [region for region in sv.regions if region] - # compute the area weight without empty regions - area_weights = sv.calculate_areas() - if (null_nodes := (~mask).sum()) > 0: - LOGGER.warning( - "%s is filling %d (%.2f%%) nodes with value %f", - self.__class__.__name__, - null_nodes, - 100 * null_nodes / len(mask), - self.fill_value, - ) - result = np.ones(points.shape[0]) * self.fill_value - result[mask] = area_weights + points = latlon_rad_to_cartesian((np.asarray(latitudes), np.asarray(longitudes))) + sv = SphericalVoronoi(points, self.radius, self.centre) + mask = np.array([bool(i) for i in sv.regions]) + sv.regions = [region for region in sv.regions if region] + # compute the area weight without empty regions + area_weights = sv.calculate_areas() + if (null_nodes := (~mask).sum()) > 0: + LOGGER.warning( + "%s is filling %d (%.2f%%) nodes with value %f", + self.__class__.__name__, + null_nodes, + 100 * null_nodes / len(mask), + self.fill_value, + ) + result = np.ones(points.shape[0]) * self.fill_value + result[mask] = area_weights LOGGER.debug( "There are %d of weights, which (unscaled) add up a total weight of %.2f.", len(result), diff --git a/src/anemoi/graphs/nodes/builders/from_file.py b/src/anemoi/graphs/nodes/builders/from_file.py index 86e7ec9..069b4ab 100644 --- a/src/anemoi/graphs/nodes/builders/from_file.py +++ b/src/anemoi/graphs/nodes/builders/from_file.py @@ -62,12 +62,26 @@ def get_coordinates(self) -> torch.Tensor: dataset = open_dataset(self.dataset) return self.reshape_coords(dataset.latitudes, dataset.longitudes) + class TextNodes(BaseNodeBuilder): - def __init__(self, dataset, name: str) -> None: + """Nodes from text file. + + Attributes + ---------- + dataset : str | DictConfig + The path to txt file containing the coordinates of the nodes. + idx_lon : int + The index of the longitude in the dataset. + idx_lat : int + The index of the latitude in the dataset. + """ + + def __init__(self, dataset, name: str, idx_lon: int = 0, idx_lat: int = 1) -> None: LOGGER.info("Reading the dataset from %s.", dataset) self.dataset = np.loadtxt(dataset) + self.idx_lon = idx_lon + self.idx_lat = idx_lat super().__init__(name) - self.hidden_attributes = BaseNodeBuilder.hidden_attributes | {"dataset"} def get_coordinates(self) -> torch.Tensor: """Get the coordinates of the nodes. @@ -77,7 +91,8 @@ def get_coordinates(self) -> torch.Tensor: torch.Tensor of shape (num_nodes, 2) A 2D tensor with the coordinates, in radians. """ - return self.reshape_coords(self.dataset[1, :], self.dataset[0, :]) + return self.reshape_coords(self.dataset[self.idx_lat, :], self.dataset[self.idx_lon, :]) + class NPZFileNodes(BaseNodeBuilder): """Nodes from NPZ defined grids. @@ -162,7 +177,10 @@ def get_coordinates(self) -> np.ndarray: ) area_mask = self.area_mask_builder.get_mask(coords) - LOGGER.info("Dropping %d nodes from the processor mesh.", len(area_mask) - area_mask.sum()) + LOGGER.info( + "Dropping %d nodes from the processor mesh.", + len(area_mask) - area_mask.sum(), + ) coords = coords[area_mask] return coords From debb6635d0812cab9d6eda06297fac6dcb08a901 Mon Sep 17 00:00:00 2001 From: omiralles Date: Fri, 6 Dec 2024 14:27:41 +0100 Subject: [PATCH 05/10] Remove Self import --- src/anemoi/graphs/nodes/attributes.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/anemoi/graphs/nodes/attributes.py b/src/anemoi/graphs/nodes/attributes.py index 88e2087..8de3805 100644 --- a/src/anemoi/graphs/nodes/attributes.py +++ b/src/anemoi/graphs/nodes/attributes.py @@ -12,7 +12,6 @@ import logging from abc import ABC from abc import abstractmethod -from typing import Self from typing import Type from typing import Union @@ -118,7 +117,7 @@ class AreaWeights(BaseNodeAttribute): Compute the area attributes for each node. """ - def __new__(cls, flat: bool = True, **kwargs) -> Self: + def __new__(cls, flat: bool = True, **kwargs): if flat: return PlanarAreaWeights(**kwargs) return SphericalAreaWeights(**kwargs) From be867479a572db8a0951050494875f8ebf2038c7 Mon Sep 17 00:00:00 2001 From: omiralles Date: Fri, 6 Dec 2024 14:29:31 +0100 Subject: [PATCH 06/10] Set default to Spherical for tests --- src/anemoi/graphs/nodes/attributes.py | 37 +++++++++++++++++---------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/anemoi/graphs/nodes/attributes.py b/src/anemoi/graphs/nodes/attributes.py index 8de3805..98819cf 100644 --- a/src/anemoi/graphs/nodes/attributes.py +++ b/src/anemoi/graphs/nodes/attributes.py @@ -10,17 +10,13 @@ from __future__ import annotations import logging -from abc import ABC -from abc import abstractmethod -from typing import Type -from typing import Union +from abc import ABC, abstractmethod +from typing import Type, Union import numpy as np import torch from anemoi.datasets import open_dataset -from scipy.spatial import ConvexHull -from scipy.spatial import SphericalVoronoi -from scipy.spatial import Voronoi +from scipy.spatial import ConvexHull, SphericalVoronoi, Voronoi from torch_geometric.data import HeteroData from torch_geometric.data.storage import NodeStorage @@ -117,7 +113,7 @@ class AreaWeights(BaseNodeAttribute): Compute the area attributes for each node. """ - def __new__(cls, flat: bool = True, **kwargs): + def __new__(cls, flat: bool = False, **kwargs): if flat: return PlanarAreaWeights(**kwargs) return SphericalAreaWeights(**kwargs) @@ -191,7 +187,9 @@ def __init__( def get_raw_values(self, nodes: NodeStorage, **kwargs) -> np.ndarray: latitudes, longitudes = nodes.x[:, 0], nodes.x[:, 1] - points = latlon_rad_to_cartesian((np.asarray(latitudes), np.asarray(longitudes))) + points = latlon_rad_to_cartesian( + (np.asarray(latitudes), np.asarray(longitudes)) + ) sv = SphericalVoronoi(points, self.radius, self.centre) mask = np.array([bool(i) for i in sv.regions]) sv.regions = [region for region in sv.regions if region] @@ -256,8 +254,12 @@ class CutOutMask(BooleanBaseNodeAttribute): """Cut out mask.""" def get_raw_values(self, nodes: NodeStorage, **kwargs) -> np.ndarray: - assert isinstance(nodes["_dataset"], dict), "The 'dataset' attribute must be a dictionary." - assert "cutout" in nodes["_dataset"], "The 'dataset' attribute must contain a 'cutout' key." + assert isinstance( + nodes["_dataset"], dict + ), "The 'dataset' attribute must be a dictionary." + assert ( + "cutout" in nodes["_dataset"] + ), "The 'dataset' attribute must contain a 'cutout' key." num_lam, num_other = open_dataset(nodes["_dataset"]).grids return np.array([True] * num_lam + [False] * num_other, dtype=bool) @@ -270,7 +272,9 @@ def __init__(self, masks: MaskAttributeType | list[MaskAttributeType]) -> None: self.masks = masks if isinstance(masks, list) else [masks] @staticmethod - def get_mask_values(mask: MaskAttributeType, nodes: NodeStorage, **kwargs) -> np.array: + def get_mask_values( + mask: MaskAttributeType, nodes: NodeStorage, **kwargs + ) -> np.array: if isinstance(mask, str): attributes = nodes[mask] assert ( @@ -284,7 +288,10 @@ def get_mask_values(mask: MaskAttributeType, nodes: NodeStorage, **kwargs) -> np def reduce_op(self, masks: list[np.ndarray]) -> np.ndarray: ... def get_raw_values(self, nodes: NodeStorage, **kwargs) -> np.ndarray: - mask_values = [BooleanOperation.get_mask_values(mask, nodes, **kwargs) for mask in self.masks] + mask_values = [ + BooleanOperation.get_mask_values(mask, nodes, **kwargs) + for mask in self.masks + ] return self.reduce_op(mask_values) @@ -292,7 +299,9 @@ class BooleanNot(BooleanOperation): """Boolean NOT mask.""" def reduce_op(self, masks: list[np.ndarray]) -> np.ndarray: - assert len(self.masks) == 1, f"The {self.__class__.__name__} can only be aplied to one mask." + assert ( + len(self.masks) == 1 + ), f"The {self.__class__.__name__} can only be aplied to one mask." return ~masks[0] From 45510166ad757029dad6c7d0ed01cdc0037168df Mon Sep 17 00:00:00 2001 From: omiralles Date: Fri, 6 Dec 2024 20:36:56 +0100 Subject: [PATCH 07/10] Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eeb48ae..126ff0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ Keep it human-readable, your future self will thank you! - feat: Add `RemoveUnconnectedNodes` post processor to clean unconnected nodes in LAM. (#71) - feat: Define node sets and edges based on an ICON icosahedral mesh (#53) - feat: Support for multiple edge builders between two sets of nodes (#70) +- feat: Support for providing lon/lat coordinates from a text file (loaded with numpy loadtxt method) to build the graph `TxtNodes` (#93) +- feat: Build 2D graphs with `Voronoi` in case `SphericalVoronoi` does not work well/is an overkill (LAM). Set `flat=true` in the nodes attributes to compute area weight using Voronoi with a qhull options preventing the empty region creation (#93) # Changed From 71cf1f832b2715eac2fad1916560e1a8a13fc7b9 Mon Sep 17 00:00:00 2001 From: omiralles Date: Fri, 6 Dec 2024 20:44:58 +0100 Subject: [PATCH 08/10] Add warning --- src/anemoi/graphs/nodes/attributes.py | 36 +++++++++++---------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/src/anemoi/graphs/nodes/attributes.py b/src/anemoi/graphs/nodes/attributes.py index 98819cf..7a9e901 100644 --- a/src/anemoi/graphs/nodes/attributes.py +++ b/src/anemoi/graphs/nodes/attributes.py @@ -10,13 +10,17 @@ from __future__ import annotations import logging -from abc import ABC, abstractmethod -from typing import Type, Union +from abc import ABC +from abc import abstractmethod +from typing import Type +from typing import Union import numpy as np import torch from anemoi.datasets import open_dataset -from scipy.spatial import ConvexHull, SphericalVoronoi, Voronoi +from scipy.spatial import ConvexHull +from scipy.spatial import SphericalVoronoi +from scipy.spatial import Voronoi from torch_geometric.data import HeteroData from torch_geometric.data.storage import NodeStorage @@ -114,6 +118,7 @@ class AreaWeights(BaseNodeAttribute): """ def __new__(cls, flat: bool = False, **kwargs): + logging.warning("Creating %s with flat=%s and kwargs=%s. In a future release, AreaWeights will be deprecated: please use directly PlanarAreaWeights or SphericalAreaWeights.", cls.__name__, flat, kwargs) if flat: return PlanarAreaWeights(**kwargs) return SphericalAreaWeights(**kwargs) @@ -187,9 +192,7 @@ def __init__( def get_raw_values(self, nodes: NodeStorage, **kwargs) -> np.ndarray: latitudes, longitudes = nodes.x[:, 0], nodes.x[:, 1] - points = latlon_rad_to_cartesian( - (np.asarray(latitudes), np.asarray(longitudes)) - ) + points = latlon_rad_to_cartesian((np.asarray(latitudes), np.asarray(longitudes))) sv = SphericalVoronoi(points, self.radius, self.centre) mask = np.array([bool(i) for i in sv.regions]) sv.regions = [region for region in sv.regions if region] @@ -254,12 +257,8 @@ class CutOutMask(BooleanBaseNodeAttribute): """Cut out mask.""" def get_raw_values(self, nodes: NodeStorage, **kwargs) -> np.ndarray: - assert isinstance( - nodes["_dataset"], dict - ), "The 'dataset' attribute must be a dictionary." - assert ( - "cutout" in nodes["_dataset"] - ), "The 'dataset' attribute must contain a 'cutout' key." + assert isinstance(nodes["_dataset"], dict), "The 'dataset' attribute must be a dictionary." + assert "cutout" in nodes["_dataset"], "The 'dataset' attribute must contain a 'cutout' key." num_lam, num_other = open_dataset(nodes["_dataset"]).grids return np.array([True] * num_lam + [False] * num_other, dtype=bool) @@ -272,9 +271,7 @@ def __init__(self, masks: MaskAttributeType | list[MaskAttributeType]) -> None: self.masks = masks if isinstance(masks, list) else [masks] @staticmethod - def get_mask_values( - mask: MaskAttributeType, nodes: NodeStorage, **kwargs - ) -> np.array: + def get_mask_values(mask: MaskAttributeType, nodes: NodeStorage, **kwargs) -> np.array: if isinstance(mask, str): attributes = nodes[mask] assert ( @@ -288,10 +285,7 @@ def get_mask_values( def reduce_op(self, masks: list[np.ndarray]) -> np.ndarray: ... def get_raw_values(self, nodes: NodeStorage, **kwargs) -> np.ndarray: - mask_values = [ - BooleanOperation.get_mask_values(mask, nodes, **kwargs) - for mask in self.masks - ] + mask_values = [BooleanOperation.get_mask_values(mask, nodes, **kwargs) for mask in self.masks] return self.reduce_op(mask_values) @@ -299,9 +293,7 @@ class BooleanNot(BooleanOperation): """Boolean NOT mask.""" def reduce_op(self, masks: list[np.ndarray]) -> np.ndarray: - assert ( - len(self.masks) == 1 - ), f"The {self.__class__.__name__} can only be aplied to one mask." + assert len(self.masks) == 1, f"The {self.__class__.__name__} can only be aplied to one mask." return ~masks[0] From f8ce841084d4327c7840cfc7dc48ba0a2d87cd68 Mon Sep 17 00:00:00 2001 From: omiralles Date: Fri, 6 Dec 2024 20:47:44 +0100 Subject: [PATCH 09/10] Formatting --- src/anemoi/graphs/nodes/attributes.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/anemoi/graphs/nodes/attributes.py b/src/anemoi/graphs/nodes/attributes.py index 7a9e901..9f6676b 100644 --- a/src/anemoi/graphs/nodes/attributes.py +++ b/src/anemoi/graphs/nodes/attributes.py @@ -118,7 +118,12 @@ class AreaWeights(BaseNodeAttribute): """ def __new__(cls, flat: bool = False, **kwargs): - logging.warning("Creating %s with flat=%s and kwargs=%s. In a future release, AreaWeights will be deprecated: please use directly PlanarAreaWeights or SphericalAreaWeights.", cls.__name__, flat, kwargs) + logging.warning( + "Creating %s with flat=%s and kwargs=%s. In a future release, AreaWeights will be deprecated: please use directly PlanarAreaWeights or SphericalAreaWeights.", + cls.__name__, + flat, + kwargs, + ) if flat: return PlanarAreaWeights(**kwargs) return SphericalAreaWeights(**kwargs) From a40974ef54150da804401b3635d856d7111246f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oph=C3=A9lia=20Miralles?= Date: Fri, 6 Dec 2024 21:13:39 +0100 Subject: [PATCH 10/10] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 126ff0a..a2d8df8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ Keep it human-readable, your future self will thank you! - feat: Add `RemoveUnconnectedNodes` post processor to clean unconnected nodes in LAM. (#71) - feat: Define node sets and edges based on an ICON icosahedral mesh (#53) - feat: Support for multiple edge builders between two sets of nodes (#70) -- feat: Support for providing lon/lat coordinates from a text file (loaded with numpy loadtxt method) to build the graph `TxtNodes` (#93) +- feat: Support for providing lon/lat coordinates from a text file (loaded with numpy loadtxt method) to build the graph `TextNodes` (#93) - feat: Build 2D graphs with `Voronoi` in case `SphericalVoronoi` does not work well/is an overkill (LAM). Set `flat=true` in the nodes attributes to compute area weight using Voronoi with a qhull options preventing the empty region creation (#93) # Changed