diff --git a/VERSION b/VERSION index 47b322c971c3ce34b223693982d647bf2e352923..1545d966571dc86b54c98f888a0e6451501f8c81 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.4.1 +3.5.0 diff --git a/doc/source/codedoc/ocean.rst b/doc/source/codedoc/ocean.rst index 52830bb9ccdc0e114c4506ed1f9d836aeda99834..acf9ad14cf9d6bbe7f6d4b62d18093bc69da3eac 100644 --- a/doc/source/codedoc/ocean.rst +++ b/doc/source/codedoc/ocean.rst @@ -25,6 +25,12 @@ earthdiagnostics.ocean.cutsection :show-inheritance: :members: +earthdiagnostics.ocean.density +------------------------------ +.. automodule:: earthdiagnostics.ocean.density + :show-inheritance: + :members: + earthdiagnostics.ocean.gyres ---------------------------- .. automodule:: earthdiagnostics.ocean.gyres diff --git a/doc/source/conf.py b/doc/source/conf.py index 1ab10a7abc67afa3513cf4c651a63152271dcee0..17ef04e572063310657798260ad3b7e2e86cb4be 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -63,9 +63,9 @@ copyright = u"2020, BSC-CNS Earth Sciences Department" # built documents.source ~/vi # # The short X.Y version. -version = "3.4" +version = "3.5" # The full version, including alpha/beta/rc tags. -release = "3.4.1" +release = "3.5.0" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/doc/source/diagnostic_list.rst b/doc/source/diagnostic_list.rst index de450a4f55f7b23e2695e6e1b91409d2452ade91..f95ab6afd905ca62f6e3fbc3bd4d8c67fed25b62 100644 --- a/doc/source/diagnostic_list.rst +++ b/doc/source/diagnostic_list.rst @@ -315,6 +315,16 @@ Options: 4. Domain = ocean: Variable's domain +density +~~~~~~~ + +Compute the total potential density anomaly. See :class:`~earthdiagnostics.ocean.density.Density` + +Options: +******** + +This diagnostic has no options + gyres ~~~~~ @@ -356,14 +366,14 @@ See :class:`~earthdiagnostics.ocean.heatcontentlayer.HeatContentLayer` Options: ******** -3. Min depth: +1. Min depth: Minimum depth for the calculation in meteres -4. Max depth: +2. Max depth: Maximum depth for the calculation in meters -5. Basin = 'Global': - Basin to calculate the heat content on. +3. Basins = ['Global']: + List of basins to calculate the heat content on. interpolate ~~~~~~~~~~~ @@ -381,8 +391,8 @@ Options: 1. Target grid: New grid for the data -2. Variable: - Variable to interpolate +2. VariableList: + List of variables to interpolate 3. Domain = ocean: Variable's domain diff --git a/earthdiagnostics/data_convention.py b/earthdiagnostics/data_convention.py index ee6bf7ca5df53776799d8e11c0185e87d04b5230..5e4dd4b5a96d8ce236f70485a6e7e36556a32df7 100644 --- a/earthdiagnostics/data_convention.py +++ b/earthdiagnostics/data_convention.py @@ -892,10 +892,15 @@ class Cmor3Convention(DataConvention): self.config.cmor.version, ) if self.config.cmor.version == "latest": - versions = os.listdir(os.path.dirname(folder_path)) + base_path = os.path.dirname(folder_path) + if not os.path.isdir(base_path): + base_path = base_path.replace( + '/original_files/cmorfiles/', '/cmorfiles/') + versions = os.listdir(base_path) versions.sort(reverse=True) self.config.cmor.version = versions[0] - folder_path = folder_path.replace('/latest/', f'/{versions[0]}/') + return self.get_cmor_folder_path( + startdate, member, domain, var, frequency, grid, cmor_var) return folder_path diff --git a/earthdiagnostics/ocean/density.py b/earthdiagnostics/ocean/density.py new file mode 100644 index 0000000000000000000000000000000000000000..d4d4aea489c0ef4916727764385fc1ce29f0829f --- /dev/null +++ b/earthdiagnostics/ocean/density.py @@ -0,0 +1,150 @@ +# coding=utf-8 +"""Compute the potential density anomalies""" +import diagonals.density +import numpy as np +import iris + +from earthdiagnostics.modelingrealm import ModelingRealms +from earthdiagnostics.utils import TempFile +from earthdiagnostics.diagnostic import Diagnostic + + +class Density(Diagnostic): + """ + Compute the total potential density anomaly + + + :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 + """ + + alias = "density" + "Diagnostic alias for the configuration file" + + def __init__( + self, + data_manager, + startdate, + member, + chunk, + ): + Diagnostic.__init__(self, data_manager) + self.startdate = startdate + self.member = member + self.chunk = chunk + self.sigmas = [0, 2] + + def __eq__(self, other): + if self._different_type(other): + return False + return ( + self.startdate == other.startdate + and self.member == other.member + and self.chunk == other.chunk + ) + + def __str__(self): + return ( + f"Density Startdate: {self.startdate} Member: {self.member} " + f"Chunk: {self.chunk}" + ) + + def __hash__(self): + return hash(str(self)) + + @classmethod + def generate_jobs(cls, diags, options): + """ + Create a job for each chunk to compute the diagnostic + + :param diags: Diagnostics manager class + :type diags: Diags + :param options: This diagnostic does not require extra options + :type options: list[str] + :return: + """ + options_available = [] + options = cls.process_options(options, options_available) + + job_list = list() + for ( + startdate, + member, + chunk, + ) in diags.config.experiment.get_chunk_list(): + job_list.append( + Density( + diags.data_manager, + startdate, + member, + chunk, + ) + ) + return job_list + + def request_data(self): + """Request data required by the diagnostic""" + self.bigthetao = self.request_chunk( + ModelingRealms.ocean, + "bigthetao", + self.startdate, + self.member, + self.chunk, + ) + self.so = self.request_chunk( + ModelingRealms.ocean, + "so", + self.startdate, + self.member, + self.chunk, + ) + + def declare_data_generated(self): + """Declare data to be generated by the diagnostic""" + self.sigma = {} + for sigma in self.sigmas: + self.sigma[sigma] = self.declare_chunk( + ModelingRealms.ocean, + f"sigma{sigma}", + self.startdate, + self.member, + self.chunk, + ) + + def compute(self): + """Run the diagnostic""" + bigthetao = iris.load_cube(self.bigthetao.local_file) + so = iris.load_cube(self.so.local_file) + # Convert from practical to absolute + so = so / 0.99530670233846 + + for sigma in self.sigmas: + ref_pressure = sigma * 1000 + sigma_values = [] + for time in range(so.shape[0]): + sigma_values.append(diagonals.density.compute( + so[time, ...].data.astype(np.float32), + bigthetao.data[time, ...].astype(np.float32), + np.full(bigthetao.shape[1:], + ref_pressure, dtype=np.float32) + )) + sigma_values = np.stack(sigma_values) + sigma_cube = bigthetao.copy(sigma_values) + sigma_cube.var_name = f'sigma{sigma}' + sigma_cube.standard_name = 'sea_water_sigma_theta' + sigma_cube.long_name = ( + "potential density anomaly (potential density minus 1000 " + f"Kg/m3) with reference pressure of {ref_pressure} dbar" + ) + sigma_cube.units = 'kg m-3' + temp = TempFile.get() + iris.save(sigma_cube, temp, zlib=True) + del sigma_cube + del sigma_values + self.sigma[sigma].set_local_file(temp) diff --git a/earthdiagnostics/work_manager.py b/earthdiagnostics/work_manager.py index 3666e2c791b58b5f3f5251718b23a79da0910458..9192b58c07464a80afe8f3c1c1eb86f7a7e76cca 100644 --- a/earthdiagnostics/work_manager.py +++ b/earthdiagnostics/work_manager.py @@ -407,6 +407,7 @@ class WorkManager(object): from .ocean.sivolume import Sivolume from .ocean.sivol2d import Sivol2d from .ocean.zonalmean import ZonalMean + from .ocean.density import Density Diagnostic.register(MixedLayerSaltContent) Diagnostic.register(Siasiesiv) @@ -433,6 +434,7 @@ class WorkManager(object): Diagnostic.register(Sivolume) Diagnostic.register(Sivol2d) Diagnostic.register(ZonalMean) + Diagnostic.register(Density) class Downloader(object): diff --git a/environment.yml b/environment.yml index 8eb7402decd5073ce1a68691c8479db362699f77..f6657b17536641cd918ae39457b0db3f353df7b2 100644 --- a/environment.yml +++ b/environment.yml @@ -9,4 +9,5 @@ dependencies: - cdo - nco - eccodes +- six - iris>=2.4 diff --git a/setup.py b/setup.py index ce66c25dd6231cdd5c44aa5401c66089f4726523..4fab770a8127a3f8437d6a259d328342f48ac23f 100644 --- a/setup.py +++ b/setup.py @@ -24,14 +24,14 @@ REQUIREMENTS = { "cdo>=1.3.4", "cfgrib", "dask[array]", - "diagonals>=0.2" + "diagonals>=0.3", "netCDF4", "nco>=0.0.3", "numba", "numpy", "psutil", "openpyxl", - "scitools-iris>=2.2", + "scitools-iris>=2.4", "six", "xxhash", ], diff --git a/test/unit/ocean/test_density.py b/test/unit/ocean/test_density.py new file mode 100644 index 0000000000000000000000000000000000000000..92b4e06db3fd089ce3168511d72c5fa12c9b4efc --- /dev/null +++ b/test/unit/ocean/test_density.py @@ -0,0 +1,42 @@ +# coding=utf-8 +from unittest import TestCase + +from mock import Mock + +from earthdiagnostics.ocean.density import Density + + +class TestHeatContent(TestCase): + def setUp(self): + self.data_manager = Mock() + + self.diags = Mock() + self.diags.model_version = "model_version" + self.diags.config.experiment.get_chunk_list.return_value = ( + ("20010101", 0, 0), + ("20010101", 0, 1), + ) + + def test_generate_jobs(self): + jobs = Density.generate_jobs( + self.diags, ["diagnostic"] + ) + self.assertEqual(len(jobs), 2) + self.assertEqual( + jobs[0], + Density(self.data_manager, "20010101", 0, 0) + ) + self.assertEqual( + jobs[1], + Density(self.data_manager, "20010101", 0, 1) + ) + + with self.assertRaises(Exception): + Density.generate_jobs(self.diags, ["diagnostic", "0"]) + + def test_str(self): + diag = Density(self.data_manager, "20010101", 0, 0) + self.assertEqual( + str(diag), + "Density Startdate: 20010101 Member: 0 Chunk: 0" + )