Version:

Attenuation

This micro-tutorial demonstrates how to run simulations in viscoacoustic / viscoelastic media with SalvusProject. It shows the concept in 2-D but it works just the same in 3-D by just adding an extra dimension.
Copy
import matplotlib.pyplot as plt
import numpy as np
import os
import pathlib
import xarray as xr

import salvus.namespace as sn

# Run simulations on this site.
SALVUS_FLOW_SITE_NAME = os.environ.get("SITE_NAME", "local")

Parameter Model

There are different ways to define meshes and models in SalvusProject. Here we'll define the multi-parameter model as an xarray data set which will later be interpolated onto our numerical meshes.
# Grid points of the velocity model.
# NOT the grid point of the simulation!
nx, ny = 200, 200

# Create a coordinate grid.
x = np.linspace(-100.0, +100.0, nx)
y = np.linspace(-100.0, +100.0, nx)
xx, yy = np.meshgrid(x, y, indexing="ij")

# Simplistic 5 parameter model with a depth gradient and
# some sinusoidal perturbation.
# This is just an example but this would be the place to add
# your own velocity model.
vp = 1500.0 * np.ones_like(xx) - yy * 0.1 + np.sin(xx / 8 + 0.5) * 2
vs = 1000.0 * np.ones_like(xx) - yy * 0.1 + np.sin(xx / 12 - 0.3) * 2
rho = 980.0 * np.ones_like(xx) - yy * 0.1 + np.sin(xx / 15 + 1) * 2
qkappa = 75.0 * np.ones_like(xx) - yy * 0.1 + np.sin(xx / 6) * 2
qmu = 75.0 * np.ones_like(xx) - yy * 0.1 + np.sin(xx / 10 - 1.2) * 2

# We build a full visco-elastic model with all fields, but will drop
# unneeded fields later on as needed.
dataset = xr.Dataset(
    data_vars={
        "vp": (["x", "y"], vp),
        "vs": (["x", "y"], vs),
        "rho": (["x", "y"], rho),
        "qkappa": (["x", "y"], qkappa),
        "qmu": (["x", "y"], qmu),
    },
    coords={"x": x, "y": y},
)

# Have a look at the Qmu model.
plt.figure(figsize=(10, 8))
dataset.qmu.T.plot()
plt.show()
# Either load an existing project or create a new one.
p = sn.Project.from_domain(
    path="project",
    # Should match the volume model.
    domain=sn.domain.dim2.BoxDomain(x0=-100, x1=100, y0=-100, y1=100),
    load_if_exists=True,
)
Here we'll create a single explosive source in the center with receivers in a ring around it. The elastic event uses a moment tensor source and records displacement, while the acoustic event requires a scalar source and records phi.
src = sn.simple_config.source.cartesian.MomentTensorPoint2D(
    x=0, y=0, mxx=1.0, myy=1.0, mxy=0.0
)

recs = sn.simple_config.receiver.cartesian.collections.RingPoint2D(
    x=0, y=0, radius=75.0, count=20, fields=["displacement"]
)

p += sn.Event(event_name="event_ela", sources=src, receivers=recs)

src = sn.simple_config.source.cartesian.ScalarPoint2D(x=0, y=0, f=1.0)

recs = sn.simple_config.receiver.cartesian.collections.RingPoint2D(
    x=0, y=0, radius=75.0, count=20, fields=["phi"]
)

p += sn.Event(event_name="event_aco", sources=src, receivers=recs)


p.viz.nb.domain()
Now we'll add models for acoustic and elastic simulations with and without attenuation. Each scenario uses the full model defined in the dataset above, and we just drop the fields that are not needed.
p += sn.model.volume.cartesian.GenericModel(
    name="model_elastic_with_Q", data=dataset
)

# An elastic simulation without attenuation neither requires QMU not QKAPPA
p += sn.model.volume.cartesian.GenericModel(
    name="model_elastic_without_Q", data=dataset.drop_vars(["qmu", "qkappa"])
)

# An acoustic simulation without attenuation neither requires Q nor VS
p += sn.model.volume.cartesian.GenericModel(
    name="model_acoustic_without_Q",
    data=dataset.drop_vars(["qmu", "qkappa", "vs"]),
)

