Skip to content

API Reference

This page contains the API reference for dymfile.

Reader

dymfile.reader

Read DYM binary files.

dym_to_dataset(filepath, *, delta_time=NB_DAY_MONTHLY, decode_times=True, normalize_longitude=False, name=None, units=None)

Read a DYM file and convert to xarray Dataset.

Parameters:

Name Type Description Default
filepath str | Path

Path to DYM file.

required
delta_time int

Time delta in days (default 30 for monthly).

NB_DAY_MONTHLY
decode_times bool

If True, convert times to datetime64.

True
normalize_longitude bool

If True, normalize longitude to [-180, 180] and sort.

False
name str | None

Variable name (default: filename stem).

None
units str | None

Units for the data variable.

None

Returns:

Type Description
Dataset

Dataset with data and mask variables.

Examples:

>>> ds = dym_to_dataset("file.dym", name="temperature", units="degC")
>>> print(ds)
Source code in src/dymfile/reader.py
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
def dym_to_dataset(
    filepath: str | Path,
    *,
    delta_time: int = NB_DAY_MONTHLY,
    decode_times: bool = True,
    normalize_longitude: bool = False,
    name: str | None = None,
    units: str | None = None,
) -> xr.Dataset:
    """
    Read a DYM file and convert to xarray Dataset.

    Parameters
    ----------
    filepath : str | Path
        Path to DYM file.
    delta_time : int
        Time delta in days (default 30 for monthly).
    decode_times : bool
        If True, convert times to datetime64.
    normalize_longitude : bool
        If True, normalize longitude to [-180, 180] and sort.
    name : str | None
        Variable name (default: filename stem).
    units : str | None
        Units for the data variable.

    Returns
    -------
    xr.Dataset
        Dataset with data and mask variables.

    Examples
    --------
    >>> ds = dym_to_dataset("file.dym", name="temperature", units="degC")
    >>> print(ds)
    """
    filepath = Path(filepath)
    if name is None:
        name = filepath.stem

    dym_data = read_dym(filepath, delta_time=delta_time, decode_times=decode_times)

    # Create mask DataArray
    mask_da = xr.DataArray(
        dym_data.mask,
        dims=(LabelsCoordinates.latitude, LabelsCoordinates.longitude),
        coords={
            LabelsCoordinates.latitude: dym_data.latitude,
            LabelsCoordinates.longitude: dym_data.longitude,
        },
        name="mask",
    )
    mask_da = generate_coordinates_attrs(mask_da)
    mask_da = generate_name(
        mask_da,
        name="mask",
        units="0=land, 1=1st layer, 2=2nd layer, 3=3rd layer",
    )

    # Create data DataArray
    data_da = xr.DataArray(
        dym_data.data,
        dims=("time", LabelsCoordinates.latitude, LabelsCoordinates.longitude),
        coords={
            "time": dym_data.time,
            LabelsCoordinates.latitude: dym_data.latitude,
            LabelsCoordinates.longitude: dym_data.longitude,
        },
    )

    # Apply mask (set land points to NaN)
    data_da = xr.where(mask_da == 0, np.nan, data_da)

    # Sort and add attributes
    data_da = data_da.sortby(
        ["time", LabelsCoordinates.latitude, LabelsCoordinates.longitude]
    )
    mask_da = mask_da.sortby([LabelsCoordinates.latitude, LabelsCoordinates.longitude])

    data_da = generate_coordinates_attrs(data_da)
    data_da = generate_name(data_da, name, units)

    if normalize_longitude:
        from dymfile._utils import normalize_longitude as _normalize_lon

        data_da = _normalize_lon(data_da)
        mask_da = _normalize_lon(mask_da)

    # Create Dataset
    return xr.Dataset({name: data_da, "mask": mask_da})

read_dym(filepath, *, delta_time=NB_DAY_MONTHLY, decode_times=True)

Read a DYM file and return structured data.

Parameters:

