1313
1414from collections import namedtuple
1515from collections .abc import Iterable
16+ from dataclasses import dataclass
1617import logging
18+ from typing import Any
1719
1820from dask .array .core import broadcast_shapes
1921import numpy as np
5658
5759_PreparedFactory = namedtuple ("PreparedFactory" , ["container" , "dependencies" ])
5860
59- _PreparedItem = namedtuple (
60- "PreparedItem" ,
61- ["metadata" , "points" , "bounds" , "dims" , "container" ],
62- )
61+
62+ @dataclass
63+ class _PreparedItem :
64+ metadata : Any
65+ points : Any
66+ bounds : Any
67+ dims : Any
68+ container : Any
69+ mesh : Any = None
70+ location : Any = None
71+ axis : Any = None
72+
73+ def create_coord (self , metadata ):
74+ from iris .experimental .ugrid .mesh import MeshCoord
75+
76+ if issubclass (self .container , MeshCoord ):
77+ # Make a MeshCoord, for which we have mesh/location/axis.
78+ result = MeshCoord (
79+ mesh = self .mesh ,
80+ location = self .location ,
81+ axis = self .axis ,
82+ )
83+ # Note: in this case we do also have "prepared metadata", but we
84+ # do *not* assign it as we do for an 'ordinary' Coord.
85+ # Instead, MeshCoord name/units/attributes are immutable, and set at
86+ # create time to those of the underlying mesh node coordinate.
87+ # cf https://github.com/SciTools/iris/issues/4670
88+
89+ else :
90+ # make a regular coord, for which we have points/bounds/metadata.
91+ result = self .container (self .points , bounds = self .bounds )
92+ # Also assign prepared metadata.
93+ result .metadata = metadata
94+
95+ return result
96+
6397
6498_PreparedMetadata = namedtuple ("PreparedMetadata" , ["combined" , "src" , "tgt" ])
6599
@@ -646,7 +680,13 @@ def _categorise_items(cube):
646680
647681 @staticmethod
648682 def _create_prepared_item (
649- coord , dims , src_metadata = None , tgt_metadata = None
683+ coord ,
684+ dims ,
685+ src_metadata = None ,
686+ tgt_metadata = None ,
687+ points = None ,
688+ bounds = None ,
689+ container = None ,
650690 ):
651691 """
652692 Convenience method that creates a :class:`~iris.common.resolve._PreparedItem`
@@ -658,35 +698,96 @@ def _create_prepared_item(
658698 * coord:
659699 The coordinate with the ``points`` and ``bounds`` to be extracted.
660700
661- * dims:
662- The dimensions that the ``coord`` spans on the resulting resolved :class:`~iris.cube.Cube`.
701+ * dims (int or tuple):
702+ The dimensions that the ``coord`` spans on the resulting resolved
703+ :class:`~iris.cube.Cube`.
704+ (Can also be a single dimension number).
663705
664706 * src_metadata:
665707 The coordinate metadata from the ``src`` :class:`~iris.cube.Cube`.
666708
667709 * tgt_metadata:
668710 The coordinate metadata from the ``tgt`` :class:`~iris.cube.Cube`.
669711
712+ * points:
713+ Override points array. When not given, use coord.points.
714+
715+ * bounds:
716+ Override bounds array. When not given, use coord.bounds.
717+
718+ * container:
719+ Override coord type (class constructor).
720+ When not given, use type(coord).
721+
670722 Returns:
671723 The :class:`~iris.common.resolve._PreparedItem`.
672724
725+ .. note::
726+
727+ If container or type(coord) is DimCoord/AuxCoord (i.e. not
728+ MeshCoord), then points+bounds define the built AuxCoord/DimCoord.
729+ Theses points+bounds come either from those args, or the 'coord'.
730+ Alternatively, when container or type(coord) is MeshCoord, then
731+ points==bounds==None and the preparted item contains
732+ mesh/location/axis properties for the resulting MeshCoord.
733+ These don't have override args: they *always* come from 'coord'.
734+
673735 """
736+ if not isinstance (dims , Iterable ):
737+ dims = (dims ,)
738+
674739 if src_metadata is not None and tgt_metadata is not None :
675740 combined = src_metadata .combine (tgt_metadata )
676741 else :
677742 combined = src_metadata or tgt_metadata
678- if not isinstance (dims , Iterable ):
679- dims = (dims ,)
680743 prepared_metadata = _PreparedMetadata (
681744 combined = combined , src = src_metadata , tgt = tgt_metadata
682745 )
683- bounds = coord .bounds
746+
747+ if container is None :
748+ container = type (coord )
749+
750+ from iris .experimental .ugrid .mesh import MeshCoord
751+
752+ if issubclass (container , MeshCoord ):
753+ # Build a prepared-item to make a MeshCoord.
754+ # This case does *NOT* use points + bounds, so alternatives to the
755+ # coord content should not have been specified by the caller.
756+ assert points is None and bounds is None
757+ mesh = coord .mesh
758+ location = coord .location
759+ axis = coord .axis
760+
761+ else :
762+ # Build a prepared-item to make a DimCoord or AuxCoord.
763+
764+ # mesh/location/axis are not used.
765+ mesh = None
766+ location = None
767+ axis = None
768+
769+ # points + bounds default to those from the coordinate, but
770+ # alternative values may be specified.
771+ if points is None :
772+ points = coord .points
773+ bounds = coord .bounds
774+ # 'ELSE' points was passed: both points+bounds come from the args
775+
776+ # Always *copy* points+bounds, to avoid any possible direct (shared)
777+ # references to existing coord arrays.
778+ points = points .copy ()
779+ if bounds is not None :
780+ bounds = bounds .copy ()
781+
684782 result = _PreparedItem (
685783 metadata = prepared_metadata ,
686- points = coord .points .copy (),
687- bounds = bounds if bounds is None else bounds .copy (),
688784 dims = dims ,
689- container = type (coord ),
785+ points = points ,
786+ bounds = bounds ,
787+ mesh = mesh ,
788+ location = location ,
789+ axis = axis ,
790+ container = container ,
690791 )
691792 return result
692793
@@ -1422,30 +1523,64 @@ def _prepare_common_aux_payload(
14221523 (tgt_item ,) = tgt_items
14231524 src_coord = src_item .coord
14241525 tgt_coord = tgt_item .coord
1425- points , bounds = self ._prepare_points_and_bounds (
1426- src_coord ,
1427- tgt_coord ,
1428- src_item .dims ,
1429- tgt_item .dims ,
1430- ignore_mismatch = ignore_mismatch ,
1431- )
1432- if points is not None :
1433- src_type = type (src_coord )
1434- tgt_type = type (tgt_coord )
1435- # Downcast to aux if there are mixed container types.
1436- container = src_type if src_type is tgt_type else AuxCoord
1437- prepared_metadata = _PreparedMetadata (
1438- combined = src_metadata .combine (tgt_item .metadata ),
1439- src = src_metadata ,
1440- tgt = tgt_item .metadata ,
1441- )
1442- prepared_item = _PreparedItem (
1443- metadata = prepared_metadata ,
1444- points = points .copy (),
1445- bounds = bounds if bounds is None else bounds .copy (),
1446- dims = tgt_item .dims ,
1447- container = container ,
1526+
1527+ prepared_item = None
1528+ src_is_mesh , tgt_is_mesh = [
1529+ hasattr (coord , "mesh" ) for coord in (src_coord , tgt_coord )
1530+ ]
1531+ if src_is_mesh and tgt_is_mesh :
1532+ # MeshCoords are a bit "special" ...
1533+ # In this case, we may need to produce an alternative form
1534+ # to the 'ordinary' _PreparedItem
1535+ # However, this only works if they have identical meshes..
1536+ if src_coord == tgt_coord :
1537+ prepared_item = self ._create_prepared_item (
1538+ src_coord ,
1539+ tgt_item .dims ,
1540+ src_metadata = src_metadata ,
1541+ tgt_metadata = tgt_item .metadata ,
1542+ )
1543+ else :
1544+ emsg = (
1545+ f"Mesh coordinate { src_coord .name ()!r} does not match between the "
1546+ f"LHS cube { self .lhs_cube .name ()!r} and "
1547+ f"RHS cube { self .rhs_cube .name ()!r} ."
1548+ )
1549+ raise ValueError (emsg )
1550+
1551+ if prepared_item is None :
1552+ # Make a "normal" _PreparedItem, which is specified using
1553+ # points + bounds arrays.
1554+ # First, convert any un-matching MeshCoords to AuxCoord
1555+ if src_is_mesh :
1556+ src_coord = AuxCoord .from_coord (src_coord )
1557+ if tgt_is_mesh :
1558+ tgt_coord = AuxCoord .from_coord (tgt_coord )
1559+ points , bounds = self ._prepare_points_and_bounds (
1560+ src_coord ,
1561+ tgt_coord ,
1562+ src_item .dims ,
1563+ tgt_item .dims ,
1564+ ignore_mismatch = ignore_mismatch ,
14481565 )
1566+ if points is not None :
1567+ src_type = type (src_coord )
1568+ tgt_type = type (tgt_coord )
1569+ # Downcast to aux if there are mixed container types.
1570+ container = (
1571+ src_type if src_type is tgt_type else AuxCoord
1572+ )
1573+ prepared_item = self ._create_prepared_item (
1574+ src_coord ,
1575+ tgt_item .dims ,
1576+ src_metadata = src_metadata ,
1577+ tgt_metadata = tgt_item .metadata ,
1578+ points = points ,
1579+ bounds = bounds ,
1580+ container = container ,
1581+ )
1582+
1583+ if prepared_item is not None :
14491584 prepared_items .append (prepared_item )
14501585
14511586 def _prepare_common_dim_payload (
@@ -1499,16 +1634,13 @@ def _prepare_common_dim_payload(
14991634 )
15001635
15011636 if points is not None :
1502- prepared_metadata = _PreparedMetadata (
1503- combined = src_metadata .combine (tgt_metadata ),
1504- src = src_metadata ,
1505- tgt = tgt_metadata ,
1506- )
1507- prepared_item = _PreparedItem (
1508- metadata = prepared_metadata ,
1509- points = points .copy (),
1510- bounds = bounds if bounds is None else bounds .copy (),
1511- dims = (tgt_dim ,),
1637+ prepared_item = self ._create_prepared_item (
1638+ src_coord ,
1639+ tgt_dim ,
1640+ src_metadata = src_metadata ,
1641+ tgt_metadata = tgt_metadata ,
1642+ points = points ,
1643+ bounds = bounds ,
15121644 container = DimCoord ,
15131645 )
15141646 self .prepared_category .items_dim .append (prepared_item )
@@ -2333,8 +2465,7 @@ def cube(self, data, in_place=False):
23332465
23342466 # Add the prepared dim coordinates.
23352467 for item in self .prepared_category .items_dim :
2336- coord = item .container (item .points , bounds = item .bounds )
2337- coord .metadata = item .metadata .combined
2468+ coord = item .create_coord (metadata = item .metadata .combined )
23382469 result .add_dim_coord (coord , item .dims )
23392470
23402471 # Add the prepared aux and scalar coordinates.
@@ -2343,8 +2474,8 @@ def cube(self, data, in_place=False):
23432474 + self .prepared_category .items_scalar
23442475 )
23452476 for item in prepared_aux_coords :
2346- coord = item . container ( item . points , bounds = item . bounds )
2347- coord . metadata = item .metadata . combined
2477+ # These items are "special"
2478+ coord = item .create_coord ( metadata = item . metadata . combined )
23482479 try :
23492480 result .add_aux_coord (coord , item .dims )
23502481 except ValueError as err :
0 commit comments