/python/pyclaw/io/netcdf.py

http://github.com/clawpack/clawpack-4.x · Python · 496 lines · 405 code · 20 blank · 71 comment · 27 complexity · 3ab1a96cb4193def8cc1b0eef8faf6e8 MD5 · raw file

  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. r"""
  4. Routines for reading and writing a NetCDF output file
  5. Routines for reading and writing a NetCDF output file via either
  6. - netcdf4-python - http://code.google.com/p/netcdf4-python/
  7. - pupynere - http://pypi.python.org/pypi/pupynere/
  8. These interfaces are very similar so if a different module needs to be used,
  9. it can more than likely be inserted with a minimal of effort.
  10. This module will first try to import the netcdf4-python module which is based
  11. on the compiled libraries and failing that will attempt to import the pure
  12. python interface pupynere which requires no libraries.
  13. To install the netCDF 4 library, please see:
  14. http://www.unidata.ucar.edu/software/netcdf/
  15. :Authors:
  16. Kyle T. Mandli (2009-02-17) Initial version
  17. Josh Jacobs (2011-04-22) NetCDF 3 Support
  18. """
  19. # ============================================================================
  20. # Copyright (C) 2009 Kyle T. Mandli <mandli@amath.washington.edu>
  21. # Copyright (C) 2011 J. Jacobs
  22. #
  23. # Distributed under the terms of the Berkeley Software Distribution (BSD)
  24. # license
  25. # http://www.opensource.org/licenses/
  26. # ============================================================================
  27. import os,sys
  28. import logging
  29. import pyclaw.solution
  30. logger = logging.getLogger('io')
  31. # Import appropriate netcdf package
  32. use_netcdf3 = True #use this implementation as default...even though it does not have groups
  33. use_netcdf4 = False
  34. use_pupynere = False
  35. try:
  36. from Scientific.IO import NetCDF
  37. use_netcdf3 = True
  38. except:
  39. try:
  40. from scipy.io import netcdf as NetCDF
  41. except:
  42. print "*** Could not import NetCDF from Scientific.IO or scipy.io"
  43. if not use_netcdf3:
  44. try:
  45. import netCDF4
  46. use_netcdf4 = True
  47. except:
  48. pass
  49. if not (use_netcdf3 or use_netcdf4):
  50. try:
  51. import pupynere
  52. use_pupynere = True
  53. except:
  54. error_msg = ("Could not import netCDF3,netCDF4 or Pupynere, please install " +
  55. "one of the available modules for netcdf files. Refer to this " +
  56. "modules doc_string for more information.")
  57. #raise Exception(error_msg)
  58. print error_msg
  59. def write_netcdf(solution,frame,path,file_prefix='fort',write_aux=False,
  60. options={}):
  61. r"""
  62. Write out a NetCDF data file representation of solution
  63. :Input:
  64. - *solution* - (:class:`~pyclaw.solution.Solution`) Pyclaw object to be
  65. output
  66. - *frame* - (int) Frame number
  67. - *path* - (string) Root path
  68. - *file_prefix* - (string) Prefix for the file name. ``default = 'claw'``
  69. - *write_aux* - (bool) Boolean controlling whether the associated
  70. auxiliary array should be written out. ``default = False``
  71. - *options* - (dict) Optional argument dictionary, see
  72. `NetCDF Option Table`_
  73. .. _`NetCDF Option Table`:
  74. +-------------------------+----------------------------------------------+
  75. | Key | Value |
  76. +=========================+==============================================+
  77. | description | Dictionary of key/value pairs that will be |
  78. | | attached to the root group as attributes, |
  79. | | i.e. {'time':3} |
  80. +-------------------------+----------------------------------------------+
  81. | format | Can be one of the following netCDF flavors: |
  82. | | NETCDF3_CLASSIC, NETCDF3_64BIT, |
  83. | | NETCDF4_CLASSIC, and NETCDF4 |
  84. | | ``default = NETCDF4`` |
  85. +-------------------------+----------------------------------------------+
  86. | clobber | if True (Default), file will be overwritten, |
  87. | | if False an exception will be raised |
  88. +-------------------------+----------------------------------------------+
  89. | zlib | if True, data assigned to the Variable |
  90. | | instance is compressed on disk. |
  91. | | ``default = False`` |
  92. +-------------------------+----------------------------------------------+
  93. | complevel | the level of zlib compression to use (1 is |
  94. | | the fastest, but poorest compression, 9 is |
  95. | | the slowest but best compression). Ignored |
  96. | | if zlib=False. ``default = 6`` |
  97. +-------------------------+----------------------------------------------+
  98. | shuffle | if True, the HDF5 shuffle filter is applied |
  99. | | to improve compression. Ignored if |
  100. | | zlib=False. ``default = True`` |
  101. +-------------------------+----------------------------------------------+
  102. | fletcher32 | if True (default False), the Fletcher32 |
  103. | | checksum algorithm is used for error |
  104. | | detection. |
  105. +-------------------------+----------------------------------------------+
  106. | contiguous | if True (default False), the variable data |
  107. | | is stored contiguously on disk. Setting to |
  108. | | True for a variable with an unlimited |
  109. | | dimension will trigger an error. |
  110. | | ``default = False`` |
  111. +-------------------------+----------------------------------------------+
  112. | chunksizes | Can be used to specify the HDF5 chunksizes |
  113. | | for each dimension of the variable. A |
  114. | | detailed discussion of HDF chunking and I/O |
  115. | | performance is available here. Basically, |
  116. | | you want the chunk size for each dimension |
  117. | | to match as closely as possible the size of |
  118. | | the data block that users will read from the |
  119. | | file. chunksizes cannot be set if |
  120. | | contiguous=True. |
  121. +-------------------------+----------------------------------------------+
  122. | least_significant_digit | If specified, variable data will be |
  123. | | truncated (quantized). In conjunction with |
  124. | | zlib=True this produces 'lossy', but |
  125. | | significantly more efficient compression. |
  126. | | For example, if least_significant_digit=1, |
  127. | | data will be quantized using around |
  128. | | (scale*data)/scale, where scale = 2**bits, |
  129. | | and bits is determined so that a precision |
  130. | | of 0.1 is retained (in this case bits=4). |
  131. | | ``default = None``, or no quantization. |
  132. +-------------------------+----------------------------------------------+
  133. | endian | Can be used to control whether the data is |
  134. | | stored in little or big endian format on |
  135. | | disk. Possible values are little, big or |
  136. | | native (default). The library will |
  137. | | automatically handle endian conversions when |
  138. | | the data is read, but if the data is always |
  139. | | going to be read on a computer with the |
  140. | | opposite format as the one used to create |
  141. | | the file, there may be some performance |
  142. | | advantage to be gained by setting the |
  143. | | endian-ness. |
  144. +-------------------------+----------------------------------------------+
  145. | fill_value | If specified, the default netCDF _FillValue |
  146. | | (the value that the variable gets filled |
  147. | | with before any data is written to it) is |
  148. | | replaced with this value. If fill_value is |
  149. | | set to False, then the variable is not |
  150. | | pre-filled. |
  151. +-------------------------+----------------------------------------------+
  152. .. note::
  153. The zlib, complevel, shuffle, fletcher32, contiguous, chunksizes and
  154. endian keywords are silently ignored for netCDF 3 files that do not
  155. use HDF5.
  156. """
  157. # Option parsing
  158. option_defaults = {'format':'NETCDF4','zlib':False,'complevel':6,
  159. 'shuffle':True,'fletcher32':False,'contiguous':False,
  160. 'chunksizes':None,'endian':'native',
  161. 'least_significant_digit':None,'fill_value':None,
  162. 'clobber':True,'description':{}}
  163. for (k,v) in option_defaults.iteritems():
  164. if options.has_key(k):
  165. exec("%s = options['%s']" % (k,k))
  166. else:
  167. exec('%s = v' % k)
  168. # Filename
  169. filename = os.path.join(path,"%s.q%s.nc" % (file_prefix,str(frame).zfill(4)))
  170. if use_netcdf3:
  171. import numpy
  172. # Open new file
  173. f = NetCDF.NetCDFFile(filename,'w')#if I want not to clobber this mode needs to be 'a'
  174. # Loop through description dictionary and add the attributes to the
  175. # root group
  176. for (k,v) in description.iteritems():
  177. exec('f.%s = %s' % (k,v))
  178. # Create Global Dimensions
  179. f.createDimension("timedimension",None) #NoneType is unlimited
  180. f.createDimension("meqn",solution.grid.meqn)
  181. # Create Global variables
  182. time=f.createVariable("timedimension",'f',("timedimension",))
  183. ngrids=f.createVariable("ngrids",'d',())
  184. naux=f.createVariable("naux",'d',())
  185. ndim=f.createVariable("ndim",'d',())
  186. # For each grid, write out attributes
  187. for grid in solution.grids:
  188. if solution.grids.index(grid)==0:
  189. time.assignValue(getattr(grid,'t'))
  190. ngrids.assignValue(len(solution.grids)) # I am not quite sure about these
  191. naux.assignValue(grid.naux) #
  192. ndim.assignValue(len(grid.dimensions)) #
  193. # Create dimensions for q (and aux)
  194. dim_names=[]
  195. for dim in grid.dimensions:
  196. f.createDimension(dim.name+str(grid.gridno),dim.n)
  197. dim_names.append(dim.name)
  198. # Write q array
  199. tmp_names=dim_names+['meqn','timedimension']
  200. index_str = ','.join( [':' for name in tmp_names] )
  201. q = f.createVariable('grid_'+str(grid.gridno),'d',tmp_names)
  202. exec("q[%s] = grid.q" % index_str)
  203. # General grid properties
  204. for attr in ['gridno','level']: #,'mbc']:
  205. setattr(q,attr,getattr(grid,attr))
  206. setattr(q,'dim_names',dim_names)
  207. for dim in grid.dimensions:
  208. setattr(q,dim.name+'.lower',dim.lower)
  209. setattr(q,dim.name+'.d',dim.d)
  210. # # Write out aux
  211. # if grid.maux > 0 and write_aux:
  212. # dim_names[-1] = 'maux'
  213. # subgroup.createDimension('maux',grid.maux)
  214. # aux = subgroup.createVariable('aux','f8',dim_names,
  215. # zlib,complevel,shuffle,fletcher32,
  216. # contiguous,chunksizes,endian,
  217. # least_significant_digit,fill_value)
  218. # exec("aux[%s] = grid.aux" % index_str)
  219. f.close()
  220. elif use_netcdf4:
  221. # Open new file
  222. f = netCDF4.Dataset(filename,'w',clobber=clobber,format=format)
  223. # Loop through description dictionary and add the attributes to the
  224. # root group
  225. for (k,v) in description.iteritems():
  226. exec('f.%s = %s' % (k,v))
  227. # For each grid, write out attributes
  228. for grid in solution.grids:
  229. # Create group for this grid
  230. subgroup = f.createGroup('grid%s' % grid.gridno)
  231. # General grid properties
  232. for attr in ['t','meqn','gridno','level','mbc']:
  233. setattr(subgroup,attr,getattr(grid,attr))
  234. # Write out dimension names
  235. setattr(subgroup,'dim_names',grid.name)
  236. # Create dimensions for q (and aux)
  237. for dim in grid.dimensions:
  238. subgroup.createDimension(dim.name,dim.n)
  239. # Write other dimension attributes
  240. for attr in ['n','lower','d','upper','mthbc_lower',
  241. 'mthbc_upper','units']:
  242. if hasattr(dim,attr):
  243. if getattr(dim,attr) is not None:
  244. attr_name = '%s.%s' % (dim.name,attr)
  245. setattr(subgroup,attr_name,getattr(dim,attr))
  246. subgroup.createDimension('meqn',grid.meqn)
  247. # Write q array
  248. dim_names = grid.name
  249. dim_names.append('meqn')
  250. index_str = ','.join( [':' for name in dim_names] )
  251. q = subgroup.createVariable('q','f8',dim_names,zlib,
  252. complevel,shuffle,fletcher32,
  253. contiguous,chunksizes,endian,
  254. least_significant_digit,fill_value)
  255. exec("q[%s] = grid.q" % index_str)
  256. # Write out aux
  257. if grid.maux > 0 and write_aux:
  258. dim_names[-1] = 'maux'
  259. subgroup.createDimension('maux',grid.maux)
  260. aux = subgroup.createVariable('aux','f8',dim_names,
  261. zlib,complevel,shuffle,fletcher32,
  262. contiguous,chunksizes,endian,
  263. least_significant_digit,fill_value)
  264. exec("aux[%s] = grid.aux" % index_str)
  265. f.close()
  266. elif use_pupynere:
  267. logging.critical("Pupynere support has not been implemented yet.")
  268. raise IOError("Pupynere support has not been implemented yet.")
  269. else:
  270. err_msg = "No netcdf python modules available."
  271. logging.critical(err_msg)
  272. raise Exception(err_msg)
  273. def read_netcdf(solution,frame,path='./',file_prefix='fort',read_aux=True,
  274. options={}):
  275. r"""
  276. Read in a NetCDF data files into solution
  277. :Input:
  278. - *solution* - (:class:`~pyclaw.solution.Solution`) Pyclaw object to be
  279. output
  280. - *frame* - (int) Frame number
  281. - *path* - (string) Root path
  282. - *file_prefix* - (string) Prefix for the file name. ``default = 'claw'``
  283. - *write_aux* - (bool) Boolean controlling whether the associated
  284. auxiliary array should be written out. ``default = False``
  285. - *options* - (dict) Optional argument dictionary, unused for reading.
  286. """
  287. # Option parsing
  288. option_defaults = {}
  289. for (k,v) in option_defaults.iteritems():
  290. if options.has_key(k):
  291. exec("%s = options['%s']" % (k,k))
  292. else:
  293. exec('%s = v' % k)
  294. # Filename
  295. filename = os.path.join(path,"%s.q%s.nc" % (file_prefix,str(frame).zfill(4)))
  296. #jj-2010.02.04-added support for netcdf classic format
  297. if use_netcdf3:
  298. from Scientific.IO import NetCDF
  299. import numpy
  300. # Open file
  301. print filename
  302. f = NetCDF.NetCDFFile(filename,'r')
  303. # Each file written by the fortran code has
  304. # Dimensions:
  305. # timedimension : UNLIMITED
  306. # meqn : The number of equations
  307. # dimx_<gridno> : X dimension for grid number <gridno>
  308. # dimy_<gridno> : Y dimension for grid number <gridno>
  309. # Variables:
  310. # timedimension : Stores the time of the frame
  311. # ngrids : Number of grids in this frame
  312. # naux : Number of Auxilary Variables
  313. # ndim : Number of Dimensions in the frame
  314. # grid_<gridno> : A grid of (dimx,dimy,meqn)
  315. # Attributes:
  316. # (grid_<no>) gridno : The number of this grid <grid_no>
  317. # level : The AMR level
  318. # dim_names : a list of dimensions [dimx,dimy]
  319. # dim<>.low : The lowest dimension value
  320. # dim<>.d : The distance between grid points
  321. time=numpy.squeeze(f.variables['timedimension'].getValue())
  322. ngrids=numpy.squeeze(f.variables['ngrids'].getValue())
  323. naux=numpy.squeeze(f.variables['naux'].getValue())
  324. ndims=numpy.squeeze(f.variables['ndim'].getValue())
  325. meqn=f.dimensions['meqn']
  326. # print time,ngrids,naux,ndims,meqn
  327. for var_name in f.variables.keys():
  328. if var_name[:4]=='grid':
  329. var=f.variables[var_name]
  330. gridno=numpy.squeeze(getattr(var,'gridno'))
  331. #print gridno
  332. # Construct each dimension
  333. dimensions = []
  334. # Read in dimension attribute to keep dimension order
  335. dim_names = eval(getattr(var,'dim_names'))
  336. for dim_name in dim_names:
  337. dim_n=f.dimensions[dim_name+'_'+str(gridno).zfill(2)]
  338. dim_lower=numpy.squeeze(getattr(var,'%s.lower' % dim_name))
  339. dim_d=numpy.squeeze(getattr(var,'%s.d' % dim_name))
  340. #print dim_lower,dim_d
  341. dim_upper=dim_lower+dim_d*dim_n
  342. dim = pyclaw.solution.Dimension(dim_name, dim_lower,dim_upper,dim_n)
  343. # # Optional attributes
  344. # # jj-2011/02/04--these are boundary conditions... don't know how to handle yet.
  345. # for attr in ['mthbc_lower','mthbc_upper','units']:
  346. # attr_name = "%s.%s" % (dim_name,attr)
  347. # if hasattr(subgroup,attr_name):
  348. # setattr(dim,attr,getattr(subgroup, "%s.%s" % (dim_name,attr)))
  349. dimensions.append(dim)
  350. # Create grid
  351. grid = pyclaw.solution.Grid(dimensions)
  352. # General grid properties
  353. for attr in ['t','meqn','gridno','level']:
  354. if attr in ['gridno','level']:
  355. setattr(grid,attr,numpy.squeeze(getattr(var,attr)))
  356. elif attr == 't':
  357. setattr(grid,attr,time)
  358. elif attr == 'meqn':
  359. setattr(grid,attr,meqn)
  360. # Read in q
  361. #index_str = ','.join( [':' for i in xrange(grid.ndim+1)] )
  362. #grid_str='grid_'+str(gridno).zfill(2)
  363. #exec("tmp_grid = f.variables['"+grid_str+"'][0,%s]" % index_str)
  364. tmp_grid = numpy.array(var.getValue())[0] #.variables[grid_str][0,:]
  365. #print type(tmp_grid),numpy.shape(tmp_grid)
  366. #swap axes from column major to row major order
  367. tmp_len=len(tmp_grid.shape)
  368. for i in range(0,tmp_len/2):
  369. tmp_grid=tmp_grid.swapaxes(i,tmp_len-(i+1))
  370. grid.q=tmp_grid
  371. # Read in aux if applicable
  372. #jj--2011.02.04--Not going to do this yet.
  373. ## if read_aux and subgroup.dimensions.has_key('maux'):
  374. ## exec("grid.aux = subgroup.variables['aux_<grid_no>'][%s]" % index_str)
  375. solution.grids.append(grid)
  376. solution.grids.sort(key=lambda grid:grid.level)
  377. f.close()
  378. elif use_netcdf4:
  379. # Open file
  380. f = netCDF4.Dataset(filename,'r')
  381. # We only expect subgroups of grids, otherwise we need to put some
  382. # sort of conditional here
  383. for subgroup in f.groups.itervalues():
  384. # Construct each dimension
  385. dimensions = []
  386. # Read in dimension attribute to keep dimension order
  387. dim_names = getattr(subgroup,'dim_names')
  388. for dim_name in dim_names:
  389. dim = pyclaw.solution.Dimension(dim_name,
  390. getattr(subgroup,'%s.lower' % dim_name),
  391. getattr(subgroup,'%s.upper' % dim_name),
  392. getattr(subgroup,'%s.n' % dim_name))
  393. # Optional attributes
  394. for attr in ['mthbc_lower','mthbc_upper','units']:
  395. attr_name = "%s.%s" % (dim_name,attr)
  396. if hasattr(subgroup,attr_name):
  397. setattr(dim,attr,getattr(subgroup, "%s.%s" % (dim_name,attr)))
  398. dimensions.append(dim)
  399. # Create grid
  400. grid = pyclaw.solution.Grid(dimensions)
  401. # General grid properties
  402. for attr in ['t','meqn','gridno','level']:
  403. setattr(grid,attr,getattr(subgroup,attr))
  404. # Read in q
  405. index_str = ','.join( [':' for i in xrange(grid.ndim+1)] )
  406. exec("grid.q = subgroup.variables['q'][%s]" % index_str)
  407. # Read in aux if applicable
  408. if read_aux and subgroup.dimensions.has_key('maux'):
  409. exec("grid.aux = subgroup.variables['aux'][%s]" % index_str)
  410. solution.grids.append(grid)
  411. solution.grids.sort(key=lambda grid:grid.level)
  412. f.close()
  413. elif use_pupynere:
  414. logging.critical("Pupynere support has not been implemented yet.")
  415. raise IOError("Pupynere support has not been implemented yet.")
  416. else:
  417. err_msg = "No netcdf python modules available."
  418. logging.critical(err_msg)
  419. raise Exception(err_msg)
  420. def read_netcdf_t(frame,path='./',file_prefix='fort'):
  421. # Option parsing
  422. option_defaults = {}
  423. for (k,v) in option_defaults.iteritems():
  424. if options.has_key(k):
  425. exec("%s = options['%s']" % (k,k))
  426. else:
  427. exec('%s = v' % k)
  428. # Filename
  429. filename = os.path.join(path,"%s.q%s.nc" % (file_prefix,str(frame).zfill(4)))
  430. # print filename
  431. if use_netcdf3:
  432. from Scientific.IO import NetCDF
  433. import numpy
  434. # Open file
  435. f = NetCDF.NetCDFFile(filename,'r')
  436. t = numpy.squeeze(f.variables['timedimension'].getValue())
  437. print t
  438. return t