Name Type Description Default
filepath str | Path

Path to DYM file.

required
delta_time int

Time delta in days (default 30 for monthly).

NB_DAY_MONTHLY
decode_times bool

If True, convert times to datetime64. If False, keep as float.

True

Returns:

Type Description
DymData

Structured data with header, arrays, coordinates.

Examples:

>>> dym_data = read_dym("file.dym")
>>> print(dym_data.header)
>>> print(dym_data.data.shape)
Source code in src/dymfile/reader.py
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
def read_dym(
    filepath: str | Path,
    *,
    delta_time: int = NB_DAY_MONTHLY,
    decode_times: bool = True,
) -> DymData:
    """
    Read a DYM file and return structured data.

    Parameters
    ----------
    filepath : str | Path
        Path to DYM file.
    delta_time : int
        Time delta in days (default 30 for monthly).
    decode_times : bool
        If True, convert times to datetime64. If False, keep as float.

    Returns
    -------
    DymData
        Structured data with header, arrays, coordinates.

    Examples
    --------
    >>> dym_data = read_dym("file.dym")
    >>> print(dym_data.header)
    >>> print(dym_data.data.shape)
    """
    filepath = Path(filepath)
    with filepath.open("rb") as file:
        header = _read_header(file)
        longitude, latitude, time_vect, mask = _read_coordinates_and_mask(file, header)
        data = _read_data(file, header)
        time = _format_time(
            time_vect, header, delta_time=delta_time, decode_times=decode_times
        )

    return DymData(
        header=header,
        data=data,
        mask=mask,
        longitude=longitude,
        latitude=latitude,
        time=time,
    )

Writer

dymfile.writer

Write DYM binary files.

dataset_to_dym(ds, filepath, *, variable=None)

Write an xarray Dataset to DYM format.

Parameters:

Name Type Description Default
ds Dataset

Dataset to write. Must contain time, latitude, longitude coordinates.

required
filepath str | Path

Path to output DYM file.

required
variable str | None

Name of data variable to write. If None, uses first data variable.

None

Raises:

Type Description
ValueError

If required coordinates are missing or data has wrong dimensions.

Examples:

>>> import xarray as xr
>>> ds = xr.open_dataset("input.nc")
>>> dataset_to_dym(ds, "output.dym", variable="temperature")
Source code in src/dymfile/writer.py
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
def dataset_to_dym(
    ds: xr.Dataset,
    filepath: str | Path,
    *,
    variable: str | None = None,
) -> None:
    """
    Write an xarray Dataset to DYM format.

    Parameters
    ----------
    ds : xr.Dataset
        Dataset to write. Must contain time, latitude, longitude coordinates.
    filepath : str | Path
        Path to output DYM file.
    variable : str | None
        Name of data variable to write. If None, uses first data variable.

    Raises
    ------
    ValueError
        If required coordinates are missing or data has wrong dimensions.

    Examples
    --------
    >>> import xarray as xr
    >>> ds = xr.open_dataset("input.nc")
    >>> dataset_to_dym(ds, "output.dym", variable="temperature")
    """
    # Select variable
    if variable is None:
        data_vars = list(ds.data_vars)
        if not data_vars:
            raise ValueError("Dataset has no data variables")
        if "mask" in data_vars:
            data_vars.remove("mask")
        if not data_vars:
            raise ValueError("Dataset only contains mask variable")
        variable = data_vars[0]

    if variable not in ds:
        raise ValueError(f"Variable {variable!r} not found in dataset")

    data_var = ds[variable]

    # Check coordinates
    required_coords = {"time", LabelsCoordinates.latitude, LabelsCoordinates.longitude}
    missing_coords = required_coords - set(data_var.dims)
    if missing_coords:
        raise ValueError(f"Missing required coordinates: {missing_coords}")

    # Extract arrays
    data = data_var.values  # shape: (nlevel, nlat, nlon)
    longitude = ds[LabelsCoordinates.longitude].values
    latitude = ds[LabelsCoordinates.latitude].values
    time_coord = ds["time"]

    # Convert time to SEAPODYM format (simplified: use year as float)
    # TODO: Implement proper conversion from datetime64 to SEAPODYM format
    if np.issubdtype(time_coord.dtype, np.datetime64):
        # Simple conversion: extract year + day_of_year/365
        times_dt = time_coord.values.astype("datetime64[D]")
        years = times_dt.astype("datetime64[Y]").astype(int) + 1970
        day_of_year = (times_dt - times_dt.astype("datetime64[Y]")).astype(int) + 1
        time = years + day_of_year / 365.0
    else:
        time = time_coord.values.astype(np.float32)

    # Extract or create mask
    if "mask" in ds:
        mask = ds["mask"].values.astype(np.int32)
    else:
        # Create mask from non-NaN values
        mask = (~np.isnan(data[0, :, :])).astype(np.int32)

    # Write DYM file
    write_dym(
        filepath=filepath,
        data=data,
        longitude=longitude,
        latitude=latitude,
        time=time,
        mask=mask,
    )

