1313
1414import enum
1515import re
16- from collections .abc import Hashable , Iterable
16+ from collections .abc import Callable , Hashable , Iterable
1717from dataclasses import dataclass
1818from textwrap import indent
1919from typing import Any , Literal , Protocol , Self , cast , overload
2222
2323from parcels ._python import repr_from_dunder_dict
2424
25- RE_DIM_DIM_PADDING = r"(\w+):(\w+)\s*\(padding:\s*(\w+)\)"
25+ RE_FACE_NODE_PADDING = r"(\w+):(\w+)\s*\(padding:\s*(\w+)\)"
2626
2727Dim = str
2828
@@ -56,22 +56,22 @@ def from_attrs(cls, d: dict[str, Hashable]) -> Self: ...
5656
5757# Note that - for some optional attributes in the SGRID spec - these IDs are not available
5858# hence this isn't full coverage
59- _ID_FETCHERS_GRID2DMETADATA = {
59+ _ID_FETCHERS_GRID2DMETADATA : dict [ str , Callable [[ Grid2DMetadata ], Dim | Padding ]] = {
6060 "node_dimension1" : lambda meta : meta .node_dimensions [0 ],
6161 "node_dimension2" : lambda meta : meta .node_dimensions [1 ],
62- "face_dimension1" : lambda meta : meta .face_dimensions [0 ].dim1 ,
63- "face_dimension2" : lambda meta : meta .face_dimensions [1 ].dim1 ,
62+ "face_dimension1" : lambda meta : meta .face_dimensions [0 ].face ,
63+ "face_dimension2" : lambda meta : meta .face_dimensions [1 ].face ,
6464 "type1" : lambda meta : meta .face_dimensions [0 ].padding ,
6565 "type2" : lambda meta : meta .face_dimensions [1 ].padding ,
6666}
6767
68- _ID_FETCHERS_GRID3DMETADATA = {
68+ _ID_FETCHERS_GRID3DMETADATA : dict [ str , Callable [[ Grid3DMetadata ], Dim | Padding ]] = {
6969 "node_dimension1" : lambda meta : meta .node_dimensions [0 ],
7070 "node_dimension2" : lambda meta : meta .node_dimensions [1 ],
7171 "node_dimension3" : lambda meta : meta .node_dimensions [2 ],
72- "face_dimension1" : lambda meta : meta .volume_dimensions [0 ].dim1 ,
73- "face_dimension2" : lambda meta : meta .volume_dimensions [1 ].dim1 ,
74- "face_dimension3" : lambda meta : meta .volume_dimensions [2 ].dim1 ,
72+ "face_dimension1" : lambda meta : meta .volume_dimensions [0 ].face ,
73+ "face_dimension2" : lambda meta : meta .volume_dimensions [1 ].face ,
74+ "face_dimension3" : lambda meta : meta .volume_dimensions [2 ].face ,
7575 "type1" : lambda meta : meta .volume_dimensions [0 ].padding ,
7676 "type2" : lambda meta : meta .volume_dimensions [1 ].padding ,
7777 "type3" : lambda meta : meta .volume_dimensions [2 ].padding ,
@@ -84,9 +84,9 @@ def __init__(
8484 cf_role : Literal ["grid_topology" ],
8585 topology_dimension : Literal [2 ],
8686 node_dimensions : tuple [Dim , Dim ],
87- face_dimensions : tuple [DimDimPadding , DimDimPadding ],
87+ face_dimensions : tuple [FaceNodePadding , FaceNodePadding ],
8888 node_coordinates : None | tuple [Dim , Dim ] = None ,
89- vertical_dimensions : None | tuple [DimDimPadding ] = None ,
89+ vertical_dimensions : None | tuple [FaceNodePadding ] = None ,
9090 ):
9191 if cf_role != "grid_topology" :
9292 raise ValueError (f"cf_role must be 'grid_topology', got { cf_role !r} " )
@@ -104,9 +104,9 @@ def __init__(
104104 if not (
105105 isinstance (face_dimensions , tuple )
106106 and len (face_dimensions ) == 2
107- and all (isinstance (fd , DimDimPadding ) for fd in face_dimensions )
107+ and all (isinstance (fd , FaceNodePadding ) for fd in face_dimensions )
108108 ):
109- raise ValueError ("face_dimensions must be a tuple of 2 DimDimPadding for a 2D grid" )
109+ raise ValueError ("face_dimensions must be a tuple of 2 FaceNodePadding for a 2D grid" )
110110
111111 if node_coordinates is not None :
112112 if not (
@@ -120,9 +120,9 @@ def __init__(
120120 if not (
121121 isinstance (vertical_dimensions , tuple )
122122 and len (vertical_dimensions ) == 1
123- and isinstance (vertical_dimensions [0 ], DimDimPadding )
123+ and isinstance (vertical_dimensions [0 ], FaceNodePadding )
124124 ):
125- raise ValueError ("vertical_dimensions must be a tuple of 1 DimDimPadding for a 2D grid" )
125+ raise ValueError ("vertical_dimensions must be a tuple of 1 FaceNodePadding for a 2D grid" )
126126
127127 # Required attributes
128128 self .cf_role = cf_role
@@ -137,8 +137,8 @@ def __init__(
137137 #! Some optional attributes aren't really important to Parcels, can be added later if needed
138138 # Optional attributes
139139 # # With defaults (set in init)
140- # edge1_dimensions: tuple[Dim, DimDimPadding ]
141- # edge2_dimensions: tuple[DimDimPadding , Dim]
140+ # edge1_dimensions: tuple[Dim, FaceNodePadding ]
141+ # edge2_dimensions: tuple[FaceNodePadding , Dim]
142142
143143 # # Without defaults
144144 # edge1_coordinates: None | Any = None
@@ -163,7 +163,7 @@ def from_attrs(cls, attrs): # type: ignore[override]
163163 cf_role = attrs ["cf_role" ],
164164 topology_dimension = attrs ["topology_dimension" ],
165165 node_dimensions = cast (tuple [Dim , Dim ], load_mappings (attrs ["node_dimensions" ])),
166- face_dimensions = cast (tuple [DimDimPadding , DimDimPadding ], load_mappings (attrs ["face_dimensions" ])),
166+ face_dimensions = cast (tuple [FaceNodePadding , FaceNodePadding ], load_mappings (attrs ["face_dimensions" ])),
167167 node_coordinates = maybe_load_mappings (attrs .get ("node_coordinates" )),
168168 vertical_dimensions = maybe_load_mappings (attrs .get ("vertical_dimensions" )),
169169 )
@@ -186,7 +186,7 @@ def to_attrs(self) -> dict[str, str | int]:
186186 def rename (self , names_dict : dict [str , str ]) -> Self :
187187 return cast (Self , _metadata_rename (self , names_dict ))
188188
189- def get_value_by_id (self , id : str ) -> str :
189+ def get_value_by_id (self , id : str ) -> Dim | Padding :
190190 """In the SGRID specification for 2D grids, different parts of the spec are identified by different "ID"s.
191191
192192 Easily extract the value for a given ID.
@@ -206,7 +206,7 @@ def __init__(
206206 cf_role : Literal ["grid_topology" ],
207207 topology_dimension : Literal [3 ],
208208 node_dimensions : tuple [Dim , Dim , Dim ],
209- volume_dimensions : tuple [DimDimPadding , DimDimPadding , DimDimPadding ],
209+ volume_dimensions : tuple [FaceNodePadding , FaceNodePadding , FaceNodePadding ],
210210 node_coordinates : None | tuple [Dim , Dim , Dim ] = None ,
211211 ):
212212 if cf_role != "grid_topology" :
@@ -225,9 +225,9 @@ def __init__(
225225 if not (
226226 isinstance (volume_dimensions , tuple )
227227 and len (volume_dimensions ) == 3
228- and all (isinstance (fd , DimDimPadding ) for fd in volume_dimensions )
228+ and all (isinstance (fd , FaceNodePadding ) for fd in volume_dimensions )
229229 ):
230- raise ValueError ("face_dimensions must be a tuple of 2 DimDimPadding for a 2D grid" )
230+ raise ValueError ("face_dimensions must be a tuple of 2 FaceNodePadding for a 2D grid" )
231231
232232 if node_coordinates is not None :
233233 if not (
@@ -249,12 +249,12 @@ def __init__(
249249 # ! Some optional attributes aren't really important to Parcels, can be added later if needed
250250 # Optional attributes
251251 # # With defaults (set in init)
252- # edge1_dimensions: tuple[DimDimPadding , Dim, Dim]
253- # edge2_dimensions: tuple[Dim, DimDimPadding , Dim]
254- # edge3_dimensions: tuple[Dim, Dim, DimDimPadding ]
255- # face1_dimensions: tuple[Dim, DimDimPadding, DimDimPadding ]
256- # face2_dimensions: tuple[DimDimPadding , Dim, DimDimPadding ]
257- # face3_dimensions: tuple[DimDimPadding, DimDimPadding , Dim]
252+ # edge1_dimensions: tuple[FaceNodePadding , Dim, Dim]
253+ # edge2_dimensions: tuple[Dim, FaceNodePadding , Dim]
254+ # edge3_dimensions: tuple[Dim, Dim, FaceNodePadding ]
255+ # face1_dimensions: tuple[Dim, FaceNodePadding, FaceNodePadding ]
256+ # face2_dimensions: tuple[FaceNodePadding , Dim, FaceNodePadding ]
257+ # face3_dimensions: tuple[FaceNodePadding, FaceNodePadding , Dim]
258258
259259 # # Without defaults
260260 # edge *i_coordinates*
@@ -280,7 +280,7 @@ def from_attrs(cls, attrs): # type: ignore[override]
280280 topology_dimension = attrs ["topology_dimension" ],
281281 node_dimensions = cast (tuple [Dim , Dim , Dim ], load_mappings (attrs ["node_dimensions" ])),
282282 volume_dimensions = cast (
283- tuple [DimDimPadding , DimDimPadding , DimDimPadding ], load_mappings (attrs ["volume_dimensions" ])
283+ tuple [FaceNodePadding , FaceNodePadding , FaceNodePadding ], load_mappings (attrs ["volume_dimensions" ])
284284 ),
285285 node_coordinates = maybe_load_mappings (attrs .get ("node_coordinates" )),
286286 )
@@ -301,7 +301,7 @@ def to_attrs(self) -> dict[str, str | int]:
301301 def rename (self , dims_dict : dict [str , str ]) -> Self :
302302 return cast (Self , _metadata_rename (self , dims_dict ))
303303
304- def get_value_by_id (self , id : str ) -> str :
304+ def get_value_by_id (self , id : str ) -> Dim | Padding :
305305 """In the SGRID specification for 3D grids, different parts of the spec are identified by different "ID"s.
306306
307307 Easily extract the value for a given ID.
@@ -316,39 +316,39 @@ def get_value_by_id(self, id: str) -> str:
316316
317317
318318@dataclass
319- class DimDimPadding :
320- """A data class representing a dimension-dimension -padding triplet for SGrid metadata.
319+ class FaceNodePadding :
320+ """A data class representing a face-node -padding triplet for SGrid metadata.
321321
322- This triplet can represent different relations depending on context within the standard
323- For example - for "face_dimensions" this can show the relation between an edge (dim1) and a node
324- (dim2) .
322+ In the context of a 2D grid, "face" corresponds with an edge.
323+
324+ Use the .to_diagram() method to visualize the representation .
325325 """
326326
327- dim1 : str
328- dim2 : str
327+ face : Dim
328+ node : Dim
329329 padding : Padding
330330
331331 def __repr__ (self ) -> str :
332- return f"DimDimPadding(dim1 ={ self .dim1 !r} , dim2 ={ self .dim2 !r} , padding={ self .padding !r} )"
332+ return f"FaceNodePadding(face ={ self .face !r} , node ={ self .node !r} , padding={ self .padding !r} )"
333333
334334 def __str__ (self ) -> str :
335- return f"{ self .dim1 } :{ self .dim2 } (padding:{ self .padding .value } )"
335+ return f"{ self .face } :{ self .node } (padding:{ self .padding .value } )"
336336
337337 @classmethod
338338 def load (cls , s : str ) -> Self :
339- match = re .match (RE_DIM_DIM_PADDING , s )
339+ match = re .match (RE_FACE_NODE_PADDING , s )
340340 if not match :
341- raise ValueError (f"String { s !r} does not match expected format for DimDimPadding " )
342- dim1 = match .group (1 )
343- dim2 = match .group (2 )
341+ raise ValueError (f"String { s !r} does not match expected format for FaceNodePadding " )
342+ face = match .group (1 )
343+ node = match .group (2 )
344344 padding = Padding (match .group (3 ).lower ())
345- return cls (dim1 , dim2 , padding )
345+ return cls (face , node , padding )
346346
347347 def to_diagram (self ) -> str :
348348 return "\n " .join (_face_node_padding_to_text (self ))
349349
350350
351- def dump_mappings (parts : Iterable [DimDimPadding | Dim ]) -> str :
351+ def dump_mappings (parts : Iterable [FaceNodePadding | Dim ]) -> str :
352352 """Takes in a list of edge-node-padding tuples and serializes them into a string
353353 according to the SGrid convention.
354354 """
@@ -361,7 +361,7 @@ def dump_mappings(parts: Iterable[DimDimPadding | Dim]) -> str:
361361@overload
362362def maybe_dump_mappings (parts : None ) -> None : ...
363363@overload
364- def maybe_dump_mappings (parts : Iterable [DimDimPadding | Dim ]) -> str : ...
364+ def maybe_dump_mappings (parts : Iterable [FaceNodePadding | Dim ]) -> str : ...
365365
366366
367367def maybe_dump_mappings (parts ):
@@ -370,7 +370,7 @@ def maybe_dump_mappings(parts):
370370 return dump_mappings (parts )
371371
372372
373- def load_mappings (s : str ) -> tuple [DimDimPadding | Dim , ...]:
373+ def load_mappings (s : str ) -> tuple [FaceNodePadding | Dim , ...]:
374374 """Takes in a string indicating the mappings of dims and dim-dim-padding
375375 and returns a tuple with this data destructured.
376376
@@ -383,25 +383,25 @@ def load_mappings(s: str) -> tuple[DimDimPadding | Dim, ...]:
383383 ret = []
384384 while s :
385385 # find next part
386- match = re .match (RE_DIM_DIM_PADDING , s )
386+ match = re .match (RE_FACE_NODE_PADDING , s )
387387 if match and match .start () == 0 :
388388 # match found at start, take that as next part
389389 part = match .group (0 )
390390 s_new = s [match .end () :].lstrip ()
391391 else :
392- # no DimDimPadding match at start, assume just a Dim until next space
392+ # no FaceNodePadding match at start, assume just a Dim until next space
393393 part , * s_new = s .split (" " , 1 )
394394 s_new = "" .join (s_new )
395395
396396 assert s != s_new , f"SGrid parsing did not advance, stuck at { s !r} "
397397
398- parsed : DimDimPadding | Dim
398+ parsed : FaceNodePadding | Dim
399399 try :
400- parsed = DimDimPadding .load (part )
400+ parsed = FaceNodePadding .load (part )
401401 except ValueError as e :
402402 e .add_note (f"Failed to parse part { part !r} from { s !r} as a dimension dimension padding string" )
403403 try :
404- # Not a DimDimPadding , assume it's just a Dim
404+ # Not a FaceNodePadding , assume it's just a Dim
405405 assert ":" not in part , f"Part { part !r} from { s !r} not a valid dim (contains ':')"
406406 parsed = part
407407 except AssertionError as e2 :
@@ -416,7 +416,7 @@ def load_mappings(s: str) -> tuple[DimDimPadding | Dim, ...]:
416416@overload
417417def maybe_load_mappings (s : None ) -> None : ...
418418@overload
419- def maybe_load_mappings (s : Hashable ) -> tuple [DimDimPadding | Dim , ...]: ...
419+ def maybe_load_mappings (s : Hashable ) -> tuple [FaceNodePadding | Dim , ...]: ...
420420
421421
422422def maybe_load_mappings (s ):
@@ -471,11 +471,11 @@ def parse_sgrid(ds: xr.Dataset):
471471 dimensions = grid .volume_dimensions
472472
473473 xgcm_coords = {}
474- for dim_dim_padding , axis in zip (dimensions , "XYZ" , strict = False ):
475- xgcm_position = SGRID_PADDING_TO_XGCM_POSITION [dim_dim_padding .padding ]
474+ for face_node_padding , axis in zip (dimensions , "XYZ" , strict = False ):
475+ xgcm_position = SGRID_PADDING_TO_XGCM_POSITION [face_node_padding .padding ]
476476
477477 coords = {}
478- for pos , dim in [("center" , dim_dim_padding . dim1 ), (xgcm_position , dim_dim_padding . dim2 )]:
478+ for pos , dim in [("center" , face_node_padding . face ), (xgcm_position , face_node_padding . node )]:
479479 # only include dimensions in dataset (ignore dimensions in metadata that may not exist - e.g., due to `.isel`)
480480 if dim in ds .dims :
481481 coords [pos ] = dim
@@ -510,16 +510,16 @@ def get_unique_names(grid: Grid2DMetadata | Grid3DMetadata) -> set[str]:
510510 f"Expected sgrid metadata attribute to be represented as a tuple, got { value !r} . This is an internal error to Parcels - please post an issue if you encounter this."
511511 )
512512 for item in value :
513- if isinstance (item , DimDimPadding ):
514- dims .add (item .dim1 )
515- dims .add (item .dim2 )
513+ if isinstance (item , FaceNodePadding ):
514+ dims .add (item .face )
515+ dims .add (item .node )
516516 else :
517517 assert isinstance (item , str )
518518 dims .add (item )
519519 return dims
520520
521521
522- def _face_node_padding_to_text (obj : DimDimPadding ) -> list [str ]:
522+ def _face_node_padding_to_text (obj : FaceNodePadding ) -> list [str ]:
523523 """Return ASCII diagram lines showing a face-node padding relationship.
524524
525525 Produces a symbolic 5-node diagram like the image below, matching the
@@ -566,7 +566,7 @@ def _face_node_padding_to_text(obj: DimDimPadding) -> list[str]:
566566 face_count += 1
567567
568568 return [
569- f"{ obj .dim1 } :{ obj .dim2 } (padding:{ padding .value } )" ,
569+ f"{ obj .face } :{ obj .node } (padding:{ padding .value } )" ,
570570 f" { bar_rendered } " ,
571571 f" { label .rstrip ()} " ,
572572 ]
@@ -640,19 +640,19 @@ def _grid2d_to_ascii(grid: Grid2DMetadata) -> str:
640640 nd = grid .node_dimensions
641641 lines = [
642642 "Grid2DMetadata" ,
643- f" X-axis: face={ fd [0 ].dim1 !r} node={ nd [0 ]!r} padding={ fd [0 ].padding .value } " ,
644- f" Y-axis: face={ fd [1 ].dim1 !r} node={ nd [1 ]!r} padding={ fd [1 ].padding .value } " ,
643+ f" X-axis: face={ fd [0 ].face !r} node={ nd [0 ]!r} padding={ fd [0 ].padding .value } " ,
644+ f" Y-axis: face={ fd [1 ].face !r} node={ nd [1 ]!r} padding={ fd [1 ].padding .value } " ,
645645 ]
646646 if grid .vertical_dimensions :
647647 vd = grid .vertical_dimensions [0 ]
648- lines .append (f" Z-axis: face={ vd .dim1 !r} node={ vd .dim2 !r} padding={ vd .padding .value } " )
648+ lines .append (f" Z-axis: face={ vd .face !r} node={ vd .node !r} padding={ vd .padding .value } " )
649649 if grid .node_coordinates :
650650 lines .append (f" Coordinates: { grid .node_coordinates [0 ]} , { grid .node_coordinates [1 ]} " )
651651
652- format_kwargs = dict (n1 = nd [0 ], n2 = nd [1 ], u = fd [0 ].dim1 , v = fd [1 ].dim1 )
652+ format_kwargs = dict (n1 = nd [0 ], n2 = nd [1 ], u = fd [0 ].face , v = fd [1 ].face )
653653
654654 if grid .vertical_dimensions :
655- format_kwargs ["w" ] = grid .vertical_dimensions [0 ].dim2
655+ format_kwargs ["w" ] = grid .vertical_dimensions [0 ].node
656656 lines += indent (_TEXT_GRID2D_WITH_Z , " " ).format (** format_kwargs ).split ("\n " )
657657 else :
658658 lines += indent (_TEXT_GRID2D_WITHOUT_Z , " " ).format (** format_kwargs ).split ("\n " )
@@ -672,16 +672,16 @@ def _grid3d_to_ascii(grid: Grid3DMetadata) -> str:
672672 nd = grid .node_dimensions
673673 lines = [
674674 "Grid3DMetadata" ,
675- f" X-axis: face={ vd [0 ].dim1 !r} node={ nd [0 ]!r} padding={ vd [0 ].padding .value } " ,
676- f" Y-axis: face={ vd [1 ].dim1 !r} node={ nd [1 ]!r} padding={ vd [1 ].padding .value } " ,
677- f" Z-axis: face={ vd [2 ].dim1 !r} node={ nd [2 ]!r} padding={ vd [2 ].padding .value } " ,
675+ f" X-axis: face={ vd [0 ].face !r} node={ nd [0 ]!r} padding={ vd [0 ].padding .value } " ,
676+ f" Y-axis: face={ vd [1 ].face !r} node={ nd [1 ]!r} padding={ vd [1 ].padding .value } " ,
677+ f" Z-axis: face={ vd [2 ].face !r} node={ nd [2 ]!r} padding={ vd [2 ].padding .value } " ,
678678 ]
679679 if grid .node_coordinates :
680680 lines .append (f" Coordinates: { ', ' .join (grid .node_coordinates )} " )
681681
682682 lines += (
683683 indent (_TEXT_GRID3D , " " )
684- .format (n1 = nd [0 ], n2 = nd [1 ], n3 = nd [2 ], u = vd [0 ].dim1 , v = vd [1 ].dim1 , w = vd [2 ].dim1 )
684+ .format (n1 = nd [0 ], n2 = nd [1 ], n3 = nd [2 ], u = vd [0 ].face , v = vd [1 ].face , w = vd [2 ].face )
685685 .split ("\n " )
686686 )
687687
@@ -738,10 +738,10 @@ def _metadata_rename(grid, names_dict):
738738 if isinstance (value , tuple ):
739739 new_value = []
740740 for item in value :
741- if isinstance (item , DimDimPadding ):
742- new_item = DimDimPadding (
743- dim1 = names_dict [item .dim1 ],
744- dim2 = names_dict [item .dim2 ],
741+ if isinstance (item , FaceNodePadding ):
742+ new_item = FaceNodePadding (
743+ face = names_dict [item .face ],
744+ node = names_dict [item .node ],
745745 padding = item .padding ,
746746 )
747747 new_value .append (new_item )
0 commit comments