Speeding up symbolic recursion in Matlab - matlab

I have a backwards recursion for a binomial tree. At each node an unknown C enters in such a way that at the starting node we get a formula, A(1,1), that depends upon C. The code is as follows:
A=sym(zeros(1,Steps));
B=zeros(1,Steps);
syms C; % The unknown that enters A at every node
tic
for t=Steps-1:-1:1
% Values needed in A and B
Lambda=1-exp(-(1./S(t,1:t).^b).*h);
Q=((1./D(t))./(1-Lambda)-d)/(u-d);
R=normcdf(a0+a1*Lambda);
% the backward recursion for A and B
A(1:t)=D(t)*C+D(t)*...
(Q.*(1-Lambda).*A(1:t) ...
+ (1-Q).*(1-Lambda).*A(2:t+1));
B(1:t)=Lambda.*(1-R)+D(t)*...
(Q.*(1-Lambda).*B(1:t)...
+ (1-Q.*(1-Lambda).*B(2:t+1)));
end
C = solve(A(1,1)==sym(B(1,1)),C);
This code takes around 4 seconds if Steps = 104. If however we remove C and set matrix A to a regular double matrix, it only takes about 0.02 seconds. Using syms thus increases the calculation time by a factor 200. This seems too much to me. Any suggestions into speeding this up?
I am using Matlab 2013b on a MacBook air 13-inch spring 2013. Furthermore, if you're interested in the code before the above part (not sure whether it is relevant):
a0 = 0.9;
a1 = -3.2557;
b = 1.2594;
S0=18.57;
sigma=0.6579;
h=1/104;
T=1;
Steps=T/h;
f=transpose(normrnd(0.04, 0.001 [1 pl]));
D=exp(-h*f); % discount values
pl=T/h; % pathlength - amount of steps in maturity
u=exp(sigma*sqrt(h));
d=1/u;
u_row = repmat(cumprod([1 u*ones(1,pl-1)]),pl,1);
d_row = cumprod(tril(d*ones(pl),-1)+triu(ones(pl)),1);
path = tril(u_row.*d_row);
S=S0*path;

Unless I'm missing something, there's no need to use symbolic math or use an unknown variable. You can effectively assume that C = 1 in your recursion relation and solve for the actual value at the end. Here's the full code with some other improvements:
rng(1); % Always seed your random number generator
a0 = 0.9;
a1 = -3.2557;
b = 1.2594;
S0 = 18.57;
sigma = 0.6579;
h = 1/104;
T = 1;
Steps = T/h;
pl = T/h;
f = 0.04+0.001*randn(pl,1);
D = exp(-h*f);
u = exp(sigma*sqrt(h));
d = 1/u;
u_row = repmat(cumprod([1 u*ones(1,pl-1)]),pl,1);
d_row = cumprod(tril(d*ones(pl),-1)+triu(ones(pl)),1);
pth = tril(u_row.*d_row);
S = S0*pth;
A = zeros(1,Steps);
B = zeros(1,Steps);
tic
for t = Steps-1:-1:1
Lambda = 1-exp(-h./S(t,1:t).^b);
Q = ((1./D(t))./(1-Lambda)-d)/(u-d);
R = 0.5*erfc((-a0-a1*Lambda)/sqrt(2)); % Faster than normcdf
% Backward recursion for A and B
A = D(t)+D(t)*(Q.*(1-Lambda).*A(1:end-1) + ...
(1-Q).*(1-Lambda).*A(2:end));
B = Lambda.*(1-R)+D(t)*(Q.*(1-Lambda).*B(1:end-1) + ...
(1-Q.*(1-Lambda).*B(2:end)));
end
C = B/A
toc
This take about 0.005 seconds to run on my MacBook Pro. There are certainly other improvements you could make. There are many combinations of variables that are used in multiple places (e.g., 1-Lambda or D(t)*(1-Lambda)), that could be calculated once. Matlab may try to optimize this a bit. And you can try moving Lambda, Q, and R out of the loop – or at least calculate parts of them outside and save the results in arrays.

