Search code examples
matlabmatlab-coder

Matlab coder & dynamic field references


I'm trying to conjure up a little parser that reads a .txt file containing parameters for an algorithm so i don't have to recompile it everytime i change a parameter. The application is C code generated from .m via coder, which unfortunately prohibits me from using a lot of handy matlab gimmicks.

Here's my code so far:

% read textfile
string = readfile(filepath);

% do fancy rearranging
linebreaks = zeros(size(string));
equals     = zeros(size(string));

% find delimiters
for n=1:size(string,2)
    if strcmp(string(n),char(10))
        linebreaks(n) = 1;
    elseif strcmp(string(n), '=')
        equals(n) = 1;
    end
end

% write first key-value pair
idx_s = find(linebreaks);idx_s = [idx_s length(string)];
idx_e = find(equals);

key     = string(1:idx_e(1)-1);
value   = str2double(string(idx_e(1)+1:idx_s(1)-1));

parameters.(key) = value;

% find number of parameters
count = length(idx_s);

% write remaining key-value pairs
for n=2:count
    key   = string(idx_s(n-1)+1:idx_e(n)-1);
    value = str2double(string(idx_e(n)+1:idx_s(n)-1));
    parameters.(key) = value;
end

The problem is that seemingly coder does not support dynamic fieldnames for structures like parameters.(key) = value.

I'm a bit at a loss as to how else i am supposed to come up with a parameter struct that holds all my key-value pairs without hardcoding it. It would somewhat (though not completely) defeat the purpose if the names of keys were not dynamically linked to the parameter file (more manual work if parameters get added/deleted, etc.). If anybody has an idea how to work around this, i'd be very grateful.


Solution

  • As you say, dynamic fieldnames for structures aren't allowed in MATLAB code to be used by Coder. I've faced situations much like yours before, and here's how I handled it.

    First, we can list some nice tools that are allowed in Coder. We're allowed to have classes (value or handle), which can be quite handy. Also, we're allowed to have variable sized data if we use coder.varsize to specifically designate it. We also can use string values in switch statements if we like. However, we cannot use coder.varsize for properties in a class, but you can have varsized persistent variables if you like.

    What I'd do in your case is create a handle class for storing and retrieving the values. The following example is pretty basic, but will work and could be expanded. If a persistent variable were used in a method, you could even create a varsized allocated storage for the data, but in my example, it's a property and has been limited in the number of values it can store.

    classdef keyval < handle %# codegen
        %KEYVAL A key and value class designed for Coder
        %   Stores an arbitrary number of keys and values.
    
        properties (SetAccess = private)
            numvals = 0
        end
    
        properties (Access = private)
            intdata
    
        end
    
        properties (Constant)
            maxvals = 100;
            maxkeylength = 30;
        end
    
        methods
            function obj = keyval
                %KEYVAL Constructor for keyval class
                obj.intdata = repmat(struct('key', char(zeros(1, obj.maxkeylength)), 'val', 0), 1, obj.maxvals);
            end
    
            function result = put(obj, key, value)
                %PUT Adds a key and value pair into storage
                % Result is 0 if successful, 1 on error
                result = 0;
                if obj.numvals >= obj.maxvals
                    result = 1;
                    return;
                end
    
                obj.numvals = obj.numvals + 1;
    
                tempstr = char(zeros(1,obj.maxkeylength));
                tempstr(1,1:min(end,numel(key))) = key(1:min(end, obj.maxkeylength));
                obj.intdata(obj.numvals).key = tempstr;
                obj.intdata(obj.numvals).value = value;
            end
    
            function keystring = getkeyatindex(obj, index)
                %GETKEYATINDEX Get a key name at an index
                keystring = deblank(obj.intdata(index).key);
    
            end
    
            function value = getvalueforkey(obj, keyname)
                %GETVALUEFORKEY Gets a value associated with a key.
                % Returns NaN if not found
    
                value = NaN;
                for i=1:obj.numvals
                    if strcmpi(keyname, deblank(obj.intdata(i).key))
                        value = obj.intdata(i).value;
                    end
                end
    
            end
        end
    
    end
    

    This class implements a simple key/value addition as well as lookup. There are a few things to note about it. First, it's very careful in the assignments to make sure we don't overrun the overall storage. Second, it uses deblank to clear out the trailing zeros that are necessary in the string storage. In this situation, it's not permitted for the strings in the structure to be of different length, so when we put a key string in there, it needs to be exactly the same length with trailing nulls. Deblank cleans this up for the calling function.

    The constant properties allocate the total amount of space we're allowed in the storage array. These can be increased, obviously, but not at runtime.

    At the MATLAB command prompt, using this class looks like:

    >> obj = keyval
    obj = 
    keyval with properties:
        numvals: 0
    
    >> obj.put('SomeKeyName', 1.23456)
    ans =
             0
    
    >> obj
    obj = 
      keyval with properties:
        numvals: 1
    
    >> obj.put('AnotherKeyName', 34567)
    ans =
         0
    
    >> obj
    obj = 
      keyval with properties:
        numvals: 2
    
    >> obj.getvalueforkey('SomeKeyName')
    ans =
        1.2346
    
    >> obj.getkeyatindex(2)
    ans =
    AnotherKeyName
    
    >> obj.getvalueforkey(obj.getkeyatindex(2))
    ans =
       34567
    

    If a totally variable storage area is desired, the use of persistent variables with coder.varsize would work, but that will limit the use of this class to a single instance. Persistent variables are nice, but you only get one of them ever. As written, you can use this class in many different places in your program for different storage. If you use a persistent variable, you may only use it once.

    If you know some of the key names and are later using them to determine functionality, remember that you can switch on strings in MATLAB, and this works in Coder.