Refactor a function in MATLAB to accept originally scalar argument as an array - matlab

Imagine you have a function in MATLAB of two variables, f(x,y). And it was written in a way that x can be a scalar or a 1D array, but y is strictly meant to be a scalar. For an array x the function returns the array of elements of the same length as x.
Next, you need to refactor this function to accept both x and y to be 1D arrays of equal length, the value of a function f([x1,x1],[y1,y2]) = [f(x1,y1), f(x2,y2)].
How would you do this in the most efficient way?

This is exactly suited for arrayfun:
f = #(x,y) y*x.^2; %an example where y should not be an array, x can
fnew = #(xv,yv) arrayfun(f,xv,yv);
This new function will return f(xv(k),yv(k)) for each k as long as the two arrays are of the same size.
Note that arrayfun is quite slow, so using a loop to the same effect or implementing the vectorization for the specific function f (possibly making use of bsxfun) might be faster.
The explicit looping alternative would be
function fv=fnew(xv,yv)
if numel(xv)~=numel(yv)
exit('fnew> xv and yv should have same length');
end
fv=zeros(size(xv));
for k=1:numel(xv)
fv(k) = f(xv(k),yv(k));
end
You can spare some runtime by skipping the size check, similar checks are the reason why arrayfun tends to be slower.

Related

Use of 'ArrayValued' in Matlab numerical integration

Why when performing numerical integration in Matlab with integral does this case need 'ArrayValued' to be set to true:
f = #(x) 5;
integral(f,0,2,'ArrayValued',true)
... while in this case the option isn't needed?:
f = #(x) x;
integral(f,0,2)
From the documentation for integral describing the integrand argument:
For scalar-valued problems, the function y = fun(x) must accept a
vector argument, x, and return a vector result, y. This generally
means that fun must use array operators instead of matrix operators.
For example, use .* (times) rather than * (mtimes). If you set the
'ArrayValued' option to true, then fun must accept a scalar and return
an array of fixed size.
So, a constant function like f = #(x) 5 does not return a result the same size as x if x is a vector. The integral function requires this because under the hood it is vectorized for scalar functions for performance – it actually evaluates the integrand at multiple points simultaneously with a single function call.
You can make your constant function compliant and not require 'ArrayValued' to be true with something like this:
f = #(x) 5+0*x;
integral(f,0,2)

bsxfun doesn't work as I expect on a constant function

