Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refine CFL condition in seismic #1348

Merged
merged 5 commits into from
Jun 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions devito/types/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from devito.parameters import configuration
from devito.symbolics import aligned_indices
from devito.tools import (Pickable, ctypes_to_cstr, dtype_to_cstr, dtype_to_ctype,
frozendict)
frozendict, memoized_meth)
from devito.types.args import ArgProvider
from devito.types.caching import Cached
from devito.types.utils import DimensionTuple
Expand Down Expand Up @@ -729,15 +729,23 @@ def dimensions(self):
def _eval_deriv(self):
return self

@cached_property
@property
mloubout marked this conversation as resolved.
Show resolved Hide resolved
def _is_on_grid(self):
"""
Check whether the object is on the grid or need averaging.
Check whether the object is on the grid and requires averaging.
For example, if the original non-staggered function is f(x)
then f(x) is on the grid and f(x + h_x/2) is off the grid.
"""
return self._check_indices(inds=self.indices)

@memoized_meth
def _check_indices(self, inds=None):
"""
Check if the function indices are aligned with the dimensions.
"""
inds = inds or self.indices
return all([aligned_indices(i, j, d.spacing) for i, j, d in
zip(self.indices, self.indices_ref, self.dimensions)])
zip(inds, self.indices_ref, self.dimensions)])

@property
def evaluate(self):
Expand Down
27 changes: 21 additions & 6 deletions examples/seismic/acoustic/acoustic_example.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import numpy as np
import pytest

from devito.logger import info
from devito import Constant, Function, smooth
from devito import Constant, Function, smooth, norm
from examples.seismic.acoustic import AcousticWaveSolver
from examples.seismic import demo_model, setup_geometry, seismic_args


def acoustic_setup(shape=(50, 50, 50), spacing=(15.0, 15.0, 15.0),
tn=500., kernel='OT2', space_order=4, nbl=10,
preset='layers-isotropic', **kwargs):
preset='layers-isotropic', fs=False, **kwargs):
model = demo_model(preset, space_order=space_order, shape=shape, nbl=nbl,
dtype=kwargs.pop('dtype', np.float32), spacing=spacing,
**kwargs)
fs=fs, **kwargs)

