This documentation is not for the latest stable Salvus version.
# 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"
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 show below. This function takes a path (which must not yet exist), and a domain object such as the one we just constructed.
# Uncomment the following line to delete a # potentially existing project for a fresh start # !rm -rf proj if pathlib.Path(PROJECT_DIR).exists(): print("Opening existing project.") p = sn.Project(path=PROJECT_DIR) else: print("Creating new project.") p = sn.Project.from_domain(path=PROJECT_DIR, domain=d)
Creating new project.
proj. This is where all the relevant information relating to your project will be stored. Just so we can get a hang of the basic structure of a project, let's open up the folder in our file browser. Most operating systems will understand the command below, and will open the project folder in another window. Just uncomment the line for your operating system.
# On Mac OSX # !open proj # On Linux: # !xdg-open proj
reference_data.h5in the current directory.
SEGYdescribe their data with associated headers. We'll see how to add these types of data in a later tutorial, but in this case we are just reading in raw waveform traces with little to no meta information. Because of this we'll need to assist Salvus a little and tell the project to what events this raw data refers to. This information is passed in the form of an
EventCollectionobject which, at is most basic, is a data structure which relates lists of source definitions to lists of receiver definitions. These definitions can be in the form of pressure injections, force vectors, or GCMT moment tensors for sources, as well as pressure, velocity, or strain (etc.) sensors for receivers. In the coordinate system of the reference dataset which we'll add, we've placed a single vector source at the location . This source can be defined with the help of the
simple_confighelper as in the cell below.
srcs = sn.simple_config.source.cartesian.VectorPoint2D( x=1000.0, y=500.0, fx=0.0, fy=-1.0 )
simple_confighelper allows us to define the set in one go.
recs = sn.simple_config.receiver.cartesian.collections.ArrayPoint2D( y=800.0, x=np.linspace(1010.0, 1410.0, 5), fields=["displacement"] )
p += sn.EventCollection.from_sources(sources=[srcs], receivers=recs)
EventCollection, along with several other relevant objects, can be added to a project by simply using the
+=operator. Once the object is succesfully 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 collection is now officially a part of our project!
"event_xxxx", which correspond to the order in which they were added. Below we add the reference data to our project with the tag
"reference", and associate it with the event we just created, or
p.waveforms.add_external( data_name="reference", event="event_0000", data_filename="./reference_data.h5", )
.plot()below to see how the different options look.
p.waveforms.get(data_name="EXTERNAL_DATA:reference", events=["event_0000"])[ 0 ].plot(component="X", receiver_field="displacement")
bm = sn.model.background.homogeneous.IsotropicElastic( rho=2200.0, vp=3000.0, vs=1847.5 ) mc = sn.ModelConfiguration(background_model=bm, volume_models=None)
EventConfiguration. Here, at a bare minimum, we need to specify what type of source wavelet we would like to model, as well provide some basic information about the temporal extent of our upcoming simulations. The reference data were computed with using a Ricker wavelet with a center frequency of and, looking at the traces plotted above, we can see that the data runs for a bit more than 0.5 seconds. These parameters are now used to define our
ec = sn.EventConfiguration( wavelet=sn.simple_config.stf.Ricker(center_frequency=15.0), waveform_simulation_configuration=sn.WaveformSimulationConfiguration( end_time_in_seconds=0.6 ), )
SimulationConfigurationis a unique identifier that brings together the model, the source wavelet parameterization, and a proxy of the resolution of the simulation together. If you recall from the theoretical presentations earlier today, we are often satisfied with a simulation mesh comprised of 1 4th order spectral-element per simulated wavelength. The question then remains: given a broadband source wavelet, which frequency do we want to mesh for? The wavelet plot above gives us a clue: the vast majority of the energy in the current wavelet is contained at frequencies below . For our first attempt at matching the analytic solution then, we'll require that our mesh be generated using one element per wavelength at a frequency of . As you are probably becoming familiar with by now, we can add the relevant
SimulationConfigurationto our project as below.
p += sn.SimulationConfiguration( name="simulation_1", max_frequency_in_hertz=30.0, elements_per_wavelength=1.0, model_configuration=mc, event_configuration=ec, )
event = sn.EventCollection.from_sources(sources=[srcs], receivers=None)
w = p.simulations.get_input_files("simulation_1", events=event)
[2021-05-06 09:27:02,317] INFO: Creating mesh. Hang on.
SimulationConfigurationdirectly 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.
<salvus.flow.simple_config.simulation.Waveform object at 0x7fbb8f2f5510>
p.viz.nb.simulation_setup( simulation_configuration="simulation_1", events=["event_0000"] )
<salvus.flow.simple_config.simulation.Waveform object at 0x7fbb8ead6790>