diff general/funutils/zipmap.m @ 4:e44f49929e56

Adding reorganised general toolbox, now in several subdirectories.
author samer
date Sat, 12 Jan 2013 19:21:22 +0000
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/general/funutils/zipmap.m	Sat Jan 12 19:21:22 2013 +0000
@@ -0,0 +1,139 @@
+function varargout=zipmap(spec,f,varargin)
+% zipmap - Map and zip function over multidimensional arrays
+%
+% ZIPMAP allows a certain simple functions to be applied to multidimensional
+% array arguments, manipulating the dimensions to combine elementwise and
+% outer-product-like behaviour. (The name comes from functional programming
+% terminology: functions/operators like .* and + which apply elementwise to
+% vector arguments are said to zip their arguments, while outer products are
+% like mapping a function over a list, or a list of functions over another list.)
+%
+% Let f be a vectorised function which knows how to zip array arguments. Eg,
+% f(x,y)=x+y. If x and y are vectors, they must be the same size, and
+% the function is applied to corresponding pairs of elements, resulting in
+% a vector of the same size. With ZIPMAP, you can build functions, that, eg,
+% add EVERY pair of elements (not just corresponding pairs) resulting in a
+% 2D array: zipmap(0,inline('x+y','x','y'),[1:4]',[1:6]') will do this
+%
+% The domain of a multidimensional array can be described a list containing the
+% size of each dimension, as returned by the SIZE function (or SIZE1 which strips
+% trailing 1s). The arguments to f must have a certain domain structure:
+%
+% 	dom arg1 = [p1 zipdom]
+% 	dom arg2 = [p2 zipdom]
+%	         :
+%
+% where p1, p2, etc are the domains of the elementary function of which f
+% is a vectorisation, eg the elementary domain of .* can be scalars and
+% hence p1=p2=[], but the two elementary domains of CROSS are both [3], because it 
+% it operates on pairs of 3-D vectors. The zipdom part must be the same for
+% all arguments, otherwise, we cannot extract matching argument tuples.
+% If this is the case, the f must return a result:
+%
+% 	dom f(...) : [rdom zipdom]
+%
+% where rdom is the domain of the elementary function, eg [3] for the vector
+% valued CROSS product, but [] for the scalar valued DOT product. Note that
+% these domains can be vectors of any length from zero up, eg the determinant
+% of a square matrix has a domain of [n n].
+% 
+% ZIPDIM allows the domains of the arguments and result to be generalised as follows:
+%
+% 	dom arg1 : [p1 m1 zipdom]
+% 	dom arg2 : [p2 m2 zipdom]
+%	         :
+%
+%	dom zipdom(...) : [rdom m1 m2 ... zipdom ]
+%
+% All the mapped dimensions m1, m2 etc go into a sort of outer product where 
+% every combination of values is evaluated. In addition, the order of the
+% m1, m2 etc can be permuted in the result at essentially no extra computational cost.
+%
+% Usage: r=zipmap(spec,f,<varargs>)
+% 
+% spec defines how the domain of each of the arguments is to be interpreted, ie
+% how is is split into elementary, mappable, and zippable domains.
+%
+% If spec is a number z, then the last z dimensions of each argument are marked for
+% zipping, while the rest are for mapping. The elementary domains are assumed to
+% be scalar and hence p1, p2 etc =[]. So, zipmap(0,...) does the complete outer
+% product type of evaluation.
+%
+% If spec is structure, the field 'zipdims' determines how many dimensions are zipped.
+% The field 'pdims' is a the vector [length(p1) length(p2) ..], ie the dimensionality
+% of each elementary domain: 0 for scalars, 1 for vectors, 2 for matrices etc.
+% The optional field 'mapord' determines the reordering of the mapped domains, eg
+% if mapord=[2 1 3], then the result domain is [m2 m1 m3] instead of [m1 m2 m3].
+%
+% If spec is a cell array, it is interpreted as {zipdims pdims mapord}.
+%
+% Extra options
+%
+% If spec is a structure, it can contain further options: if spec.shift=1,
+% then the domains of all arguments are pre-shifted to remove singleton
+% dimensions before anything else is done. Amongst other effects, this means
+% that row vectors can be used in place of column vectors.
+
+
+% unpack args from spec
+if iscell(spec)
+	[zipdims,pdims,mapord]=getargs(spec,{0 [] []});
+elseif isstruct(spec)
+	zipdims=getparam(spec,'zipdims',0);
+	pdims=getparam(spec,'pdims',[]);
+	mapord=getparam(spec,'mapord',[]);
+	if getparam(spec,'shift',0),
+		varargin=cellmap(@shiftdim,varargin);
+	end
+elseif isscalar(spec)
+	zipdims=spec; pdims=[]; mapord=[];
+end
+
+
+argdoms=cellmap(@size1,varargin);
+if isempty(pdims), pdims=zeros(size(varargin)); end
+for i=1:length(varargin)
+	[pdoms{i},mapdoms{i},zipdoms{i}]=split(argdoms{i},pdims(i),zipdims);
+end
+
+
+% check all zipdoms match and use first
+zipdom=zipdoms{1};
+for i=2:length(zipdoms)
+	if ~all(zipdoms{i}==zipdom), error('Zip domains do not match'); end
+end
+
+
+if isempty(mapord), mapord=1:length(varargin); end
+outerdom=cat(2,mapdoms{mapord});
+outer1=cellmap(@to1s,mapdoms);
+for i=1:length(varargin)
+	% need to reshape each arg and then repmat up to correct size
+	newsizes=outer1; newsizes{i}=mapdoms{i};
+	newsize=[pdoms{i} cat(2,newsizes{mapord}) zipdom];
+	varargin{i}=reshape(varargin{i},tosize([pdoms{i} cat(2,newsizes{mapord}) zipdom]));
+	newsizes=mapdoms; newsizes{i}=outer1{i};
+	newsize=[to1s(pdoms{i}) cat(2,newsizes{mapord}) to1s(zipdom)];
+	varargin{i}=repmat(varargin{i},tosize(newsize));
+%	size(varargin{i})
+end
+
+[varargout{1:max(1,nargout)}]=feval(f,varargin{:});
+
+function [a,b,c]=split(x,n,m)
+	a=x(1:n);
+	b=x(n+1:end-m);
+	c=x(end-m+1:end);
+
+% getargs - get values of optional parameters with defaults
+%
+% getargs :: {I:[N]->(A(I) | nil)}, {I:[N]->A(I)} -> A(1), ..., A(N).
+
+function varargout=getargs(args,defs)
+
+nout=max(length(args),length(defs));
+varargout=defs;
+for i=1:length(args)
+	if ~isempty(args{i}), varargout(i)=args(i); end
+end
+