Link Search Menu Expand Document

濃度差異轉成排放量(dc2em.py)

Table of contents

背景

  • 本程式主要應用在濃度差異「反衍」排放量之過程。修正對象為d1、d2、及d4範圍之面源排放量。模式版本為CAMx7。
    • 由於此處假設所有模擬誤差均來自於測站附近網格之地面排放,因此不適用在解析度較高之模擬系統,也不適用在鄰近重要點源的狀況。
    • 此程式延續ENKF的結果,以模擬瞬時之濃度差異dc_dt.csv內容修改排放量檔案。
  • 由於CAMx將5階(巢狀網格、ZYX、以及化學成分)的龐大矩陣轉成1維的指標系統,因此還原時需特別小心,以避免錯誤。
  • 基本構想:
    • Δe=Δc ×(ΔX ΔY ΔZ) / ΔT
  • 單位轉換:
    • 濃度:µg/M3或ppm
    • 排放量:g/Hr或g-mole/Hr
  • 修正uamiv排放量檔案遭遇問題
    • dc_dt.csv物質種類個數較排放檔案多
      • dc_dt.csv有40種,排放檔有35種,必須新增9種
      • In [243]: newV Out[243]: {‘ETHY’, ‘O3’, ‘PNH4’, ‘SOA1’, ‘SOA2’, ‘SOA3’, ‘SOA4’, ‘SOPA’, ‘SOPB’}
      • 新增後成為44種
    • 如為uamiv檔案,pncgen不能直接修改rec_dmn、不能新增變數、必須改成nc檔案再使用ncks
    • fortran計算不能使用omp,速度會慢很多(相較pandas)
      • 必須放棄fortran
  • 可能問題
    • DELTAX較小的網格系統、deltaT也會比較小,只修正小時排放無法反應較細微的變化。
    • 負值歸0的作法,會讓模擬值傾向高估。
    • 點源造成之觀測濃度,將無法由修正面源來減少誤差。

dc_dt.csv內容

  • 在camx700KF/IO_bin/cncupd.f內所產生
  • 為txt檔案,並沒有delimiter,須另予解析
    • date為yyjjj格式
    • time為HHMM格式,可能會有小數(dt_seconds/60有餘數,發生在DELTAX較小情況)
    • Nupd為CAMx內設指標系統
    • 定義計算方式如下所示
    • 格式為整數(必須可以計算)
    • conc c_obs為模式模擬與觀測值之瞬間值
    • 單位為µg/M3,與ppb之間差異0.0245倍,40 µg/M3 ~ 1.02 ppb (與物質種類無關)
$ head ../outputs/con10/1610NestT2dc_dt.csv
date time nupd conc c_obs
16290 2030.000 1620288 4.513743 2.040816
16290 2030.000 1620346 8.418146 17.47899
16290 2030.000 1620347 4.333055 9.387754
16290 2030.000 1620404 6.922164 5.918367
16290 2030.000 1620405 7.902810 14.89796
16290 2030.000 1620406 4.981133 25.17007
16290 2030.000 1620407 4.742090 13.06122
16290 2030.000 1620462 7.852451 5.510204
16290 2030.000 1620464 6.857380 28.16327

程式IO

引數

  1. dc_dt.csv檔案路徑名稱
  2. 欲修正之面源排放檔案名稱

污染物序號與名稱對照檔

  • '/nas1/camxruns/2016_v7/inputs/chem/CAMx7.0.chemparam.CB6r4_CF2',解讀程式如下所述。
  • 因空品名稱中有PM與NMHC,屬於綜合結果,因此先找不用的radical名稱來儲存,SOA1~SOA3對應到NMHC、PM10及PM2.5。
fname= '/nas1/camxruns/2016_v7/inputs/chem/CAMx7.0.chemparam.CB6r4_CF2'
with open(fname,'r') as f:
  part=[line.strip('\n').split() for line in f]
v4=[]
for i in part:
  if len(i) <=2:continue
  v4.append(i[1])
idx=v4.index('Spec');v4=v4[idx+1:]
idx=v4.index('Spec');v4[idx:]=v4[idx+1:]
idx=v4.index('Typ');v4=v4[:idx]
divD={'1':1000,'2':100,'4':1000} #divider for IJ
SPNAMs=['CO', 'NO2',  'O3', 'SO2','NMHC','PM10', 'PM2.5']
SPMAPs=['CO', 'NO2',  'O3', 'SO2','SOA1','SOA2', 'SOA3'] #use radical to store

執行外部程式

確認檔案可以新增污染項目

  • 使用ncdump測試檔案格式是nc檔案還是uamiv,如果是後者,就使用pncgen將其轉換成nc檔。
fname=sys.argv[2]
fnameO=fname+'_'+root
os.system('cp '+fname+' '+fnameO)
#make sure the number of var is extendable
try:
  unlimit_dim=subprocess.check_output(ncdump+' -h '+fnameO+'|head -n10|grep UNLIMITED',shell=True).decode('utf8').strip('\t').split()[0]
