Commit f9e3a165 authored by Woude, Auke van der's avatar Woude, Auke van der
Browse files

update to the comments in the observationoperator

parent 804ebc77
...@@ -83,8 +83,9 @@ class STILTObservationOperator(ObservationOperator): ...@@ -83,8 +83,9 @@ class STILTObservationOperator(ObservationOperator):
self.dacycle = dacycle self.dacycle = dacycle
self.outputdir = dacycle['dir.output'] self.outputdir = dacycle['dir.output']
self.rc = dacycle['da.obsoperator.rc'] self.rc = dacycle['da.obsoperator.rc']
# Read the stilt.rc file for settings to the observationoperator
rc_options = rc.read(self.rc) rc_options = rc.read(self.rc)
# Also read in the tracer information
rc_options['tracers'] = rc_options['tracers'].split(',') rc_options['tracers'] = rc_options['tracers'].split(',')
self.rc_options = rc_options self.rc_options = rc_options
self.tracer_info = {tracer: None for tracer in rc_options['tracers']} self.tracer_info = {tracer: None for tracer in rc_options['tracers']}
...@@ -92,9 +93,11 @@ class STILTObservationOperator(ObservationOperator): ...@@ -92,9 +93,11 @@ class STILTObservationOperator(ObservationOperator):
self.tracer_info[tracer] = rc.read(self.rc_options['tracerdir']+'/'+tracer) self.tracer_info[tracer] = rc.read(self.rc_options['tracerdir']+'/'+tracer)
def parse_samplefile(self): def parse_samplefile(self):
"""Function that reads the sample files created in obspack.py"""
infile = nc.Dataset(self.dacycle['ObsOperator.inputfile']) infile = nc.Dataset(self.dacycle['ObsOperator.inputfile'])
with warnings.catch_warnings(): with warnings.catch_warnings():
warnings.simplefilter("ignore") warnings.simplefilter("ignore")
# Add the observation ids, tracers and sites
obs_ids = infile['obs_id'][:] obs_ids = infile['obs_id'][:]
obs_ids = [b''.join(obs_id).decode() for obs_id in obs_ids] obs_ids = [b''.join(obs_id).decode() for obs_id in obs_ids]
self.tracer, self.site, self.obs_id = [], [], [] self.tracer, self.site, self.obs_id = [], [], []
...@@ -103,10 +106,12 @@ class STILTObservationOperator(ObservationOperator): ...@@ -103,10 +106,12 @@ class STILTObservationOperator(ObservationOperator):
self.tracer.append(tracer) self.tracer.append(tracer)
self.site.append(site) self.site.append(site)
self.obs_id.append(obs_id.split('~')[-1]) self.obs_id.append(obs_id.split('~')[-1])
# Times and latitude and longitudes are static; only need once
self.times = infile['date_components'][:] self.times = infile['date_components'][:]
self.lat = infile['latitude'][:] self.lat = infile['latitude'][:]
self.lon = infile['longitude'][:] self.lon = infile['longitude'][:]
# Set the start time
starttime = self.dacycle['time.sample.start'] starttime = self.dacycle['time.sample.start']
self.starttime = starttime self.starttime = starttime
self.year = starttime.year self.year = starttime.year
...@@ -115,7 +120,10 @@ class STILTObservationOperator(ObservationOperator): ...@@ -115,7 +120,10 @@ class STILTObservationOperator(ObservationOperator):
self.hour = starttime.hour self.hour = starttime.hour
def prepare_run(self,proc): def prepare_run(self,proc):
""" Prepare the running of the actual forecast model, for example compile code """ """ Prepare the running of the actual forecast model.
- Initialise the file that will hold the simulated values
- Initialise the number of ensemble members
- Update the rc file (stilt_X.rc)"""
import os import os
# Define the name of the file that will contain the modeled output of each observation # Define the name of the file that will contain the modeled output of each observation
...@@ -124,14 +132,16 @@ class STILTObservationOperator(ObservationOperator): ...@@ -124,14 +132,16 @@ class STILTObservationOperator(ObservationOperator):
self.update_rc(self.rc,proc) self.update_rc(self.rc,proc)
def update_rc(self,name,proc): def update_rc(self,name,proc):
"""Function that updates the .rc files.
The outputdir and time of the new rc file are adjusted"""
if 'stilt.rc' in name: if 'stilt.rc' in name:
path,dummy= name.split('stilt.rc') path,dummy= name.split('stilt.rc')
shutil.copyfile(name,path+'stilt_%s.rc'%proc) shutil.copyfile(name,path+'stilt_%s.rc'%proc)
name=os.path.join(path,'stilt_%s.rc'%proc) name=os.path.join(path,'stilt_%s.rc'%proc)
self.rc_filename = name self.rc_filename = name
logging.info('RC name %s'%name)
new_rc = rc.read(name) new_rc = rc.read(name)
# update the time and the outputdir
new_rc['cycstadate'] = self.starttime new_rc['cycstadate'] = self.starttime
new_rc['outdir'] = self.rc_options['outdir'] new_rc['outdir'] = self.rc_options['outdir']
rc.write(name, new_rc) rc.write(name, new_rc)
...@@ -139,26 +149,41 @@ class STILTObservationOperator(ObservationOperator): ...@@ -139,26 +149,41 @@ class STILTObservationOperator(ObservationOperator):
logging.debug('STILT rc-file updated successfully') logging.debug('STILT rc-file updated successfully')
def get_time_index_nc(self): def get_time_index_nc(self, time=None):
cycledate = self.dacycle['time.start'] """Function that gets the time index from the flux files
based on the cycletime and the first time in all the files (hardcoded in stilt.rc)
"""
if time == None:
# Get the time of the current cycle
cycledate = self.dacycle['time.start']
# Get the start date of all cycles
startdate = self.rc_options['files_startdate'] startdate = self.rc_options['files_startdate']
startdate = datetime.datetime.strptime(startdate, '%Y-%m-%d %H:%M:%S') startdate = datetime.datetime.strptime(startdate, '%Y-%m-%d %H:%M:%S')
# Get the difference between the current and the start
# Note that this is in hours, and thus assumes that the flux files are hourly as well
timediff = cycledate - startdate timediff = cycledate - startdate
timediff_hours = int(timediff.total_seconds()/3600) timediff_hours = int(timediff.total_seconds()/3600)
time_index = int(timediff_hours) time_index = int(timediff_hours)
return time_index return time_index
def get_time_indices(self): def get_time_indices(self):
"""Function that gets the time indices in the flux files
Because if the footprint is for 24 hours back, we need the fluxes 24 hours back"""
time_index = self.get_time_index_nc() time_index = self.get_time_index_nc()
return slice(time_index-self.numtimes, time_index) return slice(time_index-self.numtimes, time_index)
def get_latlon_index_nc(self, ncfile): def get_latlon_index_nc(self, ncfile):
"""Function that gets the indices of a lat/lon point in a .nc file.
This can be used for e.g. the background concentrations"""
# Initialise some names that could indicate latitude. Expand at will
lat_opts = ['lat', 'latitude', 'Lat', 'LAT', 'LATITUDE', 'Latitude'] lat_opts = ['lat', 'latitude', 'Lat', 'LAT', 'LATITUDE', 'Latitude']
# Same for longitude
lon_opts = ['lon', 'longigude', 'Lon', 'LON', 'LONGITUDE', 'Longitude'] lon_opts = ['lon', 'longigude', 'Lon', 'LON', 'LONGITUDE', 'Longitude']
# Get the index via the minimum of the difference.
for latopt in lat_opts: for latopt in lat_opts:
try: try:
lat_ind = np.argmin(abs(ncfile[latopt][:] - self.lat)) lat_ind = np.argmin(abs(ncfile[latopt][:] - self.lat))
...@@ -174,6 +199,8 @@ class STILTObservationOperator(ObservationOperator): ...@@ -174,6 +199,8 @@ class STILTObservationOperator(ObservationOperator):
return lat_ind, lon_ind return lat_ind, lon_ind
def get_foot(self, site): def get_foot(self, site):
"""Function that gets the footprint for the current time and site.
Returns a 3D np.array with dims (time, lat, lon)"""
path = self.rc_options['footdir'] + '/' + site.upper() + '24' path = self.rc_options['footdir'] + '/' + site.upper() + '24'
fname = path + '/footprint_{0}_{1}x{2:02d}x{3:02d}x{4:02d}*.nc'.format(site.upper(), fname = path + '/footprint_{0}_{1}x{2:02d}x{3:02d}x{4:02d}*.nc'.format(site.upper(),
self.year, self.month, self.day, self.hour) self.year, self.month, self.day, self.hour)
...@@ -185,21 +212,18 @@ class STILTObservationOperator(ObservationOperator): ...@@ -185,21 +212,18 @@ class STILTObservationOperator(ObservationOperator):
self.numtimes = len(footprint) self.numtimes = len(footprint)
return np.flip(np.flipud(footprint), axis=1) return np.flip(np.flipud(footprint), axis=1)
def get_center_of_mass(self): def get_center_of_mass(fp):
from scipy import ndimage from scipy import ndimage
i = -1
total_influence = 0
while total_influence < 0.0000001:
i+=1
total_influence = fp[i].sum()
foot = self.get_foot(site) center_of_mass = ndimage.measurements.center_of_mass(fp[i])
center_of_mass = [] center_of_mass = np.array(np.rint(center_of_mass), dtype=int)
# Note that we could also calculate the index in the file with this i and the 'get_time_indices' func.
for footi in foot: return center_of_mass, i
# Get the center of mass (tuple of floats)
center_of_mass_i = ndimage.measurements.center_of_mass(footi)
# Round the center of mass to nearest int to make it usable as index
center_of_mass_i = np.array(np.rint(center_of_mass_i), dtype=int)
# Append the rounded center of mass to a list
center_of_mass.append(center_of_mass_i)
return(center_of_mass)
def get_background(self, tracer_info): def get_background(self, tracer_info):
...@@ -221,14 +245,20 @@ class STILTObservationOperator(ObservationOperator): ...@@ -221,14 +245,20 @@ class STILTObservationOperator(ObservationOperator):
return 0 return 0
def get_atm_increase(self, site, tracer_info): def get_atm_increase(self, site, tracer_info):
"""Function that calculates the atmospheric increase due to the exchange of fluxes over the footprint"""
# First, get the footprint
foot = self.get_foot(site) foot = self.get_foot(site)
# Get the tracer fluxes
file = tracer_info['path'] file = tracer_info['path']
data = nc.Dataset(file, 'r') data = nc.Dataset(file, 'r')
indices = self.get_time_indices()
fluxmap = data[tracer_info['fluxname']] fluxmap = data[tracer_info['fluxname']]
# Get the time indices for the tracer fluxes
# It is assumed that the files all start at the same time
# And therefore this is not tracer-dependent
indices = self.get_time_indices()
fluxes = fluxmap[indices] fluxes = fluxmap[indices]
# Multiply the flux with the footprint. Take the half-life into account
half_life = eval(tracer_info['half_life']) half_life = eval(tracer_info['half_life'])
if half_life: if half_life:
atm_increase = 0 # Initialise the atmospheric increase atm_increase = 0 # Initialise the atmospheric increase
...@@ -242,12 +272,15 @@ class STILTObservationOperator(ObservationOperator): ...@@ -242,12 +272,15 @@ class STILTObservationOperator(ObservationOperator):
return atm_increase return atm_increase
def get_concentration(self, site, tracer_info): def get_concentration(self, site, tracer_info):
"""Function that calculates the simulated concentration for a given site and tracer as
[flux * foot] + background"""
return self.get_atm_increase(site, tracer_info) + self.get_background(tracer_info) return self.get_atm_increase(site, tracer_info) + self.get_background(tracer_info)
def run_forecast_model(self,proc,out_q): def run_forecast_model(self,proc,out_q):
"""Function that runs the forecast model parallel
First prepares run, then runs"""
self.parse_samplefile() self.parse_samplefile()
self.prepare_run(proc) self.prepare_run(proc)
# self.parse_rc(self.rc_filename)
self.run(proc) self.run(proc)
outdict = {} outdict = {}
outdict[proc] = self.simulated_file outdict[proc] = self.simulated_file
...@@ -255,8 +288,8 @@ class STILTObservationOperator(ObservationOperator): ...@@ -255,8 +288,8 @@ class STILTObservationOperator(ObservationOperator):
def run(self,proc): def run(self,proc):
""" """Function that calculates the concentration for each site and tracer.
Call stilt R executable : //give input file lists Calculates the concentration based on 'get_concentration' and then saves the data
""" """
concentrations = [] concentrations = []
for tracer, site, id in zip(self.tracer, self.site, self.obs_id): for tracer, site, id in zip(self.tracer, self.site, self.obs_id):
...@@ -274,8 +307,8 @@ class STILTObservationOperator(ObservationOperator): ...@@ -274,8 +307,8 @@ class STILTObservationOperator(ObservationOperator):
# Add a dimension # Add a dimension
dimid = f.add_dim('obs_num', len(self.concentrations)) dimid = f.add_dim('obs_num', len(self.concentrations))
dim_site = f.add_dim('charstring', 10) dim_site = f.add_dim('charstring', 10)
# dimid = ('obs_num',)
# Add the observation numbers # Add the observation numbers
savedict = io.std_savedict.copy() savedict = io.std_savedict.copy()
savedict['name'] = "obs_num" savedict['name'] = "obs_num"
savedict['dtype'] = "int" savedict['dtype'] = "int"
...@@ -319,9 +352,6 @@ class STILTObservationOperator(ObservationOperator): ...@@ -319,9 +352,6 @@ class STILTObservationOperator(ObservationOperator):
savedict['comment'] = "Tracer" savedict['comment'] = "Tracer"
f.add_data(savedict) f.add_data(savedict)
################### End Class STILT ###################
if __name__ == "__main__": if __name__ == "__main__":
pass pass
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment