How can I write this in a more dynamic way? - matlab

As the title says, I'm looking for making a dynamic version of this one but i can't figure out how to do this. In this case r=30 and the difference between the cases is 5 (i<=5, i<=10, 1<=15 etc) Can somebody help me ?
for i = 1:r
if i <= 5
a_m_o(length(a_m_o)+1) = m_o(i) - m_o(1)
elseif i <= 10
a_m_o(length(a_m_o)+1) = m_o(i) - m_o(6)
elseif i <= 15
a_m_o(length(a_m_o)+1) = m_o(i) - m_o(11)
elseif i <= 20
a_m_o(length(a_m_o)+1) = m_o(i) - m_o(16);
elseif i <= 25
a_m_o(length(a_m_o)+1) = m_o(i) - m_o(21);
elseif i <= 30
a_m_o(length(a_m_o)+1) = m_o(i) - m_o(26);
end
if a_m_o(i)<0
a_m_o(i) = a_m_o(i) + 400;
end
end

Other answers guide you towards avoiding code duplication. But the other thing you need to do is vectorize your code. Vectorizing is a way to avoid loops, which are relatively slow in MATLAB (even though they are much faster nowadays than they used to be when I started using MATLAB).
We'll start with creating an array that indexes m_o the way you do in your loop:
I = floor(((1:r)-1)/5)*5+1;
This creates an array [1,1,1,1,1,6,6,6,6,6,11,11,...]. You can also use repmat or mod as suggested in the other answers.
Indexing m_o using I (m_o(I)) is the same as writing [m_o(1),m_o(1),m_o(1),m_o(1),m_o(1),m_o(6),m_o(6),m_o(6),...]. That is, we're indexing the same element 5 times, creating 5 copies of that element in the output array. So now you can write:
a_m_o = a_m - a_m(I);
Your test for negative results can also be vectorized:
J = a_m_o < 0;
a_m_o(J) = a_m_o(J) + 400;
Besides that vectorized code is faster, it's often also a lot easier to read!
Note also that your code a_m_o(length(a_m_o)+1) = ... is very inefficient: the array a_m_o is resized every loop iteration. Newer MATLABs actually identify this use case and optimize around it, but it's still much faster to pre-allocate the array:
a_m_o = zeros(size(m_o));
for i = 1:r
a_m_o(i) = a_m(i) - a_m(1);
end
For r=30 you might not notice the difference, but for larger arrays the savings can be huge.

Basically, you group the natural numbers (without zero) in groups of 5 (or n) elements en map each element in a group to its first element. Such an operation can typically be done using the modulus:
function k = map (i)
n = 5;
m = mod(i, n);
if m == 0
m = 5;
end
k = i - m + 1;
end
This will map 1, 2, 3, 4 and 5 to 1; 6, 7, 8, 9 and 10 to 6; ...

r_help=repmat([1:5:length(r)],[5,1]); %create a helper vector which got the indices in it which you need.
r_help=r_help(:);
for i = 1:r
a_m_o(length(a_m_o)+1) = m_o(i) - m_o(r_help(i));
if a_m_o(i)<0
a_m_o(i) = a_m_o(i) + 400;
end
end

Related

How to break loop if number repeats -Matlab

I recognized this is a quite hard problem for me. I asked this problem on official Matlab side but no-one could help me either so maybe someone of you can come up with an outstanding approach.
In detail my Problem consist of:
N = 100 %some number
G = 21 %random guess < N
for x = 1:N;
a = mod(G^x,N);
end
Now I want the calculation of a to stop, if a number repeats.
For example: a = 1, 2, 3, 1 -break
Seems simple but I just can't handle it right after many tries.
For instance I've put:
for x = 1:N
a = mod(G^x,N);
b = unique(a);
if a ~= b
break
end
end
but doesn't seem to work bc. it's not element wise I guess.
This approach keeps a running log of the past Results and uses the ismember() function to check if the current value of a has been previously seen.
clc;
N = 100; %some number
G = 21; %random guess < N
Results = NaN(1,N);
for x = 1:N
a = mod(G^x,N);
disp(a);
if ismember(a,Results)
disp("-break");
break
end
Results(x) = a;
end
Ran using MATLAB R2019b

Solving probability problems with MATLAB

