source: issm/trunk/src/m/contrib/musselman/read_netCDF.m@ 27875

Last change on this file since 27875 was 27875, checked in by musselman, 20 months ago

Added support in matlab read/write_netCDF files for multidimensional structure arrays.

File size: 11.9 KB
Line 
1%{
2Given a NetCDF4 file, this set of functions will perform the following:
3 1. Enter each group of the file.
4 2. For each variable in each group, update an empty model with the variable's data
5 3. Enter nested groups and repeat
6
7
8If the model you saved has subclass instances that are not in the standard model() class
9you can:
10 1. Copy lines 30-35, set the "results" string to the name of the subclass instance,
11 2. Copy and modify the make_results_subclasses() function to create the new subclass
12 instances you need.
13From there, the rest of this script will automatically create the new subclass
14instance in the model you're writing to and store the data from the netcdf file there.
15%}
16
17
18function model_copy = read_netCDF(filename)
19 fprintf('NetCDF42C v1.1.14\n');
20
21 % make a model framework to fill that is in the scope of this file
22 global model_copy;
23 model_copy = model();
24
25 % Check if path exists
26 if exist(filename, 'file')
27 fprintf('Opening %s for reading\n', filename);
28
29 % Open the given netCDF4 file
30 global NCData;
31 NCData = netcdf.open(filename, 'NOWRITE');
32 % Remove masks from netCDF data for easy conversion: NOT WORKING
33 %netcdf.setMask(NCData, 'NC_NOFILL');
34
35 % see if results is in there, if it is we have to instantiate some classes
36 try
37 results_group_id = netcdf.inqNcid(NCData, "results");
38 make_results_subclasses();
39 catch
40 end % 'results' group doesn't exist
41
42 % see if inversion is in there, if it is we may have to instantiate some classes
43 try
44 inversion_group_id = netcdf.inqNcid(NCData, "inversion");
45 check_inversion_class();
46 catch
47 end % 'inversion' group doesn't exist
48
49 % loop over first layer of groups in netcdf file
50 for group = netcdf.inqGrps(NCData)
51 group_id = netcdf.inqNcid(NCData, netcdf.inqGrpName(group));
52 %disp(netcdf.inqGrpNameFull(group_id))
53 % hand off first level to recursive search
54 walk_nested_groups(group_id);
55 end
56
57 % Close the netCDF file
58 netcdf.close(NCData);
59 disp('Model Successfully Copied')
60 else
61 fprintf('File %s does not exist.\n', filename);
62 end
63end
64
65
66function make_results_subclasses()
67 global model_copy;
68 global NCData;
69 resultsGroup = netcdf.inqNcid(NCData, "results");
70 variables = netcdf.inqVarIDs(resultsGroup);
71 for name = variables
72 class_instance = netcdf.inqVar(resultsGroup, name);
73 class_instance_name = convertCharsToStrings(netcdf.getVar(resultsGroup, name, 'char'));
74 model_copy.results = setfield(model_copy.results, class_instance, class_instance_name);
75 end
76 disp('Successfully recreated results struct')
77end
78
79
80function check_inversion_class()
81 % get the name of the inversion class: either inversion or m1qn3inversion or taoinversion
82 global model_copy;
83 global NCData;
84 inversionGroup = netcdf.inqNcid(NCData, "inversion");
85 varid = netcdf.inqVarID(inversionGroup, 'inversion_class_name');
86 inversion_class = convertCharsToStrings(netcdf.getVar(inversionGroup, varid,'char'));
87 if strcmp(inversion_class, 'm1qn3inversion')
88 model_copy.inversion = m1qn3inversion();
89 disp('Successfully created inversion class instance: m1qn3inversion')
90 elseif strcmp(inversion_class, 'taoinversion')
91 model_copy.inversion = taoinversion();
92 disp('Successfully created inversion class instance: taoinversion')
93 else
94 disp('No inversion class was found')
95 end
96end
97
98
99
100function walk_nested_groups(group_location_in_file)
101 global model_copy;
102 global NCData;
103 % try to find vars in current level, if it doesn't work it's because there is nothing there
104 try
105 % we search the current group level for variables by getting this struct
106 variables = netcdf.inqVarIDs(group_location_in_file);
107
108 % from the variables struct get the info related to the variables
109 for variable = variables
110 [varname, xtype, dimids, numatts] = netcdf.inqVar(group_location_in_file, variable);
111
112 % keep an eye out for nested structs:
113 if strcmp(varname, 'this_is_a_nested')
114 is_nested = true;
115 copy_nested_struct(group_location_in_file)
116 else
117 is_nested = false;
118 copy_variable_data_to_new_model(group_location_in_file,varname, xtype);
119 end
120 end
121 catch ME
122 rethrow(ME)
123 end
124
125 % try to find groups in current level, if it doesn't work it's because there is nothing there
126 try
127 % if it's not a nested struct, keep searching for subgroups
128 if isnested
129 % do nothing
130 else
131 % search for nested groups in the current level to feed back to this function
132 groups = netcdf.inqGrps(group_location_in_file);
133 if not(isempty(groups))
134 for group = groups
135 %disp('found nested group!!')
136 group_id = netcdf.inqNcid(group_location_in_file, netcdf.inqGrpName(group));
137 %disp(netcdf.inqGrpNameFull(group_id))
138 walk_nested_groups(group);
139 end
140 end
141 end
142 catch % no nested groups here
143 end
144end
145
146
147
148function copy_nested_struct(group_location_in_file)
149 global model_copy;
150 global NCData;
151 %{
152 A common multidimensional struct array is the 1xn md.results.TransientSolution struct.
153 The process to recreate is as follows:
154 1. Get the name of the struct from group variable name_of_struct
155 2. Get the fieldnames from the subgroups
156 3. Recreate the struct with fieldnames
157 4. Populate the fields with their respective values
158 %}
159
160 % step 1
161 varid = netcdf.inqVarID(group_location_in_file, 'name_of_struct');
162 name_of_struct = netcdf.getVar(group_location_in_file, varid)';
163
164 % step 2
165 subgroups = netcdf.inqGrps(group_location_in_file); % numerical cell array with ID's of subgroups
166 % get single subgroup's data
167 single_subgroup_ID = subgroups(1);
168 subgroup_varids = netcdf.inqVarIDs(single_subgroup_ID);
169 fieldnames = {};
170 for variable = subgroup_varids
171 [varname, xtype, dimids, numatts] = netcdf.inqVar(single_subgroup_ID, variable);
172 fieldnames{end+1} = varname;
173 end
174
175 % step 3
176 address_in_model = strrep(netcdf.inqGrpNameFull(group_location_in_file), '/', '');
177 % we cannot assign a variable to represent this object as MATLAB treats all variables as copies
178 % and not pointers to the same memory address
179 % this means that if address_in_model is more than 1 item, we need to modify the code. For now,
180 % we just hope this will do
181
182 model_copy.(address_in_model).(name_of_struct) = struct();
183
184 % for every fieldname in the subgroup, create an empty field
185 for fieldname = string(fieldnames)
186 model_copy.(address_in_model).(name_of_struct).(fieldname) = {};
187 end
188
189 % use repmat to make the struct array multidimensional along the fields axis
190 number_of_dimensions = numel(subgroups);
191 model_copy.(address_in_model).(name_of_struct) = repmat(model_copy.(address_in_model).(name_of_struct), 1, number_of_dimensions);
192
193 % step 4
194 % for every layer of the multidimensional struct array, populate the fields
195 for current_layer = 1:number_of_dimensions
196 % choose subgroup
197 current_layer_subgroup_ID = subgroups(current_layer);
198 % get all vars
199 current_layer_subgroup_varids = netcdf.inqVarIDs(current_layer_subgroup_ID);
200 % get individual vars and set fields at layer current_layer
201 for varid = current_layer_subgroup_varids
202 [varname, xtype, dimids, numatts] = netcdf.inqVar(current_layer_subgroup_ID, varid);
203 data = netcdf.getVar(current_layer_subgroup_ID, varid);
204
205 % netcdf uses Row Major Order but MATLAB uses Column Major Order so we need to transpose all arrays w/ more than 1 dim
206 if all(size(data)~=1) || xtype == 2
207 data = data.';
208 end
209
210 % set the field
211 model_copy.(address_in_model).(name_of_struct)(current_layer).(varname) = data;
212 %address_to_struct_in_model = setfield(address_to_struct_in_model(current_layer), varname, data)
213 end
214 model_copy.(address_in_model).(name_of_struct)(current_layer);
215 fprintf("Successfully saved layer %s to multidimension struct array\n", num2str(current_layer))
216 end
217 fprintf('Successfully recreated multidimensional structure array %s in md.%s\n', name_of_struct, address_in_model)
218end
219
220
221
222
223%{
224Since there are two types of objects that MATLAB uses (classes and structs), we have to check
225which object we're working with before we can set any fields/attributes of it. After this is completed,
226we can write the data to that location in the model.
227%}
228
229function copy_variable_data_to_new_model(group_location_in_file, varname, xtype)
230 global model_copy;
231 global NCData;
232 %disp(varname)
233 % this is an inversion band-aid
234 if strcmp(varname, 'inversion_class_name') || strcmp(varname, 'name_of_struct') || strcmp(varname, 'solution')
235 % we don't need this
236 else
237 % putting try/catch here so that any errors generated while copying data are logged and not lost by the try/catch in walk_nested_groups function
238 try
239 %disp(netcdf.inqGrpNameFull(group_location_in_file))
240 %disp(class(netcdf.inqGrpNameFull(group_location_in_file)))
241 address_to_attr = strrep(netcdf.inqGrpNameFull(group_location_in_file), '/', '.');
242 varid = netcdf.inqVarID(group_location_in_file, varname);
243 data = netcdf.getVar(group_location_in_file, varid);
244
245
246 % if we have an empty string
247 if xtype == 2 && isempty(all(data))
248 data = cell(char());
249 % if we have an empty cell-char array
250 elseif numel(data) == 1 && xtype == 3 && data == -32767
251 data = cell(char());
252 end
253 % band-aid for some cell-char-arrays:
254 if xtype == 2 && strcmp(data, 'default')
255 data = {'default'};
256 end
257
258 % netcdf uses Row Major Order but MATLAB uses Column Major Order so we need to transpose all arrays w/ more than 1 dim
259 if all(size(data)~=1) || xtype == 2
260 data = data.';
261 end
262
263 % if we have a list of strings
264 if xtype == 2
265 try
266 if strcmp(netcdf.getAtt(group, varid, "type_is"), 'cell_array_of_strings')
267 data = cellstr(data)
268 end
269 catch
270 % no attr found so we pass
271 end
272 end
273
274 % the issm c compiler does not work with int64 datatypes, so we need to convert those to int16
275 % reference this (very hard to find) link for netcdf4 datatypes: https://docs.unidata.ucar.edu/netcdf-c/current/netcdf_8h_source.html
276 %xtype
277 if xtype == 10
278 arg_to_eval = ['model_copy', address_to_attr, '.', varname, ' = ' , 'double(data);'];
279 eval(arg_to_eval);
280 %disp('saved int64 as int16')
281 else
282 arg_to_eval = ['model_copy', address_to_attr, '.', varname, ' = ' , 'data;'];
283 eval(arg_to_eval);
284 end
285
286 full_addy = netcdf.inqGrpNameFull(group_location_in_file);
287 %disp(xtype)
288 %class(data)
289 fprintf('Successfully saved %s to %s\n', varname, full_addy);
290 catch e %e is an MException struct
291 fprintf(1,'There was an error with %s! \n', varname)
292 fprintf('The message was:\n%s\n',e.message);
293 fprintf(1,'The identifier was:\n%s\n',e.identifier);
294 disp()
295 % more error handling...
296 end
297 end
298end
299
300
301
Note: See TracBrowser for help on using the repository browser.