Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make derivation of total column ozone (toz) more flexible and add derivation of stratospheric and tropospheric column ozone #2509

Merged
merged 22 commits into from
Sep 26, 2024

Conversation

schlunma
Copy link
Contributor

@schlunma schlunma commented Aug 12, 2024

Description

This PR makes the derivation of total column ozone (toz) more flexible by allowing its derivation from

  • data that has hybrid levels (e.g., o3 from the AERmon table)
  • zonal mean data (e.g., o3 from the AERmonZ table)

In addition, this PR adds two derived variables:

  • stratospheric column ozone (soz), where the "stratosphere" is defined as the region where the O3 mole fraction exceeds 125 ppb
  • tropospheric column ozone (troz), where the "stratosphere" is defined as the region where the O3 mole fraction is lower than 125 ppb

Backwards-incompatible change

This PR also changes the units of toz in our custom table from DU to m. This is done to make it consistent with the CMIP6 definition. Otherwise, this will lead to various problems when directly comparing CMIP6 output with CMIP5 output (or another project that uses the custom table).

To restore the old behavior, a preprocessor can be added in the recipe to convert m to DU:

convert_units:
  units: DU

Checklist

It is the responsibility of the author to make sure the pull request is ready to review. The icons indicate whether the item will be subject to the 🛠 Technical or 🧪 Scientific review.


To help with the number pull requests:

@schlunma schlunma added the variable derivation Related to variable derivation functions label Aug 12, 2024
@schlunma schlunma added this to the v2.12.0 milestone Aug 12, 2024
@schlunma schlunma self-assigned this Aug 12, 2024
Copy link

codecov bot commented Aug 13, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 94.83%. Comparing base (f7ab83f) to head (5615c74).
Report is 62 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2509      +/-   ##
==========================================
+ Coverage   94.77%   94.83%   +0.05%     
==========================================
  Files         249      251       +2     
  Lines       14095    14191      +96     
==========================================
+ Hits        13359    13458      +99     
+ Misses        736      733       -3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@schlunma
Copy link
Contributor Author

This can be tested with the following recipe:

# ESMValTool
---
documentation:
  title: test
  description: test
  authors:
    - schlund_manuel
  maintainer:
    - schlund_manuel

preprocessors:
  du:
    convert_units:
      units: DU

diagnostics:

  test:
    variables:
      toz:
        derive: true
        force_derivation: true
        exp: historical
        start_year: 2013
        end_year: 2014
        preprocessor: du
        additional_datasets:
          - {project: CMIP6, dataset: CESM2-WACCM, ensemble: 'r1i1p1f1', grid: gn, mip: Amon}
          - {project: CMIP6, dataset: GISS-E2-1-G, ensemble: 'r1i1p3f1', grid: gn, mip: AERmon}
      troz:
        derive: true
        force_derivation: true
        exp: historical
        start_year: 2013
        end_year: 2014
        preprocessor: du
        additional_datasets:
          - {project: CMIP6, dataset: CESM2-WACCM, ensemble: 'r1i1p1f1', grid: gn, mip: Amon}
          - {project: CMIP6, dataset: GISS-E2-1-G, ensemble: 'r1i1p3f1', grid: gn, mip: AERmon}
      soz:
        derive: true
        force_derivation: true
        exp: historical
        start_year: 2013
        end_year: 2014
        preprocessor: du
        additional_datasets:
          - {project: CMIP6, dataset: CESM2-WACCM, ensemble: 'r1i1p1f1', grid: gn, mip: Amon}
          - {project: CMIP6, dataset: GISS-E2-1-G, ensemble: 'r1i1p3f1', grid: gn, mip: AERmon}
    scripts:
      null

recipe_test_ozone.yml.txt

@schlunma
Copy link
Contributor Author

As far as I can tell with, toz and soz look good. However, I get some ugly results for troz, which include lots of artifacts:

grafik

Most likely this is the result of lower pressure levels being masked in the input data (o3):

grafik

@schlunma schlunma marked this pull request as ready for review August 14, 2024 08:56
@valeriupredoi
Copy link
Contributor

Manu, can you name these islands?
islands

@schlunma
Copy link
Contributor Author

