From 3d39300506ccdbb3feaa41be8a3668abf9b0bac6 Mon Sep 17 00:00:00 2001 From: ashjbarnes Date: Wed, 21 Aug 2024 10:17:23 -0600 Subject: [PATCH 1/8] added glorys download functions with new API --- demos/reanalysis-forced.ipynb | 42 ++++++++----- regional_mom6/regional_mom6.py | 109 ++++++++++++++++++++++++++++++++- 2 files changed, 134 insertions(+), 17 deletions(-) diff --git a/demos/reanalysis-forced.ipynb b/demos/reanalysis-forced.ipynb index 72f56527..f66e8333 100644 --- a/demos/reanalysis-forced.ipynb +++ b/demos/reanalysis-forced.ipynb @@ -117,22 +117,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Step 2: Prepare ocean forcing data\n", - "\n", - "We need to cut out our ocean forcing. The package expects an initial condition and one time-dependent segment per non-land boundary. Naming convention is `\"east_unprocessed\"` for segments and `\"ic_unprocessed\"` for the initial condition.\n", - "\n", - "Data can be downloaded directly from the [Copernicus Marine data store](https://data.marine.copernicus.eu/product/GLOBAL_MULTIYEAR_PHY_001_030/download) via their GUI (once logged in).\n", - "\n", - "1. Initial condition: Using the GUI, select an area matching your `longitude_extent` and `latitude_extent` that corresponds to the first day in your date range. Download the initial condition and save it with filename `ic_unprocessed.nc` inside the `glorys_path` directory.\n", - "2. Boundary forcing: Using the GUI, select the Eastern boundary of your domain (if you have one that contains ocean). Allow for a buffer of ~0.5 degrees in all directions, and download for the prescribed `date_range`. Download and name `east_unprocessed.nc`.\n", - "3. Repeat step 2 for the remaining sections of the domain." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Step 3: Make experiment object\n", + "## Step 2: Make experiment object\n", "The `regional_mom6.experiment` contains the regional domain basics, and also generates the horizontal and vertical grids, `hgrid` and `vgrid` respectively, and sets up the directory structures. " ] }, @@ -185,6 +170,31 @@ "```" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 3: Prepare ocean forcing data\n", + "\n", + "We need to cut out our ocean forcing. The package expects an initial condition and one time-dependent segment per non-land boundary. Naming convention is `\"east_unprocessed\"` for segments and `\"ic_unprocessed\"` for the initial condition.\n", + "\n", + "In this notebook, we are forcing with the Copernicus Marine \"Glorys\" reanalysis dataset. There's a function in the `mom6-regional` package that generates a bash script to download the correct boundary forcing files for your experiment. First, you will need to create an account with Copernicus, and you'll be prompted for your username and password when you try to run the bash script.\n", + "\n", + "The function is called `get_glorys_rectangular` because the fully automated setup is only supported for domains with boundaries parallel to lines of longitude and latitude. To download more complex domain shapes you can call `rmom6.get_glorys_data` directly." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "expt.get_glorys_rectangular(\n", + " raw_boundaries_path=glorys_path,\n", + " boundaries=[\"north\", \"south\", \"east\", \"west\"],\n", + ")" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/regional_mom6/regional_mom6.py b/regional_mom6/regional_mom6.py index 26fa71be..7568a27a 100644 --- a/regional_mom6/regional_mom6.py +++ b/regional_mom6/regional_mom6.py @@ -13,7 +13,7 @@ import shutil import os import importlib.resources - +import datetime from .utils import quadrilateral_areas @@ -144,6 +144,54 @@ def longitude_slicer(data, longitude_extent, longitude_coords): return data +from pathlib import Path + +def get_glorys_data( + longitude_extent, + latitude_extent, + timerange, + segment_name, + download_path, + modify_existing = True, + buffer = 1 +): + """ + Generates a bash script to download all of the required ocean forcing data. + + Args: + longitude_extent (tuple of floats): Westward and Eastward extents of the segment + latitude_extent (tuple of floats): Southward and Northward extents of the segment + timerange (tule of datetime strings): Start and end of the segment in format %Y-%m-%d %H:%M:%S + segment_range (str): name of the segment (minus .nc extension, eg east_unprocessed) + download_path (str): Location of where this script is saved + modify_existing (bool): Whether to add to an existing script or start a new one + buffer (int): number of degrees to add to pad the file with to ensure that interpolation onto desired domain doesn't fail. + """ + path = Path(download_path) + file = open( + path / "get_glorysdata.sh", + "r" + ) + + if modify_existing: + lines = file.readlines() + else: + lines = ["#!/bin/bash\ncopernicusmarine login"] + file.close() + file = open( + path / "get_glorysdata.sh", + "w" + ) + + lines.append( + f""" +copernicusmarine subset --dataset-id cmems_mod_glo_phy_my_0.083deg_P1D-m --variable so --variable thetao --variable uo --variable vo --variable zos --start-datetime {timerange[0].replace(" ","T")} --end-datetime {timerange[1].replace(" ","T")} --minimum-longitude {longitude_extent[0]} --maximum-longitude {longitude_extent[1]} --minimum-latitude {latitude_extent[0]} --maximum-latitude {latitude_extent[1]} --minimum-depth 0 --maximum-depth 6000 -o {str(path)} -f {segment_name}.nc --force-download\n +""" + ) + file.writelines(lines) + file.close() + return + def hyperbolictan_thickness_profile(nlayers, ratio, total_depth): """Generate a hyperbolic tangent thickness profile with ``nlayers`` vertical @@ -899,6 +947,65 @@ def initial_condition( return + def get_glorys_rectangular( + self, + raw_boundaries_path, + boundaries=["south", "north", "west", "east"] + ): + """ + This function is a wrapper for `get_glorys_data`, calling this function once for each of the rectangular boundary segments and the initial condition. For more complex boundary shapes, call `get_glorys_data` directly for each of your boundaries that aren't parallel to lines of constant latitude or longitude. + + args: + raw_boundaries_path (str): Path to the directory containing the raw boundary forcing files. + boundaries (List[str]): List of cardinal directions for which to create boundary forcing files. + Default is `["south", "north", "west", "east"]`. + """ + + # Initial Condition + get_glorys_data( + self.longitude_extent, + self.latitude_extent, + [self.date_range[0],datetime.datetime.strptime(self.date_range[0], "%Y-%m-%d %H:%M:%S") + datetime.timedelta(days=1)], + "ic_unprocessed", + raw_boundaries_path, + modify_existing=False + ) + if "east" in boundaries: + get_glorys_data( + [self.longitude_extent[1] - 1,self.longitude_extent[1] + 1], + [self.latitude_extent[0] -1, self.latitude_extent[1] + 1], + self.date_range, + "east_unprocessed", + raw_boundaries_path, + ) + if "west" in boundaries: + get_glorys_data( + [self.longitude_extent[0] - 1,self.longitude_extent[0] + 1], + [self.latitude_extent[0] -1, self.latitude_extent[1] + 1], + self.date_range, + "west_unprocessed", + raw_boundaries_path, + ) + if "north" in boundaries: + get_glorys_data( + [self.longitude_extent[0] - 1,self.longitude_extent[1] + 1], + [self.latitude_extent[1] -1, self.latitude_extent[1] + 1], + self.date_range, + "north_unprocessed", + raw_boundaries_path, + ) + if "south" in boundaries: + get_glorys_data( + [self.longitude_extent[0] - 1,self.longitude_extent[1] + 1], + [self.latitude_extent[0] -1, self.latitude_extent[0] + 1], + self.date_range, + "south_unprocessed", + raw_boundaries_path, + ) + + print(f"script `get_glorys_data.sh` has been greated at {raw_boundaries_path}. Run this script via bash to download the data from a terminal with internet access. You will need to enter your Copernicus Marine username and password. If you don't have an account, make one here:\nhttps://data.marine.copernicus.eu/register") + return + def rectangular_boundaries( self, raw_boundaries_path, From 7c45afd6f5ddd98ad491d37240d5cd45f5a5b8b8 Mon Sep 17 00:00:00 2001 From: ashjbarnes Date: Wed, 21 Aug 2024 10:25:05 -0600 Subject: [PATCH 2/8] black --- regional_mom6/regional_mom6.py | 54 +++++++++++++++++----------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/regional_mom6/regional_mom6.py b/regional_mom6/regional_mom6.py index 7568a27a..34ea3ce6 100644 --- a/regional_mom6/regional_mom6.py +++ b/regional_mom6/regional_mom6.py @@ -144,16 +144,18 @@ def longitude_slicer(data, longitude_extent, longitude_coords): return data + from pathlib import Path + def get_glorys_data( longitude_extent, latitude_extent, timerange, segment_name, - download_path, - modify_existing = True, - buffer = 1 + download_path, + modify_existing=True, + buffer=1, ): """ Generates a bash script to download all of the required ocean forcing data. @@ -168,20 +170,14 @@ def get_glorys_data( buffer (int): number of degrees to add to pad the file with to ensure that interpolation onto desired domain doesn't fail. """ path = Path(download_path) - file = open( - path / "get_glorysdata.sh", - "r" - ) + file = open(path / "get_glorysdata.sh", "r") if modify_existing: lines = file.readlines() else: lines = ["#!/bin/bash\ncopernicusmarine login"] file.close() - file = open( - path / "get_glorysdata.sh", - "w" - ) + file = open(path / "get_glorysdata.sh", "w") lines.append( f""" @@ -190,7 +186,7 @@ def get_glorys_data( ) file.writelines(lines) file.close() - return + return def hyperbolictan_thickness_profile(nlayers, ratio, total_depth): @@ -948,9 +944,7 @@ def initial_condition( return def get_glorys_rectangular( - self, - raw_boundaries_path, - boundaries=["south", "north", "west", "east"] + self, raw_boundaries_path, boundaries=["south", "north", "west", "east"] ): """ This function is a wrapper for `get_glorys_data`, calling this function once for each of the rectangular boundary segments and the initial condition. For more complex boundary shapes, call `get_glorys_data` directly for each of your boundaries that aren't parallel to lines of constant latitude or longitude. @@ -965,45 +959,51 @@ def get_glorys_rectangular( get_glorys_data( self.longitude_extent, self.latitude_extent, - [self.date_range[0],datetime.datetime.strptime(self.date_range[0], "%Y-%m-%d %H:%M:%S") + datetime.timedelta(days=1)], + [ + self.date_range[0], + datetime.datetime.strptime(self.date_range[0], "%Y-%m-%d %H:%M:%S") + + datetime.timedelta(days=1), + ], "ic_unprocessed", raw_boundaries_path, - modify_existing=False - ) + modify_existing=False, + ) if "east" in boundaries: get_glorys_data( - [self.longitude_extent[1] - 1,self.longitude_extent[1] + 1], - [self.latitude_extent[0] -1, self.latitude_extent[1] + 1], + [self.longitude_extent[1] - 1, self.longitude_extent[1] + 1], + [self.latitude_extent[0] - 1, self.latitude_extent[1] + 1], self.date_range, "east_unprocessed", raw_boundaries_path, ) if "west" in boundaries: get_glorys_data( - [self.longitude_extent[0] - 1,self.longitude_extent[0] + 1], - [self.latitude_extent[0] -1, self.latitude_extent[1] + 1], + [self.longitude_extent[0] - 1, self.longitude_extent[0] + 1], + [self.latitude_extent[0] - 1, self.latitude_extent[1] + 1], self.date_range, "west_unprocessed", raw_boundaries_path, ) if "north" in boundaries: get_glorys_data( - [self.longitude_extent[0] - 1,self.longitude_extent[1] + 1], - [self.latitude_extent[1] -1, self.latitude_extent[1] + 1], + [self.longitude_extent[0] - 1, self.longitude_extent[1] + 1], + [self.latitude_extent[1] - 1, self.latitude_extent[1] + 1], self.date_range, "north_unprocessed", raw_boundaries_path, ) if "south" in boundaries: get_glorys_data( - [self.longitude_extent[0] - 1,self.longitude_extent[1] + 1], - [self.latitude_extent[0] -1, self.latitude_extent[0] + 1], + [self.longitude_extent[0] - 1, self.longitude_extent[1] + 1], + [self.latitude_extent[0] - 1, self.latitude_extent[0] + 1], self.date_range, "south_unprocessed", raw_boundaries_path, ) - print(f"script `get_glorys_data.sh` has been greated at {raw_boundaries_path}. Run this script via bash to download the data from a terminal with internet access. You will need to enter your Copernicus Marine username and password. If you don't have an account, make one here:\nhttps://data.marine.copernicus.eu/register") + print( + f"script `get_glorys_data.sh` has been greated at {raw_boundaries_path}. Run this script via bash to download the data from a terminal with internet access. You will need to enter your Copernicus Marine username and password. If you don't have an account, make one here:\nhttps://data.marine.copernicus.eu/register" + ) return def rectangular_boundaries( From 31b4e8f40b95c3c70cee5ec6d08674b5682137f6 Mon Sep 17 00:00:00 2001 From: ashjbarnes Date: Wed, 21 Aug 2024 11:23:21 -0600 Subject: [PATCH 3/8] black --- regional_mom6/regional_mom6.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/regional_mom6/regional_mom6.py b/regional_mom6/regional_mom6.py index 34ea3ce6..1e6a4a2c 100644 --- a/regional_mom6/regional_mom6.py +++ b/regional_mom6/regional_mom6.py @@ -1001,10 +1001,10 @@ def get_glorys_rectangular( raw_boundaries_path, ) - print( - f"script `get_glorys_data.sh` has been greated at {raw_boundaries_path}. Run this script via bash to download the data from a terminal with internet access. You will need to enter your Copernicus Marine username and password. If you don't have an account, make one here:\nhttps://data.marine.copernicus.eu/register" - ) - return + print( + f"script `get_glorys_data.sh` has been greated at {raw_boundaries_path}. Run this script via bash to download the data from a terminal with internet access. You will need to enter your Copernicus Marine username and password. If you don't have an account, make one here:\nhttps://data.marine.copernicus.eu/register" + ) + return def rectangular_boundaries( self, From 72b8a0a39fe2767c5a6df80afd5e5cfb2980eaf3 Mon Sep 17 00:00:00 2001 From: ashjbarnes Date: Wed, 21 Aug 2024 11:33:30 -0600 Subject: [PATCH 4/8] bugfix --- regional_mom6/regional_mom6.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/regional_mom6/regional_mom6.py b/regional_mom6/regional_mom6.py index 1e6a4a2c..8afadc52 100644 --- a/regional_mom6/regional_mom6.py +++ b/regional_mom6/regional_mom6.py @@ -961,8 +961,7 @@ def get_glorys_rectangular( self.latitude_extent, [ self.date_range[0], - datetime.datetime.strptime(self.date_range[0], "%Y-%m-%d %H:%M:%S") - + datetime.timedelta(days=1), + self.date_range[0] + datetime.timedelta(days=1), ], "ic_unprocessed", raw_boundaries_path, From 22fde6e607d6eeeced4fd06445538dd616279d20 Mon Sep 17 00:00:00 2001 From: ashjbarnes Date: Wed, 21 Aug 2024 11:40:45 -0600 Subject: [PATCH 5/8] bugfix --- regional_mom6/regional_mom6.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/regional_mom6/regional_mom6.py b/regional_mom6/regional_mom6.py index 8afadc52..6f23d6ab 100644 --- a/regional_mom6/regional_mom6.py +++ b/regional_mom6/regional_mom6.py @@ -169,14 +169,16 @@ def get_glorys_data( modify_existing (bool): Whether to add to an existing script or start a new one buffer (int): number of degrees to add to pad the file with to ensure that interpolation onto desired domain doesn't fail. """ - path = Path(download_path) - file = open(path / "get_glorysdata.sh", "r") if modify_existing: + path = Path(download_path) + file = open(path / "get_glorysdata.sh", "r") lines = file.readlines() + file.close() + else: lines = ["#!/bin/bash\ncopernicusmarine login"] - file.close() + file = open(path / "get_glorysdata.sh", "w") lines.append( From 090fed22716b6729686dc98690279a38d1e017da Mon Sep 17 00:00:00 2001 From: ashjbarnes Date: Wed, 21 Aug 2024 11:52:03 -0600 Subject: [PATCH 6/8] bugfix --- regional_mom6/regional_mom6.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/regional_mom6/regional_mom6.py b/regional_mom6/regional_mom6.py index 6f23d6ab..82921ce1 100644 --- a/regional_mom6/regional_mom6.py +++ b/regional_mom6/regional_mom6.py @@ -169,9 +169,9 @@ def get_glorys_data( modify_existing (bool): Whether to add to an existing script or start a new one buffer (int): number of degrees to add to pad the file with to ensure that interpolation onto desired domain doesn't fail. """ + path = Path(download_path) if modify_existing: - path = Path(download_path) file = open(path / "get_glorysdata.sh", "r") lines = file.readlines() file.close() From 49ecaccd16c46eb9ae6d338502cf6808121873b5 Mon Sep 17 00:00:00 2001 From: Ashley Barnes Date: Wed, 21 Aug 2024 12:57:06 -0600 Subject: [PATCH 7/8] update notebook --- regional_mom6/regional_mom6.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/regional_mom6/regional_mom6.py b/regional_mom6/regional_mom6.py index 82921ce1..928d8807 100644 --- a/regional_mom6/regional_mom6.py +++ b/regional_mom6/regional_mom6.py @@ -183,7 +183,7 @@ def get_glorys_data( lines.append( f""" -copernicusmarine subset --dataset-id cmems_mod_glo_phy_my_0.083deg_P1D-m --variable so --variable thetao --variable uo --variable vo --variable zos --start-datetime {timerange[0].replace(" ","T")} --end-datetime {timerange[1].replace(" ","T")} --minimum-longitude {longitude_extent[0]} --maximum-longitude {longitude_extent[1]} --minimum-latitude {latitude_extent[0]} --maximum-latitude {latitude_extent[1]} --minimum-depth 0 --maximum-depth 6000 -o {str(path)} -f {segment_name}.nc --force-download\n +copernicusmarine subset --dataset-id cmems_mod_glo_phy_my_0.083deg_P1D-m --variable so --variable thetao --variable uo --variable vo --variable zos --start-datetime {str(timerange[0]).replace(" ","T")} --end-datetime {str(timerange[1]).replace(" ","T")} --minimum-longitude {longitude_extent[0]} --maximum-longitude {longitude_extent[1]} --minimum-latitude {latitude_extent[0]} --maximum-latitude {latitude_extent[1]} --minimum-depth 0 --maximum-depth 6000 -o {str(path)} -f {segment_name}.nc --force-download\n """ ) file.writelines(lines) @@ -1003,7 +1003,7 @@ def get_glorys_rectangular( ) print( - f"script `get_glorys_data.sh` has been greated at {raw_boundaries_path}. Run this script via bash to download the data from a terminal with internet access. You will need to enter your Copernicus Marine username and password. If you don't have an account, make one here:\nhttps://data.marine.copernicus.eu/register" + f"script `get_glorys_data.sh` has been greated at {raw_boundaries_path}.\n Run this script via bash to download the data from a terminal with internet access. \nYou will need to enter your Copernicus Marine username and password.\nIf you don't have an account, make one here:\nhttps://data.marine.copernicus.eu/register" ) return From 121d23ea4bf757c883afacd3efd423f55193efda Mon Sep 17 00:00:00 2001 From: Ashley Barnes Date: Fri, 23 Aug 2024 12:05:10 -0600 Subject: [PATCH 8/8] black --- regional_mom6/regional_mom6.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/regional_mom6/regional_mom6.py b/regional_mom6/regional_mom6.py index 928d8807..55d6120b 100644 --- a/regional_mom6/regional_mom6.py +++ b/regional_mom6/regional_mom6.py @@ -155,7 +155,6 @@ def get_glorys_data( segment_name, download_path, modify_existing=True, - buffer=1, ): """ Generates a bash script to download all of the required ocean forcing data. @@ -167,8 +166,10 @@ def get_glorys_data( segment_range (str): name of the segment (minus .nc extension, eg east_unprocessed) download_path (str): Location of where this script is saved modify_existing (bool): Whether to add to an existing script or start a new one - buffer (int): number of degrees to add to pad the file with to ensure that interpolation onto desired domain doesn't fail. + buffer (float): number of """ + buffer = 0.24 # Pads downloads to ensure that interpolation onto desired domain doesn't fail. Default of 0.24 is twice Glorys cell width (12th degree) + path = Path(download_path) if modify_existing: @@ -183,7 +184,7 @@ def get_glorys_data( lines.append( f""" -copernicusmarine subset --dataset-id cmems_mod_glo_phy_my_0.083deg_P1D-m --variable so --variable thetao --variable uo --variable vo --variable zos --start-datetime {str(timerange[0]).replace(" ","T")} --end-datetime {str(timerange[1]).replace(" ","T")} --minimum-longitude {longitude_extent[0]} --maximum-longitude {longitude_extent[1]} --minimum-latitude {latitude_extent[0]} --maximum-latitude {latitude_extent[1]} --minimum-depth 0 --maximum-depth 6000 -o {str(path)} -f {segment_name}.nc --force-download\n +copernicusmarine subset --dataset-id cmems_mod_glo_phy_my_0.083deg_P1D-m --variable so --variable thetao --variable uo --variable vo --variable zos --start-datetime {str(timerange[0]).replace(" ","T")} --end-datetime {str(timerange[1]).replace(" ","T")} --minimum-longitude {longitude_extent[0] - buffer} --maximum-longitude {longitude_extent[1] + buffer} --minimum-latitude {latitude_extent[0] - buffer} --maximum-latitude {latitude_extent[1] + buffer} --minimum-depth 0 --maximum-depth 6000 -o {str(path)} -f {segment_name}.nc --force-download\n """ ) file.writelines(lines) @@ -971,32 +972,32 @@ def get_glorys_rectangular( ) if "east" in boundaries: get_glorys_data( - [self.longitude_extent[1] - 1, self.longitude_extent[1] + 1], - [self.latitude_extent[0] - 1, self.latitude_extent[1] + 1], + [self.longitude_extent[1], self.longitude_extent[1]], + [self.latitude_extent[0], self.latitude_extent[1]], self.date_range, "east_unprocessed", raw_boundaries_path, ) if "west" in boundaries: get_glorys_data( - [self.longitude_extent[0] - 1, self.longitude_extent[0] + 1], - [self.latitude_extent[0] - 1, self.latitude_extent[1] + 1], + [self.longitude_extent[0], self.longitude_extent[0]], + [self.latitude_extent[0], self.latitude_extent[1]], self.date_range, "west_unprocessed", raw_boundaries_path, ) if "north" in boundaries: get_glorys_data( - [self.longitude_extent[0] - 1, self.longitude_extent[1] + 1], - [self.latitude_extent[1] - 1, self.latitude_extent[1] + 1], + [self.longitude_extent[0], self.longitude_extent[1]], + [self.latitude_extent[1], self.latitude_extent[1]], self.date_range, "north_unprocessed", raw_boundaries_path, ) if "south" in boundaries: get_glorys_data( - [self.longitude_extent[0] - 1, self.longitude_extent[1] + 1], - [self.latitude_extent[0] - 1, self.latitude_extent[0] + 1], + [self.longitude_extent[0], self.longitude_extent[1]], + [self.latitude_extent[0], self.latitude_extent[0]], self.date_range, "south_unprocessed", raw_boundaries_path,