How to use a sparse Jacobian for a stiff ODE solver to avoid the error "array exceeds maximum array size preference"
9 views (last 30 days)
Show older comments
I have a model that I want to run for 1000 years. It represents an area of the sea floor (x by y), 61 size classes of organisms (B), and the food source ("detritus"; R). When you consider both B and R, the model has 62 state variables.
I give the ode15s solver an x by y by 62 matrix, which it seems to vectorise internally, which means in my internal ode function I have to reshape the inputs back into the x by y by 62 matrix to then do my matrix multiplication equations.
On a small x by y grid (i.e. when the length of x is 7, and length of y is 14), my code works fine. I can run it for 1000 years in like a minute, using the odeset function like this:
options = odeset('Refine', 2, 'abstol', 1e-10, 'MaxStep', 100, 'InitialStep', 0.01)
But, scaling up from 7x14 grid to a larger 360x180 grid, I get this error:
"Requested 4017600x4017600 (120260.6GB) array exceeds maximum array size preference (15.9GB). This might
cause MATLAB to become unresponsive.
Error in odenumjac (line 126)
ydel = y(:,ones(1,ny)) + diag(del);
Error in ode15s (line 348)
[dfdy,Joptions.fac,nF] = odenumjac(odeFcn, {t,y,odeArgs{:}}, f0, Joptions); %#ok<CCAT>
Error in dynorun_log_2D (line 178)
[t,y] = ode15s(@benthic_dydt_log2D,[tstart tfinal], y0,options);"
It's trying to create a (360x180x62)^2 matrix, which I guess is the Jacobian for the whole system. For now, there are no interactions between points, so this could be a sparse identity matrix.
I've gone back to the smaller 7x14 grid size while I work out setting the Jacobian, and have set it using:
options = odeset('JPattern',S);
I've tried making S a fully sparse matrix of size (7x14x62)^2, which caused the model to take >30 minutes to run for 1000 years, when before it took a minute. I've also tried making S a sparse identity matrix of size (7x14x62)^2, which hasn't yet finished running...
Neither option seem suitable for the bigger grid size. I thought setting the Jacobian to be sparse would make the solver faster? I'm not sure what to try next.
8 Comments
Torsten
on 2 Aug 2023
Edited: Torsten
on 2 Aug 2023
If there is no exchange between the cells, the Jacobian matrix of the whole system is a block-diagonal matrix with 98 blocks each of size 62x62. Thus there should be 98*62*62 elements that differ from 0 in a (98*62)^2 matrix.
sparsity = 1-98*62*62/(98*62)^2
The sparsity of the matrix will become smaller if there is exchange between the cells.
Answers (1)
Torsten
on 3 Aug 2023
Edited: Torsten
on 3 Aug 2023
Here is an example code how to create the sparsity pattern for a given (quite complicated) ODE function.
Use the function "S_pattern" to generate your individual sparsity pattern matrix S.
Note that ode15s will come into trouble if the sparsity pattern changes during the integration. So if, e.g., some exchange terms between the cells appear at a time t > 0 during integration that were not present at time t = 0, ode15s will start to make small time steps because the Jacobian it is working with has become incomplete.
ode45 does not need a Jacobian and thus no sparsity pattern. So it's no surprise that the computation time when using ode45 did not change.
% Set physical parameters
L = [160 10 25 10 160]*1e-6; % [m] MEA layer thicknesses
kt = [1.6 0.27 0.3 0.27 1.6]; % [W/mK] Thermal conductivity
rcp = [1.58 1.56 1.9 1.56 1.58]*1e6; % [J/m3K] Volumetric heat capacity
Qtot = [0 10 100 120 15]*1e3; % [W] Heat Source
%Qtot = zeros(1,5);
% Number of mesh points in zones
nx = [11 11 11 11 11];
% Set boundary conditions
T_start = 343;
T_end = T_start;
% Set initial conditions
T0 = 0;
% Set time span of integration
tspan = 0:0.001:0.025;
% Create grid in domains
% Number of domains
Nd = numel(L);
% Grid vectors
xstart = 0;
xend = L(1);
x{1} = linspace(xstart,xend,nx(1)).';
for i = 2:Nd
xstart = xend;
xend = xstart + L(i);
x{i} = linspace(xstart,xend,nx(i)).';
end
% Number of ODEs to be solved
nodes = sum(nx) - (Nd-1);
% Create initial solution vector
y0 = zeros(nodes,1);
y0(1) = T_start;
y0(2:nodes-1) = T0;
y0(nodes) = T_end;
S = S_pattern(@(t,y)fun(t,y,Nd,nx,x,kt,rcp,Qtot),tspan(1),y0);
figure(1)
spy(S)
% Call solver
[T,Y] = ode15s(@(t,y)fun(t,y,Nd,nx,x,kt,rcp,Qtot),tspan,y0,odeset('JPattern',S));
% Plot results
xplot = [x{1};x{2}(2:end);x{3}(2:end);x{4}(2:end);x{5}(2:end)];
figure(2)
plot(xplot,Y.');
% Function
function dy = fun(t,y,Nd,nx,x,kt,rcp,Qtot)
% Write y in local variables
ileft = 1;
iright = nx(1);
T{1} = y(ileft:iright);
for j = 2:Nd
ileft = iright ;
iright = ileft + nx(j) - 1;
T{j} = y(ileft:iright);
end
% Compute values in ghost points
for j = 1:Nd-1
T1 = T{j};
T2 = T{j+1};
x1 = x{j};
x2 = x{j+1};
lambda1 = kt(j);
lambda2 = kt(j+1);
rhocp1 = rcp(j);
rhocp2 = rcp(j+1);
nx1 = nx(j);
nx2 = nx(j+1);
Q1 = Qtot(j);
Q2 = Qtot(j+1);
xg1 = x1(end)+(x1(end)-x1(end-1));
xg2 = x2(1)-(x2(2)-x2(1));
% Set up linear system of equations for [Tg1, Tg2]
a11 = lambda1/(xg1-x1(nx1-1));
a12 = lambda2/(x2(2)-xg2);
b1 = lambda1*T1(nx1-1)/(xg1-x1(nx1-1))+lambda2*T2(2)/(x2(2)-xg2);
a21 = (lambda1/(xg1-x1(nx1))/((x1(nx1)+xg1)/2 - (x1(nx1)+x1(nx1-1))/2))*rhocp2;
a22 = (-lambda2/(x2(1)-xg2)/((x2(2)+x2(1))/2 - (x2(1)+xg2)/2))*rhocp1;
b2 = ((lambda1*T1(nx1)/(xg1-x1(nx1))+lambda1*(T1(nx1)-T1(nx1-1))/(x1(nx1)-x1(nx1-1)))/...
((x1(nx1)+xg1)/2-(x1(nx1)+x1(nx1-1))/2)+Q1)*rhocp2 +...
((lambda2*(T2(2)-T2(1))/(x2(2)-x2(1))-lambda2*T2(1)/(x2(1)-xg2))/...
((x2(2)+x2(1))/2 - (x2(1)+xg2)/2)+Q2)*rhocp1;
A = [a11,a12;a21,a22];
b = [b1;b2];
% Solve linear system for [Tg1, Tg2]
sol = A\b;
Tg1(j) = sol(1);
Tg2(j) = sol(2);
Xg1(j) = xg1;
Xg2(j) = xg2;
end
% Compute time derivatives
dTdt{1}(1) = 0.0;
for j = 1:Nd
if j < Nd
Tj = [T{j};Tg1(j)];
xj = [x{j};Xg1(j)];
else
Tj = T{j};
xj = x{j};
end
lambdaj = kt(j);
rhocpj = rcp(j);
Qj = Qtot(j);
for i=2:numel(xj)-1
dTdt{j}(i) = ((lambdaj*(Tj(i+1)-Tj(i))/(xj(i+1)-xj(i)) -...
lambdaj*(Tj(i)-Tj(i-1))/(xj(i)-xj(i-1)))/...
((xj(i+1)+xj(i))/2-(xj(i)+xj(i-1))/2)+Qj)/(rhocpj);
end
end
dTdt{Nd}(end+1) = 0.0;
% Return time derivatives
dy = dTdt{1}(1:end);
for j = 2:Nd
dy = [dy,dTdt{j}(2:end)];
end
dy = dy.';
end
function S = S_pattern(f,t,u)
y = f(t,u);
h = 1e-6;
ir = [];
ic = [];
for i = 1:numel(u)
u(i) = u(i) + h;
yh = f(t,u);
%J(:,i) = (yh-y)/h;
Jh = (yh-y)/h;
Jhnz = find(abs(Jh)>0);
if ~isempty(Jhnz)
ir = [ir;Jhnz];
ic = [ic;i*ones(size(Jhnz,1),1)];
end
u(i) = u(i) - h;
end
S = sparse(ir,ic,ones(numel(ir),1),numel(u),numel(u));
end
0 Comments
See Also
Categories
Find more on Ordinary Differential Equations in Help Center and File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!
