Skip to content

pysmo.tools.traveltime

Predicted body-wave travel times.

travel_times is the default source of predicted arrivals wherever pysmo needs them. It computes each arrival by integrating a velocity model included with pysmo, on every call rather than from precomputed tables. This is precise and fast enough for simple tasks, but it does not scale to bulk work, and it covers only the models in Model and the phases in Phase.

travel_times can be substituted with any function that takes the same arguments and returns the same type. TravelTimeBackend describes that shape.

Depth is in metres, epicentral distance in degrees, travel times are pandas.Timedelta.

Type Aliases:

Name Description
Model

A velocity model included with pysmo.

Phase

A phase name the built-in solver computes.

Classes:

Name Description
TravelTimeBackend

The shape of a replacement travel-time function.

Functions:

Name Description
travel_times

Predicted travel times for a source–receiver geometry.

Model

Model = Literal['iasp91', 'ak135']

A velocity model included with pysmo.

Phase

Phase = Literal['P', 'S', 'PcP', 'ScS', 'PcS', 'ScP']

A phase name the built-in solver computes.

TravelTimeBackend

Bases: Protocol

The shape of a replacement travel-time function.

Any callable of the form (*, depth, distance, phases) -> Mapping[str, pd.Timedelta] qualifies. depth is in metres (positive down), distance in epicentral degrees, phases a sequence of phase names, not restricted to the built-in set. The result maps each phase that has an arrival at the given geometry to its travel time, omitting the rest.

Methods:

Name Description
__call__

Return travel times for a source–receiver geometry.

Source code in src/pysmo/tools/traveltime/_types.py
@runtime_checkable
class TravelTimeBackend(Protocol):
    """The shape of a replacement travel-time function.

    Any callable of the form
    `(*, depth, distance, phases) -> Mapping[str, pd.Timedelta]` qualifies.
    *depth* is in metres (positive down), *distance* in epicentral
    degrees, *phases* a sequence of phase names, not restricted to the
    built-in set. The result maps each phase that has an arrival at the
    given geometry to its travel time, omitting the rest.
    """

    def __call__(
        self, *, depth: float, distance: float, phases: Sequence[str]
    ) -> Mapping[str, pd.Timedelta]:
        """Return travel times for a source–receiver geometry."""
        ...

__call__

__call__(
    *, depth: float, distance: float, phases: Sequence[str]
) -> Mapping[str, Timedelta]

Return travel times for a source–receiver geometry.

Source code in src/pysmo/tools/traveltime/_types.py
def __call__(
    self, *, depth: float, distance: float, phases: Sequence[str]
) -> Mapping[str, pd.Timedelta]:
    """Return travel times for a source–receiver geometry."""
    ...

travel_times

travel_times(
    *,
    depth: float,
    distance: float,
    phases: Sequence[Phase],
    model: Model = "iasp91"
) -> dict[str, Timedelta]

Predicted travel times for a source–receiver geometry.

Computed by integrating an included velocity model.

Parameters:

Name Type Description Default
depth float

Source depth in metres, positive downwards.

required
distance float

Epicentral distance in degrees.

required
phases Sequence[Phase]

Phase names to compute.

required
model Model

Velocity model.

'iasp91'

Returns:

Type Description
dict[str, Timedelta]

Mapping of phase name to travel time. Phases with no arrival at

dict[str, Timedelta]

the given geometry are omitted.

Raises:

Type Description
ValueError

If model is not a supported model, phases contains an unsupported phase name, depth is outside the surface to core–mantle boundary range, or distance is outside 0 to 180 degrees.

Note

Each phase is solved on a single turning branch. Where the model produces a travel-time triplication — mainly P and S at short distances for a shallow source — that branch need not be the first arrival.

Examples:

P and S for a 22.9 km deep source at 60°:

>>> from pysmo.tools.traveltime import travel_times
>>> tt = travel_times(depth=22900.0, distance=60.0, phases=["P", "S"])
>>> {phase: round(t.total_seconds(), 2) for phase, t in tt.items()}
{'P': 604.65, 'S': 1096.55}
>>>