Related

How do I implement Neumann series iteration to approximate Ax = b?

I am working on MatLab problems from my textbook and one of the problems (as an example of Neumann series iteration) asks me to follow the pseudocode below:
INPUT: A n x n matrix, b n x 1 vector, T a positive integer
OUTPUT: An approximation y of x after T iterations
STEP 1: Set y = zeros(n,1)
STEP 2: Set M = eye(n) - A
STEP 2: For i = 1,2,...,T do STEP 3
STEP 3: Set y = M*y + b
STEP 4: OUTPUT(y)
I am trying to find the smallest value of T such that the largest entry of the vector Ay - b in absolute value is less than the tolerance I set (the variable e as shown below). I then save T and E (the largest entry in absolute value of Ay - b).
function [T,E] = neumann(A,b,e)
n = size(A);
y = zeros(n(1,1),1);
M = eye(n(1,1)) - A;
t = 10000;
for ii = 1:t
y = M*y + b;
if max(abs(A*y - b)) < e
T = t;
E = max(abs(A*y - b));
break
end
end
end
A = [1.1,.2,-.2,.5;
.2,.9,.5,.3;
.1,0.,1.,.4;
.1,.1,.1,1.2];
b = [1;0;1;0];
[T_2, E_2] = neumann(A,b,1e-2);
[T_4, E_4] = neumann(A,b,1e-4);
[T_6, E_6] = neumann(A,b,1e-6);
output = [T_2, E_2; T_4, E_4; T_6, E_6];
Instead of getting the smallest possible T, the for loop goes through all of the iterations even though I used the break statement to end the loop's execution once the condition was met. I can't really figure out what's wrong with my loop. I followed the pseudocode as closely as possible. Any feedback or suggestions is appreciated, thank you in advance.
You always set T = t, you've perhaps forgotten what t is.
You define t = 10000 on line 5 of the neumann function, this doesn't change so your output T is always 10000.
Instead, I assume you wanted T = ii;, as ii is the current time step when the threshold is reached.

Sweeping initial conditions for a set of ODEs using parfor

I am currently trying to use parfor to sweep across a range of initial conditions for a set of differential equations solved by ode45. The code works fine using two nested for loops but I was hoping parfor could make the process more efficient. Unfortunately, I have run into an issue where the solver is able to solve one of the combinations in the matrix representing initial conditions across a range of variables, but the others seem to have their initial values all set at 0, instead of the values specified by the initial conditions. It may have something to do with the fact that I need to create a matrix of zeros ('P') that the results will be written into, perhaps overwriting the initial conditions(?) Any help would be greatly appreciated.
Thanks,
Kyle
function help(C, R)
A = 0.01;
B = 0.00;
C = [0.001,0.01];
D = 0.00;
R = [1e-10,1e-9];
[CGrid,RGrid] = meshgrid(C,R);
parfor ij = 1 : numel(CGrid)
c2 = [A; B; CGrid(ij); D; RGrid(ij)];
[t,c] = ode45('ode_sys',[0:1:300],c2);
for k=1:length([0:1:300])
for l=1:length(c2)
if c(k,l)<0
c(k,l)=0;
end
end
end
P = zeros(301,5,numel(R),numel(C));
temp = zeros(301,5);
temp(:,1) = c(:,1);
temp(:,2) = c(:,2);
temp(:,3) = c(:,3);
temp(:,4) = c(:,4);
temp(:,5) = c(:,5);
P(:,:,ij)=temp;
parsave('data.mat', P);
end
end
You have one error, and a few opportunities to simplify the code.
In the parfor loop, you have this line P = zeros(301,5,numel(R),numel(C)); which overwrites P with all zeros at each iteration. Put this before the parfor loop.
The first double-for loop that makes negative elements of c zero can be done using max(c,0), which should be more efficient. You can also do P(:,:,ij)=c(:,1:5) directly.
So you can replace your parfor loop with
P = zeros(301,5,numel(R),numel(C));
for ij = 1 : numel(CGrid)
c2 = [A; B; CGrid(ij); D; RGrid(ij)];
[t,c] = ode45('ode_sys',0:300,c2);
c = max(c,0);
P(:,:,ij) = c(:,1:5);
parsave('data.mat',P);
end

