1
+ #!/usr/bin/env python3
2
+ """
3
+ Routines to read Armadillo objects
4
+ ==================================
5
+
6
+
7
+ This is just a couple of functions which will read Armadillo exports into Python.
8
+ Directly exporting Armadillo cubes can be very useful during development and/or
9
+ debugging, but is not suitable for production runs.
10
+
11
+ To export an Armadillo cube from Aether:
12
+ (substitute the cube name & file name. This does support multiple processors)
13
+
14
+ grid.geoLon_scgc.save("geoLon_" + tostr(iProc, 3) + ".txt", arma_ascii));
15
+
16
+ Notes:
17
+ - tostr() is defined in src/tools.cpp of Aether & zero-pads an int to return a str.
18
+ - This will rewrite the existing file each time it is called.
19
+ - Output is to the same directory the executable is called from.
20
+ - This uses the arma_ascii format, which is way less efficient than HDF5 or binary.
21
+ I have found this format is the easiest to work with, but your mileage may vary.
22
+ - The several python armadillo implementations look abandoned and/or did not work for me.
23
+ - See the armadillo documentation for more information on saving cubes or other data types:
24
+ https://arma.sourceforge.net/docs.html#save_load_mat
25
+
26
+ """
27
+
28
+ import numpy as np
29
+ from glob import glob
30
+ import os , errno
31
+
32
+ def check_file_inputs (files ):
33
+ """ Make sorted list of files (that exist) from a str or list
34
+
35
+ Inputs
36
+ ------
37
+ files (str or list) Can be list of files, single file, directory, or a pattern to glob
38
+
39
+ Returns
40
+ -------
41
+ list: sorted list of files that so indeed exist
42
+
43
+ """
44
+ print (type (files ))
45
+ if isinstance (files , str ): # Probably need to glob
46
+ print (type (files ))
47
+ if "*" in files : # Definitely need to glob
48
+ files2read = np .sort (glob (files ))
49
+ elif os .path .isfile (files ):
50
+ files2read = [files ] # Single file needs to be made into list
51
+ elif os .path .isdir (files ):
52
+ # We were given a directory. Read all .txt files without log in name
53
+ files_ = np .sort (glob (os .path .join (files , "*.txt" )))
54
+ files2read = [f for f in files_ if "log" not in f ]
55
+ else : # pretty error message from stack overflow
56
+ raise FileNotFoundError (errno .ENOENT , os .strerror (errno .ENOENT ), * files )
57
+
58
+ if len (files2read ) == 0 :
59
+ raise ValueError (
60
+ f"Could not find any armadillo cubes from '{ files } '."
61
+ " Check path or provide files.\n " )
62
+
63
+ return files2read
64
+
65
+ # Sort list & check if all files exist. error if not.
66
+ try : # attempt to handle anything listlike (np arrays, dict keys, etc.)
67
+ files2read = [f for f in np .sort (files ) if os .path .isfile (f )]
68
+ if len (files2read ) != len (files ):
69
+ bad_files = [f for f in files if f not in files2read ]
70
+ raise FileNotFoundError (errno .ENOENT , os .strerror (errno .ENOENT ), * bad_files )
71
+
72
+ except : # Not expected types
73
+ raise TypeError ("Need list or str. Could not handle type: " + type (files ))
74
+
75
+ return files2read
76
+
77
+
78
+ def cube2np (files2read ):
79
+ """ Read armadillo cubes from .txt files, automatically globs and/or input.
80
+ return np array of shape (nFiles, n_x, n_y, n_x)
81
+
82
+ Inputs
83
+ ------
84
+ files (str or list-like): either path to files or list of files. If it's a str,
85
+ the pattern is globbed & sorted, or the directory's .txt files are sorted.
86
+ If it's list-like, the list is sorted.
87
+
88
+ Outputs
89
+ -------
90
+ np.array of shape (nFiles, n_x, n_y, n_z) & dtype float. If we are only reading
91
+ one file, return shape is just (n_x, n_y, n_z)
92
+
93
+ Usage
94
+ -----
95
+
96
+ lons = cube2np("../run/geolon_*.txt")
97
+ lons = cube2np(np.sort(glob.glob("../run/geolon_*.txt")))
98
+
99
+ """
100
+
101
+ # Sanitize input
102
+ files2read = check_file_inputs (files2read )
103
+
104
+ out = [] # output holder
105
+ for thisf in files2read :
106
+ with open (thisf , 'r' ) as f :
107
+ _ = f .readline () # first line is a header, not needed
108
+ shape = f .readline ().strip () # next line holds the shape of the cube
109
+ shape = shape .split (' ' )
110
+ if len (shape ) != 3 :
111
+ raise ValueError (
112
+ f"File ({ thisf } ) does not appear to be an armadillo cube.\n "
113
+ f"Found shape: { shape } " )
114
+ shape = np .array (shape , dtype = int ) # convert shape to np array of int's
115
+ ls = np .zeros (shape ) # holder for this file's outputs, dtype is float
116
+ for i in range (int (shape [0 ])): # n_x
117
+ for j in range (int (shape [2 ])): # n_z
118
+ # each line is n_y long. Convert it to a python list & retain it
119
+ l = f .readline ().strip ().replace (' ' ,',' ).split (',' )
120
+ ls [i ,:, j ] = l # n_y
121
+ out .append (ls ) # speed not a huge issue, work with lists
122
+
123
+ # remove 0th dimension if we only are reading one file
124
+ if len (files2read ) == 1 :
125
+ out = out [0 ]
126
+
127
+ return np .array (out )
0 commit comments