Step 2: Nonlinear Convection and Upwind Scheme#


Now we’re going to implement nonlinear convection using the same methods as in step 1. The 1D convection equation is:

\[\frac{\partial u}{\partial t} + u \frac{\partial u}{\partial x} = 0\]

Instead of a constant factor \(c\) multiplying the second term, now we have the solution \(u\) multiplying it. Thus, the second term of the equation is now nonlinear. We’re going to use the same discretization as in Step 1 — forward difference in time and backward difference in space. Here is the discretized equation.

\[\frac{u_i^{n+1}-u_i^n}{\Delta t} + u_i^n \frac{u_i^n-u_{i-1}^n}{\Delta x} = 0\]

Solving for the only unknown term, \(u_i^{n+1}\), yields:

\[u_i^{n+1} = u_i^n - u_i^n \frac{\Delta t}{\Delta x} (u_i^n - u_{i-1}^n)\]

As before, the Python code starts by loading the necessary libraries. Then, we declare some variables that determine the discretization in space and time (you should experiment by changing these parameters to see what happens). Then, we create the initial condition \(u_0\) by initializing the array for the solution using \(u = 2\ @\ 0.5 \leq x \leq 1\) and \(u = 1\) everywhere else in \((0,2)\) (i.e., a hat function).

import numpy as np               # we're importing numpy 
import matplotlib.pyplot as plt # and our 2D plotting library

%matplotlib inline


nx = 141
dx = 2 / (nx - 1)
nt = 20    #nt is the number of timesteps we want to calculate
#dt = .025  #dt is the amount of time each timestep covers (delta t)

u = np.ones(nx)                         #as before, we initialize u with every value equal to 1.
u[:]= 1
u[int(.5 / dx) : int(1 / dx + 1)] = 2  #then set u = 2 between 0.5 and 1 as per our I.C.s

CFL = 0.9
dt = CFL * dx / max(abs(u))

un = np.ones(nx) #initialize our placeholder array un, to hold the time-stepped solution
plt.plot(np.linspace(0, 2, nx), u);
plt.show()
_images/102c02b8887eb35d3620ecfc94e7c95b3fad846ef70ecc14f230ba7ca67b0921.png

The code snippet below is to execute the nonlinear convection:

for n in range(nt):  #iterate through time
    un = u.copy() ##copy the existing values of u into un
    

    for i in range(1, nx-1):  ##now we'll iterate through the u array
        u[i] = un[i] - un[i] * dt / dx * (un[i] - un[i-1])        
    plt.plot(np.linspace(0, 2, nx), u) ##Plot the results
    plt.show()
_images/53c2389543f992b1bddf3a746623cdda4cfdd31b981d42115283efac51185725.png _images/12c6407fb5c163f67df74a99c3abfad271464a14681af7de76d75b2cda294f13.png _images/13b27cb9b75a35e6472dd00b5267cb7db792897094966e2848089db01d30129e.png _images/d2a0ce8a49d74aed6dd42a4ad329ab305963bc0849edb7cf26664b36cfe9386e.png _images/69c9bac58f50f1ee2d50e7cad0a1ea265cbbd286925c58e33eef46c53d42ed77.png _images/465398f75075328c9fb1177a9dd9e5945b93d7329980152229f98fcd479eb3d6.png _images/8450e1455b718451c90ffa74d3a9dc8fa1a153f4bd2159f3be96cc2d11e5347b.png _images/c56582f004e1e525925d5a0c028f6329d1683e411366a7956c6b1c74c8cbda42.png _images/b51de776d9cf0760d80f6d5ac8e298db4c94395b38d0365e60443f0c02df49f9.png _images/96798cec17a5fcd6e0f34cbd6b129731bc8d76314e77bf069135d4064ce8e20d.png _images/5567469398254292fb10d9474ef9ee77b2a097612b61f8b975b60e831b8754ef.png _images/972b942b7b61ad788b8033c43e75fa755ea16ff1e1526a5c45e25f0b6b0f41b1.png _images/47caf49820ccc5ec0e702a4d26de7306ad04e1026c44baab53d79acd4d641546.png _images/c074a12ce20e930dda7a2ad840336986aade99aeb780b624a3164be9e0b13b6b.png _images/d24dab7947af007aad537e38f816bbff1a4915149ecce3ed14a53b636a37ce26.png _images/addd1213a18d5bdafcadf7d9834f00ebb704059fbad0bdaf56fdacc4b8f30f63.png _images/5f9e99b5bc8b2cfb2aac62909410635719047fddcb2d1d7fceed3ca12a7cebad.png _images/aadba90d52a34c844d0362b3bf882576caaac6a89a16b34534a4d2f71c373894.png _images/c8a11ae684291e32fdbf86a974c35460ef97a4c2f6ffbaa0bec52fea9e7d3101.png _images/0f8b4122bc6811bdf9ba30d9fe69d0bf1ed3bea02422c9c1e6b2f350b4a9bd6a.png

