|
| 1 | +import numpy as np |
| 2 | +import matplotlib.pyplot as plt |
| 3 | + |
| 4 | +class FVGrid(object): |
| 5 | + |
| 6 | + def __init__(self, nx, ng, xmin=0.0, xmax=1.0): |
| 7 | + |
| 8 | + self.xmin = xmin |
| 9 | + self.xmax = xmax |
| 10 | + self.ng = ng |
| 11 | + self.nx = nx |
| 12 | + |
| 13 | + # python is zero-based. Make easy intergers to know where the |
| 14 | + # real data lives |
| 15 | + self.ilo = ng |
| 16 | + self.ihi = ng+nx-1 |
| 17 | + |
| 18 | + # physical coords -- cell-centered, left and right edges |
| 19 | + self.dx = (xmax - xmin)/(nx) |
| 20 | + self.x = xmin + (np.arange(nx+2*ng)-ng+0.5)*self.dx |
| 21 | + self.xl = xmin + (np.arange(nx+2*ng)-ng)*self.dx |
| 22 | + self.xr = xmin + (np.arange(nx+2*ng)-ng+1.0)*self.dx |
| 23 | + |
| 24 | + # storage for the solution |
| 25 | + self.a = self.scratch_array() |
| 26 | + self.ainit = self.scratch_array() |
| 27 | + |
| 28 | + def period(self, u): |
| 29 | + """ return the period for advection with velocity u """ |
| 30 | + return (self.xmax - self.xmin)/u |
| 31 | + |
| 32 | + def scratch_array(self): |
| 33 | + """ return a scratch array dimensioned for our grid """ |
| 34 | + return np.zeros((self.nx+2*self.ng), dtype=np.float64) |
| 35 | + |
| 36 | + def fill_BCs(self, atmp): |
| 37 | + """ fill all single ghostcell with periodic boundary conditions """ |
| 38 | + |
| 39 | + # left boundary |
| 40 | + for n in range(self.ng): |
| 41 | + atmp[self.ilo-1-n] = atmp[self.ihi-n] |
| 42 | + |
| 43 | + # right boundary |
| 44 | + for n in range(self.ng): |
| 45 | + atmp[self.ihi+1+n] = atmp[self.ilo+n] |
| 46 | + |
| 47 | + def init_cond(self, ic): |
| 48 | + |
| 49 | + if ic == "tophat": |
| 50 | + self.a[np.logical_and(self.x >= 0.333, self.x <= 0.666)] = 1.0 |
| 51 | + elif ic == "sine": |
| 52 | + self.a[:] = np.sin(2.0*np.pi*self.x/(self.xmax-self.xmin)) |
| 53 | + elif ic == "gaussian": |
| 54 | + self.a[:] = 1.0 + np.exp(-60.0*(self.x - 0.5)**2) |
| 55 | + |
| 56 | + self.ainit[:] = self.a[:] |
| 57 | + |
| 58 | + def norm(self, e): |
| 59 | + """ return the norm of quantity e which lives on the grid """ |
| 60 | + if not len(e) == (2*self.ng + self.nx): |
| 61 | + return None |
| 62 | + |
| 63 | + return np.sqrt(self.dx*np.sum(e[self.ilo:self.ihi+1]**2)) |
| 64 | + |
| 65 | + |
| 66 | +def flux_update(gr, u, a, limit=False): |
| 67 | + |
| 68 | + if not limit: |
| 69 | + # slope |
| 70 | + da = gr.scratch_array() |
| 71 | + da[gr.ilo-1:gr.ihi+2] = 0.5*(a[gr.ilo:gr.ihi+3] - a[gr.ilo-2:gr.ihi+1]) |
| 72 | + |
| 73 | + else: |
| 74 | + # MC slope |
| 75 | + ib = gr.ilo-1 |
| 76 | + ie = gr.ihi+1 |
| 77 | + |
| 78 | + dc = gr.scratch_array() |
| 79 | + dl = gr.scratch_array() |
| 80 | + dr = gr.scratch_array() |
| 81 | + |
| 82 | + dc[ib:ie+1] = 0.5*(a[ib+1:ie+2] - a[ib-1:ie ]) |
| 83 | + dl[ib:ie+1] = a[ib+1:ie+2] - a[ib :ie+1] |
| 84 | + dr[ib:ie+1] = a[ib :ie+1] - a[ib-1:ie ] |
| 85 | + |
| 86 | + # these where's do a minmod() |
| 87 | + d1 = 2.0*np.where(np.fabs(dl) < np.fabs(dr), dl, dr) |
| 88 | + d2 = np.where(np.fabs(dc) < np.fabs(d1), dc, d1) |
| 89 | + da = np.where(dl*dr > 0.0, d2, 0.0) |
| 90 | + |
| 91 | + # upwinding means that we take the left state always |
| 92 | + aint = gr.scratch_array() |
| 93 | + aint[gr.ilo:gr.ihi+2] = a[gr.ilo-1:gr.ihi+1] + 0.5*da[gr.ilo-1:gr.ihi+1] |
| 94 | + |
| 95 | + flux_diff = gr.scratch_array() |
| 96 | + flux_diff[gr.ilo:gr.ihi+1] = u*(aint[gr.ilo:gr.ihi+1] - aint[gr.ilo+1:gr.ihi+2])/gr.dx |
| 97 | + |
| 98 | + return flux_diff |
| 99 | + |
| 100 | + |
| 101 | +def mol_update(C, u, nx, num_periods=1, init_cond="gaussian", limit=False): |
| 102 | + |
| 103 | + # create a grid |
| 104 | + gr = FVGrid(nx, ng=2) |
| 105 | + |
| 106 | + tmax = num_periods*gr.period(u) |
| 107 | + |
| 108 | + # setup initial conditions |
| 109 | + gr.init_cond(init_cond) |
| 110 | + |
| 111 | + # compute the timestep |
| 112 | + dt = C*gr.dx/u |
| 113 | + |
| 114 | + t = 0.0 |
| 115 | + while t < tmax: |
| 116 | + |
| 117 | + if t + dt > tmax: |
| 118 | + dt = tmax - t |
| 119 | + |
| 120 | + # second-order RK integration |
| 121 | + gr.fill_BCs(gr.a) |
| 122 | + k1 = flux_update(gr, u, gr.a, limit=limit) |
| 123 | + |
| 124 | + atmp = gr.scratch_array() |
| 125 | + atmp[:] = gr.a[:] + 0.5*dt*k1[:] |
| 126 | + |
| 127 | + gr.fill_BCs(atmp) |
| 128 | + k2 = flux_update(gr, u, atmp, limit=limit) |
| 129 | + |
| 130 | + gr.a[:] += dt*k2[:] |
| 131 | + |
| 132 | + t += dt |
| 133 | + |
| 134 | + return gr |
| 135 | + |
| 136 | +if __name__ == "__main__": |
| 137 | + |
| 138 | + C = 0.8 |
| 139 | + u = 1.0 |
| 140 | + nx = 64 |
| 141 | + |
| 142 | + # without limiting |
| 143 | + gr = mol_update(C, u, nx, num_periods=1.0, init_cond="gaussian") |
| 144 | + |
| 145 | + plt.clf() |
| 146 | + plt.plot(gr.x, gr.a) |
| 147 | + plt.plot(gr.x, gr.ainit, ls=":") |
| 148 | + plt.savefig("advection_gaussian.png", dpi=150) |
| 149 | + |
| 150 | + gr = mol_update(C, u, nx, num_periods=1.0, init_cond="tophat") |
| 151 | + |
| 152 | + plt.clf() |
| 153 | + plt.plot(gr.x, gr.a) |
| 154 | + plt.plot(gr.x, gr.ainit, ls=":") |
| 155 | + plt.savefig("advection_tophat.png", dpi=150) |
| 156 | + |
| 157 | + |
| 158 | + # with limiting |
| 159 | + gr = mol_update(C, u, nx, num_periods=1.0, init_cond="gaussian", limit=True) |
| 160 | + |
| 161 | + plt.clf() |
| 162 | + plt.plot(gr.x, gr.a) |
| 163 | + plt.plot(gr.x, gr.ainit, ls=":") |
| 164 | + plt.savefig("advection_gaussian_limit.png", dpi=150) |
| 165 | + |
| 166 | + gr = mol_update(C, u, nx, num_periods=1.0, init_cond="tophat", limit=True) |
| 167 | + |
| 168 | + plt.clf() |
| 169 | + plt.plot(gr.x, gr.a) |
| 170 | + plt.plot(gr.x, gr.ainit, ls=":") |
| 171 | + plt.savefig("advection_tophat_limit.png", dpi=150) |
| 172 | + |
| 173 | + |
| 174 | + # convergence |
| 175 | + plt.clf() |
| 176 | + |
| 177 | + Ns = [32, 64, 128, 256, 512] |
| 178 | + err_gauss = [] |
| 179 | + err_nolimit = [] |
| 180 | + for nx in Ns: |
| 181 | + gr = mol_update(C, u, nx, num_periods=1.0, init_cond="gaussian", limit=True) |
| 182 | + gr_nolimit = mol_update(C, u, nx, num_periods=1.0, init_cond="gaussian", limit=False) |
| 183 | + err_gauss.append(gr.norm(gr.a - gr.ainit)) |
| 184 | + err_nolimit.append(gr.norm(gr_nolimit.a - gr_nolimit.ainit)) |
| 185 | + print(nx, err_gauss[-1], err_nolimit[-1]) |
| 186 | + |
| 187 | + plt.scatter(Ns, err_gauss, label="gaussian") |
| 188 | + plt.scatter(Ns, err_nolimit, label="gaussian (no limit)") |
| 189 | + |
| 190 | + # plot a trend line |
| 191 | + N = np.asarray(Ns) |
| 192 | + err = np.asarray(err_gauss) |
| 193 | + plt.plot(N, err[-1]*(N[-1]/N), ls=":", color="0.5", label=r"$\mathcal{O}(\Delta x)$") |
| 194 | + plt.plot(N, err[-1]*(N[-1]/N)**2, ls="-", color="0.5", label=r"$\mathcal{O}(\Delta x^2)$") |
| 195 | + |
| 196 | + ax = plt.gca() |
| 197 | + ax.set_xscale("log") |
| 198 | + ax.set_yscale("log") |
| 199 | + plt.legend(frameon=False, fontsize="small", loc=3) |
| 200 | + plt.xlim(10, 1000) |
| 201 | + plt.ylim(1.e-4, 0.1) |
| 202 | + |
| 203 | + plt.savefig("convergence_limited.png", dpi=150) |
0 commit comments