Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
7c880a2
dev
davidhassell Sep 3, 2025
29d3954
dev
davidhassell Sep 4, 2025
352e232
dev
davidhassell Sep 5, 2025
20fc117
dev
davidhassell Sep 5, 2025
c090d25
dev
davidhassell Sep 9, 2025
95e9597
dev
davidhassell Sep 10, 2025
ab80ed3
dev
davidhassell Sep 10, 2025
da18b0e
dev
davidhassell Sep 11, 2025
a651ff9
dev
davidhassell Sep 12, 2025
0251381
dev
davidhassell Sep 12, 2025
4d05e06
dev
davidhassell Sep 13, 2025
9043f84
dev
davidhassell Sep 14, 2025
5881657
dev
davidhassell Oct 8, 2025
5a831c2
dev
davidhassell Oct 8, 2025
e0ee246
dev
davidhassell Oct 8, 2025
e21a551
dev
davidhassell Oct 9, 2025
f46f888
dev
davidhassell Oct 9, 2025
37a4fab
dev
davidhassell Oct 13, 2025
c5f229a
Merge branch 'zarr-write' into ugrid-write
davidhassell Oct 13, 2025
a267ce8
dev
davidhassell Oct 20, 2025
f5d574a
Merge branch 'zarr-write' into ugrid-write
davidhassell Oct 27, 2025
2f856f1
dev
davidhassell Oct 27, 2025
1c7a54b
dev
davidhassell Oct 28, 2025
c228a92
dev
davidhassell Oct 29, 2025
cfb180a
dev
davidhassell Nov 5, 2025
29242da
dev
davidhassell Nov 17, 2025
372660b
dev
davidhassell Jan 8, 2026
ce6411c
dev
davidhassell Jan 9, 2026
6a9be91
upstream main merge
davidhassell Jan 14, 2026
a2d56d7
dev
davidhassell Jan 15, 2026
244cbe9
dev
davidhassell Jan 15, 2026
bd3d2fe
Merge branch 'main' into ugrid-write
davidhassell Jan 21, 2026
4c6eeb4
Merge branch 'main' into ugrid-write
davidhassell Feb 19, 2026
62c2d1a
upstream merge
davidhassell Mar 3, 2026
29c2fd6
node_coordinates method
davidhassell Mar 12, 2026
69ca6d6
Merge branch 'ugrid-write' of github.com:davidhassell/cfdm into ugrid…
davidhassell Mar 12, 2026
1d1cb52
dev
davidhassell Mar 13, 2026
bd4f5ab
dev
davidhassell Mar 13, 2026
78d2df3
dev
davidhassell Mar 13, 2026
6c7de21
dev
davidhassell Mar 14, 2026
2fe1947
dev
davidhassell Mar 15, 2026
0c0a5e2
dev
davidhassell Mar 15, 2026
34e0da0
dev
davidhassell Mar 16, 2026
4e660ed
Merge branch 'main' into ugrid-write
davidhassell Mar 16, 2026
a8e7e12
Merge branch 'ugrid-write' into to-xarray-2
davidhassell Mar 16, 2026
70cda4e
dev
davidhassell Mar 16, 2026
783aecd
dev
davidhassell Mar 16, 2026
fe412eb
dev
davidhassell Mar 16, 2026
e4184c9
dev
davidhassell Mar 17, 2026
483a316
dev
davidhassell Mar 17, 2026
4cbf0cf
dev
davidhassell Mar 18, 2026
c144e28
dev
davidhassell Mar 18, 2026
fae3c4f
dev
davidhassell Mar 18, 2026
d96027d
dev
davidhassell Mar 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ Version NEXTVERSION

**2026-??-??**