MATLAB roots function different behavior in MATLAB then Simulink?

I implemented the following user defined function in MATLAB:
function Q = Calc_Q(Head, freq)
b6 = [3.7572E-07 -1.5707E-05 6.0490E-03 5.0018E-02 2.1180E-01];
b5 = [-9.0927E-06 8.9033E-04 -3.2415E-02 5.4525E-01 -8.1649E+00] / 10e2;
b4 = [7.5172E-06 -5.6565E-04 1.0024E-02 3.5888E-01 3.8894E-02] / 10e5;
b3 = [-4.8767E-06 4.8787E-04 -1.3311E-02 -1.2189E-01 -5.3522E+00] / 10e8;
b2 = [5.9227E-06 -8.1716E-04 3.5392E-02 -4.5413E-01 1.9547E+00] / 10e11;
b1 = [-2.0004E-06 2.9027E-04 -1.3754E-02 2.3490E-01 -1.2363E+00] / 10e14;
a = [polyval(b1,abs(freq)), polyval(b2, abs(freq)), polyval(b3, abs(freq)), polyval(b4, abs(freq)), polyval(b5, abs(freq)), polyval(b6, abs(freq)) - Head];
Q_roots = roots(a);
%Delete roots with imaginary part
i = 1;
while i <= length(Q_roots)
if(imag(Q_roots(i)) ~= 0)
Q_roots(i) = [];
i = i - 1;
end
i = i + 1;
end
%Delete roots with real part greater then 3100
i = 1;
while i <= length(Q_roots)
if(Q_roots(i) >= 3100 || Q_roots(i) < 0)
Q_roots(i) = [];
i = i - 1;
end
i = i +1;
end
if freq < 0
Q = real(Q_roots(1)) * -1;
else
Q = real(Q_roots(1));
end
end
When I Call this function in Matlab it works fine. However if I use this exact code as a MATLAB function in simulink it stop's working. (actually it works, but the ouput is always zero.)
I do have a suspicion of what the problem might be. When running the script in de-bugging mode, I cannot view a result for Q_roots (It just doesn't display anything).
Q_roots = roots(a);
Any ideas ?
The problem is most likely due to your logic that eliminates any roots that do not have exactly zero in the imaginary part. This is a mathematical way of thinking that does not really work well numerically, at least not in general. All the roots are probably being found in both cases (there is no limitation that implies otherwise), but in Simulink and in code generation the problem is treated as a complex one, and some roots might be coming back with tiny imaginary parts. Instead of deleting roots if their imaginary parts are not exactly zero, eliminate the roots with imaginary parts that are numerically insignificant, either very small relative to the real part or very small altogether. Something like
tol = 10*eps(class(Q_roots));
keepers = abs(imag(Q_roots)) < tol*max(abs(real(Q_roots)),1) & ...
real(Q_roots) >= 0 & real(Q_roots) <= 3100;
Q_roots = Q_roots(keepers);
would take care of all the deletions in one fell swoop. I used 10*eps as a tolerance here.
But if you only need the first qualifying root, then you could just do this:
Q = nan('like',a);
tol = 10*eps(class(a));
for k = 1:numel(Q_roots)
r = real(Q_roots(k));
if abs(imag(Q_roots(k))) < tol*max(abs(r),1) && r >= 0 && r <= 3100;
Q = r;
break
end
end
if freq < 0
Q = -Q;
end
Oke I found the problem.
From a different forum:
Hi Cosmin,
I took a look at the implementation of roots for the Embedded MATLAB
Function block (\toolbox\eml\lib\matlab\polyfun\roots.m).
It's stated there:
% Limitations: % Output is always variable size. % Output is
always complex. % Roots may not be in the same order as MATLAB. %
Roots of poorly conditioned polynomials may not match MATLAB. The last
sentence is what makes you the headache (and yes, your polynomial is
badly conditioned!). If you take a look at the plot you will see, that
the curve hardly touches the x-axis.
I have a suggestion though: the value -z/b is a (very) good
approximation of the root you are looking for ...?
Titus
http://www.mathworks.com/matlabcentral/answers/25624-roots-in-simulink
Apparently the root function in simulink does not always found all the roots of a given polynominal.
This is unfortunate and not easily solvable. I did however found a solution.
For all the different polynomials I have to solve, I know the interval of the root I am interested in ( [-3000, 3000]).
I just basically takes steps of 50 from -3000 to 3000, until the function drops below 0. I then know the approximate solution of the root. I use this approximation as seed for the Newton-Raphson method.
Straight implementation of the Newton raphson method with a given seed for all the polynomials I have to solve did not work because sometimes it iterated to a different root (one which i was not interested in.)
Here's the code:
function Q = Calc_Q(Head, freq)
b6 = [3.7572E-07 -1.5707E-05 6.0490E-03 5.0018E-02 2.1180E-01];
b5 = [-9.0927E-06 8.9033E-04 -3.2415E-02 5.4525E-01 -8.1649E+00] / 10e2;
b4 = [7.5172E-06 -5.6565E-04 1.0024E-02 3.5888E-01 3.8894E-02] / 10e5;
b3 = [-4.8767E-06 4.8787E-04 -1.3311E-02 -1.2189E-01 -5.3522E+00] / 10e8;
b2 = [5.9227E-06 -8.1716E-04 3.5392E-02 -4.5413E-01 1.9547E+00] / 10e11;
b1 = [-2.0004E-06 2.9027E-04 -1.3754E-02 2.3490E-01 -1.2363E+00] / 10e14;
%coeff for the polynominal
a = [polyval(b1,abs(freq)), polyval(b2, abs(freq)), polyval(b3, abs(freq)), polyval(b4, abs(freq)), polyval(b5, abs(freq)), polyval(b6, abs(freq)) - Head];
%coeff for the derrivative of polynominal
da = [5*a(1) 4*a(2) 3*a(3) 2*a(4) a(5)];
Q = -3000;
%Search for point where function goes below 0
while (polyval(a, Q) > 0)
Q = Q + 25;
end
error_max = 0.01
iter_counter = 1;
while abs(polyval(a,Q)) >= error_max && iter_counter <= 1000
Q = Q - polyval(a, Q)/polyval(da, Q);
iter_counter = iter_counter + 1;
error = abs(polyval(a,Q));
end
if(freq < 0)
Q = Q * - 1;
end
end

separate 'entangled' vectors in Matlab

I have a set of three vectors (stored into a 3xN matrix) which are 'entangled' (e.g. some value in the second row should be in the third row and vice versa). This 'entanglement' is based on looking at the figure in which alpha2 is plotted. To separate the vector I use a difference based approach where I calculate the difference of one value with respect the three next values (e.g. comparing (1,i) with (:,i+1)). Then I take the minimum and store that. The method works to separate two of the three vectors, but not for the last.
I was wondering if you guys can share your ideas with me how to solve this problem (if possible). I have added my coded below.
Thanks in advance!
Problem in figures:
clear all; close all; clc;
%%
alpha2 = [-23.32 -23.05 -22.24 -20.91 -19.06 -16.70 -13.83 -10.49 -6.70;
-0.46 -0.33 0.19 2.38 5.44 9.36 14.15 19.80 26.32;
-1.58 -1.13 0.06 0.70 1.61 2.78 4.23 5.99 8.09];
%%% Original
figure()
hold on
plot(alpha2(1,:))
plot(alpha2(2,:))
plot(alpha2(3,:))
%%% Store start values
store1(1,1) = alpha2(1,1);
store2(1,1) = alpha2(2,1);
store3(1,1) = alpha2(3,1);
for i=1:size(alpha2,2)-1
for j=1:size(alpha2,1)
Alpha1(j,i) = abs(store1(1,i)-alpha2(j,i+1));
Alpha2(j,i) = abs(store2(1,i)-alpha2(j,i+1));
Alpha3(j,i) = abs(store3(1,i)-alpha2(j,i+1));
[~, I] = min(Alpha1(:,i));
store1(1,i+1) = alpha2(I,i+1);
[~, I] = min(Alpha2(:,i));
store2(1,i+1) = alpha2(I,i+1);
[~, I] = min(Alpha3(:,i));
store3(1,i+1) = alpha2(I,i+1);
end
end
%%% Plot to see if separation worked
figure()
hold on
plot(store1)
plot(store2)
plot(store3)
Solution using extrapolation via polyfit:
The idea is pretty simple: Iterate over all positions i and use polyfit to fit polynomials of degree d to the d+1 values from F(:,i-(d+1)) up to F(:,i). Use those polynomials to extrapolate the function values F(:,i+1). Then compute the permutation of the real values F(:,i+1) that fits those extrapolations best. This should work quite well, if there are only a few functions involved. There is certainly some room for improvement, but for your simple setting it should suffice.
function F = untangle(F, maxExtrapolationDegree)
%// UNTANGLE(F) untangles the functions F(i,:) via extrapolation.
if nargin<2
maxExtrapolationDegree = 4;
end
extrapolate = #(f) polyval(polyfit(1:length(f),f,length(f)-1),length(f)+1);
extrapolateAll = #(F) cellfun(extrapolate, num2cell(F,2));
fitCriterion = #(X,Y) norm(X(:)-Y(:),1);
nFuncs = size(F,1);
nPoints = size(F,2);
swaps = perms(1:nFuncs);
errorOfFit = zeros(1,size(swaps,1));
for i = 1:nPoints-1
nextValues = extrapolateAll(F(:,max(1,i-(maxExtrapolationDegree+1)):i));
for j = 1:size(swaps,1)
errorOfFit(j) = fitCriterion(nextValues, F(swaps(j,:),i+1));
end
[~,j_bestSwap] = min(errorOfFit);
F(:,i+1) = F(swaps(j_bestSwap,:),i+1);
end
Initial solution: (not that pretty - Skip this part)
This is a similar solution that tries to minimize the sum of the derivatives up to some degree of the vector valued function F = #(j) alpha2(:,j). It does so by stepping through the positions i and checks all possible permutations of the coordinates of i to get a minimal seminorm of the function F(1:i).
(I'm actually wondering right now if there is any canonical mathematical way to define the seminorm so we get our expected results... I initially was going for the H^1 and H^2 seminorms, but they didn't quite work...)
function F = untangle(F)
nFuncs = size(F,1);
nPoints = size(F,2);
seminorm = #(x,i) sum(sum(abs(diff(x(:,1:i),1,2)))) + ...
sum(sum(abs(diff(x(:,1:i),2,2)))) + ...
sum(sum(abs(diff(x(:,1:i),3,2)))) + ...
sum(sum(abs(diff(x(:,1:i),4,2))));
doSwap = #(x,swap,i) [x(:,1:i-1), x(swap,i:end)];
swaps = perms(1:nFuncs);
normOfSwap = zeros(1,size(swaps,1));
for i = 2:nPoints
for j = 1:size(swaps,1)
normOfSwap(j) = seminorm(doSwap(F,swaps(j,:),i),i);
end
[~,j_bestSwap] = min(normOfSwap);
F = doSwap(F,swaps(j_bestSwap,:),i);
end
Usage:
The command alpha2 = untangle(alpha2); will untangle your functions:
It should even work for more complicated data, like these shuffled sine-waves:
nPoints = 100;
nFuncs = 5;
t = linspace(0, 2*pi, nPoints);
F = bsxfun(#(a,b) sin(a*b), (1:nFuncs).', t);
for i = 1:nPoints
F(:,i) = F(randperm(nFuncs),i);
end
Remark: I guess if you already know that your functions will be quadratic or some other special form, RANSAC would be a better idea for larger number of functions. This could also be useful if the functions are not given with the same x-value spacing.

What is wrong with my Simpson algorithm?

I was trying to write an algorithm to approximate integrals with Simpson's method. When I try to plot it in a loglog plot, however, I don't get the correct order of accuracy which is O(h^4) (I get O(n)). I can't find any errors though. This is my code:
%Reference solution with Simpson's method (the reference solution works well)
yk = 0;
yj = 0;
href = 0.0001;
mref = (b-a)/href;
for k=2:2:mref-1
yk=yk+y(k*href+a);
end
for j=1:2:mref
yj=yj+y(href*j+a);
end
Iref=(href/3)*(y(a)+y(b)+2*yk+4*yj);
%Simpson's method
iter = 1;
Ehmatrix = 0;
for n = 0:nmax
h = b/(2^n+1);
x = a:h:b;
xodd = x(2:2:end-1);
xeven = x(3:2:end);
yodd = y(xodd);
yeven = y(xeven);
Isimp = (h/3)*(y(x(1))+4*sum(yodd)+2*sum(yeven)+y(b));
E = abs(Isimp-Iref);
Ehmatrix([iter],1) = [E];
Ehmatrix([iter],2) = [h];
iter = iter + 1;
end
figure
loglog(Ehmatrix(:,2),Ehmatrix(:,1))
a and b are the integration limits and y is the integrand that we want to approximate.
Djamillah - your code looks fine though the initialization of h is probably valid only for the case where a==0, so you may want to change this line of code to
h = (b-a)/(2^n+1);
I wonder if x = a:h:b; will always be valid - sometimes b may be included in the list, and sometimes it might not be, depending upon h. You may want to reconsider and use linspace instead
x = linspace(a,b,2^n+1);
which will guarantee that x has 2^n+1 points distributed evenly in the interval [a,b]. h could then be initialized as
h = x(2)-x(1);
Also, when determining the even and odd indices, we need to ignore the last element of x for both even and odd. So instead of
xodd = x(2:2:end-1);
xeven = x(3:2:end);
do
xodd = x(2:2:end-1);
xeven = x(3:2:end-1);
Finally, rather than using a vector y (how is this set?) I might just use the function handle to the function that I'm integrating instead and replace the calculation above as
Isimp = delta/3*(func(x(1)) + 4*sum(func(xodd)) + 2*sum(func(xeven)) + ...
func(x(end)));
Other than these tiny things (which are probably negligible), there is nothing in your algorithm to indicate a problem. It produced similar results to a version that I have.
As for the order of convergence, should it be O(n^4) or O(h^4)?
Taking into account Geoff's suggestions, and making a few other changes, it all works as expected.
%Reference solution with Simpson's method (the reference solution works well)
a=0;
b=1;
y=#(x) cos(x);
nmax=10;
%Simpson's method
Ehmatrix = [];
for n = 0:nmax
x = linspace(a,b,2^n+1);
h = x(2)-x(1);
xodd = x(2:2:end-1);
xeven = x(3:2:end-1);
yodd = y(xodd);
yeven = y(xeven);
Isimp = (h/3)*(y(x(1))+4*sum(yodd)+2*sum(yeven)+y(b));
E = abs(Isimp-integral(y,0,1));
Ehmatrix(n+1,:) = [E h];
end
loglog(Ehmatrix(:,2),Ehmatrix(:,1))
P=polyfit(log(Ehmatrix(:,2)),log(Ehmatrix(:,1)),1);
OrderofAccuracy=P(1)
You were getting O(h) accuracy because xeven=x(3:2:end) was wrong. Replacing it by xeven=x(3:e:end-1) fixes the code, and thus the accuracy.