Source code for boutpy.boututils.datafile

"""File I/O class

A wrapper around various NetCDF libraries, used by
BOUT++ routines. Creates a consistent interface
across machines

NOTE: NetCDF includes unlimited dimensions,
but this library is just for very simple
I/O operations. Educated guesses are made
for the dimensions.

Supported libraries:
-------------------

* netCDF4
* Scientific.IO.NetCDF
* scipy.io.netcdf

  - old version (create_dimension, create_variable)
  - new version (createDimension, createVariable)

"""

__all__ = ['DataFile']

import time

import numpy as np

library = None  # Record which library to use

try:
    from netCDF4 import Dataset
    library = "netCDF4"
except ImportError:
    # print "netcdf4-python module not found"

    try:
        from Scientific.IO.NetCDF import NetCDFFile as Dataset
        from Scientific.N import Int, Float
        library = "Scientific"
        # print "  => Using Scientific.IO.NetCDF instead"
    except ImportError:
        try:
            from scipy.io.netcdf import netcdf_file as Dataset
            library = "scipy"
            # print "Using scipy.io.netcdf library"
        except:
            print "DataFile: No supported NetCDF modules available"
            raise


def getUserName():
    try:
        import os
        import pwd
        import string
    except ImportError:
        return 'unknown user'
    pwd_entry = pwd.getpwuid(os.getuid())
    name = string.strip(string.splitfields(pwd_entry[4], ',')[0])
    if name == '':
        name = pwd_entry[0]
    return name


