#!/usr/bin/env python # obs.py """ Author : peters Revision History: File created on 28 Jul 2010. """ import os import sys import logging import datetime identifier = 'CarbonTracker CO2' ################### Begin Class MixingRatioSample ################### class MixingRatioSample(object): """ Holds the data that defines a Mixing Ratio Sample in the data assimilation framework. Sor far, this includes all attributes listed below in the __init__ method. One can additionally make more types of data, or make new objects for specific projects. """ def __init__(self,xdate,code='XXX',obs=0.0,simulated=0.0,resid=0.0,hphr=0.0,mdm=0.0,flag=0,height=0.0,lat=-999.,lon=-999.,evn='0000',sdev=0.0): self.code = code.strip() # Site code self.xdate = xdate # Date of obs self.obs = obs # Value observed self.simulated = simulated # Value simulated by model self.resid = resid # Mixing ratio residuals self.hphr = hphr # Mixing ratio prior uncertainty from fluxes and (HPH) and model data mismatch (R) self.mdm = mdm # Model data mismatch self.flag = flag # Flag self.height = height # Sample height self.lat = lat # Sample lat self.lon = lon # Sample lon self.id = evn # Event number self.sdev = sdev # standard deviation of ensemble self.masl = True # Sample is in Meters Above Sea Level self.mag = not self.masl # Sample is in Meters Above Ground def __str__(self): day=self.xdate.strftime('%Y-%m-%d %H:%M:%S') return ' '.join(map(str,[self.code,day,self.obs,self.flag,self.id])) ################### End Class MixingRatioSample ################### ################### Begin Class MixingRatioList ################### class MixingRatioList(list): """ This is a special type of list that holds MixingRatioSample objects. It has methods to extract data from such a list easily """ from numpy import array, ndarray def getvalues(self,name,constructor=array): from numpy import ndarray result = constructor([getattr(o,name) for o in self]) if isinstance(result,ndarray): return result.squeeze() else: return result def unflagged(self): return MixingRatioSample([o for o in self if o.flag == 0]) def flagged(self): return MixingRatioSample([o for o in self if o.flag != 0]) def selectsite(self,site='mlo'): l=[o for o in self if o.code == site] return MixingRatioSample(l) def selecthours(self,hours=range(1,24)): l=[o for o in self if o.xdate.hour in hours] return MixingRatioSample(l) ################### End Class MixingRatioList ################### ################### Begin Class CtObservations ################### class CtObservations(object): """ an object that holds data + methods and attributes needed to manipulate mixing ratio values """ def __init__(self,DaSystem,sampledate): sfname = DaSystem.da_settings['obs.input.fname']+'.%s'%(sampledate.strftime('%Y%m%d'),)+'.nc' #msg = 'This filename is hardcoded but needs replacement' ; logging.warning(msg) filename = os.path.join(DaSystem.da_settings['obs.input.dir'],sfname) if not os.path.exists(filename): msg = 'Could not find the required observation input file (%s) ' % filename ; logging.error(msg) raise IOError,msg else: self.ObsFilename = filename self.MrData = MixingRatioList([]) def __str__(self): """ Prints a list of MixingRatioSample objects""" return self.ObsFilename def AddObs(self): """ Returns a MixingRatioList holding individual MixingRatioSample objects for all obs in a file Note that this routine was not yet ported to Nio because it cannot read string arrays !!! """ import pyhdf.SD as HDF import datetime as dtm from string import strip, join ncf = HDF.SD(self.ObsFilename) idates = ncf.select('date_components').get() ids = ncf.select('id').get() sites = ncf.select('site').get() sites = [s.tostring().lower() for s in sites] sites = map(strip,sites) lats = ncf.select('lat').get() lons = ncf.select('lon').get() alts = ncf.select('alt').get() obs = ncf.select('obs').get() species = ncf.select('species').get() date = ncf.select('date').get() window = ncf.select('sampling_strategy').get() flags = ncf.select('NOAA_QC_flags').get() flags = [s.tostring().lower() for s in flags] flags = map(strip,flags) dummy = ncf.end() msg = "Successfully read data from obs file (%s)"%self.ObsFilename ; logging.debug(msg) dates = [dtm.datetime(*d) for d in idates] if self.MrData == None: self.MrData = MixingRatioList([]) for n in range(len(dates)): self.MrData.append( MixingRatioSample(dates[n],sites[n],obs[n],0.0,0.0,0.0,0.0,flags[n],alts[n],lats[n],lons[n],ids[n],0.0) ) msg = "Added %d observations to the MrData list" % len(dates) ; logging.debug(msg) def AddSimulations(self, filename,silent=True): """ Adds model simulated values to the mixing ratio objects """ import pyhdf.SD as HDF import Nio if not os.path.exists(filename): msg = "Could not find required model sample file (%s)"%filename ; logging.debug(msg) raise IOError,msg ncf = Nio.open_file(filename,format='hdf') ids = ncf.variables['id'].get_value() simulated = ncf.variables['sampled_mole_frac'].get_value()*1e6 dummy = ncf.close() msg = "Successfully read data from model sample file (%s)"%filename ; logging.info(msg) obs_ids = self.MrData.getvalues('id') obs_ids = obs_ids.tolist() ids = map(int,ids) missing_samples = [] for id,val in zip(ids,simulated): if id in obs_ids: index = obs_ids.index(id) dummy = self.MrData[index].simulated = val else: missing_samples.append(id) if not silent and missing_samples != []: msg = 'Model samples were found that did not match any ID in the observation list. Skipping them...' ; logging.warning(msg) msg = '%s'%missing_samples ; logging.warning(msg) msg = "Added %d simulated values to the MrData list" % (len(ids)-len(missing_samples) ) ; logging.debug(msg) ################### End Class CtObservations ################### ################# Stand alone methods that manipulate the objects above ############## def PrepareObs(CycleInfo,lag): """ PrepareObs """ import shutil from da.tools.general import AdvanceTime # First, we make a CarbonTracker Observation object which holds all the information related to the mixing ratios going in # and coming out of the model. This object also holds the observations themselves, and the simulated value + statistics sampledate = CycleInfo.da_settings['time.start'] sampledate = AdvanceTime(sampledate,lag*int(CycleInfo.da_settings['cyclelength'])) samples = CtObservations(CycleInfo.DaSystem, sampledate) dummy = samples.AddObs() # # Next should follow a section where the observations that are read are further processes, and written to a NetCDF file for # the transport model to use for x,y,z,t smapling. # # For carbontracker the observations already come in netcdf format thanks to Andy and Ken. We will therefore simply copy # the observations.nc file to the pre-cooked obs files # filename= samples.ObsFilename saveas = os.path.join(CycleInfo.da_settings['dir.input'],'observations.nc') dummy = shutil.copy2(filename,saveas) msg = 'Successfully copied file (%s) to (%s) ' % (filename,saveas) ; logging.info(msg) return samples if __name__ == "__main__": sys.path.append('../../') import os import sys from da.tools.general import StartLogger from da.tools.initexit import CycleControl import numpy as np import da.tools.rc as rc opts = ['-v'] args = {'rc':'../../da.rc','logfile':'da_initexit.log','jobrcfilename':'test.rc'} StartLogger() DaCycle = CycleControl(opts,args) DaCycle.Initialize() print DaCycle samples = CtObservations(DaCycle.DaSystem,datetime.datetime(2005,3,5)) dummy = samples.AddObs() dummy = samples.AddSimulations('/Users/peters/tmp/test_da/output/samples.000.nc') print samples.MrData.getvalues('code') print samples.MrData.getvalues('obs') mlo=samples.MrData.selectsite('mlo_01d0')