What do you observe about the evolution of the hat function under the nonlinear convection equation? What happens when you change the numerical parameters and run again?

For example, what happens if we switch our initial values from positive to negative?

u[:]= -1
u[int(.5 / dx) : int(1 / dx + 1)] = -2  #then set u = 2 between 0.5 and 1 as per our I.C.s

plt.plot(np.linspace(0, 2, nx), u);
plt.show()
_images/ddc8f6279e1e1e84dff93dc439d1c3a05f16f7efe4afbab441cde659ce22c243.png
for n in range(nt):  #iterate through time
    un = u.copy() ##copy the existing values of u into un
    

    for i in range(1, nx-1):  ##now we'll iterate through the u array
        u[i] = un[i] - un[i] * dt / dx * (un[i] - un[i-1])        
    plt.plot(np.linspace(0, 2, nx), u) ##Plot the results
    plt.show()
_images/09fd63209cf8050ee30e33f8e9283bee0c0111c0c68e0d6fe533ed5e9373677d.png _images/30c678a913b0b394ff26f2de8a10105dea753e9b7c847fedb23d7e8963c6c376.png _images/587a498e0322c73dc6a251c2b1533c1af2ff5c2df2420a56c440a801cb64c805.png _images/23bb704ba84986df87f019fd31a66065506a0a22d16a9795469d4bef8845c6c3.png _images/c54baca06a7a61eef613c98a9f8c511b3e5b3ae4c3c3ab9f9e415b47a52ca24c.png _images/9aca12a83bc48de8d90986c2ca4e46cef426394c6770a79910b3aa7a0bd48d88.png _images/d4861e6949b9b5a12164e2604488bfbe96268a501db281e868a60d217e5130a0.png _images/4aabe6c4b821d5dff887671ed5e9aa5d313b0f68c74666cf9d4abdd7a7c12b98.png _images/d158d496e3cbe24791dbae6115bbadd393a2e49b16fc499e8cebb0e997c14ecb.png _images/3eeb1c60d0f16de76eb0bbf43fa35a5a3ebd351660983be13f1a8fc7f4078ab4.png _images/a380fe4bed8fea80e0f26b8867943e319af8dd7a7f97ee9fea5acda25c6a6d0f.png
/tmp/ipykernel_22562/2793021359.py:6: RuntimeWarning: overflow encountered in scalar multiply
  u[i] = un[i] - un[i] * dt / dx * (un[i] - un[i-1])
_images/d2503c5bbde922dca69353db8b905a4e3881d811e5d6c950b46b8eb1682a6e0b.png
/tmp/ipykernel_22562/2793021359.py:6: RuntimeWarning: invalid value encountered in scalar subtract
  u[i] = un[i] - un[i] * dt / dx * (un[i] - un[i-1])
_images/e7bbe4f37dfedf761bc0b086fc2cf81a7f0b76305da9482c405d0ac196a54223.png
/tmp/ipykernel_22562/2793021359.py:6: RuntimeWarning: overflow encountered in scalar multiply
  u[i] = un[i] - un[i] * dt / dx * (un[i] - un[i-1])
_images/dad594917ff5b039c5fac78d8a206ce27b92b81f8594c2b4136358374048251a.png
/tmp/ipykernel_22562/2793021359.py:6: RuntimeWarning: invalid value encountered in scalar subtract
  u[i] = un[i] - un[i] * dt / dx * (un[i] - un[i-1])
/tmp/ipykernel_22562/2793021359.py:6: RuntimeWarning: overflow encountered in scalar multiply
  u[i] = un[i] - un[i] * dt / dx * (un[i] - un[i-1])
_images/ace72c6837aa3e3b9e27380f094650311b71e92a837dd44613d7d4e49421be39.png
/tmp/ipykernel_22562/2793021359.py:6: RuntimeWarning: invalid value encountered in scalar subtract
  u[i] = un[i] - un[i] * dt / dx * (un[i] - un[i-1])
/tmp/ipykernel_22562/2793021359.py:6: RuntimeWarning: overflow encountered in scalar multiply
  u[i] = un[i] - un[i] * dt / dx * (un[i] - un[i-1])
_images/fc7eb7bd46deb4ed93d79f18134b1878a1a046bae0a8fdc4f514b09bb0e52556.png
/tmp/ipykernel_22562/2793021359.py:6: RuntimeWarning: overflow encountered in scalar multiply
  u[i] = un[i] - un[i] * dt / dx * (un[i] - un[i-1])
/tmp/ipykernel_22562/2793021359.py:6: RuntimeWarning: invalid value encountered in scalar subtract
  u[i] = un[i] - un[i] * dt / dx * (un[i] - un[i-1])
_images/2a5f4b498dee3278470d91d15b07e9d27065417b0051b17fd139be0e71353c1d.png
/tmp/ipykernel_22562/2793021359.py:6: RuntimeWarning: invalid value encountered in scalar subtract
  u[i] = un[i] - un[i] * dt / dx * (un[i] - un[i-1])
