Example #1
0
 def test_gridpoint2index(self):
     """
     Should convert between 1D and 3D grid indices.
     """
     vm = VM()
     # should return an integer index
     i0 = (10, 0, 3)
     idx = vm.gridpoint2index(*i0)
     self.assertEqual(type(idx), int)
     # should return original 3D indices
     i1 = vm.index2gridpoint(idx)
     self.assertEqual(type(i1), tuple)
     for j in range(3):
         self.assertEqual(i0[j], i1[j])
Example #2
0
 def test_insert_interface(self):
     """
     Should insert an interface and handle setting flags correctly
     """
     # Initialize a new model
     vm = VM(r1=(0, 0, 0), r2=(50, 0, 30), dx=0.5, dy=0.5, dz=0.5)
     # Should add a new, flat interface at 10 km
     z0 = 10.
     vm.insert_interface(z0 * np.ones((vm.nx, vm.ny)))
     self.assertEqual(vm.nr, 1)
     self.assertEqual(vm.rf[0].min(), z0)
     self.assertEqual(vm.rf[0].max(), z0)
     # New interfaces should have jp=0
     self.assertEqual(vm.jp[0].min(), 0)
     self.assertEqual(vm.jp[0].max(), 0)
     # New layers should have ir and ij = index of new interface
     self.assertEqual(vm.ir[0].min(), 0)
     self.assertEqual(vm.ir[0].max(), 0)
     self.assertEqual(vm.ij[0].min(), 0)
     self.assertEqual(vm.ij[0].max(), 0)
     # Adding a new interface should increase ir and ij of deeper layers
     for z0 in [5., 15., 1., 20.]:
         vm.insert_interface(z0 * np.ones((vm.nx, vm.ny)))
         for iref in range(0, vm.nr):
             self.assertEqual(vm.ir[iref].min(), iref)
             self.assertEqual(vm.ir[iref].max(), iref)
             self.assertEqual(vm.ij[iref].min(), iref)
             self.assertEqual(vm.ij[iref].max(), iref)
     # should take a scalar value for a constant depth interface
     vm = VM(r1=(0, 0, 0), r2=(50, 0, 30), dx=0.5, dy=0.5, dz=0.5)
     z0 = 10.
     vm.insert_interface(z0)
     self.assertEqual(vm.nr, 1)
     self.assertEqual(vm.rf[0].min(), z0)
     self.assertEqual(vm.rf[0].max(), z0)
Example #3
0
 def test_x2i(self):
     """
     Should convert between x indices and coordinates and back
     """
     r1 = (-10, -20, -30)
     r2 = (10, 20, 30)
     vm = VM(r1=r1, r2=r2, dx=1, dy=1, dz=1)
     ix = vm.x2i([vm.r1[0]])[0]
     x = vm.i2x([ix])[0]
     self.assertEqual(ix, 0)
     self.assertEqual(x, vm.r1[0])
     ix = vm.x2i([vm.r2[0]])[0]
     x = vm.i2x([ix])[0]
     self.assertEqual(ix, vm.nx - 1)
     self.assertEqual(x, vm.r2[0])
Example #4
0
 def test_z2i(self):
     """
     Should convert between z indices and coordinates and back
     """
     r1 = (-10, -20, -30)
     r2 = (10, 20, 30)
     vm = VM(r1=r1, r2=r2, dx=1, dy=1, dz=1)
     iz = vm.z2i([vm.r1[2]])[0]
     z = vm.i2z([iz])[0]
     self.assertEqual(iz, 0)
     self.assertEqual(z, vm.r1[2])
     iz = vm.z2i([vm.r2[2]])[0]
     z = vm.i2z([iz])[0]
     self.assertEqual(iz, vm.nz - 1)
     self.assertEqual(z, vm.r2[2])