A phase-relative fetch window, via haversine and a class's .fetch() (e.g. GeoCsvSeismogram.fetch):

>>> import pandas as pd
>>> from pysmo import MiniEvent, MiniStation
>>> from pysmo.classes import GeoCsvSeismogram
>>> from pysmo.tools.azdist import haversine
>>> station = MiniStation(
...     name="ANMO", network="IU", location="00", channel="LHZ",
...     latitude=34.945981, longitude=-106.457133,
... )
>>> event = MiniEvent(
...     latitude=-36.122, longitude=-72.898, depth=22900.0,
...     time=pd.Timestamp("2010-02-27T06:34:11.53Z"),
... )
>>> dist = haversine(event, station)
>>> arrivals = travel_times(depth=event.depth, distance=dist, phases=["P"])
>>> predicted_p = event.time + arrivals["P"]
>>>
>>> seismogram = GeoCsvSeismogram.fetch(
...     station=station,
...     starttime=predicted_p - pd.Timedelta(minutes=2),
...     endtime=predicted_p + pd.Timedelta(minutes=8),
... )
>>>
Source code in src/pysmo/tools/traveltime/__init__.py
def travel_times(
    *,
    depth: float,
    distance: float,
    phases: Sequence[Phase],
    model: Model = "iasp91",
) -> dict[str, pd.Timedelta]:
    """Predicted travel times for a source–receiver geometry.

    Computed by integrating an included velocity model.

    Args:
        depth: Source depth in metres, positive downwards.
        distance: Epicentral distance in degrees.
        phases: Phase names to compute.
        model: Velocity model.

    Returns:
        Mapping of phase name to travel time. Phases with no arrival at
        the given geometry are omitted.

    Raises:
        ValueError: If *model* is not a supported model, *phases* contains
            an unsupported phase name, *depth* is outside the surface to
            core–mantle boundary range, or *distance* is outside 0 to 180
            degrees.

    Note:
        Each phase is solved on a single turning branch. Where the model
        produces a travel-time triplication — mainly P and S at short
        distances for a shallow source — that branch need not be the
        first arrival.

    Examples:
        `P` and `S` for a 22.9 km deep source at 60°:

        ```python
        >>> from pysmo.tools.traveltime import travel_times
        >>> tt = travel_times(depth=22900.0, distance=60.0, phases=["P", "S"])
        >>> {phase: round(t.total_seconds(), 2) for phase, t in tt.items()}
        {'P': 604.65, 'S': 1096.55}
        >>>
        ```

        A phase-relative fetch window, via
        [`haversine`][pysmo.tools.azdist.haversine] and a class's
        `.fetch()` (e.g.
        [`GeoCsvSeismogram.fetch`][pysmo.classes.GeoCsvSeismogram.fetch]):

        ```python
        >>> import pandas as pd
        >>> from pysmo import MiniEvent, MiniStation
        >>> from pysmo.classes import GeoCsvSeismogram
        >>> from pysmo.tools.azdist import haversine
        >>> station = MiniStation(
        ...     name="ANMO", network="IU", location="00", channel="LHZ",
        ...     latitude=34.945981, longitude=-106.457133,
        ... )
        >>> event = MiniEvent(
        ...     latitude=-36.122, longitude=-72.898, depth=22900.0,
        ...     time=pd.Timestamp("2010-02-27T06:34:11.53Z"),
        ... )
        >>> dist = haversine(event, station)
        >>> arrivals = travel_times(depth=event.depth, distance=dist, phases=["P"])
        >>> predicted_p = event.time + arrivals["P"]
        >>>
        ```

        <!-- skip: start if(not run_real_web_requests) -->
        ```python
        >>> seismogram = GeoCsvSeismogram.fetch(
        ...     station=station,
        ...     starttime=predicted_p - pd.Timedelta(minutes=2),
        ...     endtime=predicted_p + pd.Timedelta(minutes=8),
        ... )
        >>>
        ```
        <!-- skip: end -->
    """
    seconds = solve(depth / 1000.0, distance, phases, model=model)
    return {phase: pd.Timedelta(seconds=value) for phase, value in seconds.items()}