Search code examples
matlabsymbolic-math

Speeding up symbolic recursion in 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;

Solution

  • 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.