In Matlab R2016a, I have a large set of small X-vectors and Y-vectors which are paired (e.g. 10,000 1x3 X-vectors paired with 10,000 1x3 Y vectors). For each {X,Y} pair, I want to calculate a 2-scalar-argument function for every pairwise combination of the elements in X and Y, (so in my example I would get 10,000 3x3 matrices).
I thought I could use bsxfun to perform these calculations, but it doesn't work when I try to do some simple tests. bsxfun(#(x,y) x*y,[1 2],[1 2]') returns:
ans =
1 2
2 4
Which is what I would expect. However, bsxfun(#(x,y) 1,[1 2],[1 2]') returns:
Error using bsxfun
Specified function handle produces invalid output dimensions. The function handle
must be a binary elementwise function.
Which makes no sense. The function handle is a binary elementwise function that always returns the scalar 1, so bsxfun should give the same result as ones(2,2), unless I'm not understanding how bsxfun works.
The inputs to the function handle that are passed to bsxfun are not scalars. In versions prior to R2016b, the inputs are either scalar or they are the same size.
FUNC can also be a handle to any binary element-wise function not listed
above. A binary element-wise function in the form of C = FUNC(A,B)
accepts arrays A and B of arbitrary but equal size and returns output
of the same size. Each element in the output array C is the result
of an operation on the corresponding elements of A and B only. FUNC must
also support scalar expansion, such that if A or B is a scalar, C is the
result of applying the scalar to every element in the other input array.
In releases since R2016b, they do not have to be equal sizes, but should be compatible sizes
In the example you have shown, the first input to the function handle is a scalar and the second is a vector (y) and the function is evaluated for every element of x and the output is expected to be the size of y
In the case you've posted, the call to bsxfun is essentially the equivalent of:
x = [1 2];
y = [1 2].';
yourfunc = #(x,y)x * y;
for k = 1:numel(x)
output(:,k) = yourfunc(x(k), y)
end
If you want to return a 1 for every entry, you need to replace your function with something that yields the appropriately sized output.
bsxfun(#(x,y)ones(max(size(x), size(y))), [1 2], [1 2]')
How you formulate the function handle really depends upon your specific problem

Matlab - make function work with vectors and scalars

I'm trying to write a function that can be a scalar and a vector. This function should handle scalars, column vector and row vector. My thought was that I try with a for loop but I'm not successful in my attempts.
function f=funk1(x);
for i =1:length(x)
f=exp(-3*x(i).^2)-log(x(i)+0.6)+1/(x(i)-6);
end
end
This only return the last value of the vector (in my function) but I want it to return every value from my vector.
For this specific function you can vectorize, that is, do all computations element-wise at once without a loop. You only need to add a dot before *, ^, / operators when applied between arrays (log and exp are element-wise already):
function f = funk1(x);
f = exp(-3*x.^2)-log(x+0.6)+1./(x-6);
end
You missed indexing of f. Do that;
function f=funk1(x);
for i =1:length(x)
f(i)=exp(-3*x(i).^2)-log(x(i)+0.6)+1/(x(i)-6);
end
end

MATLAB: bsxfun unclear. Want to accelerate minimum distance between segments

Using MATLAB,
Imagine a Nx6 array of numbers which represent N segments with 3+3=6 initial and end point coordinates.
Assume I have a function Calc_Dist( Segment_1, Segment_2 ) that takes as input two 1x6 arrays, and that after some operations returns a scalar, namely the minimal euclidean distance between these two segments.
I want to calculate the pairwise minimal distance between all N segments of my list, but would like to avoid a double loop to do so.
I cannot wrap my head around the documentation of the bsxfun function of MATLAB, so I cannot make this work. For the sake of a minimal example (the distance calculation is obviously not correct):
function scalar = calc_dist( segment_1, segment_2 )
scalar = sum( segment_1 + segment_2 )
end
and the main
Segments = rand( 1500, 6 )
Pairwise_Distance_Matrix = bsxfun( #calc_dist, segments, segments' )
Is there any way to do this, or am I forced to use double loops ?
Thank you for any suggestion
I think you need pdist rather than bsxfun. pdist can be used in two different ways, the second of which is applicable to your problem:
With built-in distance functions, supplied as strings, such as 'euclidean', 'hamming' etc.
With a custom distance function, a handle to which you supply.
In the second case, the distance function
must be of the form
function D2 = distfun(XI, XJ),
taking as arguments a 1-by-N vector XI containing a single row of X, an
M2-by-N matrix XJ containing multiple rows of X, and returning an
M2-by-1 vector of distances D2, whose Jth element is the distance
between the observations XI and XJ(J,:).
Although the documentation doesn't tell, it's very likely that the second way is not as efficient as the first (a double loop might even be faster, who knows), but you can use it. You would need to define your function so that it fulfills the stated condition. With your example function it's easy: for this part you'd use bsxfun:
function scalar = calc_dist( segment_1, segment_2 )
scalar = sum(bsxfun(#plus, segment_1, segment_2), 2);
end
Note also that
pdist works with rows (not columns), which is what you need.
pdist reduces operations by exploiting the properties that any distance function must have. Namely, the distance of an element to itself is known to be zero; and the distance for each pair can be computed just once thanks to symmetry. If you want to arrange the output in the form of a matrix, use squareform.
So, after your actual distance function has been modified appropriately (which may be the hard part), use:
distances = squareform(pdist(segments, #calc_dist));
For example:
N = 4;
segments = rand(N,6);
distances = squareform(pdist(segments, #calc_dist));
produces
distances =
0 6.1492 7.0886 5.5016
6.1492 0 6.8559 5.2688
7.0886 6.8559 0 6.2082
5.5016 5.2688 6.2082 0
Unfortunately I don't see any "smarter" (i.e. read faster) solution than the double loop. For speed consideration I'd organize the points as a 6×N array, not the other way, because column access is way faster than row access in MATLAB.
So:
N = 150000;
Segments = rand(6, N);
Pairwise_Distance_Matrix = Inf(N, N);
for i = 1:(N-1)
for j = (i+1):N
Pairwise_Distance_Matrix(i,j) = calc_dist(Segments(:,i), Segments(:,j));
end;
end;
Minimum_Pairwise_Distance = min(min(Pairwise_Distance_Matrix));
Contrary to common wisdom, explicit loops are faster now in MATLAB compared to the likes of arrayfun, cellfun or structfun; bsxfun beats everything else in terms of speed, but it doesn't apply to your case.

Apply function to every pair of columns in two matrices in MATLAB

In MATLAB, I'd like to apply a function to every pair of column vectors in matrices A and B. I know there must be an efficient (non for) way of doing this, but I can't figure it out. The function will output a scalar.
Try
na = size(A,1);
nb = size(B,1);
newvector = bsxfun(#(j,k)(func(A(j,:),B(k,:))),1:na,(1:nb)');
bsxfun performs singleton expansion on 1:na and (1:nb)'. The end result, in this case, is that func will be applied to every pair of column vectors drawn from A and B.
Note that bsxfun can be tricky: it can require that the applied function support singleton expansion itself. In this case it will work to do the job you want.
Do you mean pairwise? So in a for-loop the function will work as scalar_val = func(A(i),B(i))?
If A and B have the same size you can apply ARRAYFUN function:
newvector = arrayfun(#(x) func(A(x),B(x)), 1:numel(A));
UPDATE:
According your comment you need to run all combinations of A and B as scalar_val = func(A(i), B(j)). This is a little more complicated and for large vectors can fill the memory quickly.
If your function is one of standard you can try using BSXFUN:
out = bsxfun(#plus, A, B');
Another way is to use MESHGRID and ARRAYFUN:
[Am, Bm] = meshgrid(A,B);
out = arrayfun(#(x) func(Am(x),Bm(x)), 1:numel(Am));
out = reshape(out, numel(A), numel(B));
I believe it should work, but I don't have time to test it now.