Example #5
0
 def test_y2i(self):
     """
     Should convert between y indices and coordinates and back
     """
     r1 = (-10, -20, -30)
     r2 = (10, 20, 30)
     vm = VM(r1=r1, r2=r2, dx=1, dy=1, dz=1)
     iy = vm.y2i([vm.r1[1]])[0]
     y = vm.i2y([iy])[0]
     self.assertEqual(iy, 0)
     self.assertEqual(y, vm.r1[1])
     iy = vm.y2i([vm.r2[1]])[0]
     y = vm.i2y([iy])[0]
     self.assertEqual(iy, vm.ny - 1)
     self.assertEqual(y, vm.r2[1])
Example #6
0
 def test_get_layer_bounds(self):
     """
     Should return arrays with layer top and bottom bounding surfaces
     """
     # Create a simple 3D model
     r1 = (0, 0, 0)
     r2 = (50, 50, 20)
     vm = VM(r1=r1, r2=r2, dx=2, dy=2, dz=0.2)
     # Should have no layers
     self.assertEqual(vm.nr, 0)
     # Should return model top and bottom
     z0, z1 = vm.get_layer_bounds(0)
     self.assertEqual(z0.shape, (vm.nx, vm.ny))
     self.assertEqual(z1.shape, (vm.nx, vm.ny))
     self.assertEqual(z0.min(), r1[2])
     self.assertEqual(z0.max(), r1[2])
     self.assertEqual(z1.min(), r2[2])
     self.assertEqual(z1.max(), r2[2])
     # Insert an interfce and get bounds again
     _z = 5.
     vm.insert_interface(_z * np.ones((vm.nx, vm.ny)))
     # Top layer should be between r1[2] and _z
     z0, z1 = vm.get_layer_bounds(0)
     self.assertEqual(z0.min(), r1[2])
     self.assertEqual(z0.max(), r1[2])
     self.assertEqual(z1.min(), _z)
     self.assertEqual(z1.max(), _z)
     # Bottom layer should be between _z and r2[2]
     z0, z1 = vm.get_layer_bounds(1)
     self.assertEqual(z0.min(), _z)
     self.assertEqual(z0.max(), _z)
     self.assertEqual(z1.min(), r2[2])
     self.assertEqual(z1.max(), r2[2])
     # Should handle pinchouts
     _z1 = 2 * _z
     iref = vm.insert_interface(_z1 * np.ones((vm.nx, vm.ny)))
     ix = vm.xrange2i(0, 25)
     vm.rf[iref, ix, :] = vm.rf[iref - 1, ix, :]
     z0, z1 = vm.get_layer_bounds(1)
     self.assertEqual(z0.min(), _z)
     self.assertEqual(z0.max(), _z)
     self.assertEqual(z1.min(), _z)
     self.assertEqual(z1.max(), _z1)
Example #7
0
 def test_remove_interface(self):
     """
     Should remove an interface and handle setting flags correctly
     """
     # Initialize a new model
     vm = VM(r1=(0, 0, 0), r2=(50, 0, 30), dx=0.5, dy=0.5, dz=0.5)
     # Insert a set of interaces
     nr = 0
     for z0 in [1., 5., 10., 15., 20.]:
         nr += 1
         vm.insert_interface(z0 * np.ones((vm.nx, vm.ny)))
     # Should remove interface and decrease ir and ij of deeper layers
     for iref in range(0, nr):
         vm.remove_interface(0)
         nr -= 1
         self.assertEqual(vm.nr, nr)
         for _iref in range(0, vm.nr):
             self.assertEqual(vm.ir[_iref].min(), _iref)
             self.assertEqual(vm.ir[_iref].max(), _iref)
             self.assertEqual(vm.ij[_iref].min(), _iref)
             self.assertEqual(vm.ij[_iref].max(), _iref)
