Repository: obspy/obspy

Issue:
P arrival can't be retrieved by the get_travel_times function in taup module
**My test event-station pair is as follow**
CI.SNR..HHZ | 2018-09-01T12:19:38.928392Z - 2018-09-01T12:23:37.288392Z | 100.0 Hz, 23837 samples 38043735.CI.SNR.HHZ.sac 
***gcarc=.84 degree; source_depth=20 km***

**My velocity model goes like this**
    0.00     5.95350   3.40560   2.60000    1456.0     600.0
    5.00     6.06360   3.56020   2.60000	1456.0	   600.0
   10.00	 6.23080   3.69950   3.60000	1456.0	   600.0
   15.00     6.29620   3.71160   2.60000    1456.0     600.0
   20.00	 6.37240   3.53990   2.60000	1456.0	   600.0
   24.40     6.80000   3.90000   2.90000    1350.0     600.0
mantle
   24.40     8.11061   4.49094   3.38076    1446.0     600.0
   40.00     8.10119   4.48486   3.37906    1446.0     600.0
   60.00     8.08907   4.47715   3.37688    1447.0     600.0
   80.00     8.07688   4.46953   3.37471     195.0      80.0
  115.00     8.05540   4.45643   3.37091     195.0      80.0
  150.00     8.03370   4.44361   3.36710     195.0      80.0
...