Manu, can you name these islands? ![islands](https://private-user-images.githubusercontent.com/28983971

Not all of them, but Google Maps certainly can 😄

@FranziskaWinterstein
Copy link
Contributor

Hey @schlunma, thank you for taking care of this.

I made my own attempt to build troz.py (see below), by basically calculating troz as troz = toz - soz. This reduces the doubling of code, but introduces dependencies from troz on soz. What do you think about that?

"""Derivation of variable ``soz``."""

import dask.array as da
import iris

from ._baseclass import DerivedVariableBase
from .toz import DerivedVariable as Toz
from .soz import DerivedVariable as Soz

class DerivedVariable(DerivedVariableBase):
    """Derivation of variable ``troz``."""

    @staticmethod
    def required(project):
        """Declare the variables needed for derivation."""
        if project == 'CMIP6':
            required = [{'short_name': 'o3'}, {'short_name': 'ps'}]
        else:
            required = [{'short_name': 'tro3'}, {'short_name': 'ps'}]
        return required

    @staticmethod
    def calculate(cubes):
        """Compute tropspheric column ozone.

        Note
        ----
        In the calculation of ``troz``, the surface air pressure (``ps``) is
        used to determine the pressure level width of the lowest layer.

        The calculation of ``troz`` consists of three steps:
        (1) Use derivation function of ``toz`` to calculate ``toz``.
        (2) Use derivation function of ``soz`` to calculate ``soz`` (using the
            masked data).
        (3) Calculate difference of ``toz`` and ``soz`` to calculate ``troz``.

        """
        o3_cube = cubes.extract_cube(
            iris.Constraint(name='mole_fraction_of_ozone_in_air'))
        ps_cube = cubes.extract_cube(
            iris.Constraint(name='surface_air_pressure'))

        # (1) Use derivation function of toz to calculate toz
        cubes = iris.cube.CubeList([o3_cube, ps_cube])
        cube_toz = Toz.calculate(cubes)

        # (2) Use derivation function of soz to calculate soz
        cube_soz = Soz.calculate(cubes)

        # (3) Calculate troz as difference of toz and soz
        return cube_toz - cube_soz

@FranziskaWinterstein
Copy link
Contributor

I successfully tested the given recipe as well as applied it to the diagnostics to plot hovmoeller_vs_lat_or_lon and map plots from multi_dataset.py. No problems.

@FranziskaWinterstein
Copy link
Contributor

The map plots of the climatology 2013 - 2014 of troz do not show those artefacts. I suppose they only appear without time averaging. If it is data dependent, I guess, there is not much we can do about it?
map_CESM2-WACCM_troz
Created with:
recipe_test_ozone_map.yml.txt

Copy link
Contributor

@FranziskaWinterstein FranziskaWinterstein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for sharing this optional of ozone variables. I am impressed by the comprehensive testing.

esmvalcore/preprocessor/_derive/_shared.py Outdated Show resolved Hide resolved
@schlunma
Copy link
Contributor Author

I made my own attempt to build troz.py (see below), by basically calculating troz as troz = toz - soz. This reduces the doubling of code, but introduces dependencies from troz on soz. What do you think about that?

I first had it that way and it produced exactly the same results. However, you trade simpler code for speed in this case, as this would mean that the (potential) interpolation needs to be done twice, which can be very expensive. I think avoiding this is better than saving ~20 lines of code.

The map plots of the climatology 2013 - 2014 of troz do not show those artefacts. I suppose they only appear without time averaging. If it is data dependent, I guess, there is not much we can do about it?

Ah, nice to see that time averaging helps! I also think that there's nothing we can really do about it...

Thanks for reviewing 🎉

@schlunma
Copy link
Contributor Author

@valeriupredoi since you already commented on this PR, would you do us the honor of performing a technical review? Thanks 🍻

@valeriupredoi
Copy link
Contributor

@valeriupredoi since you already commented on this PR, would you do us the honor of performing a technical review? Thanks 🍻

on it right now, bud 🍺

Copy link
Contributor

@valeriupredoi valeriupredoi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks very good, many thanks! My only (more) serious concern is with those plev points that should be loaded straight from the CMOR table, but all else looks good, cheers 🍺

esmvalcore/preprocessor/_derive/soz.py Show resolved Hide resolved
esmvalcore/preprocessor/_derive/toz.py Outdated Show resolved Hide resolved
esmvalcore/preprocessor/_derive/toz.py Outdated Show resolved Hide resolved
Copy link
Contributor

@valeriupredoi valeriupredoi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many thanks for addressing the review comments, Manu 🍺

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backwards incompatible change variable derivation Related to variable derivation functions
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants