%matplotlib inline
# This notebook will use this variable to determine which
# remote site to run on.
import os
SALVUS_FLOW_SITE_NAME = os.environ.get("SITE_NAME", "local")
PROJECT_DIR = "project"
# Uncomment the following line to delete a
# potentially existing project for a fresh start
# !rm -rf project
import pathlib
import numpy as np
import salvus.namespace as sn
d = sn.domain.dim2.BoxDomain(x0=0.0, x1=2000.0, y0=0.0, y1=1000.0)
Project.from_domain()
constructor as shown below. This function takes a path and a domain object such as the one we just constructed. Calling this function several times will load the project state from disk instead. The project state is persistent and not tied to this notebook, so you can easily take a break and continue working on the project anytime.p = sn.Project.from_domain(path=PROJECT_DIR, domain=d, load_if_exists=True)
p.viz.nb.
followed by the name of the entity. Currently the project does not contain a lot of data yet, but at least we can visualize the domain extent.p.viz.nb.domain()
simple_config
objects from SalvusFlow above. You can use the tab
key to browse through the different categories and to see a list of all available options, or check the documentation here.src = sn.simple_config.source.cartesian.VectorPoint2D(
x=1000.0, y=500.0, fx=0.0, fy=-1e10
)
SimulationConfiguration
.y
position of 800 m. We need to specify a unique station code for each receiver, as well as a list of which fields we'd like the receivers to record (displacement in this case).recs = [
sn.simple_config.receiver.cartesian.Point2D(
y=800.0,
x=x,
network_code="XX",
station_code=f"REC{i + 1}",
fields=["displacement"],
)
for i, x in enumerate(np.linspace(1010.0, 1410.0, 5))
]
Event
to our project. Every event requires a unique name.p.add_to_project(sn.Event(event_name="event_0", sources=src, receivers=recs))
Event
, along with several other relevant objects, can be added to a project by calling add_to_project
or simply using the +=
operator. Once the object is successfully added to the project it is then "serialized", or saved, within the project directory structure. The power and usefulness of this concept will become apparent in a later tutorial. For now all you need to know is that the event is officially a part of our project!p.viz.nb.domain()
background_model
.mc = sn.ModelConfiguration(
background_model=sn.model.background.homogeneous.IsotropicElastic(
rho=2200.0, vp=3200.0, vs=1847.5
)
)
EventConfiguration
. Here, at a bare minimum, we need to specify what type of source wavelet we would like to model, and to provide some basic information about the temporal extent of our upcoming simulations. The reference data were computed with using a Ricker wavelet centered around time and with a center frequency of Hz. The time interval spans a bit more than 0.5 seconds. These parameters are now used to define our EventConfiguration
object. The start time is optional and only necessary here for comparing with the analytic solution in the next part. Salvus will auto-derive an appropriate start time if not given. Because the Ricker wavelet is centered around zero, we have non-zero contributions before and consequently need to start the simulation at a negative starting time.ec = sn.EventConfiguration(
wavelet=sn.simple_config.stf.Ricker(center_frequency=14.5),
waveform_simulation_configuration=sn.WaveformSimulationConfiguration(
start_time_in_seconds=-0.08,
end_time_in_seconds=0.6,
),
)
ec.wavelet.plot()
SimulationConfiguration
. A SimulationConfiguration
is a unique identifier that brings together the model, the source wavelet parameterization, and a proxy of the resolution of the simulation together.
Depending on the application, sufficient accuracy is typically achieved with 7 to 10 grid points per wavelength, sometimes even less. By default, we use a polynomial degree of 4 in the spectral-element mesh, which gives 5 grid points per element. Here, we need to specify elements_per_wavelength
and choose 1.0
.SimulationConfiguration
to our project as below.p.add_to_project(
sn.SimulationConfiguration(
name="my_first_simulation",
max_frequency_in_hertz=30.0,
elements_per_wavelength=1.0,
model_configuration=mc,
event_configuration=ec,
)
)
SimulationConfiguration
directly in the notebook, as below. This function takes a list of events as well, for the purpose of overplotting sources and receivers on the resultant domain. Let's have a look.p.viz.nb.simulation_setup(
simulation_configuration="my_first_simulation", events=p.events.list()
)
[2024-07-05 09:52:17,254] INFO: Creating mesh. Hang on.