**Error message**
Traceback (most recent call last):
  File "/home/lilab/jzhu/data/anaconda3/lib/python3.8/site-packages/obspy/taup/seismic_phase.py", line 1303, in _refine_arrival
    shoot = self.shoot_ray(degrees, new_estimate.ray_param)
  File "/home/lilab/jzhu/data/anaconda3/lib/python3.8/site-packages/obspy/taup/seismic_phase.py", line 1337, in shoot_ray
    raise SlownessModelError(msg % (ray_param, self.min_ray_param,
obspy.taup.helper_classes.SlownessModelError: Ray param 1796.640631 is outside range for this phase: min=479.031366 max=1794.118478

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "QC.py", line 87, in <module>
    ArrivalSnr(
  File "QC.py", line 58, in ArrivalSnr
    predict = ttime.tt(trace, Taupmodel)
  File "/storage/lilab/jzhu/EqQbDiscrimi/code/SoCalPreprocess/ttime.py", line 23, in tt
    arr = model.get_travel_times(
  File "/home/lilab/jzhu/data/anaconda3/lib/python3.8/site-packages/obspy/taup/tau.py", line 653, in get_travel_times
    tt.run()
  File "/home/lilab/jzhu/data/anaconda3/lib/python3.8/site-packages/obspy/taup/taup_time.py", line 39, in run
    self.calculate(self.degrees)
  File "/home/lilab/jzhu/data/anaconda3/lib/python3.8/site-packages/obspy/taup/taup_time.py", line 97, in calculate
    self.calc_time(degrees)
  File "/home/lilab/jzhu/data/anaconda3/lib/python3.8/site-packages/obspy/taup/taup_time.py", line 107, in calc_time
    self.arrivals += phase.calc_time(degrees)
  File "/home/lilab/jzhu/data/anaconda3/lib/python3.8/site-packages/obspy/taup/seismic_phase.py", line 1032, in calc_time
    arrivals.append(self.refine_arrival(
  File "/home/lilab/jzhu/data/anaconda3/lib/python3.8/site-packages/obspy/taup/seismic_phase.py", line 1288, in refine_arrival
    return self._refine_arrival(degrees, left, right, dist_radian,
  File "/home/lilab/jzhu/data/anaconda3/lib/python3.8/site-packages/obspy/taup/seismic_phase.py", line 1326, in _refine_arrival
    raise_from(RuntimeError('Please contact the developers. This '
  File "/home/lilab/jzhu/data/anaconda3/lib/python3.8/site-packages/future/utils/__init__.py", line 403, in raise_from
    exec(execstr, myglobals, mylocals)
  File "<string>", line 1, in <module>
RuntimeError: Please contact the developers. This error should not occur.


Discussion:

Resolving patch:
diff --git a/obspy/taup/seismic_phase.py b/obspy/taup/seismic_phase.py
--- a/obspy/taup/seismic_phase.py
+++ b/obspy/taup/seismic_phase.py
@@ -231,6 +231,20 @@ def parse_name(self, tau_model):
                 layer = tau_model.s_mod.get_slowness_layer(
                     s_layer_num, is_p_wave_previous)
                 self.max_ray_param = layer['bot_p']
+                #  check if source is in high slowness zone
+                in_high_slowness, high_slowness_range = \
+                    tau_model.s_mod.depth_in_high_slowness(
+                        tau_model.source_depth,
+                        self.max_ray_param,
+                        is_p_wave_previous,
+                        return_depth_range=True)
+                if in_high_slowness:
+                    # need to reduce max_ray_param until it can propagate out
+                    # of high slowness zone
+                    self.max_ray_param = min(
+                        self.max_ray_param,
+                        high_slowness_range.ray_param)
+
             except SlownessModelError as e:
                 msg = ('Please contact the developers. This error should not '
                        'occur.')
diff --git a/obspy/taup/slowness_model.py b/obspy/taup/slowness_model.py
--- a/obspy/taup/slowness_model.py
+++ b/obspy/taup/slowness_model.py
@@ -1195,7 +1195,8 @@ def distance_check(self):
                 print("Number of " + ("P" if curr_wave_type else "S") +
                       " slowness layers: " + str(j))
 
-    def depth_in_high_slowness(self, depth, ray_param, is_p_wave):
+    def depth_in_high_slowness(self, depth, ray_param, is_p_wave,
+                               return_depth_range=False):
         """
         Determine if depth and slowness are within a high slowness zone.
 
@@ -1208,10 +1209,6 @@ def depth_in_high_slowness(self, depth, ray_param, is_p_wave):
         turn at the top and the bottom, is in the zone at the top, but out of
         the zone at the bottom. (?)
 
-        NOTE: I changed this method a bit by throwing out some seemingly
-        useless copying of the values in temp_range, which I think are not used
-        anywhere else.
-
         :param depth: The depth to check, in km.
         :type depth: float
         :param ray_param: The slowness to check, in s/km.
@@ -1219,9 +1216,12 @@ def depth_in_high_slowness(self, depth, ray_param, is_p_wave):
         :param is_p_wave: Whether to check the P wave (``True``) or the S wave
             (``False``).
         :type is_p_wave: bool
+        :param return_depth_range: Whether to also return the DepthRange of
+            the high slowness zone.
 
         :returns: ``True`` if within a high slowness zone, ``False`` otherwise.
-        :rtype: bool
+            If return_depth_range is ``True``, also returns a DepthRange object
+        :rtype: bool, or (bool, DepthRange)
         """
         if is_p_wave:
             high_slowness_layer_depths = self.high_slowness_layer_depths_p
@@ -1232,7 +1232,11 @@ def depth_in_high_slowness(self, depth, ray_param, is_p_wave):
                 if ray_param > temp_range.ray_param \
                         or (ray_param == temp_range.ray_param and
                             depth == temp_range.top_depth):
+                    if return_depth_range:
+                        return True, temp_range
                     return True
+        if return_depth_range:
+            return False, None
         return False
 
     def approx_distance(self, slowness_turn_layer, p, is_p_wave):


Test patch:
diff --git a/obspy/taup/tests/data/high_slowness_crust.nd b/obspy/taup/tests/data/high_slowness_crust.nd
new file mode 100644
--- /dev/null
+++ b/obspy/taup/tests/data/high_slowness_crust.nd
@@ -0,0 +1,15 @@
+0.00 5.95350 3.40560 2.60000 1456.0 600.0
+5.00 6.06360 3.56020 2.60000 1456.0 600.0
+10.00 6.23080 3.69950 3.60000 1456.0 600.0
+15.00 6.29620 3.71160 2.60000 1456.0 600.0
+20.00 6.37240 3.53990 2.60000 1456.0 600.0
+24.40 6.80000 3.90000 2.90000 1350.0 600.0
+mantle
+   24.40     8.11061   4.49094   3.38076    1446.0     600.0
+ 2891.00    13.71660   7.26466   5.56645     826.0     312.0
+outer-core
+ 2891.00     8.06482   0.00000   9.90349   57822.0       0.0
+ 5149.50    10.35568   0.00000  12.16634   57822.0       0.0
+inner-core
+ 5149.50    11.02827   3.50432  12.76360     445.0      85.0
+ 6371.00    11.26220   3.66780  13.08848     431.0      85.0
diff --git a/obspy/taup/tests/test_tau.py b/obspy/taup/tests/test_tau.py
--- a/obspy/taup/tests/test_tau.py
+++ b/obspy/taup/tests/test_tau.py
@@ -1100,3 +1100,17 @@ def test_fruit(self):
             np.testing.assert_allclose(p.max_distance*180.0/np.pi,
                                        72.66, atol=atol)
             assert p.branch_seq == [0, 0]
+
+    def test_high_slowness_crust(self):
+        """
+        Check handling of sources located in a high slowness layer.
+        """
+        model_name = "high_slowness_crust"
+        with TemporaryWorkingDirectory():
+            folder = os.path.abspath(os.curdir)
+            build_taup_model(
+              filename=os.path.join(DATA, os.path.pardir, model_name + ".nd"),
+              output_folder=folder, verbose=False)
+            model = TauPyModel(os.path.join(folder, model_name + ".npz"))
+            arrivals = model.get_ray_paths(20.0, 0.84, phase_list=["sS"])
+            assert len(arrivals) == 0