Example #8
0
 def setUp(self):
     """
     Build test models
     """
     # Create a 3D model with three layers (2 boundaries)
     r1 = (0, 0, 0)
     r2 = (20, 20, 30)
     self.vm = VM(r1=r1, r2=r2)
     self.vm.insert_interface(5)
     self.vm.insert_interface(10)
     # Define a path for the test case
     self.pi = [0, 1, 1, 2, 2, 2, 1, 1, 0]
     self.px = [1, 1, 1, 1, 1, 1, 1, 1, 1]
     self.py = [1, 2, 3, 4, 5, 6, 7, 8, 9]
     self.pz = [1, 6, 8, 11, 12, 11, 8, 6, 2]
     self.path = zip(self.px, self.py, self.pz, self.pi)
Example #9
0
    def setUp(self):
        """
        Build a test VM model
        """
        self.vm = VM(r1=(0, 0, 0), r2=(100, 100, 50), dx=5, dy=5, dz=5)

        self.vm.define_constant_layer_velocity(0, 1.5)
        self.vm.insert_interface(3)
        self.vm.insert_interface(10)

        # dipping layer
        dzdx = 0.1
        dzdy = 0.1
        rf = 25 * np.ones((self.vm.nx, self.vm.ny))
        for ix in range(self.vm.nx):
            for iy in range(self.vm.ny):
                rf[ix, iy] += dzdx * (ix * self.vm.dx)
                rf[ix, iy] += dzdy * (iy * self.vm.dy)
        # XXX self.vm.insert_interface(rf)

        self.vm.insert_interface(30)

        ray0 = [  # x, y, z, ilyr, direction
            [2, 2, 1, 0, 1],
            [22, 22, 25, 2, 1],
            [23, 23, 28, 2, 1],
            [40, 40, 44, 3, 1],
            [41, 41, 46, 3, 0],
            [62, 62, 19, 2, -1],
            [80, 80, 5, 1, -1],
        ]

        ray1 = [[5, 5, 2, 0, 1], [60, 60, 45, 3, 1]]  # x, y, z, ilyr, direction

        ray2 = [[5, 5, 45, 3, -1], [60, 60, 2, 0, -1]]  # x, y, z, ilyr, direction

        # rays
        self.raypaths = np.asarray([ray0, ray1, ray2])

        self.vm.write("test.vm")
Example #10
0
 def dev_project_layer_velocities(self):
     """
     Should map 2D model velocities to a 3D model.
     """
     # Create a simple 2D model with sl set to layer IDs
     vm2d = VM(r1=(-2, 0, -2), r2=(50, 0, 30), dx=1, dy=1, dz=1)
     z = 3 + vm2d.x * 0.1
     vm2d.insert_interface([[_z] for _z in z])
     vm2d.insert_interface(10)
     z = 22 + vm2d.x * -0.1
     vm2d.insert_interface([[_z] for _z in z])
     vm2d.sl = vm2d.layers
     # Project into a 3D model
     phi = 30
     vm3d = VM(r1=(10, 1, -2), r2=(20, 20, 30), dx=1, dy=1, dz=1)
     vm3d.insert_interface(5)
     vm3d.insert_interface(10)
     vm3d.insert_interface(20)
     for ilyr in range(vm2d.nr + 1):
         project_layer_velocities(vm2d, vm3d, phi, ilyr)
     # Layers should only include values from the same 2d layers
     for ilyr in range(vm2d.nr + 1):
         idx = np.nonzero(vm3d.layers == ilyr)
         self.assertEqual(vm3d.sl[idx].min(), ilyr)