How can I simulate this question using MATLAB?
Out of 100 apples, 10 are rotten. We randomly choose 5 apples without
replacement. What is the probability that there is at least one
rotten?
The Expected Answer
0.4162476
My Attempt:
r=0
for i=1:10000
for c=1:5
a = randi(1,100);
if a < 11
r=r+1;
end
end
end
r/10000
but it didn't work, so what would be a better way of doing it?
Use randperm to choose randomly without replacement:
A = false(1, 100);
A(1:10) = true;
r = 0;
for k = 1:10000
a = randperm(100, 5);
r = r + any(A(a));
end
result = r/10000;
Short answer:
Your problem follow an hypergeometric distribution (similar to a binomial distribution but without replacement), if you have the necessary toolbox you can simply use the probability density function of the hypergeometric distribution:
r = 1-hygepdf(0,100,10,5) % r = 0.4162
Since P(x>=1) = P(x=1) + P(x=2) + P(x=3) + P(x=4) + P(x=5) = 1-P(x=0)
Of course, here I calculate the exact probability, this is not an experimental result.
To get further:
Noticed that if you do not have access to hygepdf, you can easily write the function yourself by using binomial coefficient:
N = 100; K = 10;
n = 5; k = 0;
r = 1-(nchoosek(K,k)*nchoosek(N-K,n-k))/nchoosek(N,n) % r = 0.4162
You can also use the binomial probability density function, it is a bit more tricky (but also more intuitive):
r = 1-prod(binopdf(0,1,10./(100-[0:4])))
Here we compute the probability to obtain 0 rotten apple five time in a row, the probabily increase at every step since we remove 1 good juicy apple each time. And then, according to the above explaination, we take 1-P(x=0).
There are a couple of issues with your code. First of all, implicitly in what you wrote, you replace the apple after you look at it. When you generate the random number, you need to eliminate the possibility of choosing that number again.
I've rewritten your code to include better practices:
clear
n_runs = 1000;
success = zeros(n_runs, 1);
failure = zeros(n_runs, 1);
approach = zeros(n_runs, 1);
for ii = 1:n_runs
apples = 1:100;
a = randperm(100, 5);
if any(a < 11)
success(ii) = 1;
elseif a >= 11
failure(ii) = 1;
end
approach(ii) = sum(success)/(sum(success)+sum(failure));
end
figure; hold on
plot(approach)
title("r = "+ approach(end))
hold off
The results are stored in an array (called approach), rather than a single number being updated every time, which means you can see how quickly you approach the end value of r.
Another good habit is including clear at the beginning of any script, which reduces the possibility of an error occurring due to variables stored in the workspace.

How to implement parallel-for in a 4 level nested for loop block