# Source and receiver geometries
geometry = setup_geometry(model, tn)
Expand All @@ -23,11 +24,11 @@ def acoustic_setup(shape=(50, 50, 50), spacing=(15.0, 15.0, 15.0),


def run(shape=(50, 50, 50), spacing=(20.0, 20.0, 20.0), tn=1000.0,
space_order=4, kernel='OT2', nbl=40, full_run=False,
space_order=4, kernel='OT2', nbl=40, full_run=False, fs=False,
autotune=False, preset='layers-isotropic', checkpointing=False, **kwargs):

solver = acoustic_setup(shape=shape, spacing=spacing, nbl=nbl, tn=tn,
space_order=space_order, kernel=kernel,
space_order=space_order, kernel=kernel, fs=fs,
preset=preset, **kwargs)

info("Applying Forward")
Expand Down Expand Up @@ -62,6 +63,20 @@ def run(shape=(50, 50, 50), spacing=(20.0, 20.0, 20.0), tn=1000.0,
return summary.gflopss, summary.oi, summary.timings, [rec, u.data]


@pytest.mark.parametrize('ndim', [1, 2, 3])
def test_isoacoustic_stability(ndim):
shape = tuple([11]*ndim)
spacing = tuple([20]*ndim)
_, _, _, [rec, _] = run(shape=shape, spacing=spacing, tn=20000.0, nbl=0)
assert np.isfinite(norm(rec))
FabioLuporini marked this conversation as resolved.
Show resolved Hide resolved


@pytest.mark.parametrize('fs, normrec', [(True, 369.955), (False, 459.1678)])
def test_isoacoustic(fs, normrec):
_, _, _, [rec, _] = run(fs=fs)
assert np.isclose(norm(rec), normrec, rtol=1e-3, atol=0)


if __name__ == "__main__":
description = ("Example script for a set of acoustic operators.")
parser = seismic_args(description)
Expand All @@ -76,7 +91,7 @@ def run(shape=(50, 50, 50), spacing=(20.0, 20.0, 20.0), tn=1000.0,
tn = args.tn if args.tn > 0 else (750. if ndim < 3 else 1250.)

preset = 'constant-isotropic' if args.constant else 'layers-isotropic'
run(shape=shape, spacing=spacing, nbl=args.nbl, tn=tn,
run(shape=shape, spacing=spacing, nbl=args.nbl, tn=tn, fs=args.fs,
space_order=args.space_order, preset=preset, kernel=args.kernel,
autotune=args.autotune, opt=args.opt, full_run=args.full,
checkpointing=args.checkpointing)
2 changes: 1 addition & 1 deletion examples/seismic/acoustic/operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def freesurface(model, eq):
eq : Eq
Time-stepping stencil (time update) to mirror at the freesurface.
"""
lhs, rhs = eq.evaluate
lhs, rhs = eq.evaluate.args
# Get vertical dimension and corresponding subdimension
zfs = model.grid.subdomains['fsdomain'].dimensions[-1]
z = zfs.parent
Expand Down
4 changes: 2 additions & 2 deletions examples/seismic/acoustic/wavesolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ def __init__(self, model, geometry, kernel='OT2', space_order=4, **kwargs):
self.kernel = kernel

# Time step can be \sqrt{3}=1.73 bigger with 4th order
self.dt = self.model.critical_dt
if self.kernel == 'OT4':
self.dt = model.dtype(self.dt*1.73)
self.model.dt_scale = 1.73
self.dt = self.model.critical_dt

# Cache compiler options
self._kwargs = kwargs
Expand Down
15 changes: 12 additions & 3 deletions examples/seismic/elastic/elastic_example.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import numpy as np
import pytest

from devito.logger import info
from devito import norm
from examples.seismic.elastic import ElasticWaveSolver
from examples.seismic import demo_model, setup_geometry, seismic_args

Expand Down Expand Up @@ -35,9 +37,16 @@ def run(shape=(50, 50), spacing=(20.0, 20.0), tn=1000.0,

def test_elastic():
_, _, _, [rec1, rec2, v, tau] = run()
norm = lambda x: np.linalg.norm(x.data.reshape(-1))
assert np.isclose(norm(rec1), 20.59193, atol=1e-3, rtol=0)
assert np.isclose(norm(rec2), 0.671578, atol=1e-3, rtol=0)
assert np.isclose(norm(rec1), 19.25636, atol=1e-3, rtol=0)
assert np.isclose(norm(rec2), 0.627606, atol=1e-3, rtol=0)


@pytest.mark.parametrize('ndim', [1, 2, 3])
def test_elastic_stability(ndim):
shape = tuple([11]*ndim)
spacing = tuple([20]*ndim)
_, _, _, [rec1, rec2, v, tau] = run(shape=shape, spacing=spacing, tn=20000.0, nbl=0)
assert np.isfinite(norm(rec1))


if __name__ == "__main__":
Expand Down
44 changes: 27 additions & 17 deletions examples/seismic/model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import numpy as np
from sympy import sin, Abs, finite_diff_weights

from sympy import sin, Abs, finite_diff_weights as fd_w

from devito import (Grid, SubDomain, Function, Constant,
SubDimension, Eq, Inc, Operator, div)
Expand Down Expand Up @@ -77,7 +76,7 @@ def __init__(self, so):

def define(self, dimensions):
"""
Definition of the top part of the domain for wrapped indices FS
Definition of the upper section of the domain for wrapped indices FS.
"""

return {d: (d if not d == dimensions[-1] else ('left', self.size))
Expand Down Expand Up @@ -259,6 +258,11 @@ def __init__(self, origin, spacing, shape, space_order, vp, nbl=20, fs=False,

# User provided dt
self._dt = kwargs.get('dt')
# Some wave equation need a rescaled dt that can't be infered from the model
# parameters, such as isoacoustic OT4 that can use a dt sqrt(3) larger than
# isoacoustic OT2. This property should be set from a wavesolver or after model
# instanciation only via model.dt_scale = value.
self._dt_scale = 1

def _initialize_physics(self, vp, space_order, **kwargs):
"""
Expand Down Expand Up @@ -299,31 +303,36 @@ def _max_vp(self):
return np.sqrt(mmin(self.b) * (mmax(self.lam) + 2 * mmax(self.mu)))

@property
def _scale(self):
def _thomsen_scale(self):
# Update scale for tti
if 'epsilon' in self._physical_parameters:
return np.sqrt(1 + 2 * mmax(self.epsilon))
return 1

@property
def dt_scale(self):
return self._dt_scale

@dt_scale.setter
def dt_scale(self, val):
self._dt_scale = val

@property
def _cfl_coeff(self):
"""
Courant number from the physics.
For the elastic case, we use the general `.85/sqrt(ndim)`.

For te acoustic case, we can get an accurate estimate from the spatial order.
One dan show that C = sqrt(a1/a2) where:
- a1 is the sum of absolute value of FD coefficients in time
- a2 is the sum of absolute value of FD coefficients in space
https://library.seg.org/doi/pdf/10.1190/1.1444605
Courant number from the physics and spatial discretization order.
The CFL coefficients are described in:
- https://doi.org/10.1137/0916052 for the elastic case
- https://library.seg.org/doi/pdf/10.1190/1.1444605 for the acoustic case
"""
# Elasic coefficient (see e.g )
if 'lam' in self._physical_parameters or 'vs' in self._physical_parameters:
return (.85 if self.grid.dim == 3 else .75) / np.sqrt(self.grid.dim)
coeffs = fd_w(1, range(-self.space_order//2+1, self.space_order//2+1), .5)
c_fd = sum(np.abs(coeffs[-1][-1])) / 2
return np.sqrt(self.dim) / self.dim / c_fd
a1 = 4 # 2nd order in time
coeffs = finite_diff_weights(2, range(-self.space_order, self.space_order+1), 0)
a2 = float(self.grid.dim * sum(np.abs(coeffs[-1][-1])))
return np.sqrt(a1/a2)
coeffs = fd_w(2, range(-self.space_order, self.space_order+1), 0)[-1][-1]
return np.sqrt(a1/float(self.grid.dim * sum(np.abs(coeffs))))

@property
def critical_dt(self):
Expand All @@ -334,7 +343,8 @@ def critical_dt(self):
#
# The CFL condtion is then given by
# dt <= coeff * h / (max(velocity))
dt = self._cfl_coeff * np.min(self.spacing) / (self._scale*self._max_vp)
dt = self._cfl_coeff * np.min(self.spacing) / (self._thomsen_scale*self._max_vp)
dt = self.dt_scale * dt
if self._dt:
assert self._dt < dt
return self._dt
Expand Down
3 changes: 2 additions & 1 deletion examples/seismic/skew_self_adjoint/wavesolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ def __init__(self, model, geometry, space_order=8, **kwargs):
self.space_order = space_order

# Time step is .6 time smaller due to Q
self.dt = .6*self.model.critical_dt
self.model.dt_scale = .6
self.dt = self.model.critical_dt

# Cache compiler options
self._kwargs = kwargs
Expand Down
127 changes: 74 additions & 53 deletions examples/seismic/tutorials/06_elastic_varying_parameters.ipynb

Large diffs are not rendered by default.

Loading