regionsum.py 14.3 KB
Newer Older
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
"""Diagnostic to calculate a region total"""
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
import iris
import iris.util
import iris.coords
import iris.analysis
import iris.exceptions
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
from bscearth.utils.log import Log
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed

import numpy as np

import netCDF4

from earthdiagnostics.box import Box
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
from earthdiagnostics.diagnostic import (
    Diagnostic,
    DiagnosticOption,
    DiagnosticIntOption,
    DiagnosticDomainOption,
    DiagnosticBoolOption,
    DiagnosticBasinListOption,
    DiagnosticVariableListOption,
    DiagnosticFrequencyOption
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
)
from earthdiagnostics.modelingrealm import ModelingRealms
from earthdiagnostics.utils import Utils, TempFile

Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
import diagonals.regsum as regsum
from diagonals.mesh_helpers.nemo import Nemo
class RegionSum(Diagnostic):
    """
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
    Computes the sum of the field (3D, weighted).

Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
    For 3D fields, a horizontal mean for each level is also given.
    If a spatial window is specified, the mean value is computed only
    in this window.

    :original author: Javier Vegas-Regidor <javier.vegas@bsc.es>

    :created: March 2017

    :param data_manager: data management object
    :type data_manager: DataManager
    :param startdate: startdate
    :type startdate: str
    :param member: member number
    :type member: int
    :param chunk: chunk's number
    :type chunk: int
    :param variable: variable to average
    :type variable: str
    :param box: box used to restrict the vertical mean
    :type box: Box
    """

Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
    alias = "regsum"
    "Diagnostic alias for the configuration file"

Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
    def __init__(
        self,
        data_manager,
        startdate,
        member,
        chunk,
        domain,
        variable,
        grid_point,
        box,
        save3d,
        basins,
        grid,
        frequency,
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
    ):
        Diagnostic.__init__(self, data_manager)
        self.startdate = startdate
        self.member = member
        self.chunk = chunk
        self.domain = domain
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        self.variable = variable
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        self.grid_point = grid_point
        self.box = box
        self.save3d = save3d
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        self.basins = basins
        self.frequency = frequency

Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        self.lat_name = "lat"
        self.lon_name = "lon"
        self._hash = None

    def __eq__(self, other):
        if self._different_type(other):
            return False

Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        return (
            self.startdate == other.startdate
            and self.member == other.member
            and self.chunk == other.chunk
            and self.box == other.box
            and self.variable == other.variable
            and self.grid_point == other.grid_point
            and self.grid == other.grid
            and self.basins == other.basins
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        )
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        return (
            "Region sum Startdate: {0.startdate} Member: {0.member} "
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
            "Chunk: {0.chunk} Variable: {0.variable} "
            "Grid point: {0.grid_point} Box: {0.box} Save 3D: {0.save3d} "
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
            "Original grid: {0.grid} Basin: {0.basins}".format(self)
        )
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed

    def __hash__(self):
        return hash(str(self))

    @classmethod
    def generate_jobs(cls, diags, options):
        """
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        Create a job for each chunk to compute the diagnostic

        :param diags: Diagnostics manager class
        :type diags: Diags
        :param options: variable, minimum depth (level), maximum depth (level)
        :type options: list[str]
        :return:
        """
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        options_available = (
            DiagnosticDomainOption(),
            DiagnosticVariableListOption(
                diags.data_manager.config.var_manager, "variable"
            ),
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
            DiagnosticBasinListOption("basins", "global"),
            DiagnosticOption("grid_point", "T"),
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
            DiagnosticIntOption("min_depth", -1),
            DiagnosticIntOption("max_depth", -1),
            DiagnosticBoolOption("save3D", True),
            DiagnosticOption("grid", ""),
            DiagnosticFrequencyOption("frequency", diags.config.frequency),
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        )
        options = cls.process_options(options, options_available)

        box = Box()
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        box.min_depth = options["min_depth"]
        box.max_depth = options["max_depth"]

        basins = options["basins"]
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        if not basins:
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
            Log.error("Basins not recognized")
            return ()
        basins.sort()
        for var in options["variable"]:
            for (
                startdate,
                member,
                chunk,
            ) in diags.config.experiment.get_chunk_list():
                job_list.append(
                    RegionSum(
                        diags.data_manager,
                        startdate,
                        member,
                        chunk,
                        options["domain"],
                        var,
                        options["grid_point"].lower(),
                        box,
                        options["save3D"],
                        options["basins"],
                        options["grid"],
                        options["frequency"],
                    )
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
                )
        return job_list

    def request_data(self):
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        """Request data required by the diagnostic"""
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        self.variable_file = self.request_chunk(
            self.domain,
            self.variable,
            self.startdate,
            self.member,
            self.chunk,
            grid=self.grid,
            frequency=self.frequency
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        )

    def declare_data_generated(self):
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        """Declare data to be generated by the diagnostic"""
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        if self.box.min_depth == -1 and self.box.max_depth == -1:
            box_save = None
        else:
            box_save = self.box

Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        self._declare_var("sum", False, box_save)
        self._declare_var("sum", True, box_save)
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        """Run the diagnostic"""
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        has_levels = self._fix_file_metadata()
        data = self._load_data()
        masks = {}
        for basin in self.basins:
            masks[basin] = Utils.get_mask(basin)

Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        mesh = Nemo("mesh_hgr.nc", "mask_regions.nc")
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        if has_levels:
            self._sum_3d_variable(data, mesh, masks)
        else:
            self._sum_2d_var(data, mesh, masks)

    def _sum_2d_var(self, data, mesh, masks):
        areacello = mesh.get_areacello(cell_point=self.grid_point)
        varsum = regsum.compute_regsum_2d(data.data, masks, areacello)
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        self._save_result_2d("sum", varsum, data)
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed

    def _sum_3d_variable(self, data, mesh, masks):
        areacello = mesh.get_areacello(cell_point=self.grid_point)
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        tmask = iris.load_cube(
            "mesh_hgr.nc", "{0}mask".format(self.grid_point)
        )
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        e3 = self._try_load_cube(3)
        e3 = self._rename_depth(e3)
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        e3.coord("depth").bounds = data.coord("depth").bounds
        if self.box.min_depth != -1 and self.box.max_depth != -1:
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
            depth_constraint = iris.Constraint(
                depth=lambda c: self.box.min_depth <= c <= self.box.max_depth
            )
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
            e3 = e3.extract(depth_constraint)
            data = data.extract(depth_constraint)
            tmask = tmask.extract(depth_constraint)
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        volcello = areacello * e3.data.astype(np.float32)
        varsum = regsum.compute_regsum_3d(
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
            data.data, masks, volcello, tmask.data
        )
        self._save_result_2d("sum", varsum, data)
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        if self.save3d:
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
            varsum3d = regsum.compute_regsum_levels(
                data.data, masks, volcello, tmask.data
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
            )
            self._save_result_3d("sum", varsum3d, data)
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed

    def _try_load_cube(self, number):
        try:
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
            cube = iris.load_cube(
                "mesh_hgr.nc", "e{0}{1}".format(number, self.grid_point)
            )
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        except iris.exceptions.ConstraintMismatchError:
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
            cube = iris.load_cube(
                "mesh_hgr.nc", "e{0}{1}_0".format(number, self.grid_point)
            )
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        cube = iris.util.squeeze(cube)
        dims = len(cube.shape)
        try:
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
            cube.coord("i")
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        except iris.exceptions.CoordinateNotFoundError:
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
            cube.add_dim_coord(
                iris.coords.DimCoord(
                    np.arange(cube.shape[dims - 1]), var_name="i"
                ),
                dims - 1,
            )
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        try:
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
            cube.coord("j")
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        except iris.exceptions.CoordinateNotFoundError:
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
            cube.add_dim_coord(
                iris.coords.DimCoord(
                    np.arange(cube.shape[dims - 2]), var_name="j"
                ),
                dims - 2,
            )
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        return cube

    def _load_data(self):
        coords = []
        handler = Utils.open_cdf(self.variable_file.local_file)
        for variable in handler.variables:
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
            if variable in (
                "time",
                "lev",
                "lat",
                "lon",
                "latitude",
                "longitude",
                "leadtime",
                "time_centered",
            ):
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
                coords.append(variable)
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
            if variable == "time_centered":
                handler.variables[variable].standard_name = ""
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        handler.variables[self.variable].coordinates = " ".join(coords)
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        data = iris.load_cube(self.variable_file.local_file)
        return self._rename_depth(data)

    def _rename_depth(self, data):
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        for coord_name in ("model_level_number", "Vertical T levels", "lev"):
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
            if data.coords(coord_name):
                coord = data.coord(coord_name)
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
                coord.standard_name = "depth"
                coord.long_name = "depth"
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
                break
        return data

    def _fix_file_metadata(self):
        handler = Utils.open_cdf(self.variable_file.local_file)
        var = handler.variables[self.variable]
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        coordinates = ""
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        has_levels = False
        for dimension in handler.variables.keys():
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
            if dimension in [
                "time",
                "lev",
                "lat",
                "latitude",
                "lon",
                "longitude",
                "i",
                "j",
            ]:
                coordinates += " {0}".format(dimension)
            if dimension == "lev":
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
                has_levels = True
        var.coordinates = coordinates
        handler.close()
        return has_levels
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
    def _send_var(self, threed, mean_file):
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        var = "sum"
        if threed:
            if not self.save3d:
                return
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
            original_name = "{0}_{1}".format(var, self.variable)
            final_name = "{1}3d{0}".format(var, self.variable)
            levels = ",lev"
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
            original_name = "{0}_3D{1}".format(var, self.variable)
            final_name = "{1}{0}".format(var, self.variable)
            levels = ""

        temp2 = TempFile.get()
        Utils.nco().ncks(
            input=mean_file,
            output=temp2,
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
            options=(
                "-v {0},{2.lat_name},{2.lon_name}{1}".format(
                    original_name, levels, self
                ),
            ),
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        handler = Utils.open_cdf(temp2)
        var_handler = handler.variables[original_name]
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        if hasattr(var_handler, "valid_min"):
            del var_handler.valid_min
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        if hasattr(var_handler, "valid_max"):
            del var_handler.valid_max
        handler.close()
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        self.declared[final_name].set_local_file(
            temp2,
            diagnostic=self,
            rename_var=original_name,
            region=self.basins,
        )
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
    def _declare_var(self, var, threed, box_save):
        if threed:
            if not self.save3d:
                return
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
            final_name = "{1}3d{0}".format(var, self.variable)
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
            final_name = "{1}{0}".format(var, self.variable)

        self.declared[final_name] = self.declare_chunk(
            ModelingRealms.ocean,
            final_name,
            self.startdate,
            self.member,
            self.chunk,
            box=box_save,
            region=self.basins,
            grid=self.grid,
            frequency=self.frequency,
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        )
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
    def _save_result_2d(self, var, result, data):
        final_name = "{1}{0}".format(var, self.variable)
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        temp = TempFile.get()
        handler_source = Utils.open_cdf(self.variable_file.local_file)
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        handler_temp = Utils.open_cdf(temp, "w")
        Utils.copy_variable(handler_source, handler_temp, "time", True, True)
        handler_temp.createDimension("region", len(result))
        handler_temp.createDimension("region_length", 50)
        var_region = handler_temp.createVariable(
            "region", "S1", ("region", "region_length")
        )
        var = handler_temp.createVariable(
            "{1}{0}".format(var, self.variable), float, ("time", "region",),
        )
        var.units = "{0}".format(data.units)
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        for i, basin in enumerate(result):
            var_region[i, ...] = netCDF4.stringtoarr(str(basin), 50)
            var[..., i] = result[basin]
        handler_temp.close()
        self.declared[final_name].set_local_file(temp, diagnostic=self)

Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
    def _save_result_3d(self, var, result, data):
        final_name = "{1}3d{0}".format(var, self.variable)
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        temp = TempFile.get()
        handler_source = Utils.open_cdf(self.variable_file.local_file)
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        handler_temp = Utils.open_cdf(temp, "w")
        Utils.copy_variable(handler_source, handler_temp, "time", True, True)
        handler_temp.createDimension("region", len(result))
        handler_temp.createDimension("region_length", 50)
        handler_temp.createDimension("lev", data.shape[1])
        var_level = handler_temp.createVariable("lev", float, "lev")
        var_level[...] = data.coord("depth").points
        var_level.units = "m"
        var_level.axis = "Z"
        var_level.positive = "down"
        var_level.long_name = "ocean depth coordinate"
        var_level.standard_name = "depth"
        var_region = handler_temp.createVariable(
            "region", "S1", ("region", "region_length")
        )
        var = handler_temp.createVariable(
            "{1}3d{0}".format(var, self.variable),
            float,
            ("time", "lev", "region",),
        )
        var.units = "{0}".format(data.units)
Javier Vegas-Regidor's avatar
Javier Vegas-Regidor committed
        for i, basin in enumerate(result):
            var_region[i, ...] = netCDF4.stringtoarr(str(basin), 50)
            var[..., i] = result[basin]
        handler_temp.close()
        self.declared[final_name].set_local_file(temp, diagnostic=self)