Exemplo n.º 1
0
def test_rotation():
    assert_allclose(electrodes.rotation(0, 0), [1, 0, 0])
    assert_allclose(electrodes.rotation(180, 0), [-1, 0, 0])
    assert_allclose(electrodes.rotation(90, 0), [0, 1, 0])
    assert_allclose(electrodes.rotation(-90, 0), [0, -1, 0])
    assert_allclose(electrodes.rotation(0, 90), [0, 0, 1])
    assert_allclose(electrodes.rotation(0, -90), [0, 0, -1])

    dazm, delv = 30, 60
    razm, relv = np.deg2rad(dazm), np.deg2rad(delv)
    assert_allclose(
        electrodes.rotation(dazm, delv),
        [np.cos(razm)*np.cos(relv), np.sin(razm)*np.cos(relv), np.sin(relv)])

    dazm, delv = -45, 180
    razm, relv = np.deg2rad(dazm), np.deg2rad(delv)
    assert_allclose(
        electrodes.rotation(dazm, delv),
        [np.cos(razm)*np.cos(relv), np.sin(razm)*np.cos(relv), np.sin(relv)],
        atol=1e-14)

    # Radians
    azm, elv = np.pi/3, np.pi/4
    rot1 = electrodes.rotation(azm, elv, deg=False)
    rot2 = electrodes.rotation(np.rad2deg(azm), np.rad2deg(elv))
    assert_allclose(rot1, rot2)
Exemplo n.º 2
0
def _point_vector_magnetic(grid, coordinates, frequency):
    """Get magnetic point source using discretize functionality.


    Parameters
    ----------
    grid : TensorMesh
        The grid; a :class:`emg3d.meshes.TensorMesh` instance.

    coordinates : array_like
        Source coordinates in the format (x, y, z, azimuth, elevation).

    frequency : float
        Source frequency (Hz).


    Returns
    -------
    vfield : Field
        Source field, a :class:`emg3d.fields.Field` instance.

    """

    # Use discretize to get the source field for a magnetic point.
    coords = np.array(coordinates)
    rot = electrodes.rotation(coords[3], coords[4])
    interp = (rot[0] * grid.get_interpolation_matrix(coords[:3], 'faces_x') +
              rot[1] * grid.get_interpolation_matrix(coords[:3], 'faces_y') +
              rot[2] * grid.get_interpolation_matrix(coords[:3], 'faces_z'))

    # Compute the magnetic vector.
    vfield = Field(grid, frequency=frequency)
    vfield.field = -(grid.edge_curl.T @ interp.T).toarray().ravel()

    # Divide by s*mu_0.
    if frequency is not None:
        vfield.field /= -vfield.smu0

    return vfield
Exemplo n.º 3
0
def _point_vector(grid, coordinates):
    """Get point source using the adjoint of the trilinear interpolation.


    Parameters
    ----------
    grid : TensorMesh
        The grid; a :class:`emg3d.meshes.TensorMesh` instance.

    coordinates : array_like
        Source coordinates in the format (x, y, z, azimuth, elevation).


    Returns
    -------
    vfield : Field
        Source field, a :class:`emg3d.fields.Field` instance.

    """

    # Ensure source is within nodes.
    outside = (coordinates[0] < grid.nodes_x[0]
               or coordinates[0] > grid.nodes_x[-1]
               or coordinates[1] < grid.nodes_y[0]
               or coordinates[1] > grid.nodes_y[-1]
               or coordinates[2] < grid.nodes_z[0]
               or coordinates[2] > grid.nodes_z[-1])
    if outside:
        raise ValueError(f"Provided source outside grid: {coordinates}.")

    def point_source(xx, yy, zz, coo, s):
        """Set point dipole source."""
        nx, ny, nz = s.shape

        # Get indices of cells in which source resides.
        ix = max(0, np.where(coo[0] < np.r_[xx, np.infty])[0][0] - 1)
        iy = max(0, np.where(coo[1] < np.r_[yy, np.infty])[0][0] - 1)
        iz = max(0, np.where(coo[2] < np.r_[zz, np.infty])[0][0] - 1)

        def get_index_and_strength(ic, nc, csrc, cc):
            """Return index and field strength in c-direction."""
            if ic == nc - 1:
                ic1 = ic
                rc = 1.0
                ec = 1.0
            else:
                ic1 = ic + 1
                rc = (csrc - cc[ic]) / (cc[ic1] - cc[ic])
                ec = 1.0 - rc
            return rc, ec, ic1

        rx, ex, ix1 = get_index_and_strength(ix, nx, coo[0], xx)
        ry, ey, iy1 = get_index_and_strength(iy, ny, coo[1], yy)
        rz, ez, iz1 = get_index_and_strength(iz, nz, coo[2], zz)

        s[ix, iy, iz] = ex * ey * ez
        s[ix1, iy, iz] = rx * ey * ez
        s[ix, iy1, iz] = ex * ry * ez
        s[ix1, iy1, iz] = rx * ry * ez
        s[ix, iy, iz1] = ex * ey * rz
        s[ix1, iy, iz1] = rx * ey * rz
        s[ix, iy1, iz1] = ex * ry * rz
        s[ix1, iy1, iz1] = rx * ry * rz

    # Initiate zero source field.
    vfield = Field(grid, dtype=float)

    # Return source-field depending.
    vec1 = (grid.cell_centers_x, grid.nodes_y, grid.nodes_z)
    vec2 = (grid.nodes_x, grid.cell_centers_y, grid.nodes_z)
    vec3 = (grid.nodes_x, grid.nodes_y, grid.cell_centers_z)
    point_source(*vec1, coordinates[:3], vfield.fx)
    point_source(*vec2, coordinates[:3], vfield.fy)
    point_source(*vec3, coordinates[:3], vfield.fz)

    # Multiply by fraction in each direction.
    srcdir = electrodes.rotation(*coordinates[3:])
    vfield.fx *= srcdir[0]
    vfield.fy *= srcdir[1]
    vfield.fz *= srcdir[2]

    return vfield
