|
| 1 | +%========================================================================== |
| 2 | +% This script conducts a convergence analysis in terms of time step length, |
| 3 | +% dt, for the discontinuous D(x) MTPT method employing a semi-analytical solution. |
| 4 | +% This script, will generate Figure 10 in: |
| 5 | +% "A Mass-transfer Particle-tracking Method for Simulating Transport |
| 6 | +% with Discontinuous Diffusion Coefficients," |
| 7 | +% Advances in Water Resources, 2020. |
| 8 | +%========================================================================== |
| 9 | + |
| 10 | +% close all |
| 11 | +clear variables |
| 12 | + |
| 13 | +% position of point-mass source |
| 14 | +x0 = -6.5; |
| 15 | +% length of 1D domain |
| 16 | +L = 5e1; |
| 17 | +% number of particles for mass-transfer (MT) simulation |
| 18 | +N = 5e3; |
| 19 | +% total simulation time |
| 20 | +maxT = 6; |
| 21 | + |
| 22 | +% number of iterations to normalize using the Sinkhorn-Knopp Algorithm |
| 23 | +nNorm = 1e3; |
| 24 | + |
| 25 | +% number of members in ensemble (i.e., the number of D2 values to consider) |
| 26 | +numDEns = 3; |
| 27 | + |
| 28 | +% subdomain boundary location |
| 29 | +gamma = -1; |
| 30 | + |
| 31 | +% number of refinements in dt |
| 32 | +numDT = 6; |
| 33 | +NvecStart = 1; |
| 34 | + |
| 35 | +% half refinements in dt |
| 36 | +Nvec = linspace(NvecStart, numDT + NvecStart - 1, numDT); |
| 37 | +Nvec = 2.^Nvec; |
| 38 | +dtVec = 1 ./ Nvec; |
| 39 | + |
| 40 | +% print 1 / dt and dt vectors |
| 41 | +inv_dt = Nvec |
| 42 | +dtVec |
| 43 | + |
| 44 | +% diffusion coefficients (D2vec is length numDEns) |
| 45 | +D1 = 5; |
| 46 | +D2vec = [2.5 0.5 0.05]; |
| 47 | + |
| 48 | +% locations of stationary MT particles |
| 49 | +X = linspace(x0 - L / 2, x0 + L / 2, N)'; |
| 50 | + |
| 51 | +% calculate analytical solution, using Eqs. (12)-(16) |
| 52 | +analyticalSoln = zeros(N, 3); |
| 53 | +for i = 1 : 3 |
| 54 | + analyticalSoln(:, i) = MT_CnJ_arb_dCut(X, x0, D1, D2vec(i), gamma, maxT); |
| 55 | +end |
| 56 | + |
| 57 | +% calculated height to make plots pretty |
| 58 | +height = 1.1 * max(max(analyticalSoln)); |
| 59 | + |
| 60 | +% dirac IC |
| 61 | +[~, sourceX] = min(abs(X - x0)); |
| 62 | +X(sourceX) = x0; |
| 63 | +mass = zeros(N, 1); |
| 64 | +mass(sourceX) = 1 / (L / N); |
| 65 | +mass0 = mass; |
| 66 | + |
| 67 | +%% MT simulation |
| 68 | + |
| 69 | +MTsoldtVec = zeros(N, 3); |
| 70 | + |
| 71 | +% dimension of array is: # of error metrics x dt ensemble x D2 ensemble |
| 72 | +errVec = zeros(4, numDT, 3); |
| 73 | + |
| 74 | +% dt ensemble loop |
| 75 | +for dtEns = 1 : numDT |
| 76 | + |
| 77 | +% time step length and calculated number of time steps to take |
| 78 | + dt = dtVec(dtEns); |
| 79 | + nSteps = ceil(maxT / dt); |
| 80 | + |
| 81 | +% D2 ensemble loop |
| 82 | + for Dens = 1 : numDEns |
| 83 | + |
| 84 | + D2 = D2vec(Dens); |
| 85 | + |
| 86 | +% calculate the max interaction distance for rangesearch() |
| 87 | + dist = 3 * sqrt(4 * max([D1 D2]) * dt); |
| 88 | + |
| 89 | +% conduct the rangesearch to find nearby particles |
| 90 | + [idx, r] = rangesearch(X, X, dist, 'BucketSize', ceil(1e-2 * N)); |
| 91 | + |
| 92 | +% determine how many particles are nearby and preallocate the vectors |
| 93 | +% to build the sparse weight matrix |
| 94 | + Nclose = sum(cellfun('length', idx)); |
| 95 | + row = zeros(Nclose, 1); |
| 96 | + col = zeros(Nclose, 1); |
| 97 | + val = zeros(Nclose, 1); |
| 98 | + |
| 99 | +% calculate the entries of the weight matrix |
| 100 | + start = 1; |
| 101 | + for i = 1 : N |
| 102 | + finish = start - 1 + length(idx{i}); |
| 103 | + |
| 104 | +% this builds the weight matrix with the predictor-corrector solution |
| 105 | +% according to line 3 in Algorithm 2 |
| 106 | + row(start : finish) = i; |
| 107 | + col(start : finish) = idx{i}; |
| 108 | + val(start : finish) = PrCo_1D_2omega(X(idx{i}), X(i), D1, D2, gamma, dt); |
| 109 | + start = finish + 1; |
| 110 | + end |
| 111 | + |
| 112 | +% when N is super large the sparse methods and clearing large arrays saves memory |
| 113 | + clear idx r |
| 114 | + |
| 115 | +% create the sparse weight matrix |
| 116 | + Wmat = sparse(row, col, val); |
| 117 | + clear row col val |
| 118 | + |
| 119 | +% normalize via SK, with nNorm iterations, ending with a column |
| 120 | +% normalization |
| 121 | + Wmat = sinkhornKnoppCol(Wmat, 'MaxIter', nNorm); |
| 122 | + |
| 123 | +% build the transfer matrix according to Algorithm 3 |
| 124 | + Tmat = Wmat; |
| 125 | + |
| 126 | +% initialize the mass vector |
| 127 | + mass = mass0; |
| 128 | + |
| 129 | +% conduct the mass transfers |
| 130 | + for i = 1 : nSteps |
| 131 | + mass = Tmat * mass; |
| 132 | + end |
| 133 | + |
| 134 | +% save the final mass vector for plotting |
| 135 | + MTsoldtVec(:, Dens) = mass; |
| 136 | + |
| 137 | +% calculate the error in RMSE, then inf, 2, and 1 norms |
| 138 | + errVec(1, dtEns, Dens) = sqrt(mean((MTsoldtVec(:, Dens) - analyticalSoln(:, Dens)).^2)); |
| 139 | + errVec(2, dtEns, Dens) = norm(MTsoldtVec(:, Dens) - analyticalSoln(:, Dens), inf); |
| 140 | + errVec(3, dtEns, Dens) = norm(MTsoldtVec(:, Dens) - analyticalSoln(:, Dens), 2); |
| 141 | + errVec(4, dtEns, Dens) = norm(MTsoldtVec(:, Dens) - analyticalSoln(:, Dens), 1); |
| 142 | + |
| 143 | + end |
| 144 | + |
| 145 | +end |
| 146 | + |
| 147 | +%% print out final mass and percent mass change |
| 148 | + |
| 149 | +sumM0 = sum(mass0); |
| 150 | + |
| 151 | +fprintf('MT Initial Mass = %f\n', sumM0) |
| 152 | + |
| 153 | +for i = 1 : numDEns |
| 154 | + ensMsum = sum(MTsoldtVec(:, i)); |
| 155 | + fprintf('MT Final Mass (Ens = %i) = %f\n', i, ensMsum) |
| 156 | + fprintf(' Percent Mass Change = %.2f\n', 100 * abs(sumM0 - ensMsum) / sumM0) |
| 157 | +end |
| 158 | + |
| 159 | + |
| 160 | +%% Final time solution plots |
| 161 | + |
| 162 | +% get color order for plotting |
| 163 | +color = lines(7); |
| 164 | + |
| 165 | +% spatial mass plot for final time step |
| 166 | +fig = 2; |
| 167 | +figure(fig) |
| 168 | +clf |
| 169 | +fill([X(1) gamma gamma X(1) X(1)],height.*[0 0 1 1 0],[0 0.25 1],'FaceAlpha',0.05); grid on; hold on; |
| 170 | +fill([X(end) gamma gamma X(end) X(end)],height.*[0 0 1 1 0],[1 0.25 0],'FaceAlpha',0.05); |
| 171 | + |
| 172 | +p1 = plot(X, analyticalSoln(:, 1), 'k', 'linewidth', 17); |
| 173 | +p2 = plot(X(:), MTsoldtVec(:, 1), '-.+', 'color', color(1, :), 'linewidth', 2,... |
| 174 | + 'MarkerSize', 10); |
| 175 | + |
| 176 | +plot(X, analyticalSoln(:, 2), 'k', 'linewidth', 17) |
| 177 | +plot(X(:), MTsoldtVec(:, 2), '-.+', 'color', color(2, :), 'linewidth', 2,... |
| 178 | + 'MarkerSize', 10); |
| 179 | + |
| 180 | +plot(X, analyticalSoln(:, 3), 'k', 'linewidth', 17) |
| 181 | +plot(X(:), MTsoldtVec(:, 3), '-.+', 'color', color(3, :), 'linewidth', 2,... |
| 182 | + 'MarkerSize', 10); |
| 183 | + |
| 184 | +p4 = line([x0 x0], [0 height], 'color', 'b', 'linewidth', 5); |
| 185 | +p5 = line([gamma gamma], [0 height], 'color', 'r', 'linewidth', 5); |
| 186 | +xlim([X(1) X(end)]); ylim([0 height]); |
| 187 | +[~, hobj, ~, ~] = legend([p1 p2 p4 p5], {'\textbf{Analytic Solution}',... |
| 188 | + '\textbf{Mass-Transfer}', '\textbf{Source Location} {\boldmath$(x_0)$}',... |
| 189 | + '\textbf{Subdomain Boundary} {\boldmath($\gamma$)}'}, 'Interpreter',... |
| 190 | + 'latex', 'FontSize', 32, 'Location', 'northwest'); |
| 191 | +set(hobj, 'linewidth', 4); |
| 192 | +rectangle('Position', [X(end) - 6.8, 0.48 * height, 5.9, 0.0116], 'FaceColor', 'w',... |
| 193 | + 'EdgeColor', 'k', 'LineWidth', 1) |
| 194 | +for i = 1 : 3 |
| 195 | + text(X(end) - 1, 0.6 * height - 0.004 * (i - 1), ['{\boldmath $D_{2}=\ $}\bf' num2str(D2vec(i), '%1.2f')],... |
| 196 | + 'FontSize', 32, 'color', color(i, :), 'HorizontalAlignment',... |
| 197 | + 'right', 'VerticalAlignment', 'middle', 'Interpreter', 'latex'); |
| 198 | +end |
| 199 | +rectangle('Position', [X(1) + 0.9, 0.58 * height, 5.9, 0.0036], 'FaceColor', 'w',... |
| 200 | + 'EdgeColor', 'k', 'LineWidth', 1) |
| 201 | +text(X(1) + 1, 0.6 * height, ['{\boldmath $D_{1}=\ $}\bf' num2str(D1, '%1.2f')], 'FontSize', 32, 'color', 'k',... |
| 202 | + 'HorizontalAlignment', 'left', 'VerticalAlignment', 'middle', 'Interpreter', 'latex'); |
| 203 | +xlabel('\textbf{\emph{x}-position [L]}', 'Interpreter', 'latex', 'FontSize', 32) |
| 204 | +ylabel('\textbf{Concentration [mol L{\boldmath$^{-1}$}]}', 'Interpreter', 'latex', 'FontSize', 32) |
| 205 | +figure(fig) |
| 206 | +set(gcf, 'Position', [0, 100, 1800, 900]) |
| 207 | + |
| 208 | +%% Convergence plots |
| 209 | + |
| 210 | +fig = 27; |
| 211 | +figure(fig) |
| 212 | +clf |
| 213 | +for i = 1 : Dens |
| 214 | + subplot(1, 3, i) |
| 215 | + loglog(dtVec, errVec(2, :, i), '-o', 'LineWidth', 5.5) |
| 216 | + hold on |
| 217 | + loglog(dtVec, errVec(3, :, i), '-o', 'LineWidth', 5.5) |
| 218 | + p2 = polyfit(log10(dtVec), log10(errVec(2, :, i)), 1); |
| 219 | + plot(dtVec, 10.^(p2(1) * log10(dtVec) + p2(2)), '--d', 'LineWidth', 3.5) |
| 220 | + p3 = polyfit(log10(dtVec), log10(errVec(3, :, i)), 1); |
| 221 | + plot(dtVec, 10.^(p3(1) * log10(dtVec) + p3(2)), '--d', 'LineWidth', 3.5) |
| 222 | + xlabel('\boldmath{$\Delta t$}','Interpreter','latex','FontSize', 22) |
| 223 | + ylabel('\textbf{Error}','Interpreter','latex','FontSize', 22) |
| 224 | + legend({'\boldmath{$\ell^\infty$} \textbf{Norm}', '\boldmath{$\ell^2$} \textbf{Norm}', ['\boldmath{$\mathcal{O}(\Delta t^p), p = $} \bf',... |
| 225 | + num2str(p2(1))], ['\boldmath{$\mathcal{O}(\Delta t^p), p = $} \bf',... |
| 226 | + num2str(p3(1))]}, 'Interpreter', 'latex','FontSize', 22, 'Location', 'northwest') |
| 227 | + title(['\boldmath{$D_2 = $} \bf', num2str(D2vec(i))], 'Interpreter', 'latex','FontSize', 22) |
| 228 | + ax = gca; |
| 229 | + ax.YLim(2) = ax.YLim(2) * 10; |
| 230 | +end |
| 231 | +ylim([1e-3 1e0]) |
| 232 | +figure(fig) |
| 233 | +set(gcf, 'Position', [0, 100, 1400, 450]) |
0 commit comments