No BSD License  

Highlights from
comparedata

from comparedata by Andrew Diamond
Compares two objects recursively.

retval=comparedata(data1, data2, context, Params)
function retval=comparedata(data1, data2, context, Params)
% function retval=comparedata(data1, data2, context, Params)
%   compares to see if data1 and data2 are roughly recursively equal.  "Rough" here is defined by
%   the Params.  Note that matlabs ISEQUAL function is a test for exact equality.  comparedata
%   will print out intermediate results wheras ISEQUAL just returns 0 or 1.
% Output Arg
%   retval = 0 for not roughly equal 1 for roughly equal.
% Input Args: - Note context and Params are optional and have defaults as specified below.
%   data1, data2 - objects to be compared - arbitrarily nested structurs, cell arrays, numeric arrays.
%       Anything else gets compared with ISEQUAL.
%   context('Top') - This is current context string of the recursion.  Example: if the user calls
%       comparedata with an empty context or ommits it all together the top level context will
%       be set to "Top".  If then, for example, data1 and data1 are structures and data1 has
%       a "Fred" field but data2 doesn't the program will print out:
%       "Mismatch in Top.Fred not found in second structure.
%   Params - structure
%       outfileorfid(1) - Place to print progress. Either name of a file or a file handle open for 
%           writing text. Default of 1 is handle 1 which means the screen.  String implies path
%           name.
%           Note that if there is a mismatch a message with the start word "Mismatch" will be output
%           so as to facilitate file searching.
%       displaycontextprogress(1) - outputs current context string at each new level of recursion
%           e.g Top.a.b.c would mean that data1.a.b.c is being compared to data2.a.b.c
%           Note that if turned on this gets written out to the screen and to the location specified
%           to outfileorfid (obviously if outfileorfid is 1 by user input or default it's not
%           output twice).
%       NumericTolerance(1e-10) - When comparing numeric arrays (at any level of comparison recursion)
%           equality will be determined by whether all elements of the two items have an 
%           absolute value difference less than this tolerance. That is:
%           max(abs(Array1(:) - Array2(:))) < NumericTolerance
%           1e-10 should be good enough for practical equality for real life being small enough
%               to be beyond the accuracy of data yet account for typical numeric error growth
%      ignoreunmatchedfieldnames(0) - if at some level of recursion two structures are being compared
%           such that one has fields that the other doesn't have 0 will mean that the comparedata can
%           still return 1 (i.e. rough equality) if the common fields are recursively roughly equal.
%           A value of 0, the default, means that unique fields automatically disqualify rough equality.
%           Note that in any event, the field differences will be output.
% Notes:
%   If at some level of recursive comparison the rough equality test fails the 
%   function will continue.  It will continue comparing as best it can.  This means
%   comparing just the common fields of structures even if some fields are not in common
%   but array elements (e.g. cell arrays or structur arrays) will not be compared (and
%   so not recursively compared if their size doesn't match.
%
%   Things that are defined as mismatches (in order):
%   If the type of the two objects differ.  This is determined by Matlab's CLASS function.  This means
%       that numeric arrays although numerically equal can fail if they have different numeric suptypes.
%       Example retval=comparedata(int16([1]), double([1]) fails because data1 is a int16 and data2 is
%       a double although they are numerically equal.  This could be rectified but not today.
%   size(data1) ~= size(data2) - Everything's an array internally and so all must have the same no. of 
%       elements.
%   Unique fields in a structure - if ignoreunmatchedfieldnames is non-zero then this is not a mismatch.
%   Numeric arrays whose elements differ beyond tolerance as defined by NumericTolerance
%   isequal fails
%
%   Examples
%   >> retval=comparedata(int16([1]), double([1]))
%   context = Top
%   Top not the same data type int16 vs. double
%   retval =0
%
%   >> s1.a=1; s1.b=2; s1.c=3;         s2.a=1; s2.b=2;
%   >> retval=comparedata(s1, s2, [], struct('ignoreunmatchedfieldnames', 1))
%   context = Top
%   Mismatch in Top.c not found in second structure
%   context = Top.a
%   context = Top.b
%   retval =1
%   
%   Comparing RR and RG structures output answers to comparedataRRRG.txt and comparing numeric arrays
%   with a tolerance of 1e-5.  Context will show top level as Fred.  Output is long so select pieces are below:
%   >> retval=comparedata(RR, RG, 'Fred', struct('outfileorfid', 'comparedataRRRG.txt', 'NumericTolerance',1e-5));
%   context = Fred
%   Mismatch in Fred.RepDataMR not found in second structure
%   Mismatch in Fred.fred not found in second structure
%   ....
%   context = Fred.grn
%   Mismatch in Fred.grn. Two objects not the same array size: 0 0 vs 1 49777 
%   ...
%   Mismatch : at Fred.x numeric array comparison - abs(data 1 - data 2) > tolerance:
%       mindiff = -2115.180411 at [1, 45967]
%       maxdiff = 1880.794556 at [1, 45451]
%       max abs diff = 2115.180411 at [1, 45967]
%   context = Fred.y
%   Mismatch : at Fred.y numeric array comparison - abs(data 1 - data 2) > tolerance:
%       mindiff = -1882.577620 at [1, 44844]
%       maxdiff = 1667.421048 at [1, 26426]
%       max abs diff = 1882.577620 at [1, 44844]
%   ---------------------------------
%   Author: Andrew Diamond of EnVision Systems LLC 

defaultparams = struct('outfileorfid', 1, 'displaycontextprogress',1, 'NumericTolerance', 1e-10,...
    'ignoreunmatchedfieldnames',0);
if(~exist('Params', 'var'))
    Params = [];
end
Params = mergedefaultparams(Params, defaultparams);
if(length(Params.outfileorfid) == 1 & isnumeric(Params.outfileorfid))
    Params.fid = Params.outfileorfid;
elseif(~isempty(Params.outfileorfid) & isstr(Params.outfileorfid))
    [Params.fid, message] = fopen(Params.outfileorfid, 'wt');
    if(Params.fid < 3)
        error(sprintf('Failed open file %s for reason %s', Params.outfileorfid, message));
    end
end
if(~exist('context', 'var'))
    context = 'Top';
elseif(isempty(context))
    context = 'Top';
end
retval=comparedatarecurse(data1, data2, context, Params);

function retval=comparedatarecurse(data1, data2, context, Params)
persistent ParamsP;
persistent iskindequalP;
if(exist('Params', 'var'))
    iskindequalP=1;
    ParamsP = Params;
end
if(ParamsP.displaycontextprogress)
    if(ParamsP.fid ~= 1)
        fprintf(1,'context = %s\n', context);
    end
    fprintf(ParamsP.fid,'context = %s\n', context);
end
if(~strcmp(class(data1), class(data2)))
    iskindequalP = 0;
    fprintf(ParamsP.fid,'%s not the same data type %s vs. %s\n',context, class(data1), class(data2));    
elseif(any(size(data1) ~= size(data2)))
    iskindequalP = 0;
    fprintf(ParamsP.fid,'Mismatch in %s. Two objects not the same array size: ',context);  
    fprintf(ParamsP.fid,'%d ',size(data1));    
    fprintf(ParamsP.fid,'vs ',size(data1));    
    fprintf(ParamsP.fid,'%d ',size(data2));    
    fprintf(ParamsP.fid,'\n',size(data2));    
elseif(isstruct(data1))
    names1 = fieldnames(data1);    
    names2 = fieldnames(data2);
    names1s = sort(names1);    
    names2s = sort(names2);
    matchinds = zeros(1,min(length(names1s), length(names2s)));
    imatchind=0; 
    for inames1s = 1:length(names1s)
        if(isempty(strmatch(names1s{inames1s}, names2s, 'exact')))
            fprintf(ParamsP.fid,'Mismatch in %s.%s not found in second structure\n',context, names1s{inames1s});
            if(~ParamsP.ignoreunmatchedfieldnames)
                iskindequalP = 0;
            end
        else
            imatchind = imatchind + 1;
            matchinds(imatchind) = inames1s;
        end
    end
    for inames2s = 1:length(names2s)
        if(isempty(strmatch(names2s{inames2s}, names1s, 'exact')))
            fprintf(ParamsP.fid,'Mismatch in %s.%s not found in first structure\n',context, names2s{inames2s});
            if(~ParamsP.ignoreunmatchedfieldnames)
                iskindequalP = 0;
            end
        end
    end
    if(prod(size(data1)) > 1)
        for iElt=1:length(data1(:))
            [ind2subvret, ind2subvretstr]=ind2subv(size(data1), iElt);
            comparestruct(data1(iElt), data2(iElt), names1s,matchinds(1:imatchind), sprintf('%s[%s]',context,ind2subvretstr));
        end
    else
        comparestruct(data1, data2, names1s,matchinds(1:imatchind), context);
    end
elseif(iscell(data1))
    for iElt=1:length(data1(:))
        [ind2subvret, ind2subvretstr]=ind2subv(size(data1), iElt);
        comparedatarecurse(data1{iElt}, data2{iElt}, sprintf('%s{%d}', context, ind2subvretstr));
    end
elseif(isnumeric(data1))
    diff = data1(:) - data2(:);
    [mindiff, mindiffi] = min(diff);
    [maxdiff, maxdiffi] = max(diff);
    [maxabsdiff, maxabsdiffi] = max(abs(diff));
    if(maxabsdiff > ParamsP.NumericTolerance)
        if(prod(size(data1)) == 1) % scalar
            fprintf(ParamsP.fid,'Mismatch : at %s numeric scalar comparison - abs(data 1 - data 2) > tolerance of %g:\n', context,ParamsP.NumericTolerance);  
            fprintf(ParamsP.fid,'Scalar 1 = %e, Scalar 2 = %e, Scalar1 - Scalar2 = %e\n', data1, data2, data1-data2);
        else
            fprintf(ParamsP.fid,'Mismatch : at %s numeric array comparison - abs(data 1 - data 2) > tolerance of %g:\n', context,ParamsP.NumericTolerance);
            [ind2subvret, ind2subvretstr]=ind2subv(size(data1), mindiffi);
            fprintf(ParamsP.fid,'    mindiff = %e at [%s]\n',mindiff, ind2subvretstr);
            [ind2subvret, ind2subvretstr]=ind2subv(size(data1), maxdiffi);
            fprintf(ParamsP.fid,'    maxdiff = %e at [%s]\n', maxdiff, ind2subvretstr);
            [ind2subvret, ind2subvretstr]=ind2subv(size(data1), maxabsdiffi);
            fprintf(ParamsP.fid,'    max abs diff = %e at [%s]\n',maxabsdiff, ind2subvretstr);
        end
        iskindequalP = 0;
    end
elseif(~isequal(data1, data2))
    fprintf(ParamsP.fid,'Mismatch in %s. Two non-numeric, non-cell, non-structure arrays are not equal ',context);
    iskindequalP = 0;
end
if(exist('Params', 'var'))
    if(isstr(ParamsP.outfileorfid))
        fclose(ParamsP.fid);
    end
    retval = iskindequalP;
end


function comparestruct(data1, data2, names, matchinds, context)
for imatchnames = 1:length(matchinds)
    namei = names{matchinds(imatchnames)};
    comparedatarecurse(getfield(data1,namei),getfield(data2,namei), sprintf('%s.%s',context,namei));
end

function [ind2subvret, ind2subvretstr]=ind2subv(arraysize, ind1d)
retstring = '[';
for k=1:length(arraysize)-1
    retstring = sprintf('%sI%d, ', retstring,k);
end
if(length(arraysize))
    retstring = sprintf('%sI%d', retstring,length(arraysize));
end
retstring = sprintf('%s]', retstring);
evalstring = [retstring, '= ind2sub(', 'arraysize,', num2str(ind1d), ');'];
eval(evalstring);
ind2subvret = eval(retstring);
ind2subvretstr = sprintf('%d, ',ind2subvret);
commas = find(ind2subvretstr==','); % for backward compaitibility to 6.0, etc. strfind(ind2subvretstr,',');
if(length(commas))
    ind2subvretstr = ind2subvretstr(1:commas(end)-1);
end

function params = mergedefaultparams(params, defaultparams)
if(isempty(params))
    params = defaultparams;
    return;
end
names = fieldnames(defaultparams);
for iname=1:length(names)
	namei = names{iname};
	if(~isfield(params,namei)) % add the default 
		params=setfield(params,namei,getfield(defaultparams,namei));
    elseif(isstruct(getfield(defaultparams,namei)))
        params=setfield(params,namei, mergedefaultparams(getfield(params,namei),getfield(defaultparams,namei)));
    end
end

Contact us at files@mathworks.com