[docs]class DataFile: """NETCDF file I/O class. Methods ------- open(filename, write=False, create=False, format='NETCDF3_CLASSIC') Open NETCDF file close() Close netcdf file read(name, range=None) Read variable from file. """ handle = None def open(self, filename, write=False, create=False, format='NETCDF3_CLASSIC'): """Open NETCDF file. """ if (not write) and (not create): self.handle = Dataset(filename, "r") elif create: if library == "Scientific": self.handle = Dataset(filename, "w", 'Created ' + time.ctime(time.time()) + ' by ' + getUserName()) elif library == "scipy": self.handle = Dataset(filename, "w") else: self.handle = Dataset(filename, "w", format=format) else: if library == "scipy": raise Exception("scipy.io.netcdf doesn't support appending") else: self.handle = Dataset(filename, "a") # Record if writing self.writeable = write or create def close(self): """Close Netcdf file. """ if self.handle is not None: self.handle.close() self.handle = None def __init__(self, filename=None, write=False, create=False, format='NETCDF3_CLASSIC'): if filename is not None: self.open(filename, write=write, create=create, format=format) def __del__(self): self.close() def read(self, name, ranges=None): """Read a variable from the file.""" if self.handle is None: return None try: var = self.handle.variables[name] except KeyError: # Not found. Try to find using case-insensitive search var = None for n in self.handle.variables.keys(): if n.lower() == name.lower(): print("WARNING: Reading '{}' instead of '{}'" .format(n, name)) var = self.handle.variables[n] if var is None: return None ndims = len(var.dimensions) if ndims == 0: data = var.getValue() return data # [0] else: if ranges is not None: if len(ranges) != 2 * ndims: print "Incorrect number of elements in ranges argument" return None if library == "Scientific": # Passing ranges to var[] doesn't seem to work data = var[:] if ndims == 1: data = data[ranges[0]:ranges[1]] elif ndims == 2: data = data[ranges[0]:ranges[1], ranges[2]:ranges[3]] elif ndims == 3: data = data[ranges[0]:ranges[1], ranges[2]:ranges[3], ranges[4]:ranges[5]] elif ndims == 4: data = data[(ranges[0]):(ranges[1]), (ranges[2]):(ranges[3]), (ranges[4]):(ranges[5]), (ranges[6]):(ranges[7])] else: if ndims == 1: data = var[ranges[0]:ranges[1]] elif ndims == 2: data = var[ranges[0]:ranges[1], ranges[2]:ranges[3]] elif ndims == 3: data = var[ranges[0]:ranges[1], ranges[2]:ranges[3], ranges[4]:ranges[5]] elif ndims == 4: # print "Ranges = ", ranges data = var[(ranges[0]):(ranges[1]), (ranges[2]):(ranges[3]), (ranges[4]):(ranges[5]), (ranges[6]):(ranges[7])] return data else: return var[:] def list(self): """List all variables in the file.""" if self.handle is None: return [] return self.handle.variables.keys() def dimensions(self, varname): """Array of dimension names. """ if self.handle is None: return None try: var = self.handle.variables[varname] except KeyError: return None return var.dimensions def ndims(self, varname): """Number of dimensions for a variable.""" if self.handle is None: return None try: var = self.handle.variables[varname] except KeyError: return None return len(var.dimensions) def size(self, varname): """List of dimension sizes for a variable.""" if self.handle is None: return [] try: var = self.handle.variables[varname] except KeyError: return [] def dimlen(d): dim = self.handle.dimensions[d] if dim is not None: t = type(dim).__name__ if t == 'int': return dim return len(dim) return 0 return map(lambda d: dimlen(d), var.dimensions) def write(self, name, data, dims=None, info=False): """Writes a variable to file. | - name variable in file | - data the data to be saved to file | - info show details. | WARNING: the name of dimensions(dims) may not | match the data well. so the informations about | it may be misleading. | - dims= None [ list of strings | tuple of strings | string] | the name of dimensions. | making guesses for the dimensions by default | i.e. for different size of data shape, guessing | the dimensions are | defdims = [(), | ('x',), | ('x','y'), or 'xy' | ('x','y','z'), or 'xyz' | ('t','x','y','z')] or 'txyz' """ if not self.writeable: raise Exception("File not writeable. Open with write=True keyword") s = np.shape(data) # Get the variable type t = type(data).__name__ if t == 'NoneType': print "DataFile: None passed as data to write. Ignoring" return if t == 'ndarray': # Numpy type. Get the data type t = data.dtype.str if t == 'list': # List -> convert to numpy array data = np.array(data) t = data.dtype.str if (t == 'int') or (t == '<i8'): # NetCDF 3 does not support type int64 data = np.int32(data) t = data.dtype.str try: # See if the variable already exists var = self.handle.variables[name] # Check the shape of the variable if var.shape != s: print "Datafile: Variable already exists " \ + "with different size: " + name raise except: # Not found, so add. def find_dim(dim): # Find a dimension with given name and size size, name = dim # See if it exists already try: d = self.handle.dimensions[name] # Check if it's the correct size if type(d).__name__ == 'int': if d == size: return name else: if len(d) == size: return name # Find another with the correct size for dn, d in self.handle.dimensions.iteritems(): # Some implementations need len(d) here, # some just d if type(d).__name__ == 'int': if d == size: return dn else: if len(d) == size: return dn # None found, so create a new one i = 2 while True: dn = name + str(i) try: d = self.handle.dimensions[dn] # Already exists, so keep going except KeyError: # Not found. Create if info: print "Defining dimension " \ + dn + " of size %d" % size try: self.handle.createDimension(dn, size) except AttributeError: # Try the old-style function self.handle.create_dimension(dn, size) return dn i = i + 1 except KeyError: # Doesn't exist, so add if info: print "Defining dimension " \ + name + " of size %d" % size try: self.handle.createDimension(name, size) except AttributeError: self.handle.create_dimension(name, size) return name # Get dimensions, making guesses the names of dimensions # it's best to give the value of `dims` !!!!! defdims = [(), ('x',), # may be ('t',) and so on ('x', 'y'), ('x', 'y', 'z'), ('t', 'x', 'y', 'z')] if dims and (len(dims) >= len(s)): if info: print "dimensions' names(given): ", dims else: dims = defdims[len(s)] if info: print "dimensions' names(guess): ", dims # List of (size, 'name') tuples dlist = zip(s, dims) # Get new list of variables, and turn into a tuple dims = tuple(map(find_dim, dlist)) # Create the variable if library == "Scientific": if t == 'int': tc = Int else: tc = Float var = self.handle.createVariable(name, tc, dims) elif library == "scipy": try: # New style functions var = self.handle.createVariable(name, t, dims) except AttributeError: # Old style functions var = self.handle.create_variable(name, t, dims) else: var = self.handle.createVariable(name, t, dims) if var is None: raise Exception("Couldn't create variable") # Write the data try: # Some libraries allow this for arrays var.assignValue(data) except: # And some others only this var[:] = data