except:
  rst=os.system(pncgen+' -O -f uamiv '+fnameO+' tmp')
  if rst!=0:sys.exit('pncgen fail for '+fnameO)
  rst=os.system('mv tmp '+fnameO)
  unlimit_dim=subprocess.check_output(ncdump+' -h '+fnameO+'|head -n10|grep UNLIMITED',shell=True).decode('utf8').strip('\t').split()[0]
  • 如果不能新增污染物變數,則使用ncks更改記錄維度
if unlimit_dim !='VAR':
  rst=os.system(ncks+' -O  --mk_rec_dmn VAR  '+fnameO+' tmp')
  if rst!=0:sys.exit('ncks fail for '+fnameO)
  rst=os.system('mv tmp '+fnameO)

CAMx的指標系統

副程式locat(n)

  • 副程式locat(n)將csv中的指標,轉換成層數D、i,j,k,l等5階矩陣指標
  • CAMx程式中的fortran的原始碼也提供作為參照
#parameters
nn={'1': ['59', '59', '15'],'2': ['65', '65', '15'], '4': ['83', '137', '15']}
nxyz=[{} for i in range(3)]
for D in ['1','2','4']:
  for i in range(3):
    nxyz[i].update({D:int(nn[D][i])})
ncol,nrow,nlay=(nxyz[i] for i in range(3))
...
iptr4d={'1':1,'2':ncol['1']*nrow['1']*nlay['1']*len(v4)+1,'4':ncol['1']*nrow['1']*nlay['1']*len(v4)+ncol['2']*nrow['2']*nlay['2']*len(v4)+1}
...
def locat(n):
# fortran indexing
# 86             Mbeg(igrd,isp)=i+ncol(igrd)*(j - 1) + ncol(igrd)*nrow(igrd)*(k - 1)
# 87      &       +ncol(igrd)*nrow(igrd)*nlay(igrd)*(isp-1) +(iptr4d(igrd)-1)

  D='0'
  for dd in ['1','2','4']:
    if n>=iptr4d[dd]:
      D=dd
  n4d=n+1-iptr4d[D]
  nx,ny,nz=ncol[D],nrow[D],nlay[D]
  nxyz=nx*ny*nz
  nxy=nx*ny
  l=n4d//nxyz+1
  n3d=n4d-nxyz*(l-1)
  k=n3d//nxy+1
  n2d=n3d-nxy*(k-1)
  j=n2d//nx+1
  i=n2d-nx*(j-1)
  return int(D),i,j,k,l
  • 將Nupd拆分為
    1. 網格層級(iptr4d)之起點
    2. 4階餘數n4d = n+1-iptr4d[D]
    3. 3階餘數n3d = n4d-nxyz*(l-1)
    4. 2階餘數n2d = n3d-nxy*(k-1)
    5. 沒有1階餘數,因為該值就是i值
  • iptr4d定義
    • 該層指標起始值,為上層4階網格數之總和+1
    • iptr4d={‘1’:1}
    • {‘2’:iptr4d[‘1’]+ncol[‘1’]nrow[‘1’]nlay[‘1’]*len(v4)}
    • {‘4’:iptr4d[‘2’]+ncol[‘2’]nrow[‘2’]nlay[‘2’]*len(v4)}
    • 試該次模擬有哪些層數(組合)定義有關,為動態定義。

csv記錄轉成矩陣指標

  • 讀取csv檔並加以整理
df=read_csv(fname)
#conc/c_obs are in micromole/cubic meter
iv=0
for v in 'date time nupd conc c_obs'.split():
  df[v]=[float(i.split()[iv]) for i in df.iloc[:,0]]
  if iv<=2:
    df[v]=[int(i) for i in df[v]]
  iv+=1
c=' date time nupd conc c_obs'
del df[c]
  • 1維指標Nupd轉成5階指標
    • Nupd是按照X、Y、Z、Spec、Nest的順序計算的指標(冪次)系統
  • 只處理較大的網格系統(D==1或D==2)
nupd=list(set(df.nupd))
for v in 'H,D,i,j,k,l'.split(','):
  df[v]=0
for n in nupd:
  idx=df.loc[df.nupd==n].index
  ll={v:s for v,s in zip('D,i,j,k,l'.split(','),locat(n))}
  for v in 'D,i,j,k,l'.split(','):
    df.loc[idx,v]=[ll[v] for s in idx]
df=df.loc[df.D==1].reset_index(drop=True)    

時間之定位

deltaT之計算

  • 轉換成日時(datetime),進行前後差異的計算
  • 此個案為2016年10月16日20:00開始(162902000)
df.H=df.time//100
df['dt']=[datetime.datetime.strptime(str(i)+'{:04d}'.format(j),'%y%j%H%M') for i,j in zip(list(df.date),list(df.time))]
dt=list(set(df.dt))
dt.sort()
dt=[datetime.datetime.strptime('162902000','%y%j%H%M')]+dt
deltaT=[(dt[i+1]-dt[i]).total_seconds() for i in range(len(dt)-1)]
df['delT']=0.
for t in dt[1:]:
  delT=deltaT[dt.index(t)-1]
  idx=df.loc[df.dt==t].index
  df.loc[idx,'delT']=[delT for i in idx]

