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