function a_tests_3D_db = invarValues(db, cols, in_page_unique_cols, props)
% invarValues - Finds all sets in which given columns vary while the rest are invariant.
%
% Usage:
% a_tests_3D_db = invarValues(db, cols, in_page_unique_cols, props)
%
% Parameters:
% db: A tests_db object.
% cols: Vector of column numbers to find values when others are
% invariant. Include result columns here.
% in_page_unique_cols: Vector of columns that have the same unique values in each page
% (Optional; used only if database is not symmetric, to ignore
% missing values of in_page_unique_cols)
% props: A structure with any optional properties.
% sortPages: If 1, page-sorts even symmetric databases (default=1).
%
% Returns:
% a_tests_3D_db: A tests_3D_db object of organized values.
%
% Description:
% Useful when trying to find relationships between some columns
% independent of other columns. In a database that contains results of a
% multivariate function, this function can find the effect of one or more
% parameters when other parameters are kept constant (i.e., invariant). Rows
% with the values of the desired columns are separated into the pages of a
% tests_3D_db for each unique set of the other column values. These
% invariant values of the other columns are missing from the resulting
% tests_3D_db, instead a RowIndex is kept pointing to the db in which they
% can be found. See joinRows for joining the results back with the invariant
% columns.
% If in_page_unique_cols is given, this function by default row-sorts the
% database to ensure that each page has the same parameter values in the
% same rows. This is important because when the rows and pages of database
% is swapped (see tests_3D_db/swapRowsPages) each page has the same value of
% the in_page_unique_cols variables. Other functions such as
% tests_3D_db/mergePages also depend on this property.
% In databases that contain all unique combinations of certain parameters,
% the resulting 3D database becomes symmetric. However, for databases with
% missing combinations, in_page_unique_cols specifies which columns is used
% to guide which rows of the page to place values found. This function will
% fail if you do not have such a column. Note: the trial column will be
% ignored before finding invariant values.
%
% Example:
% >> a_db = tests_db([ ... ], {'par1', 'par2', 'measure1', 'measure2'})
% % make a page for each value of par1, and list par2 values with assoc. measures:
% >> a_3d_db = invarValues(a_db, [2:4], 'par2')
% >> % get back other columns:
% >> joined_3d_db = joinRows(a_db, a_3d_db)
% >> displayRows(joined_3d_db(:, :, 1))
%
% See also: tests_3D_db, tests_3D_db/corrCoefs, tests_3D_db/plotPair,
% joinRows, tests_3D_db/swapRowsPages, tests_3D_db/mergePages
%
% $Id$
%
% Author: Cengiz Gunay <cgunay@emory.edu>, 2004/09/30
% Modifications: Li Su, bugfix for non-symmetric dbs 2008/03.
% Copyright (c) 2007-2014 Cengiz Gunay <cengique@users.sf.net>.
% This work is licensed under the Academic Free License ("AFL")
% v. 3.0. To view a copy of this license, please look at the COPYING
% file distributed with this software or visit
% http://opensource.org/licenses/afl-3.0.php.
vs = warning('query', 'verbose');
verbose = strcmp(vs.state, 'on');
props = defaultValue('props', struct);
cols = tests2cols(db, cols);
% Remove trial column from parameters that define character of data
test_names = fieldnames(get(db, 'col_idx'));
trial_col = strmatch('trial', test_names);
% Remove all given columns, left with surrounding parameters
log_cols = false(1, dbsize(db, 2));
log_cols(cols) = true(1);
log_cols(trial_col) = true(1);
wo_cols = db.data(:, ~log_cols);
if isempty(wo_cols) % added by Li Su
data=zeros(0);
else
% % Sort rows
[sorted idx] = sortrows(wo_cols);
%
% % Find unique rows
[unique_rows unique_idx] = sortedUniqueValues(sorted);
% edited by Li Su. see help sortedUniqueValues for reason
% [unique_rows unique_idx] = unique(wo_cols, 'rows', 'first')
% Get the columns back [no need for duplicate memory matrix, just use idx below]
% sorted = db.data(idx, :);
% Initialize
num_rows = length(unique_idx);
num_total_rows = dbsize(db, 1);
% If not symmetric
if any(diff(diff([unique_idx; num_total_rows+1]))) || ...
(exist('in_page_unique_cols','var') && ...
getFieldDefault(props, 'sortPages', 1) == 1)
if verbose
disp('Warning: non-symmetric database or sorting requested.');
end
if ~ exist('in_page_unique_cols','var')
error(['Database does not contain equal rows of each unique ' ...
'combination and in_page_unique_cols is not specified. Cannot fold.']);
end
in_page_unique_cols = tests2cols(db, in_page_unique_cols);
% Sort and keep the unique values of in_page_unique_cols
unique_main_vals = sortrows(uniqueValues(db.data(idx, in_page_unique_cols)));
num_uniques = size(unique_main_vals, 1);
if verbose
unique_main_vals
num_uniques
num_rows
end
max_page_rows = num_uniques;
else % if symmetric
max_page_rows = floor(num_total_rows / num_rows);
end
data = repmat(NaN, [max_page_rows, (length(cols) + 1), num_rows]);
if exist('unique_main_vals','var') && ~isempty(unique_main_vals)
unique_main_vals_exist = true;
else
unique_main_vals_exist = false;
end
% For each unique row to next, create a new page
for row_num=1:num_rows
if row_num < num_rows
page_rows = unique_idx(row_num):(unique_idx(row_num + 1) - 1);
else
page_rows = unique_idx(row_num):num_total_rows;
end
page_size = length(page_rows);
if unique_main_vals_exist
% sort in_page_unique_cols first
[page_main_vals page_idx] = sortrows(db.data(idx(page_rows), in_page_unique_cols));
% Match each page entry to uniques
unique_index = 1;
for page_index = 1:page_size
unique_index = findVectorInMatrix(unique_main_vals, ...
page_main_vals(page_index, :));
% Check for errors
if num_uniques - unique_index < page_size - page_index
page_main_vals
% check to see if duplicates exist in the page
if size(sortedUniqueValues(page_main_vals), 1) < ...
size(page_main_vals, 1)
error(['Fatal: Database must not contain multiple rows of same ' ...
'unique columns. There are multiple same ' ...
'in_page_unique_cols values (columns ' ...
test_names{in_page_unique_cols} ') for the unique value of ' ...
'[ ' sprintf('%f ', db.data(idx(unique_idx(row_num)), cols)) ']' ...
' of selected invar columns ' test_names{cols} '. See ' ...
'meanDuplicateRows to reduce redundant rows.']);
else
num_uniques
unique_index
page_size
page_index
error(['Fatal: (num_uniques - unique_index < page_size - page_index).' ...
' Cannot match within page values of in_page_unique_cols? ' ...
'See above variables.']);
end
end
% Check if remaining page size is equal to remaining uniques size,
% if so just copy the rest of the page.
if page_size - page_index == num_uniques - unique_index
% Copy contents verbatim from this index onwards
data(unique_index:end, :, row_num) = ...
[db.data(idx(page_rows(page_idx(page_index:end))), cols), ...
idx(page_rows(page_idx(page_index:end))) ];
else
% Copy only this row
data(unique_index, :, row_num) = ...
[db.data(idx(page_rows(page_idx(page_index))), cols), ...
idx(page_rows(page_idx(page_index))) ];
end
end
else
% Fill page from fixed-size unique values
this_page_idx = idx(page_rows);
data(:, :, row_num) = [db.data(this_page_idx, cols), this_page_idx ];
end
end
end
% Create the 3D database
col_name_cell = fieldnames(db.col_idx);
col_names = col_name_cell(cols);
% TODO: put the invarName in the title?
a_tests_3D_db = tests_3D_db(data, {col_names{:}, 'RowIndex'}, {}, {}, ...
[ 'Invariant values from ' db.id ], get(db, 'props'));