# An acoustic simulation with attenuation neither requires QMU nor VS,
# but requires QKAPPA
p += sn.model.volume.cartesian.GenericModel(
    name="model_acoustic_with_Q", data=dataset.drop_vars(["qmu", "vs"])
)
# A few wave-lengths are needed to see an attenuating effect.
center_frequency = 100.0

# Setup four simulations:
# Acoustic / elastic and with or without attenuation.

for medium in ["acoustic", "elastic"]:
    for attenuation in [True, False]:
        if attenuation:
            simulation_name = f"{medium}_with_attenuation"
            mc = sn.ModelConfiguration(
                background_model=None,
                volume_models=f"model_{medium}_with_Q",
                # Attenuation results in slightly frequency dependent wave speed.
                # Thus one must specify at what frequency they are specified.
                # They are often given at 1 Hz.
                linear_solids=sn.LinearSolids(reference_frequency=1.0),
            )
        else:
            simulation_name = f"{medium}_without_attenuation"
            mc = sn.ModelConfiguration(
                background_model=None,
                volume_models=f"model_{medium}_without_Q",
            )

        p += sn.SimulationConfiguration(
            name=simulation_name,
            max_frequency_in_hertz=center_frequency * 2.0,
            elements_per_wavelength=2.0,
            model_configuration=mc,
            event_configuration=sn.EventConfiguration(
                wavelet=sn.simple_config.stf.Ricker(
                    center_frequency=center_frequency
                ),
                waveform_simulation_configuration=sn.WaveformSimulationConfiguration(
                    end_time_in_seconds=0.07,
                    # This is to tell the simulation to actually
                    # simulate with attenuation or not.
                    attenuation=attenuation,
                ),
            ),
        )
As an example, we visualize the simulation setup for the viscoelastic simulation.
p.viz.nb.simulation_setup("elastic_with_attenuation")
[2024-07-05 10:07:24,075] INFO: Creating mesh. Hang on.
<salvus.mesh.data_structures.unstructured_mesh.unstructured_mesh.UnstructuredMesh object at 0x7f2abab005d0>
Now actually run the simulations. Note that event_ela has a moment tensor source, hence requires an elastic medium, while event_aco uses a scalar source for an acoustic medium.
for name in [
    "elastic_with_attenuation",
    "elastic_without_attenuation",
]:
    p.simulations.launch(
        simulation_configuration=name,
        events="event_ela",
        site_name=SALVUS_FLOW_SITE_NAME,
        ranks_per_job=1,
    )

for name in [
    "acoustic_with_attenuation",
    "acoustic_without_attenuation",
]:
    p.simulations.launch(
        simulation_configuration=name,
        events="event_aco",
        site_name=SALVUS_FLOW_SITE_NAME,
        ranks_per_job=1,
    )

# Wait for all simulations to finish
p.simulations.query(block=True)
[2024-07-05 10:07:24,458] INFO: Submitting job ...
Uploading 1 files...

🚀  Submitted job_2407051007561309_5c1c89835e@local
[2024-07-05 10:07:24,625] INFO: Creating mesh. Hang on.
[2024-07-05 10:07:24,698] INFO: Submitting job ...
Uploading 1 files...

🚀  Submitted job_2407051007701713_4feedc86f5@local
[2024-07-05 10:07:24,760] INFO: Creating mesh. Hang on.
[2024-07-05 10:07:24,876] INFO: Submitting job ...
Uploading 1 files...

🚀  Submitted job_2407051007879451_d247932b86@local
[2024-07-05 10:07:24,934] INFO: Creating mesh. Hang on.
[2024-07-05 10:07:24,982] INFO: Submitting job ...
Uploading 1 files...

🚀  Submitted job_2407051007986118_49b3f8381c@local
True
The amplitude of the attenuated waves is of course smaller but they also arrive a tad bit later.
p.viz.waveforms(
    event="event_ela",
    receiver_name="XX.A0006",
    data=["elastic_with_attenuation", "elastic_without_attenuation"],
    receiver_field="displacement",
)
plt.show()
p.viz.waveforms(
    event="event_aco",
    receiver_name="XX.A0006",
    data=["acoustic_with_attenuation", "acoustic_without_attenuation"],
    receiver_field="phi",
)
plt.show()
PAGE CONTENTS