comparison 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
comparison
equal deleted inserted replaced
3:3f77126f7b5f 4:e44f49929e56
1 function varargout=zipmap(spec,f,varargin)
2 % zipmap - Map and zip function over multidimensional arrays
3 %
4 % ZIPMAP allows a certain simple functions to be applied to multidimensional
5 % array arguments, manipulating the dimensions to combine elementwise and
6 % outer-product-like behaviour. (The name comes from functional programming
7 % terminology: functions/operators like .* and + which apply elementwise to
8 % vector arguments are said to zip their arguments, while outer products are
9 % like mapping a function over a list, or a list of functions over another list.)
10 %
11 % Let f be a vectorised function which knows how to zip array arguments. Eg,
12 % f(x,y)=x+y. If x and y are vectors, they must be the same size, and
13 % the function is applied to corresponding pairs of elements, resulting in
14 % a vector of the same size. With ZIPMAP, you can build functions, that, eg,
15 % add EVERY pair of elements (not just corresponding pairs) resulting in a
16 % 2D array: zipmap(0,inline('x+y','x','y'),[1:4]',[1:6]') will do this
17 %
18 % The domain of a multidimensional array can be described a list containing the
19 % size of each dimension, as returned by the SIZE function (or SIZE1 which strips
20 % trailing 1s). The arguments to f must have a certain domain structure:
21 %
22 % dom arg1 = [p1 zipdom]
23 % dom arg2 = [p2 zipdom]
24 % :
25 %
26 % where p1, p2, etc are the domains of the elementary function of which f
27 % is a vectorisation, eg the elementary domain of .* can be scalars and
28 % hence p1=p2=[], but the two elementary domains of CROSS are both [3], because it
29 % it operates on pairs of 3-D vectors. The zipdom part must be the same for
30 % all arguments, otherwise, we cannot extract matching argument tuples.
31 % If this is the case, the f must return a result:
32 %
33 % dom f(...) : [rdom zipdom]
34 %
35 % where rdom is the domain of the elementary function, eg [3] for the vector
36 % valued CROSS product, but [] for the scalar valued DOT product. Note that
37 % these domains can be vectors of any length from zero up, eg the determinant
38 % of a square matrix has a domain of [n n].
39 %
40 % ZIPDIM allows the domains of the arguments and result to be generalised as follows:
41 %
42 % dom arg1 : [p1 m1 zipdom]
43 % dom arg2 : [p2 m2 zipdom]
44 % :
45 %
46 % dom zipdom(...) : [rdom m1 m2 ... zipdom ]
47 %
48 % All the mapped dimensions m1, m2 etc go into a sort of outer product where
49 % every combination of values is evaluated. In addition, the order of the
50 % m1, m2 etc can be permuted in the result at essentially no extra computational cost.
51 %
52 % Usage: r=zipmap(spec,f,<varargs>)
53 %
54 % spec defines how the domain of each of the arguments is to be interpreted, ie
55 % how is is split into elementary, mappable, and zippable domains.
56 %
57 % If spec is a number z, then the last z dimensions of each argument are marked for
58 % zipping, while the rest are for mapping. The elementary domains are assumed to
59 % be scalar and hence p1, p2 etc =[]. So, zipmap(0,...) does the complete outer
60 % product type of evaluation.
61 %
62 % If spec is structure, the field 'zipdims' determines how many dimensions are zipped.
63 % The field 'pdims' is a the vector [length(p1) length(p2) ..], ie the dimensionality
64 % of each elementary domain: 0 for scalars, 1 for vectors, 2 for matrices etc.
65 % The optional field 'mapord' determines the reordering of the mapped domains, eg
66 % if mapord=[2 1 3], then the result domain is [m2 m1 m3] instead of [m1 m2 m3].
67 %
68 % If spec is a cell array, it is interpreted as {zipdims pdims mapord}.
69 %
70 % Extra options
71 %
72 % If spec is a structure, it can contain further options: if spec.shift=1,
73 % then the domains of all arguments are pre-shifted to remove singleton
74 % dimensions before anything else is done. Amongst other effects, this means
75 % that row vectors can be used in place of column vectors.
76
77
78 % unpack args from spec
79 if iscell(spec)
80 [zipdims,pdims,mapord]=getargs(spec,{0 [] []});
81 elseif isstruct(spec)
82 zipdims=getparam(spec,'zipdims',0);
83 pdims=getparam(spec,'pdims',[]);
84 mapord=getparam(spec,'mapord',[]);
85 if getparam(spec,'shift',0),
86 varargin=cellmap(@shiftdim,varargin);
87 end
88 elseif isscalar(spec)
89 zipdims=spec; pdims=[]; mapord=[];
90 end
91
92
93 argdoms=cellmap(@size1,varargin);
94 if isempty(pdims), pdims=zeros(size(varargin)); end
95 for i=1:length(varargin)
96 [pdoms{i},mapdoms{i},zipdoms{i}]=split(argdoms{i},pdims(i),zipdims);
97 end
98
99
100 % check all zipdoms match and use first
101 zipdom=zipdoms{1};
102 for i=2:length(zipdoms)
103 if ~all(zipdoms{i}==zipdom), error('Zip domains do not match'); end
104 end
105
106
107 if isempty(mapord), mapord=1:length(varargin); end
108 outerdom=cat(2,mapdoms{mapord});
109 outer1=cellmap(@to1s,mapdoms);
110 for i=1:length(varargin)
111 % need to reshape each arg and then repmat up to correct size
112 newsizes=outer1; newsizes{i}=mapdoms{i};
113 newsize=[pdoms{i} cat(2,newsizes{mapord}) zipdom];
114 varargin{i}=reshape(varargin{i},tosize([pdoms{i} cat(2,newsizes{mapord}) zipdom]));
115 newsizes=mapdoms; newsizes{i}=outer1{i};
116 newsize=[to1s(pdoms{i}) cat(2,newsizes{mapord}) to1s(zipdom)];
117 varargin{i}=repmat(varargin{i},tosize(newsize));
118 % size(varargin{i})
119 end
120
121 [varargout{1:max(1,nargout)}]=feval(f,varargin{:});
122
123 function [a,b,c]=split(x,n,m)
124 a=x(1:n);
125 b=x(n+1:end-m);
126 c=x(end-m+1:end);
127
128 % getargs - get values of optional parameters with defaults
129 %
130 % getargs :: {I:[N]->(A(I) | nil)}, {I:[N]->A(I)} -> A(1), ..., A(N).
131
132 function varargout=getargs(args,defs)
133
134 nout=max(length(args),length(defs));
135 varargout=defs;
136 for i=1:length(args)
137 if ~isempty(args{i}), varargout(i)=args(i); end
138 end
139