* New methods to convert to `xarray`: `cf.Field.to_xarray` and
`cf.Domain.to_xarray`
(https://github.com/NCAS-CMS/cfdm/issues/394)
* New output format for `cfdm.write` that creates an `xarray` dataset
in memory: ``'XARRAY'``
(https://github.com/NCAS-CMS/cfdm/issues/394)
* Write UGRID datasets with `cfdm.write`
(https://github.com/NCAS-CMS/cfdm/issues/271)
* New keyword to `cfdm.read`: ``filesystem``
(https://github.com/NCAS-CMS/cfdm/issues/397)
* New keyword parameter to `cfdm.Data.compute`: ``persist``
Expand Down Expand Up @@ -31,6 +39,7 @@ Version NEXTVERSION
different ``formula_terms``
(https://github.com/NCAS-CMS/cfdm/issues/380).
* New dependency: ``pyfive>=1.1.1``
* New optional dependency: ``xarray>=2026.2.0``
* Changed dependency: ``h5netcdf>=1.8.0``

----
Expand Down
1 change: 1 addition & 0 deletions cfdm/cellconnectivity.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

class CellConnectivity(
mixin.NetCDFVariable,
mixin.NetCDFConnectivityDimension,
mixin.Topology,
mixin.PropertiesData,
mixin.Files,
Expand Down
69 changes: 47 additions & 22 deletions cfdm/cfdmimplementation.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,25 @@ def get_bounds_ncvar(self, parent, default=None):

return self.nc_get_variable(bounds, default=default)

def get_cell_connectivities(self, parent):
"""Return the cell connectivities from a parent.

.. versionadded:: (cfdm) NEXTVERSION

:Parameters:

parent: `Field` or `Domain`
The parent object.

:Returns:

`dict`
A dictionary whose values are cell connectivity
objects, keyed by unique identifiers.

"""
return parent.cell_connectivities(todict=True)

def get_cell_measures(self, field):
"""Return all of the cell measure constructs of a field.

Expand Down Expand Up @@ -1014,6 +1033,25 @@ def get_domain_axis_size(self, field, axis):
"""
return field.domain_axes(todict=True)[axis].get_size()

def get_domain_topologies(self, parent):
"""Return the domain topologies from a parent.

.. versionadded:: (cfdm) NEXTVERSION

:Parameters:

parent: `Field` or `Domain`
The parent object.

:Returns:

`dict`
A dictionary whose values are domain topology objects,
keyed by unique identifiers.

"""
return parent.domain_topologies(todict=True)

def get_sample_dimension_position(self, construct):
"""Returns the position of the compressed data sample dimension.

Expand Down Expand Up @@ -1985,16 +2023,23 @@ def get_tie_points(self, construct, default=None):

return data.source(default)

def initialise_AuxiliaryCoordinate(self):
def initialise_AuxiliaryCoordinate(self, **kwargs):
"""Return an auxiliary coordinate construct.

:Parameters:

kwargs: optional
Parameters with which to intialising the object.

.. versionadded:: (cfdm) NEXTVERSION

:Returns:

`AuxiliaryCoordinate`

"""
cls = self.get_class("AuxiliaryCoordinate")
return cls()
return cls(**kwargs)

def initialise_Bounds(self):
"""Return a bounds component.
Expand Down Expand Up @@ -3459,26 +3504,6 @@ def set_dependent_tie_points(self, construct, tie_points, dimensions):
)
construct.set_data(data)

def set_mesh_id(self, parent, mesh_id):
"""Set a mesh identifier.

.. versionadded:: (cfdm) 1.11.0.0

:Parameters:

parent: construct
The construct on which to set the mesh id

mesh_id:
The mesh identifier.

:Returns:

`None`

"""
parent.set_mesh_id(mesh_id)

def nc_set_external(self, construct):
"""Set the external status of a construct.

Expand Down
6 changes: 0 additions & 6 deletions cfdm/data/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -770,12 +770,6 @@ def __getitem__(self, indices):
# ------------------------------------------------------------
new._set_dask(dx, clear=self._ALL ^ self._CFA, in_memory=None)

if 0 in new.shape:
raise IndexError(
f"Index [{original_indices}] selects no elements from "
f"data with shape {original_shape}"
)

# ------------------------------------------------------------
# Get the axis identifiers for the subspace
# ------------------------------------------------------------
Expand Down
54 changes: 0 additions & 54 deletions cfdm/data/mixin/compressedarraymixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,49 +5,6 @@ class CompressedArrayMixin:

"""

def _lock_file_read(self, array):
"""Try to return an array that doesn't support concurrent reads.

.. versionadded:: (cfdm) 1.11.2.0

:Parameters:

array: array_like
The array to process.

:Returns"

`dask.array.Array` or array_like
The new `dask` array, or the orginal array if it
couldn't be ascertained how to form the `dask` array.

"""
try:
return array.to_dask_array()
except AttributeError:
pass

try:
chunks = array.chunks
except AttributeError:
chunks = "auto"

try:
array = array.source()
except (ValueError, AttributeError):
pass

try:
array.get_filename()
except AttributeError:
pass
else:
import dask.array as da

array = da.from_array(array, chunks=chunks, lock=True)

return array

def to_dask_array(self, chunks="auto"):
"""Convert the data to a `dask` array.

Expand Down Expand Up @@ -87,18 +44,7 @@ def to_dask_array(self, chunks="auto"):

context = partial(config.set, scheduler="synchronous")

# If possible, convert the compressed data to a dask array
# that doesn't support concurrent reads. This prevents
# "compute called by compute" failures problems at compute
# time.
#
# TODO: This won't be necessary if this is refactored so that
# the compressed data is part of the same dask graph as
# the compressed subarrays.
conformed_data = self.conformed_data()
conformed_data = {
k: self._lock_file_read(v) for k, v in conformed_data.items()
}
subarray_kwargs = {**conformed_data, **self.subarray_parameters()}

# Get the (cfdm) subarray class
Expand Down
10 changes: 6 additions & 4 deletions cfdm/data/netcdfindexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,13 +254,15 @@ def __getitem__(self, index):
# E.g. index : (1, np.newaxis, slice(1, 5))
# => index1: (1, slice(1, 5))
# and index2: (slice(None), np.newaxis, slice(None))
except ValueError:
except (ValueError, TypeError):
# Something went wrong, which is indicative of the
# variable not supporting the appropriate slicing method
# (e.g. `h5netcdf` might have returned "ValueError: Step
# must be >= 1 (got -2)"). Therefore we'll just get the
# entire array as a numpy array, and then try indexing
# that.
# must be >= 1 (got -2)" or "TypeError: Indexing elements
# must be in increasing order").
#
# Therefore we'll just get the entire array as a numpy
# array, and then try indexing that.
data = self._index(Ellipsis)
data = self._index(index, data=data)

Expand Down
1 change: 1 addition & 0 deletions cfdm/data/subarray/cellconnectivitysubarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def __getitem__(self, indices):
stop += 1

data = self._select_data(check_mask=True)

if np.ma.isMA(data):
empty = np.ma.empty
else:
Expand Down
50 changes: 47 additions & 3 deletions cfdm/data/subarray/mixin/pointtopology.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,29 @@ def __getitem__(self, indices):
from cfdm.functions import integer_dtype

start_index = self.start_index
node_connectivity = self._select_data(check_mask=False)
node_connectivity = self._select_data(check_mask=True)

# ------------------------------------------------------------
# E.g. For faces, 'node_connectivity' might be (two
# quadrilaterals and one triangle):
#
# [[3 4 2 1 ]
# [5 6 4 3 ]
# [7 2 4 --]]
#
# E.g. For the nine edges of the above faces,
# 'node_connectivity' could be:
#
# [[2 7]
# [4 7]
# [4 2]
# [1 2]
# [3 1]
# [3 4]
# [3 5]
# [6 5]
# [4 6]]
# ------------------------------------------------------------

masked = np.ma.isMA(node_connectivity)

Expand All @@ -49,8 +71,15 @@ def __getitem__(self, indices):
cols_extend = cols.extend
u_extend = u.extend

unique_nodes = np.unique(node_connectivity)
if masked:
# Remove the missing value from unique nodes
unique_nodes = unique_nodes[:-1]

unique_nodes = unique_nodes.tolist()

# WARNING (TODO): This loop is a potential performance bottleneck.
for node in np.unique(node_connectivity).tolist():
for node in unique_nodes:
# Find the collection of all nodes that are joined to this
# node via links in the mesh, including this node itself
# (which will be at the start of the list).
Expand All @@ -62,10 +91,11 @@ def __getitem__(self, indices):
cols_extend(range(n_nodes))
u_extend(nodes)

del unique_nodes

u = np.array(u, dtype=integer_dtype(largest_node_id))
u = csr_array((u, cols, pointers))
u = u.toarray()

if any(map(isnan, self.shape)):
# Store the shape, now that is it known.
self._set_component("shape", u.shape, copy=False)
Expand All @@ -76,6 +106,20 @@ def __getitem__(self, indices):
# Mask all zeros
u = np.ma.where(u == 0, np.ma.masked, u)

# ------------------------------------------------------------
# E.g. For either of the face and edge examples above, 'u'
# would now be:
#
# [[1 2 3 -- --]
# [2 1 4 7 --]
# [3 1 4 5 --]
# [4 2 3 6 7]
# [5 3 6 -- --]
# [6 4 5 -- --]
# [7 2 4 -- --]]
#
# ------------------------------------------------------------

if not start_index:
# Subtract 1 to get back to zero-based node identities
u -= 1
Expand Down
Loading
Loading