Changeset 27875 for issm/trunk


Ignore:
Timestamp:
08/10/23 18:34:53 (20 months ago)
Author:
musselman
Message:

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

Location:
issm/trunk/src/m/contrib/musselman
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • issm/trunk/src/m/contrib/musselman/read_netCDF.m

    r27874 r27875  
    1717
    1818function model_copy = read_netCDF(filename)
    19     fprintf('NetCDF42C v1.1.13\n');
     19    fprintf('NetCDF42C v1.1.14\n');
    2020
    2121    % make a model framework to fill that is in the scope of this file
     
    109109        for variable = variables
    110110            [varname, xtype, dimids, numatts] = netcdf.inqVar(group_location_in_file, variable);
    111             copy_variable_data_to_new_model(group_location_in_file,varname, xtype);
     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
    112120        end
    113121    catch ME
     
    117125    % try to find groups in current level, if it doesn't work it's because there is nothing there
    118126    try
    119         % search for nested groups in the current level to feed back to this function
    120         groups = netcdf.inqGrps(group_location_in_file);
    121         if not(isempty(groups))
    122             for group = groups
    123                 %disp('found nested group!!')
    124                 group_id = netcdf.inqNcid(group_location_in_file, netcdf.inqGrpName(group));
    125                 %disp(netcdf.inqGrpNameFull(group_id))
    126                 walk_nested_groups(group);
    127             end
    128         end
    129     catch
    130     end
    131 end
     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
    132222
    133223%{
     
    142232    %disp(varname)
    143233    % this is an inversion band-aid
    144     if strcmp(varname, 'inversion_class_name')
     234    if strcmp(varname, 'inversion_class_name') || strcmp(varname, 'name_of_struct') || strcmp(varname, 'solution')
    145235        % we don't need this
    146236    else
     
    149239            %disp(netcdf.inqGrpNameFull(group_location_in_file))
    150240            %disp(class(netcdf.inqGrpNameFull(group_location_in_file)))
    151             adress_to_attr = strrep(netcdf.inqGrpNameFull(group_location_in_file), '/', '.');
    152            
    153             data = netcdf.getVar(group_location_in_file, netcdf.inqVarID(group_location_in_file, varname));
    154            
    155    
    156             % matlab needs to know that ' ' = char()
     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
    157247            if xtype == 2 && isempty(all(data))
    158248                data = cell(char());
     249            % if we have an empty cell-char array
    159250            elseif numel(data) == 1 && xtype == 3 && data == -32767
    160251                data = cell(char());
    161252            end
    162             % band-aid for cell-char-arrays:
     253            % band-aid for some cell-char-arrays:
    163254            if xtype == 2 && strcmp(data, 'default')
    164255                data = {'default'};
     
    168259            if all(size(data)~=1) || xtype == 2
    169260                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
    170272            end
    171273           
     
    174276            %xtype
    175277            if xtype == 10
    176                 arg_to_eval = ['model_copy', adress_to_attr, '.', varname, ' = ' , 'double(data);'];
     278                arg_to_eval = ['model_copy', address_to_attr, '.', varname, ' = ' , 'double(data);'];
    177279                eval(arg_to_eval);
    178280                %disp('saved int64 as int16')
    179281            else
    180                 arg_to_eval = ['model_copy', adress_to_attr, '.', varname, ' = ' , 'data;'];
     282                arg_to_eval = ['model_copy', address_to_attr, '.', varname, ' = ' , 'data;'];
    181283                eval(arg_to_eval);
    182284            end
  • issm/trunk/src/m/contrib/musselman/write_netCDF.m

    r27874 r27875  
    1111
    1212function write_netCDF(model_var, filename)
    13     disp('MATLAB C2NetCDF4 v1.1.13');
     13    disp('MATLAB C2NetCDF4 v1.1.14');
    1414    % model_var = class object to be saved
    1515    % filename = path and name to save file under
     
    179179            % there are 2 cases: the location is a struct, the location is a class
    180180            if isstruct(model_subclass)
     181                % if the current field is a nested struct assume it has valuable data that needs to be saved
     182                if isstruct(location_of_child) && any(size(location_of_child) > 1)
     183                    create_group(location_of_child, list_of_layers);
     184               
    181185                % this would mean that the layer above the layer we're interested in is a struct, so
    182186                % we can navigate our empty model as such
    183                 if isfield(empty_model_subclass, current_child)
     187                elseif isfield(empty_model_subclass, current_child)
    184188                    % the layer we're interested in does exist, we just need to compare states
    185189                    location_of_child_in_empty_model = empty_model_subclass.(current_child);
     190
    186191                    % if the current attribute is a numerical array assume it has valuable data that needs to be saved
    187192                    if isnumeric(location_of_child) && logical(numel(location_of_child) > 1)
     
    264269    % first we make the group at the highest level (ie, inversion)
    265270    group_name = list_of_layers{1};
     271    variable_name = list_of_layers{end};
    266272   
    267273    % if the group is already made, get it's ID instead of creating it again
     
    284290        end
    285291    end
    286     % if the object is not a variable, we have nothing to save
     292    % we may be dealing with an object
    287293    try
    288294        % if this line works, we're still dealing with a struct and need lower levels
    289295        if isempty(fieldnames(location_of_child))
    290296            % do nothing
     297        elseif isstruct(location_of_child) && any(size(location_of_child) > 1)
     298            % we have a nested struct
     299            copy_nested_struct(variable_name, location_of_child, group)
    291300        end
    292301    catch
    293         % if that line doesn't work, it means we're dealing with data
    294         % Lastly, handle the variable(s)
    295         variable_name = list_of_layers{end};
     302        % if that line doesn't work, it means we're dealing with raw data
     303        % not a nested struct, so we can pass
    296304        create_var(variable_name, location_of_child, group);
    297305    end
    298306end
     307
     308
     309
     310function copy_nested_struct(parent_struct_name, address_of_struct, group)
     311    %{
     312        This function takes a struct of structs and saves them to netcdf.
     313
     314        To do this, we get the number of dimensions (substructs) of the parent struct.
     315        Next, we iterate through each substruct and record the data.
     316        For each substruct, we create a subgroup of the main struct.
     317        For each variable, we create dimensions that are assigned to each subgroup uniquely.
     318    %}
     319
     320    disp("Beginning transfer of nested MATLAB struct to the NetCDF")
     321
     322    % make sure other systems can flag the nested struct type
     323    dimID = netcdf.defDim(group, 'struct', 6);
     324    string_var = netcdf.defVar(group, 'this_is_a_nested', "NC_CHAR", dimID);
     325    uint_method=uint8('struct').';
     326    method_ID = char(uint_method);
     327    netcdf.putVar(group, string_var, method_ID);
     328
     329    % make sure other systems know the name of the parent struct
     330    uint_method=uint8(parent_struct_name).';
     331    method_ID = char(uint_method);
     332    dimID = netcdf.defDim(group, 'struct_name', [numel(method_ID)]);
     333    string_var = netcdf.defVar(group, 'name_of_struct', "NC_CHAR", dimID);
     334    netcdf.putVar(group, string_var, method_ID);
     335   
     336    % 'a' will always be 1 and is not useful to us
     337    [a, no_of_dims] = size(address_of_struct);
     338
     339    for substruct = 1:no_of_dims
     340        % we start by making subgroups with nice names like "TransientSolution_substruct_44"
     341        name_of_subgroup = [parent_struct_name, '_substruct_', num2str(substruct)];
     342        subgroup = netcdf.defGrp(group, name_of_subgroup);
     343
     344        % do some housekeeping to keep track of the current layer
     345        current_substruct = address_of_struct(substruct);
     346        substruct_fields = fieldnames(current_substruct)'; % transpose because matlab only interates over n x 1 arrays
     347       
     348        % now we need to iterate over each variable of the nested struct and save it to this new subgroup
     349        for variable_name = string(substruct_fields)
     350            address_of_child = current_substruct.(variable_name);
     351            create_var(variable_name, address_of_child, subgroup);
     352        end
     353    end
     354    fprintf('Succesfully transferred nested MATLAB struct %s to the NetCDF\n', parent_struct_name)
     355end
     356
    299357
    300358
     
    338396   
    339397    % This first conditional statement will catch numeric arrays (matrices) of any dimension and save them
    340     if any(size(address_of_child)>1) && ~ischar(address_of_child)
     398    if any(size(address_of_child)>1) && ~iscellstr(address_of_child) && ~ischar(address_of_child)
    341399        write_numeric_array_to_netcdf(variable_name, address_of_child, group);
    342400
     
    349407        variable = netcdf.defVar(group, variable_name, "NC_DOUBLE", intdim);
    350408
    351     % or a list of strings -- this needs work as it can only handle a list of 1 string
    352     elseif iscell(address_of_child) && ischar(address_of_child{1})
    353         for i = 1:numel(address_of_child)
    354             write_string_to_netcdf(variable_name, address_of_child{i}, group, true);
    355         end
     409    % or a list of strings
     410    elseif iscellstr(address_of_child) || iscell(address_of_child) && ischar(address_of_child{1})
     411        write_cell_with_strings(variable_name, address_of_child, group)
    356412       
    357413    % or an empty list
     
    396452    end
    397453
    398     disp(['Successfully transferred data from ', variable_name, ' to the NetCDF']);
    399 end
    400 
    401 
    402 
    403 
    404 function write_string_to_netcdf(variable_name, address_of_child, group, list)
     454    fprintf('Successfully transferred data from %s to the NetCDF\n', variable_name);
     455end
     456
     457
     458function write_cell_with_strings(variable_name, address_of_child, group)
     459    %{
     460    Write cell array (ie {'one' 'two' 'three'}) to netcdf
     461    %}
     462    global NetCDF
     463   
     464    if isempty(address_of_child)
     465        % if the char array is empty, save an empty char
     466        name_of_dimension = ['char', num2str(0)];
     467        try
     468            dimID = netcdf.defDim(group, name_of_dimension, 0);
     469        catch
     470            dimID = netcdf.inqDimID(group, name_of_dimension);
     471        end
     472        % Now we can make a variable in this dimension:
     473        string_var = netcdf.defVar(group, variable_name, "NC_CHAR", [dimID]);
     474        % we leave empty now
     475    else
     476        % covert data to char array
     477        method_ID = char(address_of_child);
     478   
     479        % make dimensions
     480        [rows, cols] = size(method_ID);
     481       
     482        IDDim1 = netcdf.defDim(group,'cols',cols);
     483        IDDim2 = netcdf.defDim(group,'rows',rows);
     484   
     485        % create the variable slot
     486        IDVarId = netcdf.defVar(group,variable_name,'NC_CHAR', [IDDim1 IDDim2]);
     487   
     488        % save the variable
     489        netcdf.putVar(group, IDVarId, method_ID'); %transpose
     490   
     491        % tell other platforms that this is a cell of strings
     492        netcdf.putAtt(group, IDVarId, 'type_is','cell_array_of_strings');
     493    end
     494end
     495
     496
     497function write_string_to_netcdf(variable_name, address_of_child, group)
    405498    % netcdf and strings don't get along.. we have to do it 'custom':
    406499    global NetCDF;
    407     % If 'list' is not provided, assume it is false
    408     if nargin < 4
    409         list = false;
    410     end
     500
    411501    the_string_to_save = address_of_child;
    412502
Note: See TracChangeset for help on using the changeset viewer.