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

Last change on this file since 27877 was 27877, checked in by musselman, 19 months ago

Streamlined some code in both scripts. Added functionality for multiple struct fields under the results subclass.

File size: 12.5 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_names_raw = netcdf.getVar(resultsGroup, name, 'char').';
74 class_instance_names = cellstr(class_instance_names_raw);
75 for index = 1:numel(class_instance_names)
76 class_instance_name = class_instance_names{index};
77 model_copy.results = setfield(model_copy.results, class_instance_name, struct());
78 end
79 %model_copy.results = setfield(model_copy.results, class_instance, class_instance_name);
80 end
81 disp('Successfully recreated results structs:')
82 for fieldname = string(fieldnames(model_copy.results))
83 disp(fieldname)
84 end
85end
86
87
88function check_inversion_class()
89 % get the name of the inversion class: either inversion or m1qn3inversion or taoinversion
90 global model_copy;
91 global NCData;
92 inversionGroup = netcdf.inqNcid(NCData, "inversion");
93 varid = netcdf.inqVarID(inversionGroup, 'inversion_class_name');
94 inversion_class = convertCharsToStrings(netcdf.getVar(inversionGroup, varid,'char'));
95 if strcmp(inversion_class, 'm1qn3inversion')
96 model_copy.inversion = m1qn3inversion();
97 disp('Successfully created inversion class instance: m1qn3inversion')
98 elseif strcmp(inversion_class, 'taoinversion')
99 model_copy.inversion = taoinversion();
100 disp('Successfully created inversion class instance: taoinversion')
101 else
102 disp('No inversion class was found')
103 end
104end
105
106
107
108function walk_nested_groups(group_location_in_file)
109 global model_copy;
110 global NCData;
111 % we search the current group level for variables by getting this struct
112 variables = netcdf.inqVarIDs(group_location_in_file);
113
114 % from the variables struct get the info related to the variables
115 for variable = variables
116 [varname, xtype, dimids, numatts] = netcdf.inqVar(group_location_in_file, variable);
117
118 % keep an eye out for nested structs:
119 if strcmp(varname, 'this_is_a_nested')
120 is_nested = true;
121 copy_nested_struct(group_location_in_file)
122 elseif strcmp(varname, 'solution')
123 % band-aid pass..
124 else
125 copy_variable_data_to_new_model(group_location_in_file, varname, xtype);
126 end
127 end
128
129 % try to find groups in current level, if it doesn't work it's because there is nothing there
130 %try
131 % if it's a nested struct the function copy_nested_struct has already been called
132 if logical(exist('is_nested', 'var'))
133 % do nothing
134 else
135 % search for nested groups in the current level to feed back to this function
136 groups = netcdf.inqGrps(group_location_in_file);
137 if not(isempty(groups))
138 for group = groups
139 group_id = netcdf.inqNcid(group_location_in_file, netcdf.inqGrpName(group));
140 %disp(netcdf.inqGrpNameFull(group_id))
141 walk_nested_groups(group);
142 end
143 end
144 end
145 %catch % no nested groups here
146 %end
147end
148
149
150
151function copy_nested_struct(group_location_in_file)
152 global model_copy;
153 global NCData;
154 %{
155 A common multidimensional struct array is the 1xn md.results.TransientSolution struct.
156 The process to recreate is as follows:
157 1. Get the name of the struct from group name
158 2. Get the fieldnames from the subgroups
159 3. Recreate the struct with fieldnames
160 4. Populate the fields with their respective values
161 %}
162
163 % step 1
164 name_of_struct = netcdf.inqGrpName(group_location_in_file);
165
166 % step 2
167 subgroups = netcdf.inqGrps(group_location_in_file); % numerical cell array with ID's of subgroups
168 % get single subgroup's data
169 single_subgroup_ID = subgroups(1);
170 subgroup_varids = netcdf.inqVarIDs(single_subgroup_ID);
171 fieldnames = {};
172 for variable = subgroup_varids
173 [varname, xtype, dimids, numatts] = netcdf.inqVar(single_subgroup_ID, variable);
174 fieldnames{end+1} = varname;
175 end
176
177 % step 3
178 address_in_model_raw = split(netcdf.inqGrpNameFull(group_location_in_file), '/');
179 address_in_model = address_in_model_raw{2};
180
181 % we cannot assign a variable to represent this object as MATLAB treats all variables as copies
182 % and not pointers to the same memory address
183 % this means that if address_in_model has more than 1 layer, we need to modify the code. For now,
184 % we just hope this will do. An example of a no-solution would be model().abc.def.ghi.field
185
186 model_copy.(address_in_model).(name_of_struct) = struct();
187 % for every fieldname in the subgroup, create an empty field
188 for fieldname = string(fieldnames)
189 model_copy.(address_in_model).(name_of_struct).(fieldname) = {};
190 end
191
192 % use repmat to make the struct array multidimensional along the fields axis
193 number_of_dimensions = numel(subgroups);
194 model_copy.(address_in_model).(name_of_struct) = repmat(model_copy.(address_in_model).(name_of_struct), 1, number_of_dimensions);
195
196 % step 4
197 % for every layer of the multidimensional struct array, populate the fields
198 for current_layer = 1:number_of_dimensions
199 % choose subgroup
200 current_layer_subgroup_ID = subgroups(current_layer);
201 % get all vars
202 current_layer_subgroup_varids = netcdf.inqVarIDs(current_layer_subgroup_ID);
203 % get individual vars and set fields at layer current_layer
204 for varid = current_layer_subgroup_varids
205 [varname, xtype, dimids, numatts] = netcdf.inqVar(current_layer_subgroup_ID, varid);
206 data = netcdf.getVar(current_layer_subgroup_ID, varid);
207
208 % netcdf uses Row Major Order but MATLAB uses Column Major Order so we need to transpose all arrays w/ more than 1 dim
209 if all(size(data)~=1) || xtype == 2
210 data = data.';
211 end
212
213 % set the field
214 model_copy.(address_in_model).(name_of_struct)(current_layer).(varname) = data;
215 %address_to_struct_in_model = setfield(address_to_struct_in_model(current_layer), varname, data)
216 end
217 model_copy.(address_in_model).(name_of_struct)(current_layer);
218 fprintf("Successfully saved layer %s to multidimension struct array\n", num2str(current_layer))
219 end
220 fprintf('Successfully recreated multidimensional structure array %s in md.%s\n', name_of_struct, address_in_model)
221end
222
223
224
225
226%{
227Since there are two types of objects that MATLAB uses (classes and structs), we have to check
228which object we're working with before we can set any fields/attributes of it. After this is completed,
229we can write the data to that location in the model.
230%}
231
232function copy_variable_data_to_new_model(group_location_in_file, varname, xtype)
233 global model_copy;
234 global NCData;
235 %disp(varname)
236 % this is an inversion band-aid
237 if strcmp(varname, 'inversion_class_name') || strcmp(varname, 'name_of_struct') || strcmp(varname, 'solution')
238 % we don't need this
239 else
240 % 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
241 try
242 %disp(netcdf.inqGrpNameFull(group_location_in_file))
243 %disp(class(netcdf.inqGrpNameFull(group_location_in_file)))
244 address_to_attr = strrep(netcdf.inqGrpNameFull(group_location_in_file), '/', '.');
245 varid = netcdf.inqVarID(group_location_in_file, varname);
246 data = netcdf.getVar(group_location_in_file, varid);
247
248
249 % if we have an empty string
250 if xtype == 2 && isempty(all(data))
251 data = cell(char());
252 % if we have an empty cell-char array
253 elseif numel(data) == 1 && xtype == 3 && data == -32767
254 data = cell(char());
255 elseif isempty(all(data))
256 data = []
257 end
258 % band-aid for some cell-char-arrays:
259 if xtype == 2 && strcmp(data, 'default')
260 data = {'default'};
261 end
262
263 % netcdf uses Row Major Order but MATLAB uses Column Major Order so we need to transpose all arrays w/ more than 1 dim
264 if all(size(data)~=1) || xtype == 2
265 data = data.';
266 end
267
268 % if we have a list of strings
269 if xtype == 2
270 try
271 if strcmp(netcdf.getAtt(group_location_in_file, varid, "type_is"), 'cell_array_of_strings')
272 data = cellstr(data);
273 end
274 catch
275 % no attr found so we pass
276 end
277 end
278
279 % the issm c compiler does not work with int64 datatypes, so we need to convert those to int16
280 % reference this (very hard to find) link for netcdf4 datatypes: https://docs.unidata.ucar.edu/netcdf-c/current/netcdf_8h_source.html
281 %xtype
282 if xtype == 10
283 arg_to_eval = ['model_copy', address_to_attr, '.', varname, ' = ' , 'double(data);'];
284 eval(arg_to_eval);
285 %disp('saved int64 as int16')
286 else
287 arg_to_eval = ['model_copy', address_to_attr, '.', varname, ' = data;'];
288 eval(arg_to_eval);
289 end
290
291 full_addy = netcdf.inqGrpNameFull(group_location_in_file);
292 %disp(xtype)
293 %class(data)
294 fprintf('Successfully saved %s to %s\n', varname, full_addy);
295
296 catch Me %e is an MException struct
297 % Some error occurred if you get here.
298 fprintf(1,'There was an error with %s! \n', varname)
299 errorMessage = sprintf('Error in function %s() at line %d.\n\nError Message:\n%s', ME.stack.name, ME.stack.line, ME.message);
300 fprintf(1, '%s\n', errorMessage);
301 uiwait(warndlg(errorMessage));
302 %line = Me.stack.line
303 %fprintf(1,'There was an error with %s! \n', varname)
304 %fprintf('The message was:\n%s\n',Me.message);
305 %fprintf(1,'The identifier was:\n%s\n',Me.identifier);
306
307 % more error handling...
308 end
309 end
310end
311
312
313
Note: See TracBrowser for help on using the repository browser.