Source code for ants.fileformats.namelist

# (C) Crown Copyright, Met Office. All rights reserved.
#
# This file is part of ANTS and is released under the BSD 3-Clause license.
# See LICENSE.txt in the root of the repository for full licensing details.
"""
Module for reading Fortran namelist files and constructing Python or Iris
objects, as appropriate, from the contents.
"""
import warnings

import ants
import ants.utils
import iris

from .umgrid import CAPGridRegular, CAPGridVariable, VerticalLevels

try:
    import f90nml

    _F90NML_IMPORT_ERROR = None
except Exception as _F90NML_IMPORT_ERROR:
    f90nml = None
    msg = (
        ' {}\nUnable to import "f90nml", proceeding without the '
        "capabilities it provides.  See install.rst"
    )
    warnings.warn(msg.format(str(_F90NML_IMPORT_ERROR)))


def _read_namelist(filename):
    """
    Wrapper around the f90nml namelist reader.

    Read a Fortran namelist file into a dictionary-like object.  Dictionary
    keys equate to the top-level namelist group(s) specified within
    `filename`.  This function rationalises f90nml exceptions, raising
    IOError instead.

    Parameters
    ----------
    filename : str
        The filename of the Fortran namelist.

    Returns
    -------
    :class:`f90nml.NmlDict` object
        Dictionary keys represent namelist groups while values represent group
        variables and their values.

    """
    if f90nml is None:
        raise _F90NML_IMPORT_ERROR

    msg = 'Invalid Fortran namelist file: "{}"{}.'
    nldict = None
    try:
        nldict = f90nml.read(filename)
    except (StopIteration, ValueError, AssertionError):
        raise IOError(msg.format(filename, ""))
    if not nldict:
        raise IOError(msg.format(filename, " no groups found"))
    return nldict


[docs] def apply_um_conventions(cube): """ Apply UM specific conventions to the resulting cube. - Latitude cells are enforced S-N in direction. """ # Enforce S-N for latitudes x, y = ants.utils.cube.horizontal_grid(cube) points = y.points.copy() bounds = y.bounds.copy() if points[-1] < points[0]: points = points[::-1] if bounds is not None and (bounds[-1, -1] < bounds[0, 0]): bounds = bounds[::-1, ::-1] y.points = points y.bounds = bounds return cube
def _gen_dict(filenames): if isinstance(filenames, str): filenames = [filenames] # Read each of the supplied namelists, checking that no duplicate groups # are found. groups = {} for filename in filenames: nldict = _read_namelist(filename) for group, subdict in nldict.items(): if group in groups: msg = "Cannot handle duplicate namelist groups." raise RuntimeError(msg) groups[group] = subdict return groups
[docs] def load_um_vertical(filenames, callback=None): """ Load the vertical namelist definition. Applies a correction to the loaded namelist to be consistent with the UM expectations. Specifically, removes the zeroth level, and modifies the boundaries of the first level to compensate. Parameters ---------- filenames : str Pathname of a Fortran namelist file, or a list of such paths. Returns ------- : :class:`iris.cube.Cube` Cube representing the vertical grid defined. See Also -------- :class:`umgrid.VerticalLevels` : for the vertical definition specification. """ return _load_vertical(filenames, True, callback)
[docs] def load_lfric_vertical(filenames, callback=None): """ Load the vertical namelist definition. Includes the zeroth level in the returned cube. Parameters ---------- filenames : str Pathname of a Fortran namelist file, or a list of such paths. Returns ------- : :class:`iris.cube.Cube` Cube representing the vertical grid defined. See Also -------- :class:`umgrid.VerticalLevels` : for the vertical definition specification. """ return _load_vertical(filenames, False, callback)
def _load_vertical(filenames, apply_um_workaround=True, callback=None): groups = _gen_dict(filenames) result = None if "vertlevs" in groups: result = VerticalLevels(groups).get_cube(apply_um_workaround) else: msg = "No supported groups found: {}. Supported groups include: {}." raise ValueError(msg.format(list(groups.keys()), "vertlevs")) result = iris.io.run_callback(callback, result, groups, filenames) yield result
[docs] def load_cap_horizontal(filenames, callback=None): """ Load the horizontal namelist definition. Load a model grid definition from one or more Fortran namelists, supporting both regular and variable resolution, global and regional grids. See Also -------- :class:`umgrid.CAPGridRegular` : for regular grids, :class:`umgrid.CAPGridVariable` : for variable resolution grids Parameters ---------- filenames : str Pathname of a Fortran namelist file, or a list of such paths. Returns ------- : :class:`iris.cube.Cube` Cube representing the grid defined. """ groups = _gen_dict(filenames) # Groups identify which namelist convention (CAP/UM) result = None mapping = {"horizgrid": CAPGridVariable, "grid": CAPGridRegular} if "horizgrid" in groups: result = mapping["horizgrid"](groups).get_cube() elif "grid" in groups: result = mapping["grid"](groups).get_cube() else: msg = "No supported groups found: {}. Supported groups include: {}." raise ValueError(msg.format(list(groups.keys()), list(mapping.keys()))) if result is None: raise RuntimeError("No grid found") apply_um_conventions(result) result = iris.io.run_callback(callback, result, groups, filenames) yield result