Example #11
0
class raygeometryTestCase(unittest.TestCase):
    """
    Test cases for the raygeometry module.
    """
    def setUp(self):
        """
        Build test models
        """
        # Create a 3D model with three layers (2 boundaries)
        r1 = (0, 0, 0)
        r2 = (20, 20, 30)
        self.vm = VM(r1=r1, r2=r2)
        self.vm.insert_interface(5)
        self.vm.insert_interface(10)
        # Define a path for the test case
        self.pi = [0, 1, 1, 2, 2, 2, 1, 1, 0]
        self.px = [1, 1, 1, 1, 1, 1, 1, 1, 1]
        self.py = [1, 2, 3, 4, 5, 6, 7, 8, 9]
        self.pz = [1, 6, 8, 11, 12, 11, 8, 6, 2]
        self.path = zip(self.px, self.py, self.pz, self.pi)

    def test_distance(self):
        """
        Should calculate cartesian distance
        """
        # should work in 2D
        p0 = (0, 0)
        p1 = (0, 1.)
        self.assertEqual(distance(p0, p1), 1.)
        # should work in 3D
        p0 = (0, 0, 0)
        p1 = (0, 0, 1)
        self.assertEqual(distance(p0, p1), 1.)

    def test_assign_points_to_layers(self):
        """
        Should assign a layer index to each point.
        """

        # Should label each point with its layer index
        pi = assign_points_to_layers(self.vm, self.px, self.py, self.pz)
        for _pi0, _pi in zip(self.pi, pi):
            self.assertEqual(_pi0, _pi)

    def test_get_indices_near_piercing(self):
        """
        Should find indices for points near a piercing points.
        """
        # should get indices if path crosses layer
        iref = 0
        ip = get_indices_near_piercing(self.pi, iref)
        self.assertEqual(len(ip), 2)
        self.assertEqual(ip[0][0], 0)
        self.assertEqual(ip[0][1], 1)
        self.assertEqual(ip[1][0], 8)
        self.assertEqual(ip[1][1], 7)
        # should return empty array if path does not cross interface
        pi = 999 * np.ones(len(self.pi))
        ip = get_indices_near_piercing(pi, iref)
        self.assertEqual(len(ip), 0)
        # should just return points on the downward leg
        ip = get_indices_near_piercing(self.pi, iref, downward=True,
                                       upward=False)
        self.assertEqual(len(ip), 1)
        self.assertEqual(ip[0][0], 0)
        self.assertEqual(ip[0][1], 1)
        # should just return points on the upward leg
        ip = get_indices_near_piercing(self.pi, iref, downward=False,
                                       upward=True)
        self.assertEqual(len(ip), 1)
        self.assertEqual(ip[0][0], 8)
        self.assertEqual(ip[0][1], 7)

    
    def test_get_piercing_points(self):
        """
        Should find coordinates of piercing points.
        """
        iref = 0
        # indices of points near the piercing points
        ip = get_indices_near_piercing(self.pi, iref)
        # should find piercing points if path crosses interface
        pp = get_piercing_points(self.vm, iref, self.px, self.py, self.pz,
                                 ip)
        self.assertEqual(pp[0, 0], 1.)
        self.assertEqual(pp[0, 1], 1.8)
        self.assertEqual(pp[0, 2], 5.)
        self.assertEqual(pp[1, 0], 1.)
        self.assertEqual(pp[1, 1], 8.75)
        self.assertEqual(pp[1, 2], 5.)
        # should return empty array if path does not cross interface
        px = np.ones(len(self.pi))
        py = np.linspace(1, 9, len(self.pi))
        pz = 12 * np.ones(len(self.pi))
        pi = assign_points_to_layers(self.vm, px, py, pz)
        ip = get_indices_near_piercing(pi, iref)
        pp = get_piercing_points(self.vm, 0, px, py, pz, ip)
        self.assertEqual(len(pp), 0)
        # should just return points on the downward leg
        ip = get_indices_near_piercing(self.pi, iref, downward=True,
                                       upward=False)
        pp = get_piercing_points(self.vm, 0, self.px, self.py, self.pz, ip)
        self.assertEqual(pp[0, 0], 1.)
        self.assertEqual(pp[0, 1], 1.8)
        self.assertEqual(pp[0, 2], 5.)
        # should just return points on the upward leg
        ip = get_indices_near_piercing(self.pi, iref, downward=False,
                                       upward=True)
        pp = get_piercing_points(self.vm, 0, self.px, self.py, self.pz, ip)
        self.assertEqual(pp[0, 0], 1.)
        self.assertEqual(pp[0, 1], 8.75)
        self.assertEqual(pp[0, 2], 5.)

        
    def test_path_in_layer(self):
        """
        Should get coordinates for portion of path in layer.
        """
        down_npts = [2, 4, 5]
        up_npts = [2, 4, 0]
        for ilyr in range(3):
            d, u = get_path_in_layer(self.vm, ilyr, self.px, self.py, self.pz)
            self.assertEqual(len(d), down_npts[ilyr])
