A fascinating new class of solitary waves was discovered in a 2003 paper by R.J. LeVeque and D. Yong. Solitary waves usually appear as solutions of nonlinear dispersive wave equations, like the KdV or NLS equations. But the waves discovered by LeVeque and Yong arise in a system of nonlinear wave equations with no dispersion! The nonlinear elasticity equations they investigated are: \begin{align} \epsilon_t(x,t) - u_x(x,t) & = 0 \\ (\rho(x) u(x,t))_t - \sigma(\epsilon(x,t),x)_x & = 0. \end{align} They took the density $\rho(x)$ and bulk modulus $K(x)$ to be periodic functions and the stress strain relation $\sigma(\epsilon,x)$ nonlinear (they used an exponential function, but any nonlinear function will work).
If $\rho$ and $K$ are chosen so that the impedance $Z = \sqrt{\rho K}$ varies in space, then waves undergo reflection on the fine scale. Remarkably, the effect of these reflections is an effective behavior that mimics dispersion -- as predicted already by Santosa & Symes in 1993.
Here we reproduce some of their original experiments in PyClaw. If you're interested in the simulations, all of the code is provided here and you can run it yourself. If you're only interested in the results, feel free to just skip over the code.
from clawpack import riemann
from clawpack import pyclaw
import numpy as np
riemann_solver = riemann.nonlinear_elasticity_fwave_1D
solver = pyclaw.ClawSolver1D(riemann_solver)
solver.fwave = True
# Boundary conditions
solver.bc_lower[0] = pyclaw.BC.extrap
solver.bc_upper[0] = pyclaw.BC.extrap
solver.aux_bc_lower[0] = pyclaw.BC.extrap
solver.aux_bc_upper[0] = pyclaw.BC.extrap
xlower=0.0; xupper=1000.0
cells_per_layer=12; mx=int(round(xupper-xlower))*cells_per_layer
x = pyclaw.Dimension('x',xlower,xupper,mx)
domain = pyclaw.Domain(x)
state = pyclaw.State(domain,solver.num_eqn,3)
xc=state.grid.x.centers
#Initialize q and aux
KA = 1.0; rhoA = 1.0
KB = 4.0; rhoB = 4.0
xfrac = xc-np.floor(xc)
state.aux[0,:] = rhoA*(xfrac<0.5)+rhoB*(xfrac>=0.5) #Density
state.aux[1,:] = KA *(xfrac<0.5)+KB *(xfrac>=0.5) #Bulk modulus
state.aux[2,:] = 0. # not used
sigma = 0.5*np.exp(-((xc-500.)/5.)**2.)
state.q[0,:] = np.log(sigma+1.)/state.aux[1,:] # Strain
state.q[1,:] = 0. # Momentum
claw = pyclaw.Controller()
claw.solution = pyclaw.Solution(state,domain)
claw.output_style = 1
claw.num_output_times = 100
claw.tfinal = 550.
claw.solver = solver
claw.keep_copy = True
claw.output_format = None
Before running the simulation, let's take a quick look at the setup. The initial condition (stored in state.q
) is a Gaussian stress perturbation with zero velocity, while the impedance $Z(x)$ is piecewise constant and periodic. Notice in the plot below that, though the stress is a Gaussian, the strain is discontinuous since the bulk modulus $K(x)$ is discontinuous.
In each plot, the inset is a closeup of the central region.
import plotly
py = plotly.plotly(username_or_email='DavidKetcheson', key='mgs2lgb203')
data = [{'x':xc, 'y':state.q[0,:]},{'x':xc[5800:6200],'y':state.q[0,5800:6200],"xaxis":"x2","yaxis":"y2"}]
layout = {"title": "$\\text{Strain }(\epsilon)$",'showlegend':False, "xaxis2": {"domain": [0.6, 0.95],"anchor": "y2"},
"yaxis2":{"domain": [0.2, 0.6],"anchor": "x2"}}
py.iplot(data,layout=layout)
Z = np.sqrt(state.aux[0,:]*state.aux[1,:])
data = [{'x':xc, 'y':Z},{'x':xc[5800:6200],'y':Z[5800:6200],"xaxis":"x2","yaxis":"y2"}]
layout = {"title": "$\\text{Impedance }(Z)$",'showlegend':False, "xaxis2": {"domain": [0.6, 0.95],"anchor": "y2"},
"yaxis2":{"domain": [0.2, 0.6],"anchor": "x2"}}
py.iplot(data,layout = layout)
claw.run()
{'cflmax': 0.9014990065747429, 'dtmax': 0.06465763584693647, 'dtmin': 0.06436880097776293, 'numsteps': 86}
%pylab inline
from matplotlib import animation
import matplotlib.pyplot as plt
from clawpack.visclaw.JSAnimation import IPython_display
fig = plt.figure(figsize=[8,4])
ax = plt.axes(xlim=(xc[0], xc[-1]), ylim=(0, 0.4))
line, = ax.plot([], [], lw=1)
def fplot(i):
frame = claw.frames[i]
strain = frame.q[0,:]
line.set_data(xc, strain)
ax.set_title('Strain at t='+str(frame.t))
return line,
animation.FuncAnimation(fig, fplot, frames=len(claw.frames))
Populating the interactive namespace from numpy and matplotlib
It's hard to see the details in the animated plot above. Let's use Plotly to create a plot where we can zoom in:
data = [{'x':xc, 'y':state.q[0,:]},{'x':xc[11000:11700],'y':state.q[0,11000:11700],"xaxis":"x2","yaxis":"y2"}]
layout = {"title": "Strain ($\epsilon$)",'showlegend':False,"xaxis2": {"domain": [0.4, 0.85],"anchor": "y2"},
"yaxis2":{"domain": [0.15, 0.65],"anchor": "x2"}}
py.iplot(data, layout=layout)
It's easy to see why these waves were dubbed stegotons: they're solitary waves that resemble the back of a stegosaurus!
It's clear that the initial pulse is breaking up into a train of waves, but we'd like to study their behavior over longer periods of time, when the solitary waves separate completely. A nice trick for this purpose is to use a periodic domain and let the waves travel around it several times, thus avoiding the need for a huge computational domain. This works well as long as the leading waves don't catch up with the tail. Also, it's enough to examine one of the two wave trains (say, the right-going one). Indeed, in our periodic domain, the left- and right-going trains would interact, so we'll want to eliminate one of them.
In order to accomplish this, we'll add two advanced features to our simulation code. First, we'll implement a custom boundary condition at the left edge, corresponding to an oscillating wall, that will only generate a right-going wave. Second, we'll add a before_step()
function. before_step
is a hook to do anything special that needs to occur at every (or any) time step in a PyClaw simulation. We'll write a before_step
function that changes the boundary conditions to be periodic after the initial wave is generated.
def set_bc_periodic(solver,state):
"Change to periodic BCs after initial pulse"
if state.t>5*state.problem_data['tw1']:
solver.bc_lower[0] = pyclaw.BC.periodic
solver.bc_upper[0] = pyclaw.BC.periodic
solver.aux_bc_lower[0] = pyclaw.BC.periodic
solver.aux_bc_upper[0] = pyclaw.BC.periodic
solver.before_step = None
def moving_wall_bc(state,dim,t,qbc,num_ghost):
"Initial pulse generated at left boundary by prescribed motion"
if dim.on_lower_boundary:
qbc[0,:num_ghost]=qbc[0,num_ghost]
t=state.t; t1=state.problem_data['t1']; tw1=state.problem_data['tw1']
amp = state.problem_data['amp'];
t0 = (t-t1)/tw1
if abs(t0)<=1.: vwall = -amp*(1.+np.cos(t0*np.pi))
else: vwall=0.
for ibc in xrange(num_ghost-1):
qbc[1,num_ghost-ibc-1] = 2*vwall*state.aux[1,ibc] - qbc[1,num_ghost+ibc]
Now well set up the new simulation.
riemann_solver = riemann.nonlinear_elasticity_fwave_1D
solver = pyclaw.ClawSolver1D(riemann_solver)
solver.fwave = True
solver.before_step = set_bc_periodic
# Boundary conditions
solver.bc_lower[0] = pyclaw.BC.custom
solver.user_bc_lower = moving_wall_bc
solver.bc_upper[0] = pyclaw.BC.extrap
solver.aux_bc_lower[0] = pyclaw.BC.extrap
solver.aux_bc_upper[0] = pyclaw.BC.extrap
xlower=0.0; xupper=300.0
cells_per_layer=24; mx=int(round(xupper-xlower))*cells_per_layer
x = pyclaw.Dimension('x',xlower,xupper,mx)
domain = pyclaw.Domain(x)
state = pyclaw.State(domain,solver.num_eqn,3)
xc=state.grid.x.centers
#Initialize q and aux
KA = 1.0; rhoA = 1.0
KB = 4.0; rhoB = 4.0
xfrac = xc-np.floor(xc)
state.aux[0,:] = rhoA*(xfrac<0.5)+rhoB*(xfrac>=0.5) #Density
state.aux[1,:] = KA *(xfrac<0.5)+KB *(xfrac>=0.5) #Bulk modulus
state.aux[2,:] = 0. # not used
state.q[0,:] = 0. # Strain
state.q[1,:] = 0. # Momentum
state.problem_data = {}
state.problem_data['t1'] = 10.0
state.problem_data['tw1'] = 10.0
state.problem_data['amp'] = 0.1
claw = pyclaw.Controller()
claw.solution = pyclaw.Solution(state,domain)
claw.solver = solver
claw.num_output_times = 100
claw.tfinal = 1000.
claw.keep_copy = True
claw.output_format = None
claw.run()
{'cflmax': 0.903205656001333, 'dtmax': 0.029932833102633821, 'dtmin': 0.028770133517290325, 'numsteps': 343}
fig = plt.figure(figsize=[8,4])
ax = plt.axes(xlim=(0, 300), ylim=(0, 0.6))
line, = ax.plot([], [])
animation.FuncAnimation(fig, fplot, frames=len(claw.frames))
py.iplot(xc,claw.frames[-1].q[0,:])
It's easy to run further experiments on your own, and there are lots of interesting questions to be asked. For instance, you might wonder what happens if the impedance varies smoothly (say, sinusoidally) instead of being discontinuous. Here's how you can find out:
import copy
claw.solution = copy.deepcopy(claw.frames[0]) # Reset simulation
claw.solution.state.aux[0,:] = 0.5*(rhoA+rhoB) + 0.5*(rhoA-rhoB)*np.sin(2*np.pi*xc) #Density
claw.solution.state.aux[1,:] = 0.5*(KA+KB) + 0.5*(KA-KB)*np.sin(2*np.pi*xc) #Bulk modulus
claw.solution.state.aux[2,:] = 0. # not used
solver = pyclaw.ClawSolver1D(riemann_solver)
solver.fwave = True
solver.bc_lower[0] = pyclaw.BC.custom
solver.bc_upper[0] = pyclaw.BC.extrap
solver.aux_bc_lower[0] = pyclaw.BC.extrap
solver.aux_bc_upper[0] = pyclaw.BC.extrap
solver.user_bc_lower = moving_wall_bc
solver.before_step = set_bc_periodic
claw.solver = solver
claw.tfinal = 1000
claw.frames = []
claw.run()
{'cflmax': 0.9041175834753584, 'dtmax': 0.029922467479281765, 'dtmin': 0.028532986337375367, 'numsteps': 345}
fig = plt.figure(figsize=[8,4])
ax = plt.axes(xlim=(0, 300), ylim=(0, 0.6))
line, = ax.plot([], [])
animation.FuncAnimation(fig, fplot, frames=len(claw.frames))
py.iplot(xc,claw.frames[-1].q[0,:])
Zoom in on one of the waves above; they have an interesting shape! Here are some other things to try, off the top of my head:
You can also look at multi-dimensional waves like these, but you'll want to run PyClaw in parallel (not in the notebook) to do so. Take a look at this paper and some preprints listed here.