SimulationConfiguration
object, and investigate how high-order accurate topography / material interpolation can be used to improve simulation performance. To accomplish this, we'll focus on a real-life use case using the area around Mt. St. Helens in Washington state as an example. Of course please feel free to adapt the geographical area.%matplotlib inline
import os
import pathlib
import salvus.namespace as sn
# This notebook will use this variable to determine which
# remote site to run on.
SALVUS_FLOW_SITE_NAME = os.environ.get("SITE_NAME", "local")
PROJECT_DIR = "project"
UtmDomain.from_spherical_chunk
constructor that takes WGS84 coordinates and converts them to an appropriate UTM domain. The UTM zone and coordinates could of course also be specified directly.d = sn.domain.dim3.UtmDomain.from_spherical_chunk(
min_latitude=46.15,
max_latitude=46.30,
min_longitude=-122.28,
max_longitude=-122.12,
)
# Have a look at the domain to make sure it is correct.
d.plot()
# Where to download topography data to.
topo_filename = "data/gmrt_topography.nc"
# Now query data from the GMRT web service.
if not pathlib.Path(topo_filename).exists():
d.download_topography_and_bathymetry(
filename=topo_filename,
# The buffer is useful for example when adding sponge layers
# for absorbing boundaries so we recommend to always use it.
buffer_in_degrees=0.1,
resolution="default",
)
# Load the data to Salvus compatible SurfaceTopography object. It will resample
# to a regular grid and convert to the UTM coordinates of the domain.
# This will later be added to the Salvus project.
t = sn.topography.cartesian.SurfaceTopography.from_gmrt_file(
name="st_helens_topo",
data=topo_filename,
resample_topo_nx=200,
# If the coordinate conversion is very slow, consider decimating.
decimate_topo_factor=5,
# Smooth if necessary.
gaussian_std_in_meters=0.0,
# Make sure to pass the correct UTM zone.
utm=d.utm,
)
t.ds.dem.T.plot(aspect=1, size=7)
<matplotlib.collections.QuadMesh at 0x7e4c613e07d0>
# Uncomment the following line to delete a
# potentially existing project for a fresh start
# !rm -rf project
p = sn.Project.from_domain(path=PROJECT_DIR, domain=d, load_if_exists=True)
SurfaceTopography
object to Salvus:p += t
p.entities.list("topography_model")
['st_helens_topo']
SideSet
source and receiver classes. The functionality of these classes is documented here. Essentially, they help when the actual surface of the domain is non-trivial, as is the case in this example. When these sources and receivers are associated with a mesh, a small optimization problem is solved to ensure that they are placed exactly at the surface of the deformed mesh, plus or minus any offset one might specify. Here we specify the entities in UTM coordinates, place the source 1000 meters below the free surface, and place a line of 50 receivers directly at the free surface.# Src / Rec reference coordinates.
src_x, src_y, src_z = 562700.0, 5112500.0, 4000.0
rec_x, rec_y, rec_z = 564700.0, 5115500.0, 4000.0
# Place an explosive source 1000 m below the free surface.
src = sn.simple_config.source.cartesian.SideSetMomentTensorPoint3D(
point=(src_x, src_y, src_z),
direction=(0, 0, 1),
side_set_name="z1",
mxx=1e21,
myy=1e21,
mzz=1e21,
myz=0.0,
mxz=0.0,
mxy=0.0,
offset=-1000.0,
)
# Place a line of receivers at the free surface.
rec = [
sn.simple_config.receiver.cartesian.SideSetPoint3D(
point=(rec_x, rec_y + _i * 100, rec_z),
direction=(0, 0, 1),
side_set_name="z1",
fields=["velocity"],
station_code=f"XX_{_i}",
)
for _i in range(50)
]
# Add the event collection to the project.
p += sn.EventCollection.from_sources(sources=[src], receivers=rec)
bm = sn.model.background.homogeneous.IsotropicElastic(
rho=2600.0, vp=3000.0, vs=1875.5
)
mc = sn.model.ModelConfiguration(background_model=bm)
ec = sn.EventConfiguration(
wavelet=sn.simple_config.stf.Ricker(center_frequency=1.0),
waveform_simulation_configuration=sn.WaveformSimulationConfiguration(
end_time_in_seconds=6.0
),
)
TopographyConfiguration
object. As is true with the ModelConfiguration
, this object takes as arguments one or more topography models. A background topography model does not have much of a meaning, so it is not defined for topography configurations.tc = sn.topography.TopographyConfiguration(topography_models="st_helens_topo")
ab = sn.AbsorbingBoundaryParameters(
reference_velocity=3000.0,
reference_frequency=5.0,
number_of_wavelengths=3.5,
)
p += sn.SimulationConfiguration(
name="sim_1st_order_topo",
tensor_order=1,
model_configuration=mc,
event_configuration=ec,
absorbing_boundaries=ab,
elements_per_wavelength=1,
max_depth_in_meters=2000.0,
max_frequency_in_hertz=2.0,
topography_configuration=tc,
)
p.viz.nb.simulation_setup("sim_1st_order_topo", ["event_0000"])
[2024-11-20 13:13:49,660] INFO: Creating mesh. Hang on.
<salvus.flow.simple_config.simulation.waveform.Waveform object at 0x7e4d58638510>
p.simulations.launch(
"sim_1st_order_topo",
events=["event_0000"],
site_name=SALVUS_FLOW_SITE_NAME,
ranks_per_job=2,
)
[2024-11-20 13:13:50,351] INFO: Submitting job ... Uploading 1 files... 🚀 Submitted job_2411201313430409_25637ce18c@local
1
p.simulations.query(block=True)
True
p.waveforms.get(data_name="sim_1st_order_topo", events=["event_0000"])[0].plot(
component="X",
receiver_field="velocity",
plot_types=["wiggles", "shotgather"],
)
# Create the configuration.
p += sn.SimulationConfiguration(
name="sim_4th_order_topo",
tensor_order=4,
model_configuration=mc,
event_configuration=ec,
absorbing_boundaries=ab,
elements_per_wavelength=1,
max_depth_in_meters=2000.0,
max_frequency_in_hertz=2.0,
topography_configuration=tc,
)
# Visualize
p.viz.nb.simulation_setup("sim_4th_order_topo", ["event_0000"])
[2024-11-20 13:14:01,477] INFO: Creating mesh. Hang on.
<salvus.flow.simple_config.simulation.waveform.Waveform object at 0x7e4c522d1a90>
# Launch the simulation.
p.simulations.launch(
"sim_4th_order_topo",
events=["event_0000"],
site_name=SALVUS_FLOW_SITE_NAME,
ranks_per_job=2,
)
[2024-11-20 13:14:02,731] INFO: Submitting job ... Uploading 1 files... 🚀 Submitted job_2411201314734031_6af0d72461@local
1
p.simulations.query(block=True)
p.viz.nb.waveforms(
["sim_1st_order_topo", "sim_4th_order_topo"], receiver_field="velocity"
)
# Create the configuration.
# p += sn.SimulationConfiguration(
# name="sim_1st_order_topo_large",
# tensor_order=1,
# model_configuration=mc,
# event_configuration=ec,
# absorbing_boundaries=ab,
# elements_per_wavelength=1,
# max_depth_in_meters=2000.0,
# max_frequency_in_hertz=5.0,
# topography_configuration=tc,
# )
# if False:
# p.simulations.launch(
# "sim_1st_order_topo_large",
# events=["event_0000"],
# site_name="cluster",
# ranks_per_job=36,
# wall_time_in_seconds_per_job=600,
# )
# if False:
# p.waveforms.get(
# data_name="sim_1st_order_topo_large", events=["event_0000"]
# )[0].plot(["wiggles", "shotgather"])
.json
file containing the parameters of your request, as well as the spatial domain for which it was madeDomain
constructor within Salvus project can recognize AppEEARS request files, and can use them to set automatically set up a cartesian domain in the correct UTM zone. We do this below. You'll notice that we supply an extra parameter: shrink_domain
. This factor exists to make it easy to isotropically shrink the domain specified in the AppEEARS request. Specifying such a value is necessary in our case as the mesh we create will eventually require absorbing boundary layers to be attached. If these layers extend past the boundaries of the topography files, we should expect some artefacts in the interpolation of the topography. Shrinking the domain here provides an easy way to ensure that the topography model is still valid for the absorbing region.domain_appeears = sn.domain.dim3.UtmDomain.from_appeears_request(
json_file="./data/appeears.json", shrink_domain=10000.0
)
domain_appeears.plot()
decimate_topo_factor
which only reads every "nth" value in the data file. Since we will be working with relatively low-frequency simulations we will not need topography data every 30m. Decimating the data allows us to work with a smaller file in memory with almost no effect on the final mesh. We also pass the utm
object stored within the Domain
. The object is a pyproj.Proj
projection object which defines how we transform the coordinates in the APPEEARS request to those in our UTM domain.topo_appeears = (
sn.topography.cartesian.SurfaceTopography.from_appeears_request(
name="appears_topo",
data="./data/sthelens.nc",
decimate_topo_factor=100,
utm=domain_appeears.utm,
)
)
topo_appeears.ds.dem.T.plot(aspect=1, size=7)
<matplotlib.collections.QuadMesh at 0x7e4c50b2e510>