Example #12
0
def two2three(vm, sol, eol, dx=None, dy=None, phi=90.0, head_only=False):
    """
    Convert a 2D model to a 3D model by giving the coordinates of the line
    endpoints.

    Creates a cube with edges that are oriented parallel to the coordinate
    system.
    
    :See also: :meth:`rockfish.tomography.model.VM.extrude`
    
    Parameters
    ----------
    vm : :class:`rockfish.tomography.model.VM`
        2D model to convert to a 3D model. Must have ``vm.ny==1``.
    sol, eol : (float, float)
        Coordinates for the start and end of the line, respectively.
    dx : float, optional
        Grid spacing in the x direction. Default is to use the same x spacing
        in the 2D input mdel.
    dy : float, optional
        Grid spacing in the y direction.  Default is to set ``dy=dx``.
    phi : float, optional
        Angle of the extrusion direction, measured in degrees from the line
        azimuth. Default is ``90.0`` (i.e., in the line-perpendicular 
        direction).
    head_only : bool, optional
        If ``True``, only calculates the size values for the 3D model and does
        not perform the actual extrusion.
    """
    # check that current model is 2D
    assert vm.ny == 1, 'vm must be 2D with vm.ny == 1'
    # set defaults
    if dx is None:
        dx = vm.dx
    if dy is None:
        dy = vm.dx
    # calculate line angle 
    theta = np.arctan2(eol[1] - sol[1], eol[0] - sol[0]) \
            + np.deg2rad(phi - 90.)
    # initialize a new model
    vm1 = VM(r1=(sol[0], sol[1], vm.r1[2]), 
             r2=(eol[0], eol[1], vm.r2[2]),
             dx=dx, dy=dy, nr=vm.nr)
    if head_only:
        return vm1
    # build surface interpolators
    x2rf = []
    x2jp = []
    x2ir = []
    x2ij = []
    xp = vm.x - vm.r1[0]    # x in 2D model relative to 2D origin
    for iref in range(0, vm.nr):
        x2rf.append(interp1d(xp, [v[0] for v in vm.rf[iref]],
                             kind='linear'))
        x2jp.append(interp1d(xp, [v[0] for v in vm.jp[iref]],
                             kind='linear'))
        x2ir.append(interp1d(xp, [v[0] for v in vm.ir[iref]],
                             kind='nearest'))
        x2ij.append(interp1d(xp, [v[0] for v in vm.ij[iref]],
                             kind='nearest'))
    # map 2D model to the 3D model
    x = vm1.x - vm1.r1[0]   # x in 3D model relative to origin

    for _iy in vm1.yrange2i():
        _y = vm1.y[_iy] - vm1.r1[1]  # y in 3D model relative to origin
        a, _, _ = project_point(x, _y, theta)
        a = a.clip(vm.r1[0], vm.r2[0])
        nclip = len(np.nonzero(a == vm.r1[0]))\
                + len(np.nonzero(a == vm.r2[0]))
        if nclip > 2:
            warnings.warn('{:} points off the 2D line at y={:}'\
                          .format(nclip - 2, _y))
        # interpolate interface values
        for iref in range(0, vm.nr):
            try:
                vm1.rf[iref, :, _iy] = x2rf[iref](a)
                vm1.jp[iref, :, _iy] = x2jp[iref](a)
                vm1.ir[iref, :, _iy] = x2ir[iref](a)
                vm1.ij[iref, :, _iy] = x2ij[iref](a)
            except ValueError as e:
                msg = e.message
                msg += ' (min(a),max(a)={:},{:}; vm.r1[0]={:}; vm.r2[0]={:})'\
                        .format(min(a), max(a), vm.r1[0], vm.r2[0])
                raise ValueError(msg)
        # assign velocities
        for _ix, _a in enumerate(a):
            _ixp = vm.x2i([_a])[0]
            vm1.sl[_ix, _iy, :] = vm.sl[_ixp, 0, :]
    return vm1