write_dym(filepath, data, longitude, latitude, time, mask=None, *, t0=None, tfin=None)

Write a DYM file from numpy arrays.

Parameters:

Name Type Description Default
filepath str | Path

Path to output DYM file.

required
data ndarray

Data array with shape (nlevel, nlat, nlon).

required
longitude ndarray

Longitude array (nlon,).

required
latitude ndarray

Latitude array (nlat,).

required
time ndarray

Time array (nlevel,) in SEAPODYM float format.

required
mask ndarray | None

Mask array (nlat, nlon). If None, creates mask with all 1s.

None
t0 float | None

Initial time in SEAPODYM format. If None, uses time[0].

None
tfin float | None

Final time in SEAPODYM format. If None, uses time[-1].

None

Examples:

>>> import numpy as np
>>> data = np.random.rand(12, 10, 20)  # 12 months, 10 lats, 20 lons
>>> lon = np.linspace(-180, 180, 20)
>>> lat = np.linspace(-90, 90, 10)
>>> time = np.arange(2020.0, 2021.0, 1/12)  # SEAPODYM format
>>> write_dym("output.dym", data, lon, lat, time)
Source code in src/dymfile/writer.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
def write_dym(
    filepath: str | Path,
    data: np.ndarray,
    longitude: np.ndarray,
    latitude: np.ndarray,
    time: np.ndarray,
    mask: np.ndarray | None = None,
    *,
    t0: float | None = None,
    tfin: float | None = None,
) -> None:
    """
    Write a DYM file from numpy arrays.

    Parameters
    ----------
    filepath : str | Path
        Path to output DYM file.
    data : np.ndarray
        Data array with shape (nlevel, nlat, nlon).
    longitude : np.ndarray
        Longitude array (nlon,).
    latitude : np.ndarray
        Latitude array (nlat,).
    time : np.ndarray
        Time array (nlevel,) in SEAPODYM float format.
    mask : np.ndarray | None
        Mask array (nlat, nlon). If None, creates mask with all 1s.
    t0 : float | None
        Initial time in SEAPODYM format. If None, uses time[0].
    tfin : float | None
        Final time in SEAPODYM format. If None, uses time[-1].

    Examples
    --------
    >>> import numpy as np
    >>> data = np.random.rand(12, 10, 20)  # 12 months, 10 lats, 20 lons
    >>> lon = np.linspace(-180, 180, 20)
    >>> lat = np.linspace(-90, 90, 10)
    >>> time = np.arange(2020.0, 2021.0, 1/12)  # SEAPODYM format
    >>> write_dym("output.dym", data, lon, lat, time)
    """
    filepath = Path(filepath)
    nlevel, nlat, nlon = data.shape

    # Validate shapes
    if len(longitude) != nlon:
        raise ValueError(f"longitude length {len(longitude)} doesn't match data nlon={nlon}")
    if len(latitude) != nlat:
        raise ValueError(f"latitude length {len(latitude)} doesn't match data nlat={nlat}")
    if len(time) != nlevel:
        raise ValueError(f"time length {len(time)} doesn't match data nlevel={nlevel}")

    # Create default mask if not provided
    if mask is None:
        mask = np.ones((nlat, nlon), dtype=np.int32)
    elif mask.shape != (nlat, nlon):
        raise ValueError(f"mask shape {mask.shape} doesn't match data (nlat={nlat}, nlon={nlon})")

    # Create header
    if t0 is None:
        t0 = float(time[0])
    if tfin is None:
        tfin = float(time[-1])

    header = DymHeader(
        nlon=nlon,
        nlat=nlat,
        nlevel=nlevel,
        t0=t0,
        tfin=tfin,
    )

    # Write file
    with filepath.open("wb") as file:
        _write_header(file, header)
        _write_coordinates_and_mask(file, longitude, latitude, time, mask)
        _write_data(file, data, mask)

