diff --git a/boxmox/data.py b/boxmox/data.py index 46a7f879aed3018824c5540e5a09697381cb29be..45c744f38890af382e7f0583ddf84a109ada88a6 100644 --- a/boxmox/data.py +++ b/boxmox/data.py @@ -3,18 +3,36 @@ import sys import shutil import StringIO import csv -import warnings import numpy as np +def _mygenfromtxt2(f): + curpos = f.tell() + try: + dialect = csv.Sniffer().sniff(f.read(1048576), delimiters=";, ") + dialect.skipinitialspace = True + f.seek(curpos) + spamreader = csv.reader(f, dialect) + except: + # could not determine dialect, falling back to default. + f.seek(curpos) + spamreader = csv.reader(f, skipinitialspace = True, delimiter=" ") + # twice as fast as np.genfromtxt(..., names=True) + hdr = spamreader.next() + dat = [] + for row in spamreader: + dat.append( tuple(map(float, row)) ) + return dat, hdr + def _mygenfromtxt(f): curpos = f.tell() try: dialect = csv.Sniffer().sniff(f.read(1048576), delimiters=";, ") + dialect.skipinitialspace = True f.seek(curpos) spamreader = csv.reader(f, dialect) except: - warnings.warn('Could not determine dialect, falling back to default.') + # could not determine dialect, falling back to default. f.seek(curpos) spamreader = csv.reader(f, skipinitialspace = True, delimiter=" ") # twice as fast as np.genfromtxt(..., names=True) @@ -24,9 +42,132 @@ def _mygenfromtxt(f): dat.append( tuple(map(float, row)) ) return np.array(dat, dtype=[(_, float) for _ in hdr]) -class InputFile: +def InputFile(fpath=None, version=1.0): + if not fpath is None: + # file format discovery: 3 lines with numbers ==> 1.7 + with open(fpath, 'rb') as f: + one = f.readline() + two = f.readline() + tre = f.readline() + try: + test = int(tre) + version = 1.7 + except: + version = 1.0 + pass + + if version >= 1.7: + return InputFile17(fpath=fpath, version=version) + else: + return InputFileOrig(fpath=fpath) + +class InputFile17: ''' - A generic BOXMOX input file. Getting and setting of values + A generic BOXMOX input file (>= v 1.7). Getting and setting of values + works like a dictionary:: + + print(inp['O3']) + inp['O3'] = 0.040 + + ''' + @property + def nvar(self): + ''' + Number of variables. + ''' + return len( [ x for x in self.keys() ] ) + @property + def nanc(self): + ''' + Number of ancillary variables. + ''' + return len( [ x for x in self.anc.keys() ] ) + def __getitem__(self, item): + return [ self.__data[i] for i in item ] if isinstance(item, list) else self.__data[item] + def __setitem__(self, item, values): + if isinstance(item, list): + for i, v in zip(item, values): + self.__data[i] = v + else: + self.__data[item] = values + def keys(self): + return self.__data.keys() + + def read(self, fpath): + ''' + Read input file from path. + ''' + with open(fpath, 'rb') as f: + nvar = int(f.readline().replace(',', '')) + nanc = int(f.readline().replace(',', '')) + self.timeFormat = int(f.readline().replace(',', '')) + dmp, hdr = _mygenfromtxt2(f) + + ntime = 0 + if not self.timeFormat == 0: + self.timeVar = hdr[0] + self.time = [ x[0] for x in dmp ] + ntime = 1 + self.anc = { hdr[i]: [ x[i] for x in dmp ] for i in range(ntime, ntime+nanc) } + self.__data = { hdr[i]: [ x[i] for x in dmp ] for i in range(ntime+nanc, len(dmp[0])) } + + def __str__(self): + f = StringIO.StringIO() + self.write(f) + return(f.getvalue()) + + def write(self, f=sys.stdout, version=1.7): + ''' + Write to <f>. <f> can be file handle or other connection. Defaults to sys.stdout. + ''' + # possibility to create old version output from new version data + if version >= 1.7: + f.write('{0:1d}'.format(self.nvar) +'\n') + f.write('{0:1d}'.format(self.nanc) +'\n') + else: + f.write('{0:1d}'.format(self.nanc+self.nvar) +'\n') + f.write('{0:1d}'.format(self.timeFormat) +'\n') + + data_names = [ key for key in self.keys() ] + anc_names = [ key for key in self.anc.keys() ] + hdr_line = '{0:s}\n' .format(' '.join(anc_names + data_names)) + if str(self.timeFormat) != "0" : + hdr_line = self.timeVar + ' ' + hdr_line + f.write(hdr_line) + + if isinstance(self.time, list): + for itime, xtime in enumerate(self.time): + line = [ xtime ] + [ self.anc[key][itime] for key in anc_names ] + [ self.__data[key][itime] for key in data_names ] + f.write(' '.join('{0:e}'.format(x) for x in line) + '\n') + else: + data_line = ' '.join( '{0:e}'.format(float(self.anc[x])) for x in self.anc.keys() ) + ' ' + ' '.join( '{0:e}'.format(float(self.__data[x])) for x in self.keys() ) + '\n' + if not self.time is None: + data_line = '{0:e}'.format(float(self.time)) + ' ' + data_line + f.write(data_line) + + def __init__(self, fpath=None, version=1.7): + #: Time format (0: constant, 1: seconds since start, 2: hour of diurnal cycle) + self.timeFormat = 0 + self.timeVar = 'time' + self.time = None + + self.anc = {} + + self.__data = {} + + self.version = version + + #: File path of the underlying file (if it exists (yet)) + self.fpath = fpath + if not self.fpath is None: + try: + self.read(self.fpath) + except Exception as e: + print("Reading input file {:s} failed: {:s}".format(self.fpath, str(e))) + +class InputFileOrig: + ''' + A generic BOXMOX input file (< 1.7). Getting and setting of values works like a dictionary:: print(inp['O3']) @@ -49,6 +190,7 @@ class InputFile: self.__data[item] = values def keys(self): return self.__data.keys() + def read(self, fpath): ''' Read input file from path. @@ -67,11 +209,13 @@ class InputFile: self.write(f) return(f.getvalue()) - def write(self, f=sys.stdout): + def write(self, f=sys.stdout, version=1.0): ''' Write to <f>. <f> can be file handle or other connection. Defaults to sys.stdout. ''' f.write('{0:1d}'.format(self.nvar) +'\n') + if version >= 1.7: + f.write('{0:1d}'.format(0) +'\n') f.write('{0:1d}'.format(self.timeFormat) +'\n') column_names = [ key for key in self.keys() if key != self.__timeVar ] @@ -91,6 +235,8 @@ class InputFile: self.__timeVar = 'time' self.__data = {} + self.version = 0.0 + #: File path of the underlying file (if it exists (yet)) self.fpath = fpath if not self.fpath is None: diff --git a/boxmox/experiment.py b/boxmox/experiment.py index 48a3a815e22784564075da6f114a1cce32712aa7..0f8a89e0ebbd543e427f6dd3cd874d8862881b7c 100644 --- a/boxmox/experiment.py +++ b/boxmox/experiment.py @@ -113,6 +113,25 @@ class Experiment: #: Namelist self.namelist = Namelist(os.path.join(self.path, 'BOXMOX.nml')) + self.version = self._determineVersion(self.path) + + def _determineVersion(self, path): + version = 1.0 + versionFile = os.path.join(path, 'VERSION') + if os.path.exists(versionFile): + try: + with open(versionFile, 'rb') as f: + line = f.readline().rstrip() + line = ".".join(line.split(".")[0:min(2, len(line.split(".")))]) + # for development only: + if line == "__BOXMOX_VERSION__": + line = 1.7 + version = float(line) + except: + version = 1.0 + pass + return version + def _populateInput(self): inputFileNames = [f for f in os.listdir(self.path) if f.endswith('.csv')] self.input = { x.replace('.csv', '') : InputFile(os.path.join(self.path, x)) for x in inputFileNames } @@ -120,7 +139,6 @@ class Experiment: def __new(self): try: s.call(["new_BOXMOX_experiment", self.mechanism, self.path]) -# s.check_output(["new_BOXMOX_experiment", "-f", self.mechanism, self.path], stderr=s.STDOUT) self._populateInput() except s.CalledProcessError as e: @@ -172,7 +190,7 @@ class Experiment: for type in self.input: with open( os.path.join(self.path, type + '.csv'), 'wb') as f: - self.input[type].write(f) + self.input[type].write(f, version=self.version) pwd = os.getcwd() os.chdir(self.path)