Example #13
0
class geometryTestCase(unittest.TestCase):
    """
    Test cases for the geometry module
    """

    def setUp(self):
        """
        Build a test VM model
        """
        self.vm = VM(r1=(0, 0, 0), r2=(100, 100, 50), dx=5, dy=5, dz=5)

        self.vm.define_constant_layer_velocity(0, 1.5)
        self.vm.insert_interface(3)
        self.vm.insert_interface(10)

        # dipping layer
        dzdx = 0.1
        dzdy = 0.1
        rf = 25 * np.ones((self.vm.nx, self.vm.ny))
        for ix in range(self.vm.nx):
            for iy in range(self.vm.ny):
                rf[ix, iy] += dzdx * (ix * self.vm.dx)
                rf[ix, iy] += dzdy * (iy * self.vm.dy)
        # XXX self.vm.insert_interface(rf)

        self.vm.insert_interface(30)

        ray0 = [  # x, y, z, ilyr, direction
            [2, 2, 1, 0, 1],
            [22, 22, 25, 2, 1],
            [23, 23, 28, 2, 1],
            [40, 40, 44, 3, 1],
            [41, 41, 46, 3, 0],
            [62, 62, 19, 2, -1],
            [80, 80, 5, 1, -1],
        ]

        ray1 = [[5, 5, 2, 0, 1], [60, 60, 45, 3, 1]]  # x, y, z, ilyr, direction

        ray2 = [[5, 5, 45, 3, -1], [60, 60, 2, 0, -1]]  # x, y, z, ilyr, direction

        # rays
        self.raypaths = np.asarray([ray0, ray1, ray2])

        self.vm.write("test.vm")

    def plot_init(self):
        """
        setup should make a test model
        """
        if not PLOT:
            return

        fig = plt.figure()
        ax = fig.add_subplot(111)

        y0 = 50
        vm = self.vm.slice_along_xy_line(self.vm.x, y0 * np.ones(self.vm.nx))
        vm.plot_layers(ax=ax)

        for path in self.raypaths:
            _path = np.asarray(path)
            ax.plot(_path[:, 0], _path[:, 2], ".-c")

        plt.title("test_init: Test model at y = {:}".format(y0))

        plt.show()

    def test_assign_points_to_layers(self):
        """
        Should determine what layers a series of points are in
        """
        pi = []
        for path in self.raypaths:
            _path = np.asarray(path)
            _pi = assign_points_to_layers(self.vm, _path[:, 0], _path[:, 1], _path[:, 2])
            pi.append(_pi)

            for i0, i1 in zip(_path[:, 3], _pi):
                self.assertEqual(i0, i1)

        if PLOT:
            fig = plt.figure()
            ax = fig.add_subplot(111)

            y0 = 50
            vm = self.vm.slice_along_xy_line(self.vm.x, y0 * np.ones(self.vm.nx))
            vm.plot(ax=ax, show_grid=True)

            for i in range(len(pi)):
                path = np.asarray(self.raypaths[i])

                ax.plot(path[:, 0], path[:, 2], "-c")
                ax.scatter(path[:, 0], path[:, 2], c=pi[i], s=50)

            plt.title("test_assign_points_to_layers: nodes colored by layer")
            plt.show()

    def test_get_points_in_layer(self):
        """
        Should return points within a layer
        """
        for path in self.raypaths:
            for ilyr in range(self.vm.nr):
                _path = np.asarray(path)
                px, py, pz = _path[:, 0], _path[:, 1], _path[:, 2]

                d, u = split_downup(px, py, pz)
                for p in [d, u]:
                    px, py, pz = p[:, 0], p[:, 1], p[:, 2]

                    pi = assign_points_to_layers(self.vm, px, py, pz)
                    pi0 = np.nonzero(pi == ilyr)[0]

                    # should only return points that fall within a layer
                    px1, py1, pz1, pi1 = get_points_in_layer(self.vm, ilyr, px, py, pz, overlap=False)

                    self.assertEqual(len(pi0), len(px1))
                    self.assertEqual(len(pi0), len(py1))
                    self.assertEqual(len(pi0), len(pz1))
                    self.assertEqual(len(pi0), len(pi1))

                    # should include neighboring points
                    px2, py2, pz2, pi2 = get_points_in_layer(self.vm, ilyr, px, py, pz, overlap=True)
                    self.assertGreaterEqual(len(px2), len(px1))
                    self.assertGreaterEqual(len(py2), len(py1))
                    self.assertGreaterEqual(len(pz2), len(pz1))
                    self.assertGreaterEqual(len(pi2), len(pi1))

                    # should be empty or have more than 1 point
                    self.assertNotEqual(len(px2), 1)
                    self.assertNotEqual(len(py2), 1)
                    self.assertNotEqual(len(pz2), 1)
                    self.assertNotEqual(len(pi2), 1)

                    if PLOT:
                        fig = plt.figure()
                        ax = fig.add_subplot(111)
                        self.vm.plot(ax=ax)

                        # ax.scatter(px, pz, c=pi, marker='.', s=30, zorder=0)
                        ax.plot(px, pz, ".-k", lw=5, zorder=1)
                        ax.plot(px1, pz1, ".-m", lw=4, zorder=2)
                        ax.plot(px2, pz2, ".-c", lw=1, zorder=3)

                        plt.title("test_get_points_in_layer: ilyr={:}".format(ilyr))

                        plt.show()

    def test_split_downup(self):
        """
        Should split path into down-going and up-coming segments
        """
        down = []
        up = []
        for path in self.raypaths:
            _path = np.asarray(path)
            px, py, pz = _path[:, 0], _path[:, 1], _path[:, 2]

            d, u = split_downup(px, py, pz)
            down.append(d)
            up.append(u)

            ibot = np.nonzero(_path[:, 4] == 0)
            idown = np.append(np.nonzero(_path[:, 4] == 1), ibot)
            iup = np.append(ibot, np.nonzero(_path[:, 4] == -1))

            self.assertEqual(len(d), len(idown))
            self.assertEqual(len(u), len(iup))

        if PLOT:
            fig = plt.figure()
            ax = fig.add_subplot(111)

            self.vm.plot(ax=ax, ir=False, ij=False, rf=False)

            for i in range(len(self.raypaths)):
                path = np.asarray(self.raypaths[i])

                ax.plot(path[:, 0], path[:, 2], "-k", lw=5)

                ax.plot(down[i][:, 0], down[i][:, 2], "-r")
                ax.plot(up[i][:, 0], up[i][:, 2], "-g")

            plt.title("test_split_downup: up and down paths")
            plt.show()

    def test_find_line_intersection(self):
        """
        Should find where a point crosses an interface
        """
        p0 = [1, 2, 1]
        p1 = [82, 40, 40]
        for iref in [0, 1, 2]:

            x, y, z = find_line_intersection(self.vm, iref, p0, p1)

            ix = self.vm.x2i(x)
            iy = self.vm.y2i(y)
            z0 = self.vm.rf[iref][ix, iy]

            self.assertAlmostEqual(z, z0, 1)

            if PLOT:
                fig = plt.figure()
                ax = fig.add_subplot(111)

                self.vm.plot(ax=ax, show_grid=True)

                ax.plot([p0[0], p1[0]], [p0[2], p1[2]], ".-c")
                ax.plot(x, z, ".w")

                plt.title("test_find_line_intersection: iref={:}".format(iref))

                plt.show()

    def test__trim_path_to_layer(self):
        """
        Should trim a pre-processed path to single layer
        """
        # path that crosses layer 2
        ray0 = [[1, 1, 1], [40, 1, 20], [80, 1, 40]]

        # path that terminates in layer 2
        ray1 = [[1, 1, 1], [40, 1, 20], [80, 1, 22]]

        # path that starts in layer 2
        ray2 = [[1, 1, 19], [40, 1, 20], [80, 1, 40]]

        # path that starts and ends in layer 2
        ray3 = [[1, 1, 19], [40, 1, 20], [80, 1, 22]]

        for ray in [ray0, ray1, ray2, ray3]:

            px = [r[0] for r in ray]
            py = [r[1] for r in ray]
            pz = [r[2] for r in ray]

            if PLOT:
                fig = plt.figure()
                ax = fig.add_subplot(111)
                self.vm.plot(ax=ax, show_grid=True)
                ax.plot(px, pz, ".-g")

            _trim_path_ends_to_layer(self.vm, px, py, pz)

            pi = assign_points_to_layers(self.vm, px, py, pz)
            for i in pi:
                self.assertEqual(i, 2)

            if PLOT:
                ax.plot(px, pz, ".-m")
                plt.show()

    def test_insert_intersections(self):
        """
        Should add layer intersections
        """
        for path in self.raypaths[0:1]:
            _path = np.asarray(path)
            px, py, pz = _path[:, 0], _path[:, 1], _path[:, 2]

            for duplicate in [True, False]:
                if PLOT:
                    fig = plt.figure()
                    ax = fig.add_subplot(111)
                    plt.title("duplicate = {}".format(duplicate))
                    self.vm.plot(ax=ax, show_grid=True)
                    ax.plot(px, pz, ".-g", markersize=10, lw=3)

                px, py, pz, pi = insert_intersections(self.vm, px, py, pz, duplicate=duplicate)

                if PLOT:
                    ax.plot(px, pz, ".-m")
                    plt.show()

                self.assertEqual(len(px), len(pi))
                self.assertEqual(len(py), len(pi))
                self.assertEqual(len(pz), len(pi))

    def test_split_path_at_interface_intersections(self):
        """
        Should split a path into layer segments 
        """
        sym = [".-r", ".-c", ".-m", ".-w"]
        for path in self.raypaths:
            _path = np.asarray(path)
            px, py, pz = _path[:, 0], _path[:, 1], _path[:, 2]

            _px, _, _pz, _ = insert_intersections(self.vm, px, py, pz)
            if PLOT:
                fig = plt.figure()
                ax = fig.add_subplot(111)
                self.vm.plot(ax=ax, show_grid=True)
                ax.plot(px, pz, ".-g", markersize=20, lw=5)
                ax.plot(_px, _pz, ".-k", markersize=10, lw=3)

            segments = split_path_at_interface_intersections(self.vm, px, py, pz)

            if PLOT:
                for i, segments in enumerate(segments):
                    for p in segments:
                        if len(p) == 0:
                            continue

                        ax.plot(p[:, 0], p[:, 2], sym[i])

                plt.show()

            self.assertEqual(len(segments), self.vm.nr + 1)

    def test_resample_path(self):
        """
        Should interpolate path
        """
        if PLOT:
            fig = plt.figure()
            ax = fig.add_subplot(111)

        for path in self.raypaths:
            _path = np.asarray(path)
            px, py, pz = _path[:, 0], _path[:, 1], _path[:, 2]

            pxi, pyi, pzi = resample_path(px, py, pz, dx=1)

            if PLOT:
                self.vm.plot(ax=ax, show_grid=True)
                ax.plot(px, pz, ".-g", markersize=20, lw=5)
                ax.plot(pxi, pzi, ".-k", markersize=10, lw=3)

        if PLOT:
            plt.show()