Exemplo n.º 4
0
def get_receiver(field, receiver, method='cubic'):
    """Return the field (response) at receiver coordinates.

    Note that in order to avoid boundary effects from the PEC boundary the
    outermost cells are neglected. Field values for coordinates outside of the
    grid are set to NaN's. However, take into account that for good results all
    receivers should be far away from the boundary.


    Parameters
    ----------
    field : Field
        The electric or magnetic field; a :class:`emg3d.fields.Field` instance.

    receiver : {Rx*, list, tuple}
        Receiver coordinates. The following formats are accepted:

        - ``Rx*`` instance, any receiver object from :mod:`emg3d.electrodes`.
        - ``list``: A list of ``Rx*`` instances.
        - ``tuple``: ``(x, y, z, azimuth, elevation)``; receiver coordinates
          and angles (m, °). All values can either be a scalar or having the
          same length as number of receivers.

        Note that the actual receiver type of the ``Rx*`` instances has no
        effect here, it just takes the coordinates from the receiver instances.

    method : str, default: 'cubic'
        Interpolation method to obtain the response at receiver location;
        'cubic' or 'linear'.


    Returns
    -------
    responses : EMArray
        Responses at receiver.

    """

    # Rx* instance.
    if hasattr(receiver, 'coordinates'):
        coordinates = receiver.coordinates

    # List of Rx* instances.
    elif hasattr(tuple(receiver)[0], 'coordinates'):
        nrec = len(receiver)
        coordinates = np.zeros((nrec, 5))
        for i, r in enumerate(receiver):
            coordinates[i, :] = r.coordinates
        coordinates = tuple(coordinates.T)

    # Tuple of coordinates.
    else:
        coordinates = receiver

        # Check receiver dimension.
        if len(coordinates) != 5:
            raise ValueError(
                "`receiver` needs to be in the form "
                "(x, y, z, azimuth, elevation). "
                f"Length of provided `receiver`: {len(coordinates)}.")

    # Grid.
    grid = field.grid

    # Pre-allocate the response.
    _, xi, shape = maps._points_from_grids(grid, field.fx, coordinates[:3],
                                           'cubic')
    resp = np.zeros(xi.shape[0], dtype=field.field.dtype)

    # Get weighting factors per direction.
    factors = electrodes.rotation(*coordinates[3:])

    # Add the required responses.
    opts = {'method': method, 'extrapolate': False, 'log': False}
    # Set receivers outside of grid to NaN (they should be FAR from boundary).
    if method == 'linear':
        opts['fill_value'] = np.nan
    else:
        opts['cval'] = np.nan
    for i, ff in enumerate((field.fx, field.fy, field.fz)):
        if np.any(abs(factors[i]) > 1e-10):
            resp += factors[i] * maps.interpolate(grid, ff, xi, **opts)

    # PEC: If receivers are in the outermost cell, set them to NaN.
    # Note: Receivers should be MUCH further away from the boundary.
    ind = ((xi[:, 0] < grid.nodes_x[1]) | (xi[:, 0] > grid.nodes_x[-2]) |
           (xi[:, 1] < grid.nodes_y[1]) | (xi[:, 1] > grid.nodes_y[-2]) |
           (xi[:, 2] < grid.nodes_z[1]) | (xi[:, 2] > grid.nodes_z[-2]))
    resp[ind] = np.nan

    # Return response.
    return utils.EMArray(resp.reshape(shape, order='F'))
Exemplo n.º 5
0
# Coordinate system
# The first three are not visible, but for the aspect ratio of the plot.
ax.plot([-2, 8], [0, 0], [0, 0], c='w')
ax.plot([0, 0], [-2, 12], [0, 0], c='w')
ax.plot([0, 0], [0, 0], [-1.5, 6], c='w')
ax.add_artist(Arrow3D([-2, 10], [0, 0], [0, 0]))
ax.add_artist(Arrow3D([0, 0], [-2, 12], [0, 0]))
ax.add_artist(Arrow3D([0, 0], [0, 0], [-2, 5]))

fact = 5

# Theta
azm = np.arcsin(P[1]/np.sqrt(P[0]**2+P[1]**2))
lazm = np.linspace(0, azm, 31)
rot1 = rotation(lazm, lazm*0, deg=False)
ax.plot(*(rot1*fact), lw=2, c='C2', solid_capstyle='round')
ax.text(5.2, 1.5, 0, r"$\theta$", color='C2', fontsize=14)

# Theta and Phi
elevation = np.pi/2-np.arcsin(
        np.sqrt(P[0]**2+P[1]**2)/np.sqrt(P[0]**2+P[1]**2+P[2]**2))
# print(f"theta = {np.rad2deg(azm):.1f}°; phi = {np.rad2deg(elevation):.1f}°")
lelevation = np.linspace(0, elevation, 31)

rot2 = rotation(azm, lelevation, deg=False)
ax.plot(*(rot2*fact), c='C0', lw=2, zorder=11, solid_capstyle='round')
ax.text(3.3, 3.5, 1.5, r"$\varphi$", color='C0', fontsize=14)

# Helper lines
ax.plot([0, P[0]], [P[1], P[1]], [0, 0], ':', c='.8')