Newer
Older
# Copyright 2022 - Barcelona Supercomputing Center
# Authors: Etienne Tourigny, Rodrigo Martín Posada
# MIT License
from netCDF4 import Dataset # pylint: disable=no-name-in-module
import logging
import numpy as np
from datetime import date, timedelta
from DataCouplerUtils import days_since_refyear, filename_from_pattern
import pyoasis
Etienne Tourigny
committed
class FileInputVar:
def __init__(self, conf, fix_year, start_year):
self.id = conf['id']
self.oasis_name = conf['oasis_name']
self.grid_name = conf['grid_name']
self.file_pattern = conf['file_pattern']
self.var_name = conf['netcdf_variable']
self.ref_year = conf['yref_min']
self.timestep = conf['timestep']
self.scale_factor = conf['scale_factor']
self.offset = conf['offset']
self.min = conf['min'] if conf['min'] is not None else np.NINF
self.max = conf['max'] if conf['max'] is not None else np.Inf
self.interpolate = conf['interpolate']
self.fix_year = fix_year
# Initialization
self.__get_grids(start_year)
self.field = pyoasis.asarray(np.empty((2,self.grid_nx,self.grid_ny)))
# Field that will be sent
Etienne Tourigny
committed
self._send_field = pyoasis.asarray(np.empty((self.grid_nx,self.grid_ny)))
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# Current netCDF dataset
self.dataset = None
# Name of the current dataset file
self.current_file = ''
self.time_array = None
# Index in time_array
self.t_index = 0
# Dummy values
# Timesteps [days since refdate]
# Step before with data
self.t1 = -9999.
# Step after with data
self.t2 = -9999.
# Step at Jan 1st
self.t0101 = -9999.
# Step at Dec 31st
self.t1231 = -9999.
self.tnewyear = -9999.
def setup(self, yy, t_global, tnewyear_global):
# Dummy values
self.t1 = -9999.
self.t2 = -9999.
self.t_index = 0
self.t0101 = float(days_since_refyear(yy, 1, 1, self.ref_year))
self.t1231 = float(days_since_refyear(yy, 12, 31, self.ref_year))
# Current timestep
t_local = self.t0101 + t_global
self.tnewyear = self.t0101 + tnewyear_global
logging.debug('{}: INIT - t : {}'.format(self.id, t_local))
logging.debug('{}: INIT - t0101 : {}'.format(self.id, self.t0101))
logging.debug('{}: INIT - t1231 : {}'.format(self.id, self.t1231))
logging.debug('{}: INIT - tnewyear: {}'.format(self.id, self.tnewyear))
curr_date = date(self.ref_year, 1, 1) + timedelta(days=t_local)
file_name = filename_from_pattern(self.file_pattern, curr_date)
self.__open_file(file_name)
if t_local >= self.time_array[0]:
self.t_index = np.searchsorted(self.time_array, t_local, side='right')-1
self.t2 = self.time_array[self.t_index]
self.t1 = self.t2
self.__read_file()
def update(self, t_global):
t_local = self.t0101 + t_global
logging.debug('update var {} at time {}'.format(self.id, t_local))
while t_local > self.t2:
self.__read_next_timestep_from_files(t_local)
var_max = self.max
var_min = self.min
# Monthly interpolation
if self.timestep == 'monthly':
if self.interpolate:
if self.t1 < self.t2:
logging.debug('{}: time interpolation t1,t,t2 {} {} {}'.format(self.id, self.t1, t_local, self.t2))
raw_field = ((t_local-self.t1)*self.field[1] + (self.t2-t_local)*self.field[0])/(self.t2-self.t1)
else:
logging.debug('{}: no time interpolation t,t2 {} {}'.format(self.id, t_local, self.t2))
# Hardcoded to match original Fortran amip-forcing clipping avoidance
if self.oasis_name == 'AMIP_sst':
var_min = np.NINF
raw_field = self.field[1]
else:
# Nearest neighbour
if self.t1 < self.t2:
if np.floor(t_local)-np.floor(self.t1) < np.floor(self.t2)-np.floor(t_local):
nn = 0
else:
nn = 1
else:
nn = 1
logging.debug('{}: nearest neigbour nn,t1,t,t2 {} {} {} {}'.format(
self.id, nn, self.t1, t_local, self.t2))
# Hardcoded to match Fortran amip-forcing clipping avoidance
if self.oasis_name == 'AMIP_sst':
var_min = np.NINF
raw_field = self.field[nn]
# Daily (no interpolation)
else:
raw_field = self.field[1]
# Clipping required for CMIP6 BCs
Etienne Tourigny
committed
self._send_field = np.maximum(var_min, np.minimum(var_max, raw_field))
def send_field(self):
return self._send_field
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
def finalise(self):
self.__close_current_file()
def __get_grids(self, start_year):
'''
Sets `self.grid_lat` and `self.grid_lon` from the first input netCDF file.
'''
yy = self.fix_year if self.fix_year > 0 else start_year
file_name = filename_from_pattern(self.file_pattern, date(yy, 1, 1))
ds = Dataset(file_name, 'r')
variable_names = ds.variables.keys()
# Look for lat and lon variables to create a 2d grid.
# If not found, look for sector to greate a 1d grid (only 3 sectors for co2 supported for now).
if 'lat' in variable_names:
lat = ds['lat'][:]
elif 'latitude' in variable_names:
lat = ds['latitude'][:]
else:
lat = None
if 'lon' in variable_names:
lon = ds['lon'][:]
elif 'longitude' in variable_names:
lon = ds['longitude'][:]
else:
lon = None
if lat is None or lon is None:
if 'sector' in variable_names:
num_sectors = len(ds['sector'][:])
if num_sectors == 3:
self.grid_ny = 2
self.grid_nx = 1
lat=[-45,45]
lon=0
self.dy=lat[1]-lat[0]
self.dx=0
# Create 2-D grid for OASIS
self.grid_lon = np.tile(lon, (self.grid_ny, 1)).T
self.grid_lat = np.tile(lat, (self.grid_nx, 1))
else:
raise Exception(
Etienne Tourigny
committed
'FileInputVar: {}: Could not find sector variable of length 3. Invalid input netCDF file {}.'.format(self.id, file_name))
else:
raise Exception(
Etienne Tourigny
committed
'FileInputVar: {}: Could not find lon or longitude variable name. Invalid input netCDF file {}.'.format(self.id, file_name))
else:
self.grid_ny = lat.size
self.grid_nx = lon.size
self.dy=lat[1]-lat[0]
self.dx=lon[1]-lon[0]
# Create 2-D grid for OASIS
self.grid_lon = np.tile(lon, (self.grid_ny, 1)).T
self.grid_lat = np.tile(lat, (self.grid_nx, 1))
ds.close()
def __read_next_timestep_from_files(self, t_local):
self.t1 = self.t2
self.field[0] = self.field[1]
if t_local > np.max(self.time_array):
old_file_name = self.current_file
self.__close_current_file()
curr_date = date(self.ref_year, 1, 1) + timedelta(days=t_local)
file_name = filename_from_pattern(self.file_pattern, curr_date)
if file_name == old_file_name:
Etienne Tourigny
committed
raise Exception('FileInputVar: {}: Could not find the next file for date {}.'.format(self.id, curr_date))
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
self.__open_file(file_name)
self.t_index = -1
self.t_index += 1
self.t2 = self.time_array[self.t_index]
self.__read_file()
def __open_file(self, file_name):
self.current_file = file_name
logging.debug('{}: open file {}'.format(self.id, self.current_file))
self.dataset = Dataset(file_name, 'r')
self.time_array = self.dataset['time'][:]
def __read_file(self):
if self.fix_year > 0 and self.time_array[self.t_index] >= self.tnewyear:
logging.debug('{}:...use first BC data for timestep {}'.format(self.id, self.time_array[self.t_index]))
# Assign BC only if values have been assigned previously
first_t_index = np.searchsorted(self.time_array, self.t0101, side='right')-1
raw_field = self.dataset[self.var_name][first_t_index+1]
elif self.fix_year > 0 and self.time_array[self.t_index] < self.t0101:
logging.debug('{}:...use last BC data for timestep {}'.format(self.id, self.time_array[self.t_index]))
# Assign BC only if values have been assigned previously
last_t_index = np.searchsorted(self.time_array, self.t1231, side='right')-1
raw_field = self.dataset[self.var_name][last_t_index]
else:
logging.debug('{}: ...read timestep {} {:<018}'.format(
self.id, self.t_index, self.time_array[self.t_index]))
raw_field = self.dataset[self.var_name][self.t_index]
# This section can be "hardcoded" by the user
# Hardcoded CO2 emissions data.
logging.debug('reading {}'.format(self.var_name))
if self.var_name == 'CO2_em_anthro' or self.var_name == 'CO2_em_AIR_anthro':
#Read sum of all sectors / levels
raw_field = np.sum(raw_field,axis=0)
elif self.var_name == 'mole_fraction_of_carbon_dioxide_in_air':
raw_field = raw_field[1:]
logging.debug('reading co2 raw_field={} raw_field.shape={} field.shape={}'.format(str(raw_field),str(raw_field.shape),str(self.field[1].shape)))
self.field[1] = pyoasis.asarray(np.transpose(raw_field)).astype('float64')*self.scale_factor+self.offset
def __close_current_file(self):
logging.debug('{}: close file {}'.format(self.id, self.current_file))
self.dataset.close()