Backend

dymfile.backend

Xarray backend for DYM files.

DymBackendEntrypoint

Bases: BackendEntrypoint

Xarray backend for reading DYM files.

This backend allows opening DYM files directly with xarray: >>> import xarray as xr >>> ds = xr.open_dataset("file.dym", engine="dym")

The backend is automatically registered when dymfile is installed.

Source code in src/dymfile/backend.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
class DymBackendEntrypoint(BackendEntrypoint):
    """
    Xarray backend for reading DYM files.

    This backend allows opening DYM files directly with xarray:
        >>> import xarray as xr
        >>> ds = xr.open_dataset("file.dym", engine="dym")

    The backend is automatically registered when dymfile is installed.
    """

    def open_dataset(
        self,
        filename_or_obj,
        *,
        drop_variables=None,
        decode_times=True,
        normalize_longitude=False,
        delta_time=30,
    ) -> xr.Dataset:
        """
        Open a DYM file as an xarray Dataset.

        Parameters
        ----------
        filename_or_obj : str or Path
            Path to DYM file.
        drop_variables : list of str, optional
            Variables to drop from the dataset.
        decode_times : bool, default=True
            If True, decode time coordinates to datetime64.
        normalize_longitude : bool, default=False
            If True, normalize longitude to [-180, 180] range.
        delta_time : int, default=30
            Time step in days (default 30 for monthly data).

        Returns
        -------
        xr.Dataset
            Dataset with data and mask variables.
        """
        ds = dym_to_dataset(
            filename_or_obj,
            decode_times=decode_times,
            normalize_longitude=normalize_longitude,
            delta_time=delta_time,
        )

        if drop_variables:
            ds = ds.drop_vars(drop_variables, errors="ignore")

        return ds

    def guess_can_open(self, filename_or_obj) -> bool:
        """
        Guess if the backend can open the given file.

        Parameters
        ----------
        filename_or_obj : str or Path
            Path to file.

        Returns
        -------
        bool
            True if filename ends with .dym
        """
        return str(filename_or_obj).endswith(".dym")

    description = "Read DYM files from SEAPODYM project"
    url = "https://github.com/Ash12H/dymfile"

guess_can_open(filename_or_obj)

Guess if the backend can open the given file.

Parameters:

Name Type Description Default
filename_or_obj str or Path

Path to file.

required

Returns:

Type Description
bool

True if filename ends with .dym

Source code in src/dymfile/backend.py
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
def guess_can_open(self, filename_or_obj) -> bool:
    """
    Guess if the backend can open the given file.

    Parameters
    ----------
    filename_or_obj : str or Path
        Path to file.

    Returns
    -------
    bool
        True if filename ends with .dym
    """
    return str(filename_or_obj).endswith(".dym")

open_dataset(filename_or_obj, *, drop_variables=None, decode_times=True, normalize_longitude=False, delta_time=30)