I have to calculate the std and mean of a large data set with respect to quite a few models. The final loop block is nested to four levels.
This is what it looks like:
count = 1;
alpha = 0.5;
%%%Below if each individual block is to be posterior'd and then average taken
c = 1;
for i = 1:numel(writers) %no. of writers
for j = 1: numel(test_feats{i}) %no. of images
for k = 1: numel(gmm) %no. of models
for n = 1: size(test_feats{i}{j},1)
[~, scores(c)] = posterior(gmm{k}, test_feats{i}{j}(n,:));
c = c + 1;
end
c = 1;
index_kek=find(abs(scores-mean(scores))>alpha*std(scores));
avg = mean(scores(index_kek)); %using std instead of mean... beacause of ..reasons
NLL(count) = avg;
count = count + 1;
end
count = 1; %reset count
NLL_scores{i}(j,:) = NLL;
end
fprintf('***score for model_%d done***\n', i)
end
It works and gives the desired result but it takes 3 days to give me the final calculation, even on my i7 processor. During processing the task manager tells me that only 20% of the cpu is being used, so I would rather put more load on the cpu to get the result faster.
Going by the official help here if I suppose want to make the outer most loop a parfor while keeping the rest normal for all I have to do is to insert integer limits rather than function calls such as size or numel.
So making these changes the above code will become:
count = 1;
alpha = 0.5;
%%%Below if each individual block is to be posterior'd and then average taken
c = 1;
num_writers = numel(writers);
num_images = numel(test_feats{1});
num_models = numel(gmm);
num_feats = size(test_feats{1}{1},1);
parfor i = 1:num_writers %no. of writers
for j = 1: num_images %no. of images
for k = 1: num_models %no. of models
for n = 1: num_feats
[~, scores(c)] = posterior(gmm{k}, test_feats{i}{j}(n,:));
c = c + 1;
end
c = 1;
index_kek=find(abs(scores-mean(scores))>alpha*std(scores));
avg = mean(scores(index_kek)); %using std instead of mean... beacause of ..reasons
NLL(count) = avg;
count = count + 1;
end
count = 1; %reset count
NLL_scores{i}(j,:) = NLL;
end
fprintf('***score for model_%d done***\n', i)
end
Is this the most optimum way to implement parfor in my case? Can it be improved or optimized further?
I couldn't test in Matlab for now but it should be close to a working solution. It has a reduced number of loops and changes a few implementation details but overall it might perform just as fast (or even slower) as your earlier code.
If gmm and test_feats take lots of memory then it is important that parfor is able to determine which peaces of data need to be delivered to which workers. The IDE should warn you if inefficient memory access is detected. This modification is especially useful if num_writers is much less than the number of cores in your CPU, or if it is only slightly larger (like 5 writers for 4 cores would take about as long as 8 writers).
[i_writer i_image i_model] = ndgrid(1:num_writers, 1:num_images, 1:num_models);
idx_combined = [i_writer(:) i_image(:) i_model(:)];
n_combined = size(idx_combined, 1);
NLL_scores = zeros(n_combined, 1);
parfor i_for = 1:n_combined
i = idx_combined(i_for, 1)
j = idx_combined(i_for, 2)
k = idx_combined(i_for, 3)
% pre-allocate
scores = zeros(num_feats, 1)
for i_feat = 1:num_feats
[~, scores(i_feat)] = posterior(gmm{k}, test_feats{i}{j}(i_feat,:));
end
% "find" is redundant here and performs a bit slower, might be insignificant though
index_kek = abs(scores - mean(scores)) > alpha * std(scores);
NLL_scores(i_for) = mean(scores(index_kek));
end

Matlab simple matrix manip

I'm new to Matlab and I want to achieve a very simple operation : I have a 792 x 1046 uint8 matrix called mg and want to convert its cells values (from 0 to 255) to values between 1 and 4 (1,2,3,4) in a new matrix called mgc accordingly to simple conditions.
Strangely, the new matrix is filled with only 1s and 2s but not any 3s or 4s...
Here is my code :
[x,y]=size(mg);
mgc = zeros(x,y);
for i=1:x
for j=1:y
if (mg(i,j)<=100)
mgc(i,j)=1;
elseif (100<mg(i,j)<=110)
mgc(i,j)=2;
elseif (110<mg(i,j)<=120)
mgc(i,j)=3;
else
mgc(i,j)=4;
end
end
end
If anyone could help me solve this stupid issue, it would be great !
THX
You shouldn't use expressions such as 100<mg(i,j)<=110 in MATLAB. Instead, use something like 100<mg(i,j) && mg(i,j)<=110.
At the moment, MATLAB is evaluating the expression 100<mg(i,j)<=110 as (100<mg(i,j))<=110. (100<mg(i,j)) is going to be either one or zero (true or false), and therefore will always be <=110. So it never gets past the second else, and your array is all either 1 or 2.
Edit: although this answer explains the specific issue you're having, you should probably instead be using logical indexing, which would be much more efficient than a double for loop (and more idiomatic in MATLAB). See the answers from #excaza or #Benoit_11 for examples of that).
As stated in the comments you need to use logical operators in your elseif statements. Just so you know, you can vectorize this whole for loop with those same logical operators as follows:
Let's define mgc2 as you did for mgc:
mgc2 = zeros(x,y);
Then you can fill mgc2 like this:
mgc2(mg<=100) =1;
mgc2(mg>100 & mg<=110) =2;
mgc2(mg>110 & mg <=120) =3;
mgc2(mg>120) =4;
You need to use and operators:
% Dummy data
mg = [10 115; 125 140];
[x,y]=size(mg);
mgc = zeros(x,y);
for i=1:x
for j=1:y
if (mg(i,j)<=100)
mgc(i,j)=1;
elseif (100 < mg(i,j) && mg(i,j) <= 110)
mgc(i,j)=2;
elseif (110 < mg(i,j) && mg(i,j) <= 120)
mgc(i,j)=3;
else
mgc(i,j)=4;
end
end
end
Returns:
mgc =
1 3
4 4
You also don't need to use a loop here, and can leverage MATLAB's logical indexing instead:
% Dummy data
mg = [10 115; 125 140];
mgc = zeros(size(mg));
mgc(mg <= 100) = 1;
mgc((mg > 100 & mg <= 110)) = 2;
mgc((mg > 110 & mg <= 120)) = 3;
mgc(mg > 120) = 4;
Which returns the same matrix.
This is because any value greater than 100 will return true for the first elseif statement.
100 < my(i,j) returns 1.
When you want to do a double condition, you must use the & operator otherwise you may have false statements
>> x = 4
>> res = 2<x<=3
res =
1
%%Using the `&` operator instead
>> res = 2<x && x<=3
res =
0

