| 1 | import numpy as np
|
|---|
| 2 | import struct
|
|---|
| 3 | from os import path
|
|---|
| 4 | from collections import OrderedDict
|
|---|
| 5 |
|
|---|
| 6 |
|
|---|
| 7 | def archwrite(filename, *args): # {{{
|
|---|
| 8 | """
|
|---|
| 9 | ARCHWRITE - Write data to a field, given the file name, field name, and data.
|
|---|
| 10 | Usage:
|
|---|
| 11 | archwrite('archive101.arch', 'variable_name', data)
|
|---|
| 12 | """
|
|---|
| 13 |
|
|---|
| 14 | nargs = len(args)
|
|---|
| 15 | if nargs % 2 != 0:
|
|---|
| 16 | raise ValueError('Incorrect number of arguments.')
|
|---|
| 17 | # open file
|
|---|
| 18 | try:
|
|---|
| 19 | if not path.isfile(filename):
|
|---|
| 20 | fid = open(filename, 'wb')
|
|---|
| 21 | else:
|
|---|
| 22 | fid = open(filename, 'ab')
|
|---|
| 23 | except IOError as e:
|
|---|
| 24 | raise IOError("archwrite error: could not open '{}' to write to due to:".format(filename), e)
|
|---|
| 25 | nfields = len(args) / 2
|
|---|
| 26 | # generate data to write
|
|---|
| 27 | for i in range(int(nfields)):
|
|---|
| 28 | # write field name
|
|---|
| 29 | name = args[2 * i]
|
|---|
| 30 | write_field_name(fid, name)
|
|---|
| 31 | # write data associated with field name
|
|---|
| 32 | data = args[2 * i + 1]
|
|---|
| 33 | code = format_archive_code(data)
|
|---|
| 34 | if code == 1:
|
|---|
| 35 | raise ValueError("archwrite : error writing data, string should not be written as field data")
|
|---|
| 36 | elif code == 2:
|
|---|
| 37 | write_scalar(fid, data)
|
|---|
| 38 | elif code == 3:
|
|---|
| 39 | write_vector(fid, data)
|
|---|
| 40 | else:
|
|---|
| 41 | raise ValueError("archwrite : error writing data, invalid code entered '{}'".format(code))
|
|---|
| 42 | fid.close()
|
|---|
| 43 | # }}}
|
|---|
| 44 |
|
|---|
| 45 |
|
|---|
| 46 | def archread(filename, fieldname): # {{{
|
|---|
| 47 | """
|
|---|
| 48 | ARCHREAD - Given an arch file name, and a field name, find and return the data
|
|---|
| 49 | associated with that field name.
|
|---|
| 50 | Usage:
|
|---|
| 51 | archread('archive101.arch', 'field_var_1')
|
|---|
| 52 | """
|
|---|
| 53 | try:
|
|---|
| 54 | if path.isfile(filename):
|
|---|
| 55 | fid = open(filename, 'rb')
|
|---|
| 56 | else:
|
|---|
| 57 | raise IOError("archread error : file '{}' does not exist".format(filename))
|
|---|
| 58 | except IOError as e:
|
|---|
| 59 | raise IOError("archread error : could not open file '{}' to read from due to :".format(filename), e)
|
|---|
| 60 |
|
|---|
| 61 | archive_results = []
|
|---|
| 62 | # read first result
|
|---|
| 63 | result = read_field(fid)
|
|---|
| 64 | while result:
|
|---|
| 65 | if fieldname == result['field_name']:
|
|---|
| 66 | # found the data we wanted
|
|---|
| 67 | archive_results = result['data'] # we only want the data
|
|---|
| 68 | break
|
|---|
| 69 | # read next result
|
|---|
| 70 | result = read_field(fid)
|
|---|
| 71 | # close file
|
|---|
| 72 | fid.close()
|
|---|
| 73 | return archive_results
|
|---|
| 74 | # }}}
|
|---|
| 75 |
|
|---|
| 76 |
|
|---|
| 77 | def archdisp(filename): # {{{
|
|---|
| 78 | """
|
|---|
| 79 | ARCHDISP - Given an arch filename, display the contents of that file
|
|---|
| 80 |
|
|---|
| 81 | Usage:
|
|---|
| 82 | archdisp('archive101.arch')
|
|---|
| 83 | """
|
|---|
| 84 | try:
|
|---|
| 85 | if path.isfile(filename):
|
|---|
| 86 | fid = open(filename, 'rb')
|
|---|
| 87 | else:
|
|---|
| 88 | raise IOError("archread error : file '{}' does not exist".format(filename))
|
|---|
| 89 | except IOError as e:
|
|---|
| 90 | raise IOError("archread error : could not open file '{}' to read from due to ".format(filename), e)
|
|---|
| 91 | print('Source file: ')
|
|---|
| 92 | print(('\t{0}'.format(filename)))
|
|---|
| 93 | print('Variables: ')
|
|---|
| 94 | result = read_field(fid)
|
|---|
| 95 | while result:
|
|---|
| 96 | print(('\t{0}'.format(result['field_name'])))
|
|---|
| 97 | print(('\t\tSize:\t\t{0}'.format(result['size'])))
|
|---|
| 98 | print(('\t\tDatatype:\t{0}'.format(result['data_type'])))
|
|---|
| 99 | # go to next result
|
|---|
| 100 | result = read_field(fid)
|
|---|
| 101 | # close file
|
|---|
| 102 | fid.close()
|
|---|
| 103 | # }}}
|
|---|
| 104 |
|
|---|
| 105 |
|
|---|
| 106 | # Helper functions
|
|---|
| 107 | def write_field_name(fid, data): # {{{
|
|---|
| 108 | """
|
|---|
| 109 | Routine to write field name (variable name) to an archive file.
|
|---|
| 110 | """
|
|---|
| 111 | # write the length of the record
|
|---|
| 112 | # length to write + string size (len) + format code
|
|---|
| 113 | reclen = len(data) + 4 + 4
|
|---|
| 114 | fid.write(struct.pack('>i', reclen))
|
|---|
| 115 | # write format code
|
|---|
| 116 | code = format_archive_code(data)
|
|---|
| 117 | if code != 1:
|
|---|
| 118 | raise TypeError("archwrite : error writing field name, expected string, but got %s" % type(data))
|
|---|
| 119 | fid.write(struct.pack('>i', 1))
|
|---|
| 120 | # write string length, and then the string
|
|---|
| 121 | fid.write(struct.pack('>i', len(data)))
|
|---|
| 122 | fid.write(struct.pack('>{}s'.format(len(data)), data.encode('utf8')))
|
|---|
| 123 | # }}}
|
|---|
| 124 |
|
|---|
| 125 |
|
|---|
| 126 | def write_scalar(fid, data): # {{{
|
|---|
| 127 | """
|
|---|
| 128 | Procedure to write a double to an arch file pointed to by fid
|
|---|
| 129 | """
|
|---|
| 130 | # write length of record
|
|---|
| 131 | # double (8 bytes) + format code (4 bytes)
|
|---|
| 132 | reclen = 8 + 4
|
|---|
| 133 | fid.write(struct.pack('>i', reclen))
|
|---|
| 134 |
|
|---|
| 135 | # write the format code (2 for scalar)
|
|---|
| 136 | fid.write(struct.pack('>i', 2))
|
|---|
| 137 | # write the double
|
|---|
| 138 | fid.write(struct.pack('>d', data))
|
|---|
| 139 | # }}}
|
|---|
| 140 |
|
|---|
| 141 |
|
|---|
| 142 | def write_vector(fid, data): # {{{
|
|---|
| 143 | """
|
|---|
| 144 | Procedure to write a np.array to an arch file
|
|---|
| 145 | """
|
|---|
| 146 | # Make sure our vector is the correct shape.
|
|---|
| 147 | # Reshape it into a row vector if it is not correct.
|
|---|
| 148 | if isinstance(data, (bool, int, float)):
|
|---|
| 149 | data = np.array([data])
|
|---|
| 150 | elif isinstance(data, (list, tuple)):
|
|---|
| 151 | data = np.array(data).reshape(-1, )
|
|---|
| 152 | if np.ndim(data) == 1:
|
|---|
| 153 | if np.size(data):
|
|---|
| 154 | data = data.reshape(np.size(data), )
|
|---|
| 155 | else:
|
|---|
| 156 | data = data.reshape(0, 0)
|
|---|
| 157 | # get size of data
|
|---|
| 158 | sz = data.shape
|
|---|
| 159 | # write length of record
|
|---|
| 160 | # format code + row size + col size + (double size * row amt * col amt)
|
|---|
| 161 | reclen = 4 + 4 + 4 + 8 * sz[0] * sz[1]
|
|---|
| 162 | # make sure we can fit data into file
|
|---|
| 163 | if reclen > 2**31:
|
|---|
| 164 | raise ValueError("archwrite error : can not write vector to binary file because it is too large")
|
|---|
| 165 | fid.write(struct.pack('>i', reclen))
|
|---|
| 166 | # write format code
|
|---|
| 167 | fid.write(struct.pack('>i', 3))
|
|---|
| 168 | # write vector
|
|---|
| 169 | fid.write(struct.pack('>i', sz[0]))
|
|---|
| 170 | fid.write(struct.pack('>i', sz[1]))
|
|---|
| 171 | for i in range(sz[0]):
|
|---|
| 172 | for j in range(sz[1]):
|
|---|
| 173 | fid.write(struct.pack('>d', float(data[i][j])))
|
|---|
| 174 | # }}}
|
|---|
| 175 |
|
|---|
| 176 |
|
|---|
| 177 | def read_field(fid): # {{{
|
|---|
| 178 | """
|
|---|
| 179 | Procedure to read a field and return a results list with the following attributes:
|
|---|
| 180 | result['field_name'] -> the name of the variable that was just read
|
|---|
| 181 | result['size'] -> size (dimensions) of the variable just read
|
|---|
| 182 | result['data_type'] -> the type of data that was just read
|
|---|
| 183 | result['data'] -> the actual data
|
|---|
| 184 | """
|
|---|
| 185 |
|
|---|
| 186 | try:
|
|---|
| 187 | # first, read the string
|
|---|
| 188 | #first read the size and continue reading
|
|---|
| 189 | struct.unpack('>i', fid.read(struct.calcsize('>i')))[0] #name length
|
|---|
| 190 | check_name = struct.unpack('>i', fid.read(struct.calcsize('>i')))[0]
|
|---|
| 191 | if check_name != 1:
|
|---|
| 192 | raise ValueError('archread error : a string was not present at the start of the arch file')
|
|---|
| 193 | namelen = struct.unpack('>i', fid.read(struct.calcsize('>i')))[0]
|
|---|
| 194 | fieldname = struct.unpack('>{}s'.format(namelen), fid.read(namelen))[0]
|
|---|
| 195 | # then, read the data
|
|---|
| 196 | #first read the size and continue reading
|
|---|
| 197 | struct.unpack('>i', fid.read(struct.calcsize('>i')))[0] #data length
|
|---|
| 198 | data_type = struct.unpack('>i', fid.read(struct.calcsize('>i')))[0]
|
|---|
| 199 |
|
|---|
| 200 | if data_type == 2:
|
|---|
| 201 | # struct.upack scalar
|
|---|
| 202 | data = struct.unpack('>d', fid.read(struct.calcsize('>d')))[0]
|
|---|
| 203 | elif data_type == 3:
|
|---|
| 204 | rows = struct.unpack('>i', fid.read(struct.calcsize('>i')))[0]
|
|---|
| 205 | cols = struct.unpack('>i', fid.read(struct.calcsize('>i')))[0]
|
|---|
| 206 | raw_data = np.zeros(shape=(rows, cols), dtype=float)
|
|---|
| 207 | for i in range(rows):
|
|---|
| 208 | raw_data[i, :] = struct.unpack('>{}d'.format(cols), fid.read(cols * struct.calcsize('>d')))
|
|---|
| 209 | # The matrix will be struct.upacked in order and will be filled left -> right by column
|
|---|
| 210 | # We need to reshape and transpose the matrix so it can be read correctly
|
|---|
| 211 | data = raw_data.reshape(raw_data.shape[::-1]).T
|
|---|
| 212 | else:
|
|---|
| 213 | raise TypeError("Cannot read data type {}".format(data_type))
|
|---|
| 214 |
|
|---|
| 215 | # give additional data to user
|
|---|
| 216 | if data_type == 2:
|
|---|
| 217 | data_size = '1x1'
|
|---|
| 218 | data_type_str = 'double'
|
|---|
| 219 | elif data_type == 3:
|
|---|
| 220 | data_size = '{0}x{1}'.format(rows, cols)
|
|---|
| 221 | data_type_str = 'vector/matrix'
|
|---|
| 222 |
|
|---|
| 223 | result = OrderedDict()
|
|---|
| 224 | result['field_name'] = fieldname.decode('utf8')
|
|---|
| 225 | result['size'] = data_size
|
|---|
| 226 | result['data_type'] = data_type_str
|
|---|
| 227 | result['data'] = data
|
|---|
| 228 |
|
|---|
| 229 | except struct.error as e:
|
|---|
| 230 | result = None
|
|---|
| 231 | print("result is empty due to", e)
|
|---|
| 232 |
|
|---|
| 233 | return result
|
|---|
| 234 | # }}}
|
|---|
| 235 |
|
|---|
| 236 |
|
|---|
| 237 | def format_archive_code(format): # {{{
|
|---|
| 238 | """
|
|---|
| 239 | Given a variable, determine it's type and return
|
|---|
| 240 | an integer value:
|
|---|
| 241 |
|
|---|
| 242 | 1 : string
|
|---|
| 243 | 2 : double (scalar)
|
|---|
| 244 | 3 : vector or matrix (of type double)
|
|---|
| 245 |
|
|---|
| 246 | """
|
|---|
| 247 | if isinstance(format, str):
|
|---|
| 248 | code = 1
|
|---|
| 249 | elif format.shape[0] == 1 and format.shape[1] == 1:
|
|---|
| 250 | code = 2
|
|---|
| 251 | elif isinstance(format, (list, tuple, np.ndarray)):
|
|---|
| 252 | code = 3
|
|---|
| 253 | else:
|
|---|
| 254 | raise TypeError("archwrite error: data type '%s' is not valid." % type(format))
|
|---|
| 255 | return code
|
|---|
| 256 | # }}}
|
|---|