Open a DYM file as an xarray Dataset.

Parameters:

Name Type Description Default
filename_or_obj str or Path

Path to DYM file.

required
drop_variables list of str

Variables to drop from the dataset.

None
decode_times bool

If True, decode time coordinates to datetime64.

True
normalize_longitude bool

If True, normalize longitude to [-180, 180] range.

False
delta_time int

Time step in days (default 30 for monthly data).

30

Returns:

Type Description
Dataset

Dataset with data and mask variables.

Source code in src/dymfile/backend.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def open_dataset(
    self,
    filename_or_obj,
    *,
    drop_variables=None,
    decode_times=True,
    normalize_longitude=False,
    delta_time=30,
) -> xr.Dataset:
    """
    Open a DYM file as an xarray Dataset.

    Parameters
    ----------
    filename_or_obj : str or Path
        Path to DYM file.
    drop_variables : list of str, optional
        Variables to drop from the dataset.
    decode_times : bool, default=True
        If True, decode time coordinates to datetime64.
    normalize_longitude : bool, default=False
        If True, normalize longitude to [-180, 180] range.
    delta_time : int, default=30
        Time step in days (default 30 for monthly data).

    Returns
    -------
    xr.Dataset
        Dataset with data and mask variables.
    """
    ds = dym_to_dataset(
        filename_or_obj,
        decode_times=decode_times,
        normalize_longitude=normalize_longitude,
        delta_time=delta_time,
    )

    if drop_variables:
        ds = ds.drop_vars(drop_variables, errors="ignore")

    return ds

Data Structures

dymfile._formats

Data structures for DYM file format.

DymData dataclass

Complete data from a DYM file.

Attributes:

Name Type Description
header DymHeader

Header information.

data ndarray

Data array with shape (nlevel, nlat, nlon).

mask ndarray

Mask array with shape (nlat, nlon). Values: 0=land, 1=1st layer, 2=2nd layer, 3=3rd layer.

longitude ndarray

Longitude coordinates with shape (nlon,).

latitude ndarray

Latitude coordinates with shape (nlat,).

time ndarray

Time coordinates with shape (nlevel,). Can be float (SEAPODYM format) or datetime64.

Source code in src/dymfile/_formats.py
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
@dataclass
class DymData:
    """
    Complete data from a DYM file.

    Attributes
    ----------
    header : DymHeader
        Header information.
    data : np.ndarray
        Data array with shape (nlevel, nlat, nlon).
    mask : np.ndarray
        Mask array with shape (nlat, nlon).
        Values: 0=land, 1=1st layer, 2=2nd layer, 3=3rd layer.
    longitude : np.ndarray
        Longitude coordinates with shape (nlon,).
    latitude : np.ndarray
        Latitude coordinates with shape (nlat,).
    time : np.ndarray
        Time coordinates with shape (nlevel,).
        Can be float (SEAPODYM format) or datetime64.
    """

    header: DymHeader
    data: np.ndarray
    mask: np.ndarray
    longitude: np.ndarray
    latitude: np.ndarray
    time: np.ndarray

DymHeader dataclass

Header information from a DYM file.

Attributes:

Name Type Description
nlon int

Number of longitude points.

nlat int

Number of latitude points.

nlevel int

Number of vertical levels (time steps).

t0 float

Initial time in SEAPODYM format.

tfin float

Final time in SEAPODYM format.

Source code in src/dymfile/_formats.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@dataclass
class DymHeader:
    """
    Header information from a DYM file.

    Attributes
    ----------
    nlon : int
        Number of longitude points.
    nlat : int
        Number of latitude points.
    nlevel : int
        Number of vertical levels (time steps).
    t0 : float
        Initial time in SEAPODYM format.
    tfin : float
        Final time in SEAPODYM format.
    """

    nlon: int
    nlat: int
    nlevel: int
    t0: float
    tfin: float