_images/da53db20c29c9c66e2e0a8e727c212095d75affe8da59e26d48ac008bc12c075.png _images/c7c3ee51826906d8aa6744c89b791be670059454edbcd7f5ba515b4d5f297ef6.png _images/7ce876ee92add40efd28ed2b8114c9add943d171ecc18c9328393c305e87b519.png

What causes the divergence in computational results when a negative initial condition is applied?

The divergence in results when using a negative initial condition in your computational model can be attributed to the limitations of the backward scheme used for discretizing the convection term. The backward scheme, represented by \(\frac{u_i^n - u_{i-1}^n}{\Delta x}\), is efficient for positive convection coefficients. However, it becomes less effective when dealing with negative nonlinear convection coefficients, like \(u^n_i\) in your case.

To address this issue, you can introduce the upwind scheme for discretizing the convection term. The upwind scheme is designed to handle both positive and negative convection coefficients effectively. Its fundamental idea is to adjust the discretization direction based on the sign of the convection coefficient. When the convection coefficient is positive, the upwind scheme uses values from the upstream (or ‘upwind’) side of the computational domain for discretization, similar to the backward scheme. Conversely, when the convection coefficient is negative, it uses values from the downstream (or ‘downwind’) side, similar to the forward scheme.

Here is the code implementation of the upwind scheme:

u[:]= -1
u[int(.5 / dx) : int(1 / dx + 1)] = -2  #then set u = 2 between 0.5 and 1 as per our I.C.s

plt.plot(np.linspace(0, 2, nx), u);
plt.show()

for n in range(nt):  #iterate through time
    un = u.copy() ##copy the existing values of u into un
    
    F = lambda c: (max(c/(abs(c)+1e-6), 0), max(-c/(abs(c)+1e-6), 0))
    
    for i in range(1, nx-1):  ##now we'll iterate through the u array
        # Coefficients to the east side of the node (i+1)
        fe1, fe2 = F(u[i])
        # Coefficients to the west side of the node (i-1)
        fw1, fw2 = F(u[i])
        # Differential values on the east side interface
        ue = un[i] * fe1 + un[i+1] * fe2
        # Differential values on the wast side interface
        uw = un[i-1] * fw1 + un[i]* fw2
        u[i] = un[i] - un[i] * dt / dx * (ue - uw)

        #u[i] = un[i] - un[i] * dt / dx * (un[i] - un[i-1])        
    plt.plot(np.linspace(0, 2, nx), u) ##Plot the results
    plt.show()
_images/ddc8f6279e1e1e84dff93dc439d1c3a05f16f7efe4afbab441cde659ce22c243.png _images/4cf109adfc279e7f9fe23314ead1a66ce610f314f2c1762168c94aa6513064be.png _images/504de17f8856669763ae75e96416051d69f6ebc6104a53765e807cb70dae7ca9.png _images/08e7efd1f7dce031bc8528d29d74a91f163816af23beba923d1a1b33921a0988.png _images/ea3e016d2e67fe08796fb8ad45ad50985689640feec8feea166ed383b6df063c.png _images/7e3c84f6c4ffbb801fce6a51c5d03800ada67b4020f261a7c7150ddbfe1aec84.png _images/1ea3880b9278f4fd02d33d213a9026a2958f46f27867dc5c0cb55b0c57aabc02.png _images/eb1362dd116ce5ab48186c5080f30ad0517d1cc49690af6f1768c5dc5456f5ed.png _images/82638c65ff0af365bff6636968b4ddadc2deaf507fbfed7281eb983ade1f4111.png _images/b43dfab5ddda73127df6cf72605ba7756adda202c81fcfe6f239944fc8701e65.png _images/021b1b23358f2d06b5e5bbfa95ff35783856ace689cd7fc4d1f0dd3b3f355e54.png _images/189b9a35ace74d7c0175269e7f71278314753e580c0eeb78ca59b37022f89503.png _images/a8aa1e8e024e6d9e8f0b21e25ecf8aa3fe7959b901d573e8e7e790a68d59fd61.png _images/02c590f46978808670ba2df9b2474cdc3388c0ef7e2eb7b0ecffd89587777e01.png _images/ed700618183671552d29a398ffa961ef809b330f7d4ebeede32a201377db82b1.png _images/b0295140dd90c12f7c49b814eed5156c88bbae16a082cbaf5697c517d7b95252.png _images/13092611ef10171a7ef446bf9da84df302fc3e7993b31d3b97f547bdc3de0082.png _images/2553ca2650518e3c47560813b57c6bfb8c3945247c62f54f1c194eed3ea0bf1c.png _images/00deff020b91c9b654e4287c3e077454680715c04a2481a612a76948e4f20b13.png _images/38609a7e30d5362c56bf72d27a23bad18e6e7d66565c9747b1eb29122c1c9ccd.png _images/7fe5ebade6a99a39a66b3c261ad1f99d8359d42f3cc724d03a13bc26b93bd841.png