@@ -77,9 +77,42 @@ def _coriolis_parameter(latitudes: np.ndarray) -> np.ndarray:
77
77
return 2 * omega * np .sin (latitudes * torad )
78
78
79
79
80
- def current_speed_from_mdt ( mdt : np .ndarray ) -> np .ndarray :
80
+ def clipped_coriolis_param ( latitudes : np .ndarray , clip_at : float ) -> np .ndarray :
81
81
"""
82
- Convert geodetic MDT to currents.
82
+ Calculate the coriolis parameter at each latitude, clipping it to the value at `clip_at` degrees
83
+
84
+ Sets values at exactly 0 to + (could equally choose it to be negative, but we don't)
85
+ """
86
+ orig = _coriolis_parameter (latitudes )
87
+ to_clip = np .abs (latitudes ) <= clip_at
88
+
89
+ clipped = orig .copy ()
90
+ clipped [to_clip ] = np .min (np .abs (orig [~ to_clip ]))
91
+ # If the original coriolis parameter was negative, make sure we keep it negative
92
+ clipped [to_clip & (orig < 0 )] *= - 1
93
+
94
+ return clipped
95
+
96
+
97
+ def _dlat_dlong (shape : tuple [int , int ]) -> tuple [float , np .ndarray ]:
98
+ """
99
+ Get the grid spacing in metres, given the shape of the grid.
100
+ """
101
+ torad = np .pi / 180.0
102
+ R = 6_371_229.0
103
+
104
+ lats , longs = util .lat_long_grid (shape )
105
+ dlat = np .abs (lats [1 ] - lats [0 ]) * torad * R
106
+ dlong = (
107
+ np .abs (longs [1 ] - longs [0 ]) * torad * R * np .cos (torad * lats )[:, np .newaxis ]
108
+ )
109
+
110
+ return dlat , dlong
111
+
112
+
113
+ def current_speed_from_mdt (mdt : np .ndarray , clip_at : float = 2.5 ) -> np .ndarray :
114
+ """
115
+ Convert geodetic MDT to currents, clipping the coriolis parameter to avoid infinities at the equator.
83
116
84
117
By assuming geostrophic balance, we can take the gradient of the MDT to get the steady-state
85
118
currents.
@@ -91,32 +124,27 @@ def current_speed_from_mdt(mdt: np.ndarray) -> np.ndarray:
91
124
:returns: the current speed in m/s
92
125
93
126
"""
94
- g = 9.80665
95
- torad = np .pi / 180.0
96
- R = 6_371_229.0
127
+
128
+ lats , _ = util .lat_long_grid (mdt .shape )
97
129
98
130
# Find the grid spacing (in m)
99
- lats , longs = util .lat_long_grid (mdt .shape )
100
- dlat = np .abs (lats [1 ] - lats [0 ]) * torad * R
101
- dlong = (
102
- np .abs (longs [1 ] - longs [0 ]) * torad * R * np .cos (torad * lats )[:, np .newaxis ]
103
- )
131
+ dlat , dlong = _dlat_dlong (mdt .shape )
104
132
105
133
# Find the coriolis parameter at each latitude
106
- f = _coriolis_parameter (lats )
134
+ f = clipped_coriolis_param (lats , clip_at )
107
135
108
136
# Velocities are gradients * coriolis param for geostrophic balance
109
137
dmdt_dlat = np .gradient (mdt , axis = 0 ) / dlat
110
138
dmdt_dlon = np .gradient (mdt , axis = 1 ) / dlong
111
139
112
140
# u should be negative, but it doesnt matter for speed
113
- u = g / f [:, np .newaxis ] * dmdt_dlat
114
- v = g / f [:, np .newaxis ] * dmdt_dlon
141
+ u = GRAVITY / f [:, np .newaxis ] * dmdt_dlat
142
+ v = GRAVITY / f [:, np .newaxis ] * dmdt_dlon
115
143
116
144
return np .sqrt (u ** 2 + v ** 2 )
117
145
118
146
119
- def read_clean_currents (
147
+ def read_clean_mdt (
120
148
path : pathlib .Path ,
121
149
metadata_path : pathlib .Path ,
122
150
* ,
@@ -125,29 +153,7 @@ def read_clean_currents(
125
153
name : str = "r1i1p1f1_gn" ,
126
154
) -> np .ndarray :
127
155
"""
128
- Read clean current data from a .dat file.
129
-
130
- Read a .dat file containing clean current data,
131
- given the model/name/year, returning a 720x1440 numpy array giving the current
132
- in m/s.
133
- Sets land grid points to np.nan.
134
- Since the clean current data is stored in a large file containing multiple years and models, we need
135
- to choose the correct one.
136
-
137
- Notes on the name convention from the CMIP6 documentation can be found in docs/current_denoising/generation/ioutils.md,
138
- or in the original at https://docs.google.com/document/d/1h0r8RZr_f3-8egBMMh7aqLwy3snpD6_MrDz1q8n5XUk.
139
-
140
- :param path: location of the .dat file; clean current data is located in
141
- data/projects/SING/richard_stuff/Table2/clean_currents/ on the RDSF
142
- :param metadata_path: location of the metadata .csv file describing the contents of the .dat file
143
- :param year: start of the 5-year period for which to extract data
144
- :param model: the climate model to use
145
- :param name: the model variant to use. Name follows the convention {realisation/initialisation/physics/forcing}_grid
146
-
147
- :returns: a numpy array holding current speeds
148
- :raises ValueError: if the requested year/model/name is not found in the metadata
149
- :raises IOError: if the file is malformed, or has a different length to expected from the metadata
150
-
156
+ Read clean MDT data from a .dat file.
151
157
"""
152
158
metadata = _read_clean_current_metadata (metadata_path )
153
159
@@ -202,9 +208,47 @@ def read_clean_currents(
202
208
retval [retval == - 1.9e19 ] = np .nan
203
209
204
210
# Make it look right
205
- retval = np .flipud (retval .reshape ((720 , 1440 )))
211
+ return np .flipud (retval .reshape ((720 , 1440 )))
212
+
213
+
214
+ def read_clean_currents (
215
+ path : pathlib .Path ,
216
+ metadata_path : pathlib .Path ,
217
+ * ,
218
+ year : int ,
219
+ model : str = "ACCESS-CM2" ,
220
+ name : str = "r1i1p1f1_gn" ,
221
+ clip_at : float = 2.5 ,
222
+ ) -> np .ndarray :
223
+ """
224
+ Read clean current data from a .dat file, clipping the speed to the provided value
225
+
226
+ Read a .dat file containing clean current data,
227
+ given the model/name/year, returning a 720x1440 numpy array giving the current
228
+ in m/s.
229
+ Sets land grid points to np.nan.
230
+ Since the clean current data is stored in a large file containing multiple years and models, we need
231
+ to choose the correct one.
232
+
233
+ Notes on the name convention from the CMIP6 documentation can be found in docs/current_denoising/generation/ioutils.md,
234
+ or in the original at https://docs.google.com/document/d/1h0r8RZr_f3-8egBMMh7aqLwy3snpD6_MrDz1q8n5XUk.
235
+
236
+ :param path: location of the .dat file; clean current data is located in
237
+ data/projects/SING/richard_stuff/Table2/clean_currents/ on the RDSF
238
+ :param metadata_path: location of the metadata .csv file describing the contents of the .dat file
239
+ :param year: start of the 5-year period for which to extract data
240
+ :param model: the climate model to use
241
+ :param name: the model variant to use. Name follows the convention {realisation/initialisation/physics/forcing}_grid
242
+ :param clip_at: latitude (in degrees) at which to clip the coriolis parameter
243
+
244
+ :returns: a numpy array holding current speeds
245
+ :raises ValueError: if the requested year/model/name is not found in the metadata
246
+ :raises IOError: if the file is malformed, or has a different length to expected from the metadata
247
+
248
+ """
249
+ mdt = read_clean_mdt (path , metadata_path , year = year , model = model , name = name )
206
250
207
- return current_speed_from_mdt (retval )
251
+ return current_speed_from_mdt (mdt , clip_at = clip_at )
208
252
209
253
210
254
def _included_indices (
0 commit comments