dc_dt.csv時間之定位

  • 因為時間(datetime)很難像整數一般定位,在此改成時間標籤(timestamp)排序,用bisect來定位。
  • 先讀取排放量nc檔案的TFLAG內容,轉成日時格式、再轉成時間標籤、形成小時序列。
  • 將前述csv檔案之日時,也轉成時間標籤
  • 逐一進行bisect定位。如為整點,則另以tstamp.index直接找到位置。
  • 將it儲存到df正確的位置
nc = netCDF4.Dataset(fnameO,'r+')
sdate,stime=(np.array(nc.variables['TFLAG'][:,0,i]) for i in range(2))
tflag=[datetime.datetime.strptime(str(i)+'{:02d}'.format(int(j/10000)),'%Y%j%H') for i,j in zip(sdate,stime)]
#only stamp can be properly located
tstamp=[datetime.datetime.timestamp(t) for t in tflag] #tflag in stamp form
dtstamp=[datetime.datetime.timestamp(t) for t in dt[1:]]# csv_dt's in stamp form
t_idx=[bisect.bisect_left(tstamp,t)-1 for t in dtstamp] #use bisect to locate appropriate hour, unless 00 sharp
df['IT']=-1
for t in dt[1:]:
  it=dt[1:].index(t)  #dt[0] is dummy start
  ti=t_idx[it]        #index of csv_dt in the tflag list (before sharp 00)
  if dtstamp[it] in tstamp:ti=tstamp.index(dtstamp[it]) #if csv_dt is 00 sharp, take index directly
  idx=df.loc[df.dt==t].index  #apply to all same dt
  df.loc[idx,'IT']=[ti for i in idx]

nc檔案VAR維度的擴張

  • 首先使用nc.createVariable新增nc檔案的污染項目
V0=[list(filter(lambda x:nc.variables[x].ndim==j, [i for i in nc.variables])) for j in [1,2,3,4]]
nt, nlay, nrow, ncol = (nc.variables[V0[3][0]].shape[i] for i in range(4))
V1=set([v4[l-1] for l in set(df.l)])
newV=V1-set(V0[3])
if len(newV)>0:
  for v in newV:
    nc.createVariable(v,"f4",('TSTEP LAY ROW COL'.split()))
    nc.variables[v].units = "mol/time"
    nc.variables[v].long_name = v
    nc.variables[v].var_desc = v
    nc.variables[v][:]=0.
  • 不能只作nc.createVariable,VAR也是個維度
  • VAR的維度存在TFLAG、ETFLAG 2個變數中,必須逐一擴充。
    • 類似TSTEP的增加方式
#extending the VAR dimension
for iv in range(nc.NVARS,nc.NVARS+len(newV)):
  for v in ['TFLAG','ETFLAG']:
    for i in range(2):
      nc.variables[v][:,iv,i]=nc.variables[v][:,0,i]
V0=[list(filter(lambda x:nc.variables[x].ndim==j, [i for i in nc.variables])) for j in [1,2,3,4]]
nc.NVARS=len(V0[3])
nc.close()
  • 此二者都正確之後,還必須修改global attribution中的變數名稱序列(VAR-LIST)
    • 必須先存檔、用ncatted來修改,因為python不接受變數含有減號(VAR - LIST)
#the sequence of V's is control in VAR-LIST, it must be ncatted before further processing
rst=os.system('/nas1/camxruns/2016_v7/emis/add_ncatt.cs '+fnameO+' emis')
if rst !=0:sys.exit('fail to add_ncatt.cs')

排放量差異之計算及填寫

  • 3個迴圈
    • 污染物先,這是因應nc檔案的結構
    • 空間(測站)其次
    • 時間:同一小時須累加,必須是最內圈。
  • 注意單位
  • 因同一小時內的紀錄次數,累加之後就是3600/deltaT的效果,因此不必另外除deltaT。
  • 注意排放量不允許有負值
  • vol必須搭配前述D==1或D==2
nc = netCDF4.Dataset(fnameO,'r+')
#micro mole/M3 *Km*Km*m /(sec/3600)
vol=81*81*40
for l in set(df.l): #loop for specs
  v=v4[l-1]
  dfv=df.loc[df.l==l]
  var=np.array(nc.variables[v][:,:,:,:])
  for n in set(dfv.nupd): #loop for locations
    dfvn=dfv.loc[dfv.nupd==n].reset_index(drop=True)
#fortran index is one greater than python index
    i,j=dfvn.loc[0,'i']-1,dfvn.loc[0,'j']-1
    for t in range(len(dfvn)): #loop for times (delT en_effect)
      delT,it,conc,c_obs=(dfvn.loc[t,c] for c in 'delT IT conc c_obs'.split())
      var[it,0,j,i]+=(c_obs-conc)*vol

# keep emis >0 (CAMx will fail if emis<0)
  var[var<0]=0.
  nc.variables[v][:]=var[:]
nc.close()

程式碼下載