Removing duplicate entries in a vector, when entries are complex and rounding errors are causing problems

I want to remove duplicate entries from a vector on Matlab. The problem I'm having is that rounding errors are stopping the inbuilt Matlab function 'unique' from working properly. Ideally I'd like a way to set some sort of tolerance on the 'unique' function, or a small procedure that will remove the duplicates otherwise. If both the real and imaginary parts of two entries differ by less than 0.0001, then I'm happy to consider them equal. How can I do this?
Any help will be greatly appreciated. Thanks
A simple approximation would be to round the numbers and the use the indices returned by unique:
X = ... (input vector)
[b, i] = unique(round(X / (tolerance * (1 + i))));
output = X(i);
(you can probably replace b with ~ depending on your Matlab version).
it won't quite have the behaviour you want, since it is possible that two numbers are very close but will be rounded differently. I think you could mitigate this by doing:
X = ... (input vector)
[b, ind] = unique(round(X / (tolerance * (1 + i))));
X = X(ind);
[b, ind] = unique(round(X / (tolerance * (1 + i)) + 0.5 * (1 + i)));
X = X(ind);
This will round them twice, so any numbers that are exactly on a rounding boundary will be caught by the second unique.
There is still some messiness in this - some numbers will be affected as though the tolerance was doubled. But it might be sufficient for your needs.
The alternative is probably a for loop:
X = sort(X);
last = X(1);
indices = ones(numel(X), 1);
for j=2:numel(X)
if X(j) > last + tolerance * (1 + i)
last = X(j) + tolerance * (1 + i) / 2;
else
indices(j) = 0;
end
end
X = X(logical(indices));
I think this has the best behaviour you can expect (because you want to represent the vector by as few unique values as possible - when there are lots of numbers that differ by less than the tolerance level, there may be multiple ways of splitting them. This algorithm does so greedily, starting with the smallest).
I'm almost certain the below ill always assume any values closer than 1e-8 are equal. Simply replace 1e-8 with whatever value you want.
% unique function that assumes 1e-8 is equal
function [out, I] = unique(input, first_last)
threshold = 1e-8;
if nargin < 2
first_last = 'last';
end
[out, I] = sort(input);
db = diff(out);
k = find(abs(db) < threshold);
if strcmpi(first_last, 'last')
k2 = min(I(k), I(k+1));
elseif strcmpi(first_last, 'first')
k2 = max(I(k), I(k+1));
else
error('unknown flag option for unique, must be first or last');
end
k3 = true(1, length(input));
k3(k2) = false;
out = out(k3(I));
I = I(k3(I));
end
The following might serve your purposes. Given X, an array of complex doubles, it sorts them, then checks whether the absolute value differences between elements is within the complex tolerance, real_tol and imag_tol. It removes elements that satisfy this tolerance.
function X_unique = unique_complex_with_tolerance(X,real_tol,imag_tol)
X_sorted = sort(X); %Sorts by magnitude first, then imaginary part.
dX_sorted = diff(X_sorted);
dX_sorted_real = real(dX_sorted);
dX_sorted_imag = imag(dX_sorted);
remove_idx = (abs(dX_sorted_real)<real_tol) & (abs(dX_sorted_imag)<imag_tol);
X_unique = X_sorted;
X_unique(remove_idx) = [];
return
Note that this code will remove all elements which satisfy this difference tolerance. For example, if X = [1+i,2+2i,3+3i,4+4i], real_tol = 1.1, imag_tol = 1.1, then this function will return only one element, X_unique = [4+4i], even though you might consider, for example, X_unique = [1+i,4+4i] to also be a valid answer.