Example #1
0
    def test_vector3(self):
        class HasVec3(properties.HasProperties):
            vec3 = properties.Vector3('simple vector')

        hv3 = HasVec3()
        hv3.vec3 = [1., 2., 3.]
        assert isinstance(hv3.vec3, vmath.Vector3)
        hv3.vec3 = 'east'
        assert np.allclose(hv3.vec3, [1., 0., 0.])
        with self.assertRaises(ValueError):
            hv3.vec3 = 'around'
        with self.assertRaises(ValueError):
            hv3.vec3 = [1., 2.]
        with self.assertRaises(ValueError):
            hv3.vec3 = [[1., 2., 3.]]

        class HasLenVec3(properties.HasProperties):
            vec3 = properties.Vector3('length 5 vector', length=5)

        hv3 = HasLenVec3()
        hv3.vec3 = 'down'
        assert np.allclose(hv3.vec3, [0., 0., -5.])

        assert isinstance(properties.Vector3.from_json([5., 6., 7.]),
                          vmath.Vector3)

        assert properties.Vector3('').equal(vmath.Vector3(1., 2., 3.),
                                            vmath.Vector3(1., 2., 3.))
        assert not properties.Vector3('').equal(vmath.Vector3(1., 2., 3.),
                                                np.array([1., 2., 3.]))
Example #2
0
class CameraStandard(_BaseCamera):
    """Base class for Standard Cameras

    Both orthographic and perspective cameras are built on this
    """
    mode = properties.StringChoice(
        'View mode of camera',
        choices=['perspective', 'orthographic'],
        default='orthographic',
    )
    target = properties.Vector3(
        'Center of rotation of camera relative to the scene origin',
        default=lambda: [0., 0., 0.],
    )
    radius = properties.Float(
        'Distance of camera to target',
        default=5.,
    )
    zoom = properties.Float(
        'Zoom level of camera',
        default=1.,
    )
    rotation = properties.List(
        'Quaternion rotation of the camera relative to the scene',
        properties.Float(''),
        min_length=4,
        max_length=4,
        default=lambda: [0., 0., 0., 1.],
    )
    up_direction = properties.Vector3(
        'Up direction of camera',
        default='up',
    )
Example #3
0
class SimulationDC(BaseSimulation):

    src_a = properties.Vector3(
        "a electrode location", required=True
    )

    src_b = properties.Vector3(
        "return electrode location", required=True
    )

    fields_filename = properties.String(
        "filename for the fields",
        default="fieldsDC.npy"
    )

    formulation = properties.String(
        "field that we are solving for",
        default="phi"
    )

    def __init__(self, **kwargs):
        super(SimulationDC, self).__init__(**kwargs)

        self._prob = DC.Problem3D_CC(
            self.meshGenerator.mesh,
            sigmaMap=self.physprops.wires.sigma,
            bc_type='Dirichlet',
            Solver=Pardiso
        )
        self._src = DC.Src.Dipole([], self.src_a, self.src_b)
        self._survey = DC.Survey([self._src])

        self._prob.pair(self._survey)
Example #4
0
class TextureProjection(_BaseTexture):
    """Simple texture data that projects an image onto an element

    Using origin and axes, this defines an image location in space. When
    this texture is applied to an element, the image from the texture
    is projected normal to its plane onto the element.

    This is ideal for projecting surface imagery onto a topographic
    surface.
    """

    SUB_TYPE = 'projection'

    origin = properties.Vector3('Origin point of the texture')
    axis_u = properties.Vector3(
        'Vector corresponding to the image horizontal axis')
    axis_v = properties.Vector3(
        'Vector corresponding to the image vertical axis')
    image = Pointer(
        'Texture image file',
        Image,
    )

    def to_omf(self):
        self.validate()
        omf_texture = omf.ImageTexture(
            name=self.name or '',
            description=self.description or '',
            origin=self.origin,
            axis_u=self.axis_u,
            axis_v=self.axis_v,
            image=self.image.image,
        )
        return omf_texture
Example #5
0
class Plot(_BaseSceneComponent):
    """Object describing the contents of the 3D scene

    Contains info about how a plot is positioned in the scene, as well
    as the plot limits. Also contains active elements, slices, and
    measurements in the plot.
    """
    position = properties.Vector3(
        'x,y,z position of the plot center relative to scene origin',
        default=lambda: [0., 0, 0],
    )
    scale = properties.Vector3(
        'x,y,z scale of the plot coordinate system relative to scene '
        'coordinate system',
        default=lambda: [1., 1, 1],
    )
    rotation = properties.List(
        'Quaternion rotation of plot axes relative to scene axes',
        properties.Float(''),
        min_length=4,
        max_length=4,
        default=lambda: [0., 0, 0, 1],
    )
    lims = properties.List(
        'x,y,z limits, defined in plot coordinates',
        properties.Vector2(''),
        min_length=3,
        max_length=3,
        default=lambda: [[0, 0.00001]] * 3,
    )
    exaggeration = properties.List(
        'x,y,z exaggeration of the plot coordinates',
        properties.Integer('', min=1),
        default=lambda: [1, 1, 1],
    )
    slice_groups = properties.List(
        'Active slice groups; currently only one is supported',
        SliceGroup,
        max_length=1,
        default=lambda: [SliceGroup()],
    )
    measurements = properties.List(
        'List of active ruler instances; currently only one is supported',
        Ruler,
        max_length=1,
        default=list,
    )
    views = properties.List(
        'List of element views in the plot',
        properties.Union('', [
            view for key, view in _BaseSceneComponent._REGISTRY.items()
            if key[0] != '_' and issubclass(view, _BaseElementView)
        ]),
        max_length=100,
        default=list,
    )
Example #6
0
class ImageTexture(ContentModel):
    """Contains an image that can be mapped to a point set or surface"""
    origin = properties.Vector3('Origin point of the texture',
                                default=[0., 0., 0.])
    axis_u = properties.Vector3('Vector corresponding to the image x-axis',
                                default='X')
    axis_v = properties.Vector3('Vector corresponding to the image y-axis',
                                default='Y')
    image = properties.ImagePNG('PNG image file',
                                serializer=png_serializer,
                                deserializer=png_deserializer)
Example #7
0
class Slice(_BaseSceneComponent):
    """Position and color information for a slice in the Scene"""
    _defaults = {'uid': 'slice'}
    position_a = properties.Vector3(
        'Position of first plane',
        default=lambda: [0, 0, 0],
    )
    position_b = properties.Vector3(
        'Position of second plane; only required in section mode',
        default=[0, 0, 0],
        required=False,
    )
    normal = properties.Vector3(
        'Normal vector to the slice plane',
        default=lambda: [1., 0, 0],
    )
    mode = properties.StringChoice(
        'Slice mode',
        ['inactive', 'normal', 'reversed', 'section'],
        descriptions={
            'inactive': 'Slice is currently disabled',
            'normal': 'Standard single slice slicing mode',
            'reversed': 'Single slice with swapped normal',
            'section': 'Mode with two parallel sliceplanes'
        },
        default='inactive',
    )
    color_a = properties.Color(
        'Color of first slice',
        default=[0, 120, 255],
        serializer=spatial.mappings.to_hex,
        deserializer=spatial.mappings.from_hex,
    )
    color_b = properties.Color(
        'Color of second slice; only required in section mode',
        default=[253, 102, 0],
        required=False,
        serializer=spatial.mappings.to_hex,
        deserializer=spatial.mappings.from_hex,
    )

    @properties.validator
    def _validate_section(self):
        """Validate that position_b and color_b are set in section mode"""
        if (self.mode == 'section'
                and (self.position_b is None or self.color_b is None)):
            raise properties.ValidationError(
                message='Section mode requires position_b and color_b',
                reason='invalid',
                prop='mode',
                instance=self,
            )
Example #8
0
class VolumeGridGeometry(ProjectElementGeometry):
    """Contains spatial information of a 3D grid volume."""
    tensor_u = properties.Array('Tensor cell widths, u-direction',
                                shape=('*', ),
                                dtype=float)
    tensor_v = properties.Array('Tensor cell widths, v-direction',
                                shape=('*', ),
                                dtype=float)
    tensor_w = properties.Array('Tensor cell widths, w-direction',
                                shape=('*', ),
                                dtype=float)
    axis_u = properties.Vector3('Vector orientation of u-direction',
                                default='X',
                                length=1)
    axis_v = properties.Vector3('Vector orientation of v-direction',
                                default='Y',
                                length=1)
    axis_w = properties.Vector3('Vector orientation of w-direction',
                                default='Z',
                                length=1)

    _valid_locations = ('vertices', 'cells')

    def location_length(self, location):
        """Return correct data length based on location"""
        if location == 'cells':
            return self.num_cells
        return self.num_nodes

    @property
    def num_nodes(self):
        """Number of nodes (vertices)"""
        return ((len(self.tensor_u) + 1) * (len(self.tensor_v) + 1) *
                (len(self.tensor_w) + 1))

    @property
    def num_cells(self):
        """Number of cells"""
        return len(self.tensor_u) * len(self.tensor_v) * len(self.tensor_w)

    @properties.validator
    def _validate_mesh(self):
        """Check if mesh content is built correctly"""
        if not (np.abs(self.axis_u.dot(self.axis_v) < 1e-6) and  #pylint: disable=no-member
                np.abs(self.axis_v.dot(self.axis_w) < 1e-6) and  #pylint: disable=no-member
                np.abs(self.axis_w.dot(self.axis_u) < 1e-6)):  #pylint: disable=no-member
            raise ValueError('axis_u, axis_v, and axis_w must be orthogonal')
        return True
Example #9
0
class SurfaceGridGeometry(ProjectElementGeometry):
    """Contains spatial information of a 2D grid"""
    tensor_u = properties.Array('Grid cell widths, u-direction',
                                shape=('*', ),
                                dtype=float)
    tensor_v = properties.Array('Grid cell widths, v-direction',
                                shape=('*', ),
                                dtype=float)
    axis_u = properties.Vector3('Vector orientation of u-direction',
                                default='X',
                                length=1)
    axis_v = properties.Vector3('Vector orientation of v-direction',
                                default='Y',
                                length=1)
    offset_w = properties.Instance('Node offset', ScalarArray, required=False)

    _valid_locations = ('vertices', 'faces')

    def location_length(self, location):
        """Return correct data length based on location"""
        if location == 'faces':
            return self.num_cells
        return self.num_nodes

    @property
    def num_nodes(self):
        """Number of nodes (vertices)"""
        return (len(self.tensor_u) + 1) * (len(self.tensor_v) + 1)

    @property
    def num_cells(self):
        """Number of cells (faces)"""
        return len(self.tensor_u) * len(self.tensor_v)

    @properties.validator
    def _validate_mesh(self):
        """Check if mesh content is built correctly"""
        if not np.abs(self.axis_u.dot(self.axis_v)) < 1e-6:  #pylint: disable=no-member
            raise ValueError('axis_u and axis_v must be orthogonal')
        if self.offset_w is properties.undefined or self.offset_w is None:
            return True
        if len(self.offset_w.array) != self.num_nodes:
            raise ValueError(
                'Length of offset_w, {zlen}, must equal number of nodes, '
                '{nnode}'.format(zlen=len(self.offset_w),
                                 nnode=self.num_nodes))
        return True
Example #10
0
class BaseDipole(BaseEM):
    """
    Base class for dipoles.
    """

    orientation = properties.Vector3(
        "orientation of dipole",
        default="X",
        length=1.0
    )

    location = properties.Vector3(
        "location of the electric dipole source",
        default="ZERO"
    )

    def vector_distance(self, xyz):
        """
        Vector distance from the dipole location
        :param numpy.ndarray xyz: grid
        """
        return spatial.vector_distance(xyz, np.array(self.location))

    def distance(self, xyz):
        """
        Distance from the dipole location
        """
        return spatial.distance(xyz, np.array(self.location))

    def dot_orientation(self, xyz):
        """
        Take the dot product between a grid and the orientation of the dipole
        """
        return spatial.vector_dot(xyz, np.array(self.orientation))

    def cross_orientation(self, xyz):
        """
        Take the cross product between a grid and the orientation of the dipole
        """
        orientation = np.kron(
            np.atleast_2d(
                np.array(self.orientation)
            ), np.ones((xyz.shape[0], 1))
        )
        return np.cross(xyz, orientation)
Example #11
0
File: base.py Project: gz159/geoana
class BaseDipole(BaseEM):

    orientation = properties.Vector3(help='orientation of dipole',
                                     default='X',
                                     length=1.0)

    location = properties.Vector3(
        help='location of the electric dipole source', default='ZERO')

    def offset_from_location(self, xyz):

        # TODO: validate stuff
        # xyz = Utils.asArray_N_x_Dim(xyz, 3)

        return np.c_[xyz[:, 0] - self.location[0],
                     xyz[:, 1] - self.location[1],
                     xyz[:, 2] - self.location[2]]

    def distance_from_location(self, xyz):
        return np.sqrt((self.offset_from_location(xyz)**2).sum(axis=1))
Example #12
0
class Project(ContentModel):
    """OMF Project for serializing to .omf file"""
    author = properties.String('Author', default='')
    revision = properties.String('Revision', default='')
    date = properties.DateTime('Date associated with the project data',
                               required=False)
    units = properties.String('Spatial units of project', default='')
    elements = properties.List(
        'Project Elements',
        prop=ProjectElement,
        default=list,
    )
    origin = properties.Vector3('Origin point for all elements in the project',
                                default=[0., 0., 0.])
Example #13
0
class Ruler(_BaseSceneComponent):
    """Ruler class

    Contains the path of the ruler as well as info
    about the objects it is measuring to and from
    """
    positions = properties.List(
        'Endpoints of the ruler',
        properties.Vector3(''),
        min_length=2,
        max_length=2,
    )
    objects = properties.List(
        'URL pointers of objects we are measuring from/to',
        Pointer('', spatial.elements._BaseElement),
        min_length=2,
        max_length=2,
    )
Example #14
0
class ProjectElementGeometry(UidModel):
    """Base class for all ProjectElement meshes"""

    _valid_locations = None

    origin = properties.Vector3(
        'Origin of the Mesh relative to origin of the Project',
        default=[0., 0., 0.])

    def location_length(self, location):
        """Return correct data length based on location"""
        raise NotImplementedError()

    @property
    def num_nodes(self):
        """get number of nodes"""
        raise NotImplementedError()

    @property
    def num_cells(self):
        """get number of cells"""
        raise NotImplementedError()
Example #15
0
class MagDipole(BaseTDEMSrc):

    moment = properties.Float(
        "dipole moment of the transmitter", default=1., min=0.
    )
    mu = properties.Float(
        "permeability of the background", default=mu_0, min=0.
        )
    orientation = properties.Vector3(
        "orientation of the source", default='Z', length=1., required=True
    )

    def __init__(self, rxList, **kwargs):
        # assert(self.orientation in ['X', 'Y', 'Z']), (
        #     "Orientation (right now) doesn't actually do anything! The methods"
        #     " in SrcUtils should take care of this..."
        #     )
        # self.integrate = False
        BaseTDEMSrc.__init__(self, rxList, **kwargs)

    @properties.validator('orientation')
    def _warn_non_axis_aligned_sources(self, change):
        value = change['value']
        axaligned = [
            True for vec in [np.r_[1.,0.,0.], np.r_[0.,1.,0.], np.r_[0.,0.,1.]]
            if np.all(value == vec)
        ]
        if len(axaligned) != 1:
            warnings.warn(
                'non-axes aligned orientations {} are not rigorously'
                ' tested'.format(value)
            )

    def _srcFct(self, obsLoc, component):
        return MagneticDipoleVectorPotential(
            self.loc, obsLoc, component, mu=self.mu, moment=self.moment
        )

    def _bSrc(self, prob):
        if prob._formulation == 'EB':
            gridX = prob.mesh.gridEx
            gridY = prob.mesh.gridEy
            gridZ = prob.mesh.gridEz
            C = prob.mesh.edgeCurl

        elif prob._formulation == 'HJ':
            gridX = prob.mesh.gridFx
            gridY = prob.mesh.gridFy
            gridZ = prob.mesh.gridFz
            C = prob.mesh.edgeCurl.T

        if prob.mesh._meshType is 'CYL':
            if not prob.mesh.isSymmetric:
                raise NotImplementedError(
                    'Non-symmetric cyl mesh not implemented yet!'
                )
            a = self._srcFct(gridY, 'y')

        else:
            ax = self._srcFct(gridX, 'x')
            ay = self._srcFct(gridY, 'y')
            az = self._srcFct(gridZ, 'z')
            a = np.concatenate((ax, ay, az))

        return C*a

    def bInitial(self, prob):

        if self.waveform.hasInitialFields is False:
            return Zero()

        return self._bSrc(prob)

    def hInitial(self, prob):

        if self.waveform.hasInitialFields is False:
            return Zero()

        return 1./self.mu * self._bSrc(prob)

    def eInitial(self, prob):
        # when solving for e, it is easier to work with an initial source than
        # initial fields
        # if self.waveform.hasInitialFields is False or prob._fieldType is 'e':
        return Zero()

        # b = self.bInitial(prob)
        # MeSigmaI = prob.MeSigmaI
        # MfMui = prob.MfMui
        # C = prob.mesh.edgeCurl

        # return MeSigmaI * (C.T * (MfMui * b))

    def eInitialDeriv(self, prob, v=None, adjoint=False):

        return Zero()

        # if self.waveform.hasInitialFields is False:
        #     return Zero()

        # b = self.bInitial(prob)
        # MeSigmaIDeriv = prob.MeSigmaIDeriv
        # MfMui = prob.MfMui
        # C = prob.mesh.edgeCurl
        # s_e = self.s_e(prob, prob.t0)

        # # s_e doesn't depend on the model

        # if adjoint:
        #     return MeSigmaIDeriv( -s_e + C.T * ( MfMui * b ) ).T * v

        # return MeSigmaIDeriv( -s_e + C.T * ( MfMui * b ) ) * v

    def s_m(self, prob, time):
        if self.waveform.hasInitialFields is False:
            # raise NotImplementedError
            return Zero()
        return Zero()

    def s_e(self, prob, time):
        C = prob.mesh.edgeCurl
        b = self._bSrc(prob)

        if prob._formulation == 'EB':

            MfMui = prob.MfMui

            if self.waveform.hasInitialFields is True and time < prob.timeSteps[1]:
                # if time > 0.0:
                #     return Zero()
                if prob._fieldType == 'b':
                    return Zero()
                elif prob._fieldType == 'e':
                    # Compute s_e from vector potential
                    return C.T * (MfMui * b)
            else:
                # b = self._bfromVectorPotential(prob)
                return C.T * (MfMui * b) * self.waveform.eval(time)
        # return Zero()

        elif prob._formulation == 'HJ':

            h = 1./self.mu * b

            if self.waveform.hasInitialFields is True and time < prob.timeSteps[1]:
                # if time > 0.0:
                #     return Zero()
                if prob._fieldType == 'h':
                    return Zero()
                elif prob._fieldType == 'j':
                    # Compute s_e from vector potential
                    return C * h
            else:
                # b = self._bfromVectorPotential(prob)
                return C * h * self.waveform.eval(time)
Example #16
0
class Mesh3DGrid(BaseMesh):
    """Contains spatial information of a 3D grid volume."""

    h1 = properties.Array(doc='Tensor cell widths, x-direction',
                          shape=('*', ),
                          dtype=(float, int))
    h2 = properties.Array(doc='Tensor cell widths, y-direction',
                          shape=('*', ),
                          dtype=(float, int))
    h3 = properties.Array(doc='Tensor cell widths, z-direction',
                          shape=('*', ),
                          dtype=(float, int))
    x0 = properties.Renamed('O')
    O = properties.Vector3(doc='Origin vector', default=[0., 0., 0.])
    U = properties.Vector3(doc='Orientation of h1 axis', default='X')
    V = properties.Vector3(doc='Orientation of h2 axis', default='Y')
    W = properties.Vector3(doc='Orientation of h3 axis', default='Z')
    opts = properties.Instance(
        doc='Mesh3D Options',
        instance_class=_Mesh3DOptions,
        default=_Mesh3DOptions,
    )

    @property
    def nN(self):
        """ get number of nodes """
        return (len(self.h1) + 1) * (len(self.h2) + 1) * (len(self.h3) + 1)

    @property
    def nC(self):
        """ get number of cells """
        return len(self.h1) * len(self.h2) * len(self.h3)

    def _nbytes(self, arr=None):
        filenames = ('h1', 'h2', 'h3', 'O', 'U', 'V', 'W')
        if arr is None:
            return sum(self._nbytes(fn) for fn in filenames)
        if isinstance(arr, string_types) and arr in filenames:
            if getattr(self, arr, None) is None:
                return 0
            arr = getattr(self, arr)
        if isinstance(arr, ndarray):
            return arr.astype('f4').nbytes
        raise ValueError('Mesh3DGrid cannot calculate the number of '
                         'bytes of {}'.format(arr))

    def _get_dirty_data(self, force=False):
        datadict = super(Mesh3DGrid, self)._get_dirty_data(force)
        dirty = self._dirty_props
        if force or ('h1' in dirty or 'h2' in dirty or 'h3' in dirty):
            datadict['tensors'] = dumps(
                dict(h1=self.h1.tolist(),
                     h2=self.h2.tolist(),
                     h3=self.h3.tolist()))
        if force or any(
            [item in dirty
             for item in ['O', 'U', 'V', 'W', 'h1', 'h2', 'h3']]):
            datadict['OUVZ'] = dumps(
                dict(O=self.O.tolist(),
                     U=self.U.as_length(self.h1.sum()).tolist(),
                     V=self.V.as_length(self.h2.sum()).tolist(),
                     Z=self.W.as_length(self.h3.sum()).tolist()))
        return datadict

    @classmethod
    def _build_from_json(cls, json, **kwargs):
        mesh = Mesh3DGrid(title=kwargs['title'],
                          description=kwargs['description'],
                          h1=json['tensors']['h1'],
                          h2=json['tensors']['h2'],
                          h3=json['tensors']['h3'],
                          O=json['OUVZ']['O'],
                          U=json['OUVZ']['U'],
                          V=json['OUVZ']['V'],
                          W=json['OUVZ']['Z'],
                          opts=json['meta'])
        return mesh

    @classmethod
    def _build_from_omf(cls, omf_geom, omf_project):
        mesh = Mesh3DGrid(h1=omf_geom.tensor_u,
                          h2=omf_geom.tensor_v,
                          h3=omf_geom.tensor_w,
                          O=omf_geom.origin + omf_project.origin,
                          U=omf_geom.axis_u,
                          V=omf_geom.axis_v,
                          W=omf_geom.axis_w)
        return mesh

    def _to_omf(self):
        import omf
        geometry = omf.VolumeGridGeometry(
            tensor_u=self.h1,
            tensor_v=self.h2,
            tensor_w=self.h3,
            axis_u=self.U,
            axis_v=self.V,
            axis_w=self.W,
            origin=self.O,
        )
        return geometry
Example #17
0
class MagDipole(BaseFDEMSrc):
    """
    Point magnetic dipole source calculated by taking the curl of a magnetic
    vector potential. By taking the discrete curl, we ensure that the magnetic
    flux density is divergence free (no magnetic monopoles!).

    This approach uses a primary-secondary in frequency. Here we show the
    derivation for E-B formulation noting that similar steps are followed for
    the H-J formulation.

    .. math::
        \mathbf{C} \mathbf{e} + i \omega \mathbf{b} = \mathbf{s_m} \\\\
        {\mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} \mathbf{b} -
        \mathbf{M_{\sigma}^e} \mathbf{e} = \mathbf{s_e}}

    We split up the fields and :math:`\mu^{-1}` into primary
    (:math:`\mathbf{P}`) and secondary (:math:`\mathbf{S}`) components

    - :math:`\mathbf{e} = \mathbf{e^P} + \mathbf{e^S}`
    - :math:`\mathbf{b} = \mathbf{b^P} + \mathbf{b^S}`
    - :math:`\\boldsymbol{\mu}^{\mathbf{-1}} =
      \\boldsymbol{\mu}^{\mathbf{-1}^\mathbf{P}} +
      \\boldsymbol{\mu}^{\mathbf{-1}^\mathbf{S}}`

    and define a zero-frequency primary simulation, noting that the source is
    generated by a divergence free electric current

    .. math::
        \mathbf{C} \mathbf{e^P} = \mathbf{s_m^P} = 0 \\\\
        {\mathbf{C}^T \mathbf{{M_{\mu^{-1}}^f}^P} \mathbf{b^P} -
        \mathbf{M_{\sigma}^e} \mathbf{e^P} = \mathbf{M^e} \mathbf{s_e^P}}

    Since :math:`\mathbf{e^P}` is curl-free, divergence-free, we assume that
    there is no constant field background, the :math:`\mathbf{e^P} = 0`, so our
    primary problem is

    .. math::
        \mathbf{e^P} =  0 \\\\
            {\mathbf{C}^T \mathbf{{M_{\mu^{-1}}^f}^P} \mathbf{b^P} =
            \mathbf{s_e^P}}

    Our secondary problem is then

    .. math::
        \mathbf{C} \mathbf{e^S} + i \omega \mathbf{b^S} =
        - i \omega \mathbf{b^P} \\\\
        {\mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} \mathbf{b^S} -
        \mathbf{M_{\sigma}^e} \mathbf{e^S} =
        -\mathbf{C}^T \mathbf{{M_{\mu^{-1}}^f}^S} \mathbf{b^P}}

    :param list receiver_list: receiver list
    :param float freq: frequency
    :param numpy.ndarray location: source location
        (ie: :code:`np.r_[xloc,yloc,zloc]`)
    :param string orientation: 'X', 'Y', 'Z'
    :param float moment: magnetic dipole moment
    :param float mu: background magnetic permeability

    """

    moment = properties.Float("dipole moment of the transmitter",
                              default=1.0,
                              min=0.0)
    mu = properties.Float("permeability of the background",
                          default=mu_0,
                          min=0.0)
    orientation = properties.Vector3("orientation of the source",
                                     default="Z",
                                     length=1.0,
                                     required=True)
    location = LocationVector("location of the source",
                              default=np.r_[0.0, 0.0, 0.0],
                              shape=(3, ))
    loc = deprecate_property(location,
                             "loc",
                             new_name="location",
                             removal_version="0.15.0")

    def __init__(self,
                 receiver_list=None,
                 frequency=None,
                 location=None,
                 **kwargs):
        super(MagDipole, self).__init__(receiver_list,
                                        frequency=frequency,
                                        **kwargs)
        if location is not None:
            self.location = location

    def _srcFct(self, obsLoc, coordinates="cartesian"):
        if getattr(self, "_dipole", None) is None:
            self._dipole = MagneticDipoleWholeSpace(
                mu=self.mu,
                orientation=self.orientation,
                location=self.location,
                moment=self.moment,
            )
        return self._dipole.vector_potential(obsLoc, coordinates=coordinates)

    def bPrimary(self, simulation):
        """
        The primary magnetic flux density from a magnetic vector potential

        :param BaseFDEMSimulation simulation: FDEM simulation
        :rtype: numpy.ndarray
        :return: primary magnetic field
        """
        formulation = simulation._formulation
        coordinates = "cartesian"

        if formulation == "EB":
            gridX = simulation.mesh.gridEx
            gridY = simulation.mesh.gridEy
            gridZ = simulation.mesh.gridEz
            C = simulation.mesh.edgeCurl

        elif formulation == "HJ":
            gridX = simulation.mesh.gridFx
            gridY = simulation.mesh.gridFy
            gridZ = simulation.mesh.gridFz
            C = simulation.mesh.edgeCurl.T

        if simulation.mesh._meshType == "CYL":
            coordinates = "cylindrical"

            if simulation.mesh.isSymmetric is True:
                if not (np.linalg.norm(self.orientation - np.r_[0.0, 0.0, 1.0])
                        < 1e-6):
                    raise AssertionError(
                        "for cylindrical symmetry, the dipole must be oriented"
                        " in the Z direction")
                a = self._srcFct(gridY)[:, 1]

                return C * a

        ax = self._srcFct(gridX, coordinates)[:, 0]
        ay = self._srcFct(gridY, coordinates)[:, 1]
        az = self._srcFct(gridZ, coordinates)[:, 2]
        a = np.concatenate((ax, ay, az))

        return C * a

    def hPrimary(self, simulation):
        """
        The primary magnetic field from a magnetic vector potential

        :param BaseFDEMSimulation simulation: FDEM simulation
        :rtype: numpy.ndarray
        :return: primary magnetic field
        """
        b = self.bPrimary(simulation)
        return 1.0 / self.mu * b

    def s_m(self, simulation):
        """
        The magnetic source term

        :param BaseFDEMSimulation simulation: FDEM simulation
        :rtype: numpy.ndarray
        :return: primary magnetic field
        """

        b_p = self.bPrimary(simulation)
        if simulation._formulation == "HJ":
            b_p = simulation.Me * b_p
        return -1j * omega(self.frequency) * b_p

    def s_e(self, simulation):
        """
        The electric source term

        :param BaseFDEMSimulation simulation: FDEM simulation
        :rtype: numpy.ndarray
        :return: primary magnetic field
        """

        if all(np.r_[self.mu] == np.r_[simulation.mu]):
            return Zero()
        else:
            formulation = simulation._formulation

            if formulation == "EB":
                mui_s = simulation.mui - 1.0 / self.mu
                MMui_s = simulation.mesh.getFaceInnerProduct(mui_s)
                C = simulation.mesh.edgeCurl
            elif formulation == "HJ":
                mu_s = simulation.mu - self.mu
                MMui_s = simulation.mesh.getEdgeInnerProduct(mu_s, invMat=True)
                C = simulation.mesh.edgeCurl.T

            return -C.T * (MMui_s * self.bPrimary(simulation))

    def s_eDeriv(self, simulation, v, adjoint=False):
        if not hasattr(simulation, "muMap") or not hasattr(
                simulation, "muiMap"):
            return Zero()
        else:
            formulation = simulation._formulation

            if formulation == "EB":
                mui_s = simulation.mui - 1.0 / self.mu
                MMui_sDeriv = (simulation.mesh.getFaceInnerProductDeriv(mui_s)(
                    self.bPrimary(simulation)) * simulation.muiDeriv)
                C = simulation.mesh.edgeCurl

                if adjoint:
                    return -MMui_sDeriv.T * (C * v)

                return -C.T * (MMui_sDeriv * v)

            elif formulation == "HJ":
                return Zero()
                # raise NotImplementedError
                mu_s = simulation.mu - self.mu
                MMui_s = simulation.mesh.getEdgeInnerProduct(mu_s, invMat=True)
                C = simulation.mesh.edgeCurl.T

                return -C.T * (MMui_s * self.bPrimary(simulation))
Example #18
0
class BaseMesh(properties.HasProperties, InterfaceMixins):
    """
    BaseMesh does all the counting you don't want to do.
    BaseMesh should be inherited by meshes with a regular structure.
    """

    _REGISTRY = {}
    _aliases = {
        "nC": "n_cells",
        "nN": "n_nodes",
        "nEx": "n_edges_x",
        "nEy": "n_edges_y",
        "nEz": "n_edges_z",
        "nE": "n_edges",
        "vnE": "n_edges_per_direction",
        "nFx": "n_faces_x",
        "nFy": "n_faces_y",
        "nFz": "n_faces_z",
        "nF": "n_faces",
        "vnF": "n_faces_per_direction",
    }

    # Properties
    _n = properties.Tuple(
        "Tuple of number of cells in each direction (dim, )",
        prop=properties.Integer("Number of cells along a particular direction",
                                cast=True,
                                min=1),
        min_length=1,
        max_length=3,
        coerce=True,
        required=True,
    )

    origin = properties.Array(
        "origin of the mesh (dim, )",
        dtype=(float, int),
        shape=("*", ),
        required=True,
    )

    # Instantiate the class
    def __init__(self, n=None, origin=None, **kwargs):
        if n is not None:
            self._n = n  # number of dimensions

        if "x0" in kwargs:
            origin = kwargs.pop('x0')
        if origin is None:
            self.origin = np.zeros(len(self._n))
        else:
            self.origin = origin

        super(BaseMesh, self).__init__(**kwargs)

    def __getattr__(self, name):
        if name == "_aliases":
            raise AttributeError  # http://nedbatchelder.com/blog/201010/surprising_getattr_recursion.html
        name = self._aliases.get(name, name)
        return object.__getattribute__(self, name)

    @property
    def x0(self):
        return self.origin

    @x0.setter
    def x0(self, val):
        self.origin = val

    @classmethod
    def deserialize(cls, value, **kwargs):
        if "x0" in value:
            value["origin"] = value.pop("x0")
        return super().deserialize(value, **kwargs)

    # Validators
    @properties.validator("_n")
    def _check_n_shape(self, change):
        if change["previous"] != properties.undefined:
            # _n can only be set once
            if change["previous"] != change["value"]:
                raise AttributeError(
                    "Cannot change n. Instead, create a new mesh")
        else:
            # check that if h has been set, sizes still agree
            if getattr(self, "h", None) is not None and len(self.h) > 0:
                for i in range(len(change["value"])):
                    if len(self.h[i]) != change["value"][i]:
                        raise properties.ValidationError(
                            "Mismatched shape of n. Expected {}, len(h[{}]), got "
                            "{}".format(len(self.h[i]), i, change["value"][i]))

            # check that if nodes have been set for curvi mesh, sizes still
            # agree
            if getattr(self, "node_list",
                       None) is not None and len(self.node_list) > 0:
                for i in range(len(change["value"])):
                    if self.node_list[0].shape[i] - 1 != change["value"][i]:
                        raise properties.ValidationError(
                            "Mismatched shape of n. Expected {}, len(node_list[{}]), "
                            "got {}".format(self.node_list[0].shape[i] - 1, i,
                                            change["value"][i]))

    @properties.validator("origin")
    def _check_origin(self, change):
        if not (not isinstance(change["value"], properties.utils.Sentinel)
                and change["value"] is not None):
            raise Exception("n must be set prior to setting origin")

        if len(self._n) != len(change["value"]):
            raise Exception(
                "Dimension mismatch. origin has length {} != len(n) which is "
                "{}".format(len(self.origin), len(self._n)))

    @property
    def dim(self):
        """The dimension of the mesh (1, 2, or 3).

        Returns
        -------
        int
            dimension of the mesh
        """
        return len(self._n)

    @property
    def n_cells(self):
        """Total number of cells in the mesh.

        Returns
        -------
        int
            number of cells in the mesh

        Notes
        -----
        Also accessible as `nC`.

        Examples
        --------
        >>> import discretize
        >>> import numpy as np
        >>> import matplotlib.pyplot as plt
        >>> mesh = discretize.TensorMesh([np.ones(n) for n in [2,3]])
        >>> mesh.plot_grid(centers=True, show_it=True)
        >>> print(mesh.n_cells)
        """
        return int(np.prod(self._n))

    def __len__(self):
        """The number of cells on the mesh."""
        return self.n_cells

    @property
    def n_nodes(self):
        """Total number of nodes

        Returns
        -------
        int
            number of nodes in the mesh

        Notes
        -----
        Also accessible as `nN`.

        Examples
        --------
        >>> import discretize
        >>> import numpy as np
        >>> import matplotlib.pyplot as plt
        >>> mesh = discretize.TensorMesh([np.ones(n) for n in [2,3]])
        >>> mesh.plot_grid(nodes=True, show_it=True)
        >>> print(mesh.n_nodes)
        """
        return int(np.prod(x + 1 for x in self._n))

    @property
    def n_edges_x(self):
        """Number of x-edges

        Returns
        -------
        int

        Notes
        -----
        Also accessible as `nEx`.

        """
        return int(np.prod(x + y for x, y in zip(self._n, (0, 1, 1))))

    @property
    def n_edges_y(self):
        """Number of y-edges

        Returns
        -------
        int

        Notes
        -----
        Also accessible as `nEy`.

        """
        if self.dim < 2:
            return None
        return int(np.prod(x + y for x, y in zip(self._n, (1, 0, 1))))

    @property
    def n_edges_z(self):
        """Number of z-edges

        Returns
        -------
        int

        Notes
        -----
        Also accessible as `nEz`.

        """
        if self.dim < 3:
            return None
        return int(np.prod(x + y for x, y in zip(self._n, (1, 1, 0))))

    @property
    def n_edges_per_direction(self):
        """The number of edges in each direction

        Returns
        -------
        n_edges_per_direction : tuple
            [n_edges_x, n_edges_y, n_edges_z], (dim, )

        Notes
        -----
        Also accessible as `vnE`.

        Examples
        --------
        >>> import discretize
        >>> import matplotlib.pyplot as plt
        >>> import numpy as np
        >>> M = discretize.TensorMesh([np.ones(n) for n in [2,3]])
        >>> M.plot_grid(edges=True, show_it=True)
        """
        return tuple(x
                     for x in [self.n_edges_x, self.n_edges_y, self.n_edges_z]
                     if x is not None)

    @property
    def n_edges(self):
        """Total number of edges.

        Returns
        -------
        int
            sum([n_edges_x, n_edges_y, n_edges_z])

        Notes
        -----
        Also accessible as `nE`.

        """
        n = self.n_edges_x
        if self.dim > 1:
            n += self.n_edges_y
        if self.dim > 2:
            n += self.n_edges_z
        return n

    @property
    def n_faces_x(self):
        """Number of x-faces

        Returns
        -------
        int

        Notes
        -----
        Also accessible as `nFx`.
        """
        return int(np.prod(x + y for x, y in zip(self._n, (1, 0, 0))))

    @property
    def n_faces_y(self):
        """Number of y-faces

        Returns
        -------
        int

        Notes
        -----
        Also accessible as `nFy`.
        """
        if self.dim < 2:
            return None
        return int(np.prod(x + y for x, y in zip(self._n, (0, 1, 0))))

    @property
    def n_faces_z(self):
        """Number of z-faces

        Returns
        -------
        int

        Notes
        -----
        Also accessible as `nFz`.
        """
        if self.dim < 3:
            return None
        return int(np.prod(x + y for x, y in zip(self._n, (0, 0, 1))))

    @property
    def n_faces_per_direction(self):
        """The number of faces in each direction

        Returns
        -------
        n_faces_per_direction : tuple
            [n_faces_x, n_faces_y, n_faces_z], (dim, )

        Notes
        -----
        Also accessible as `vnF`.

        Examples
        --------
        >>> import discretize
        >>> import numpy as np
        >>> import matplotlib.pyplot as plt
        >>> M = discretize.TensorMesh([np.ones(n) for n in [2,3]])
        >>> M.plot_grid(faces=True, show_it=True)
        """
        return tuple(x
                     for x in [self.n_faces_x, self.n_faces_y, self.n_faces_z]
                     if x is not None)

    @property
    def n_faces(self):
        """Total number of faces.

        Returns
        -------
        int
            sum([n_faces_x, n_faces_y, n_faces_z])

        Notes
        -----
        Also accessible as `nF`.

        """
        n = self.n_faces_x
        if self.dim > 1:
            n += self.n_faces_y
        if self.dim > 2:
            n += self.n_faces_z
        return n

    @property
    def face_normals(self):
        """Face Normals

        Returns
        -------
        numpy.ndarray
            normals, (n_faces, dim)
        """
        if self.dim == 2:
            nX = np.c_[np.ones(self.n_faces_x), np.zeros(self.n_faces_x)]
            nY = np.c_[np.zeros(self.n_faces_y), np.ones(self.n_faces_y)]
            return np.r_[nX, nY]
        elif self.dim == 3:
            nX = np.c_[np.ones(self.n_faces_x),
                       np.zeros(self.n_faces_x),
                       np.zeros(self.n_faces_x), ]
            nY = np.c_[np.zeros(self.n_faces_y),
                       np.ones(self.n_faces_y),
                       np.zeros(self.n_faces_y), ]
            nZ = np.c_[np.zeros(self.n_faces_z),
                       np.zeros(self.n_faces_z),
                       np.ones(self.n_faces_z), ]
            return np.r_[nX, nY, nZ]

    @property
    def edge_tangents(self):
        """Edge Tangents

        Returns
        -------
        numpy.ndarray
            normals, (n_edges, dim)
        """
        if self.dim == 2:
            tX = np.c_[np.ones(self.n_edges_x), np.zeros(self.n_edges_x)]
            tY = np.c_[np.zeros(self.n_edges_y), np.ones(self.n_edges_y)]
            return np.r_[tX, tY]
        elif self.dim == 3:
            tX = np.c_[np.ones(self.n_edges_x),
                       np.zeros(self.n_edges_x),
                       np.zeros(self.n_edges_x), ]
            tY = np.c_[np.zeros(self.n_edges_y),
                       np.ones(self.n_edges_y),
                       np.zeros(self.n_edges_y), ]
            tZ = np.c_[np.zeros(self.n_edges_z),
                       np.zeros(self.n_edges_z),
                       np.ones(self.n_edges_z), ]
            return np.r_[tX, tY, tZ]

    def project_face_vector(self, face_vector):
        """Project vectors onto the faces of the mesh.

        Given a vector, face_vector, in cartesian coordinates, this will project
        it onto the mesh using the normals

        Parameters
        ----------
        face_vector : numpy.ndarray
            face vector with shape (n_faces, dim)

        Returns
        -------
        numpy.ndarray
            projected face vector, (n_faces, )

        """
        if not isinstance(face_vector, np.ndarray):
            raise Exception("face_vector must be an ndarray")
        if not (len(face_vector.shape) == 2 and face_vector.shape[0]
                == self.n_faces and face_vector.shape[1] == self.dim):
            raise Exception(
                "face_vector must be an ndarray of shape (n_faces x dim)")
        return np.sum(face_vector * self.face_normals, 1)

    def project_edge_vector(self, edge_vector):
        """Project vectors onto the edges of the mesh

        Given a vector, edge_vector, in cartesian coordinates, this will project
        it onto the mesh using the tangents

        Parameters
        ----------
        edge_vector : numpy.ndarray
            edge vector with shape (n_edges, dim)

        Returns
        -------
        numpy.ndarray
            projected edge vector, (n_edges, )

        """
        if not isinstance(edge_vector, np.ndarray):
            raise Exception("edge_vector must be an ndarray")
        if not (len(edge_vector.shape) == 2 and edge_vector.shape[0]
                == self.n_edges and edge_vector.shape[1] == self.dim):
            raise Exception(
                "edge_vector must be an ndarray of shape (nE x dim)")
        return np.sum(edge_vector * self.edge_tangents, 1)

    def save(self, file_name="mesh.json", verbose=False, **kwargs):
        """
        Save the mesh to json
        :param str file: file_name for saving the casing properties
        :param str directory: working directory for saving the file
        """

        if 'filename' in kwargs:
            file_name = kwargs['filename']
            warnings.warn(
                "The filename keyword argument has been deprecated, please use file_name. "
                "This will be removed in discretize 1.0.0",
                DeprecationWarning,
            )
        f = os.path.abspath(
            file_name)  # make sure we are working with abs path
        with open(f, "w") as outfile:
            json.dump(self.serialize(), outfile)

        if verbose:
            print("Saved {}".format(f))

        return f

    def copy(self):
        """
        Make a copy of the current mesh
        """
        return properties.copy(self)

    axis_u = properties.Vector3(
        "Vector orientation of u-direction. For more details see the docs for the :attr:`~discretize.base.BaseMesh.rotation_matrix` property.",
        default="X",
        length=1,
    )
    axis_v = properties.Vector3(
        "Vector orientation of v-direction. For more details see the docs for the :attr:`~discretize.base.BaseMesh.rotation_matrix` property.",
        default="Y",
        length=1,
    )
    axis_w = properties.Vector3(
        "Vector orientation of w-direction. For more details see the docs for the :attr:`~discretize.base.BaseMesh.rotation_matrix` property.",
        default="Z",
        length=1,
    )

    @properties.validator
    def _validate_orientation(self):
        """Check if axes are orthogonal"""
        tol = 1E-6
        if not (np.abs(self.axis_u.dot(self.axis_v) < tol)
                and np.abs(self.axis_v.dot(self.axis_w) < tol)
                and np.abs(self.axis_w.dot(self.axis_u) < tol)):
            raise ValueError("axis_u, axis_v, and axis_w must be orthogonal")
        return True

    @property
    def reference_is_rotated(self):
        """True if the axes are rotated from the traditional <X,Y,Z> system
        with vectors of :math:`(1,0,0)`, :math:`(0,1,0)`, and :math:`(0,0,1)`
        """
        if (np.allclose(self.axis_u,
                        (1, 0, 0)) and np.allclose(self.axis_v, (0, 1, 0))
                and np.allclose(self.axis_w, (0, 0, 1))):
            return False
        return True

    @property
    def rotation_matrix(self):
        """Builds a rotation matrix to transform coordinates from their coordinate
        system into a conventional cartesian system. This is built off of the
        three `axis_u`, `axis_v`, and `axis_w` properties; these mapping
        coordinates use the letters U, V, and W (the three letters preceding X,
        Y, and Z in the alphabet) to define the projection of the X, Y, and Z
        durections. These UVW vectors describe the placement and transformation
        of the mesh's coordinate sytem assuming at most 3 directions.

        Why would you want to use these UVW mapping vectors the this
        `rotation_matrix` property? They allow us to define the realationship
        between local and global coordinate systems and provide a tool for
        switching between the two while still maintaing the connectivity of the
        mesh's cells. For a visual example of this, please see the figure in the
        docs for the :class:`~discretize.mixins.vtk_mod.InterfaceVTK`.
        """
        return np.array([self.axis_u, self.axis_v, self.axis_w])

    reference_system = properties.String(
        "The type of coordinate reference frame. Can take on the values " +
        "cartesian, cylindrical, or spherical. Abbreviations of these are allowed.",
        default="cartesian",
        change_case="lower",
    )

    @properties.validator
    def _validate_reference_system(self):
        """Check if the reference system is of a known type."""
        choices = ["cartesian", "cylindrical", "spherical"]
        # Here are a few abbreviations that users can harnes
        abrevs = {
            "car": choices[0],
            "cart": choices[0],
            "cy": choices[1],
            "cyl": choices[1],
            "sph": choices[2],
        }
        # Get the name and fix it if it is abbreviated
        self.reference_system = abrevs.get(self.reference_system,
                                           self.reference_system)
        if self.reference_system not in choices:
            raise ValueError("Coordinate system ({}) unknown.".format(
                self.reference_system))
        return True

    def _parse_location_type(self, location_type):
        if len(location_type) == 0:
            return location_type
        elif location_type[0] == "F":
            if len(location_type) > 1:
                return "faces_" + location_type[-1]
            else:
                return "faces"
        elif location_type[0] == "E":
            if len(location_type) > 1:
                return "edges_" + location_type[-1]
            else:
                return "edges"
        elif location_type[0] == "N":
            return "nodes"
        elif location_type[0] == "C":
            if len(location_type) > 2:
                return "cell_centers_" + location_type[-1]
            else:
                return "cell_centers"
        else:
            return location_type

    # DEPRECATED
    normals = deprecate_property("face_normals",
                                 "normals",
                                 removal_version="1.0.0")
    tangents = deprecate_property("edge_tangents",
                                  "tangents",
                                  removal_version="1.0.0")
    projectEdgeVector = deprecate_method("project_edge_vector",
                                         "projectEdgeVector",
                                         removal_version="1.0.0")
    projectFaceVector = deprecate_method("project_face_vector",
                                         "projectFaceVector",
                                         removal_version="1.0.0")
Example #19
0
class Texture2DImage(BaseTexture2D):
    """Contains an image that can be mapped to a 2D surface"""

    _resource_class = 'image'

    O = properties.Vector3(doc='Origin of the texture')
    U = properties.Vector3(doc='U axis of the texture')
    V = properties.Vector3(doc='V axis of the texture')
    image = properties.ImagePNG(
        doc='Image file',
        deserializer=image_download,
    )

    def _nbytes(self, img=None):
        if img is None or (isinstance(img, string_types) and img == 'image'):
            img = self.image
        try:
            img.seek(0)
            return len(img.read())
        except:
            raise ValueError('Texture2DImage cannot calculate the number of '
                             'bytes of {}'.format(img))

    @properties.validator('image')
    def _reject_large_files(self, change):
        self._validate_file_size(change['name'], change['value'])

    @properties.validator
    def _validate_image(self):
        self._validate_file_size('image', self.image)
        return True

    def _get_dirty_files(self, force=False):
        files = super(Texture2DImage, self)._get_dirty_files(force)
        dirty = self._dirty_props
        if 'image' in dirty or force:
            self.image.seek(0)
            copy = BytesIO()
            copy.name = 'texture_copy.png'
            copy.write(self.image.read())
            copy.seek(0)
            files['image'] = FileProp(copy, 'png')
        return files

    def _get_dirty_data(self, force=False):
        datadict = super(Texture2DImage, self)._get_dirty_data(force)
        dirty = self._dirty_props
        if ('O' in dirty or 'U' in dirty or 'V' in dirty) or force:
            datadict['OUV'] = dumps(
                dict(
                    O=self.O.tolist(),
                    U=self.U.tolist(),
                    V=self.V.tolist(),
                ))
        return datadict

    def _repr_png_(self):
        """For IPython display"""
        if self.image is None:
            return None
        self.image.seek(0)
        return self.image.read()

    @classmethod
    def _build_from_json(cls, json, **kwargs):
        tex = Texture2DImage(title=kwargs['title'],
                             description=kwargs['description'],
                             O=json['OUV']['O'],
                             U=json['OUV']['U'],
                             V=json['OUV']['V'],
                             image=cls._props['image'].deserialize(
                                 json['image']))
        return tex

    @classmethod
    def _build_from_omf(cls, omf_tex, omf_project):
        tex = Texture2DImage(title=omf_tex.name,
                             description=omf_tex.description,
                             O=omf_tex.origin + omf_project.origin,
                             U=omf_tex.axis_u,
                             V=omf_tex.axis_v,
                             image=omf_tex.image)
        return tex

    def _to_omf(self):
        import omf
        tex = omf.ImageTexture(
            name=self.title or '',
            description=self.description or '',
            origin=self.O,
            axis_u=self.U,
            axis_v=self.V,
            image=self.image,
        )
        return tex
class ElementVolumeGrid(_BaseElementVolume):
    """Volume element with geometry defined by a grid

    The grid is defined by three axes and cell spacing along these
    axes.
    """

    SUB_TYPE = 'volumegrid'

    origin = properties.Vector3(
        'Grid origin, where axis_u, axis_v, and axis_w vectors extend from', )
    tensor_u = properties.List(
        'Tensor cell widths, u-direction',
        properties.Float('', min=0),
        max_length=2000,
        coerce=True,
    )
    tensor_v = properties.List(
        'Tensor cell widths, v-direction',
        properties.Float('', min=0),
        max_length=2000,
        coerce=True,
    )
    tensor_w = properties.List(
        'Tensor cell widths, w-direction',
        properties.Float('', min=0),
        max_length=2000,
        coerce=True,
    )
    axis_u = properties.Vector3(
        'Vector orientation of u-direction',
        length=1,
    )
    axis_v = properties.Vector3(
        'Vector orientation of v-direction',
        length=1,
    )
    axis_w = properties.Vector3(
        'Vector orientation of w-direction',
        length=1,
    )

    @property
    def num_nodes(self):
        """Number of nodes (vertices)"""
        try:
            nodes = ((len(self.tensor_u) + 1) * (len(self.tensor_v) + 1) *
                     (len(self.tensor_w) + 1))
            return nodes
        except (AttributeError, IndexError, TypeError):
            return None

    @property
    def num_cells(self):
        """Number of cells (faces)"""
        try:
            cells = (len(self.tensor_u) * len(self.tensor_v) *
                     len(self.tensor_w))
            return cells
        except (AttributeError, IndexError, TypeError):
            return None

    def to_omf(self):
        self.validate()
        omf_grid_volume = omf.VolumeElement(
            name=self.name or '',
            description=self.description or '',
            geometry=omf.VolumeGridGeometry(
                origin=self.origin,
                tensor_u=self.tensor_u,
                tensor_v=self.tensor_v,
                tensor_w=self.tensor_w,
                axis_u=self.axis_u,
                axis_v=self.axis_v,
                axis_w=self.axis_w,
            ),
            data=[attr.to_omf(cell_location='cells') for attr in self.data],
            color=self.defaults.color.value,
        )
        return omf_grid_volume
Example #21
0
class Light(_BaseSceneComponent):
    """Light source for a scene"""
    direction = properties.Vector3(
        'Vector pointing from plot center to light', )
    brightness = properties.Float('Intensity of light source')
    enabled = properties.Boolean('Whether light is on or off')
Example #22
0
 class HasLenVec3(properties.HasProperties):
     vec3 = properties.Vector3('length 5 vector', length=5)
Example #23
0
class Mesh2DGrid(BaseMesh):
    """Contains spatial information of a 2D grid."""
    h1 = properties.Array(doc='Grid cell widths, U-direction',
                          shape=('*', ),
                          dtype=(float, int))
    h2 = properties.Array(doc='Grid cell widths, V-direction',
                          shape=('*', ),
                          dtype=(float, int))
    x0 = properties.Renamed('O')
    O = properties.Vector3(doc='Origin vector', default=[0., 0., 0.])
    U = properties.Vector3(doc='Orientation of h1 axis', default='X')
    V = properties.Vector3(doc='Orientation of h2 axis', default='Y')
    Z = properties.Array(
        doc='Node topography',
        shape=('*', ),
        dtype=float,
        required=False,
        serializer=array_serializer,
        deserializer=array_download(('*', ), (float, )),
    )
    opts = properties.Instance(
        doc='Mesh2D Options',
        instance_class=_Mesh2DOptions,
        default=_Mesh2DOptions,
    )

    @property
    def nN(self):
        """ get number of nodes """
        return (len(self.h1) + 1) * (len(self.h2) + 1)

    @property
    def nC(self):
        """ get number of cells """
        return len(self.h1) * len(self.h2)

    def _nbytes(self, arr=None):
        filenames = ('h1', 'h2', 'O', 'U', 'V', 'Z')
        if arr is None:
            return sum(self._nbytes(fn) for fn in filenames)
        if isinstance(arr, string_types) and arr in filenames:
            if getattr(self, arr, None) is None:
                return 0
            arr = getattr(self, arr)
        if isinstance(arr, ndarray):
            return arr.astype('f4').nbytes
        raise ValueError('Mesh2DGrid cannot calculate the number of '
                         'bytes of {}'.format(arr))

    @properties.observer('Z')
    def _reject_large_files(self, change):
        self._validate_file_size(change['name'], change['value'])

    @properties.validator
    def _validate_Z(self):
        """Check if mesh content is built correctly"""
        if self.Z is None or len(self.Z) == 0:
            return True
        if len(self.Z) != self.nN:
            raise ValueError(
                'Length of Z, {zlen}, must equal number of nodes, '
                '{nnode}'.format(zlen=len(self.Z), nnode=self.nN))
        self._validate_file_size('Z', self.Z)
        return True

    def _get_dirty_data(self, force=False):
        datadict = super(Mesh2DGrid, self)._get_dirty_data(force)
        dirty = self._dirty_props
        if ('h1' in dirty or 'h2' in dirty) or force:
            datadict['tensors'] = dumps(
                dict(
                    h1=self.h1.tolist(),
                    h2=self.h2.tolist(),
                ))
        if ('O' in dirty or 'U' in dirty or 'V' in dirty) or force:
            datadict['OUV'] = dumps(
                dict(
                    O=self.O.tolist(),
                    U=self.U.as_length(self.h1.sum()).tolist(),
                    V=self.V.as_length(self.h2.sum()).tolist(),
                ))
        return datadict

    def _get_dirty_files(self, force=False):
        files = super(Mesh2DGrid, self)._get_dirty_files(force)
        dirty = self._dirty_props
        if self.Z is not None and len(self.Z) > 0 and ('Z' in dirty or force):
            files['Z'] = self._props['Z'].serialize(self.Z)
        return files

    @classmethod
    def _build_from_json(cls, json, **kwargs):
        mesh = Mesh2DGrid(title=kwargs['title'],
                          description=kwargs['description'],
                          h1=json['tensors']['h1'],
                          h2=json['tensors']['h2'],
                          O=json['OUV']['O'],
                          U=json['OUV']['U'],
                          V=json['OUV']['V'],
                          opts=json['meta'])
        try:
            mesh.Z = cls._props['Z'].deserialize(json['Z'], )
        except:
            mesh.Z = []

        return mesh

    @classmethod
    def _build_from_omf(cls, omf_geom, omf_project):
        mesh = Mesh2DGrid(h1=omf_geom.tensor_u,
                          h2=omf_geom.tensor_v,
                          O=omf_geom.origin + omf_project.origin,
                          U=omf_geom.axis_u,
                          V=omf_geom.axis_v)
        if omf_geom.offset_w is not None:
            mesh.Z = omf_geom.offset_w.array
        return mesh

    def _to_omf(self):
        import omf
        geometry = omf.SurfaceGridGeometry(
            tensor_u=self.h1,
            tensor_v=self.h2,
            axis_u=self.U,
            axis_v=self.V,
            origin=self.O,
            offset_w=omf.ScalarArray(self.Z, )
            if self.Z is not None else properties.undefined,
        )
        return geometry
Example #24
0
class DrawingPlane(_BaseSlideComponent):
    """2D drawing plane of the slide"""
    origin = properties.Vector3('Origin of drawing plane')
    axis_u = properties.Vector3('Horizontal axis of drawing plane')
    axis_v = properties.Vector3('Vertical axis of drawing plane')
Example #25
0
class MagDipole(BaseFDEMSrc):
    """
    Point magnetic dipole source calculated by taking the curl of a magnetic
    vector potential. By taking the discrete curl, we ensure that the magnetic
    flux density is divergence free (no magnetic monopoles!).

    This approach uses a primary-secondary in frequency. Here we show the
    derivation for E-B formulation noting that similar steps are followed for
    the H-J formulation.

    .. math::
        \mathbf{C} \mathbf{e} + i \omega \mathbf{b} = \mathbf{s_m} \\\\
        {\mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} \mathbf{b} -
        \mathbf{M_{\sigma}^e} \mathbf{e} = \mathbf{s_e}}

    We split up the fields and :math:`\mu^{-1}` into primary
    (:math:`\mathbf{P}`) and secondary (:math:`\mathbf{S}`) components

    - :math:`\mathbf{e} = \mathbf{e^P} + \mathbf{e^S}`
    - :math:`\mathbf{b} = \mathbf{b^P} + \mathbf{b^S}`
    - :math:`\\boldsymbol{\mu}^{\mathbf{-1}} =
      \\boldsymbol{\mu}^{\mathbf{-1}^\mathbf{P}} +
      \\boldsymbol{\mu}^{\mathbf{-1}^\mathbf{S}}`

    and define a zero-frequency primary problem, noting that the source is
    generated by a divergence free electric current

    .. math::
        \mathbf{C} \mathbf{e^P} = \mathbf{s_m^P} = 0 \\\\
        {\mathbf{C}^T \mathbf{{M_{\mu^{-1}}^f}^P} \mathbf{b^P} -
        \mathbf{M_{\sigma}^e} \mathbf{e^P} = \mathbf{M^e} \mathbf{s_e^P}}

    Since :math:`\mathbf{e^P}` is curl-free, divergence-free, we assume that
    there is no constant field background, the :math:`\mathbf{e^P} = 0`, so our
    primary problem is

    .. math::
        \mathbf{e^P} =  0 \\\\
            {\mathbf{C}^T \mathbf{{M_{\mu^{-1}}^f}^P} \mathbf{b^P} =
            \mathbf{s_e^P}}

    Our secondary problem is then

    .. math::
        \mathbf{C} \mathbf{e^S} + i \omega \mathbf{b^S} =
        - i \omega \mathbf{b^P} \\\\
        {\mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} \mathbf{b^S} -
        \mathbf{M_{\sigma}^e} \mathbf{e^S} =
        -\mathbf{C}^T \mathbf{{M_{\mu^{-1}}^f}^S} \mathbf{b^P}}

    :param list rxList: receiver list
    :param float freq: frequency
    :param numpy.ndarray loc: source location
        (ie: :code:`np.r_[xloc,yloc,zloc]`)
    :param string orientation: 'X', 'Y', 'Z'
    :param float moment: magnetic dipole moment
    :param float mu: background magnetic permeability

    """
    moment = properties.Float(
        "dipole moment of the transmitter", default=1., min=0.
    )
    mu = properties.Float(
        "permeability of the background", default=mu_0, min=0.
    )
    orientation = properties.Vector3(
        "orientation of the source", default='Z', length=1., required=True
    )
    freq = properties.Float(
        "frequency of the source (Hz)", required=True
    )
    loc = properties.Vector3(
        "location of the source", default=np.r_[0.,0.,0.]
    )

    def __init__(
        self, rxList, freq, loc, **kwargs
    ):

        super(MagDipole, self).__init__(
            rxList, **kwargs
        )

        self.freq = freq
        self.loc = loc


    @properties.validator('orientation')
    def _warn_non_axis_aligned_sources(self, change):
        value = change['value']
        axaligned = [
            True for vec in [np.r_[1.,0.,0.], np.r_[0.,1.,0.], np.r_[0.,0.,1.]]
            if np.all(value == vec)
        ]
        if len(axaligned) != 1:
            warnings.warn(
                'non-axes aligned orientations {} are not rigorously'
                ' tested'.format(value)
            )



    def _srcFct(self, obsLoc, component):
        return emutils.MagneticDipoleVectorPotential(
            self.loc, obsLoc, component, mu=self.mu, moment=self.moment,
            orientation=self.orientation
        )

    def bPrimary(self, prob):
        """
        The primary magnetic flux density from a magnetic vector potential

        :param BaseFDEMProblem prob: FDEM problem
        :rtype: numpy.ndarray
        :return: primary magnetic field
        """
        formulation = prob._formulation

        if formulation == 'EB':
            gridX = prob.mesh.gridEx
            gridY = prob.mesh.gridEy
            gridZ = prob.mesh.gridEz
            C = prob.mesh.edgeCurl

        elif formulation == 'HJ':
            gridX = prob.mesh.gridFx
            gridY = prob.mesh.gridFy
            gridZ = prob.mesh.gridFz
            C = prob.mesh.edgeCurl.T

        if prob.mesh._meshType == 'CYL':
            if not prob.mesh.isSymmetric:
                # TODO ?
                raise NotImplementedError(
                    'Non-symmetric cyl mesh not implemented yet!')
            assert (np.linalg.norm(self.orientation - np.r_[0., 0., 1.]) <
                    1e-6), ('for cylindrical symmetry, the dipole must be '
                            'oriented in the Z direction')
            a = self._srcFct(gridY, 'y')
        else:
            ax = self._srcFct(gridX, 'x')
            ay = self._srcFct(gridY, 'y')
            az = self._srcFct(gridZ, 'z')
            a = np.concatenate((ax, ay, az))

        return C*a

    def hPrimary(self, prob):
        """
        The primary magnetic field from a magnetic vector potential

        :param BaseFDEMProblem prob: FDEM problem
        :rtype: numpy.ndarray
        :return: primary magnetic field
        """
        b = self.bPrimary(prob)
        return 1./self.mu * b

    def s_m(self, prob):
        """
        The magnetic source term

        :param BaseFDEMProblem prob: FDEM problem
        :rtype: numpy.ndarray
        :return: primary magnetic field
        """

        b_p = self.bPrimary(prob)
        if prob._formulation == 'HJ':
            b_p = prob.Me * b_p
        return -1j*emutils.omega(self.freq)*b_p

    def s_e(self, prob):
        """
        The electric source term

        :param BaseFDEMProblem prob: FDEM problem
        :rtype: numpy.ndarray
        :return: primary magnetic field
        """

        if all(np.r_[self.mu] == np.r_[prob.mu]):
            return Zero()
        else:
            formulation = prob._formulation

            if formulation == 'EB':
                mui_s = prob.mui - 1./self.mu
                MMui_s = prob.mesh.getFaceInnerProduct(mui_s)
                C = prob.mesh.edgeCurl
            elif formulation == 'HJ':
                mu_s = prob.mu - self.mu
                MMui_s = prob.mesh.getEdgeInnerProduct(mu_s, invMat=True)
                C = prob.mesh.edgeCurl.T

            return -C.T * (MMui_s * self.bPrimary(prob))

    def s_eDeriv(self, prob, v, adjoint=False):
        if not hasattr(prob, 'muMap') or not hasattr(prob, 'muiMap'):
            return Zero()
        else:
            formulation = prob._formulation

            if formulation == 'EB':
                mui_s = prob.mui - 1./self.mu
                MMui_sDeriv = prob.mesh.getFaceInnerProductDeriv(mui_s)(
                    self.bPrimary(prob)
                ) * prob.muiDeriv
                C = prob.mesh.edgeCurl

                if adjoint:
                    return -MMui_sDeriv.T * (C * v)

                return -C.T * (MMui_sDeriv * v)

            elif formulation == 'HJ':
                return Zero()
                # raise NotImplementedError
                mu_s = prob.mu - self.mu
                MMui_s = prob.mesh.getEdgeInnerProduct(mu_s, invMat=True)
                C = prob.mesh.edgeCurl.T

                return -C.T * (MMui_s * self.bPrimary(prob))
Example #26
0
class BaseMesh(properties.HasProperties, InterfaceMixins):
    """
    BaseMesh does all the counting you don't want to do.
    BaseMesh should be inherited by meshes with a regular structure.
    """

    _REGISTRY = {}

    # Properties
    _n = properties.Array("number of cells in each direction (dim, )",
                          dtype=int,
                          required=True,
                          shape=('*', ))

    x0 = properties.Array("origin of the mesh (dim, )",
                          dtype=float,
                          shape=('*', ),
                          required=True)

    # Instantiate the class
    def __init__(self, n, x0=None, **kwargs):
        self._n = n  # number of dimensions

        if x0 is None:
            self.x0 = np.zeros(len(self._n))
        else:
            self.x0 = x0

        super(BaseMesh, self).__init__(**kwargs)

    # Validators
    @properties.validator('_n')
    def _check_n_shape(self, change):
        if not (not isinstance(change['value'], properties.utils.Sentinel)
                and change['value'] is not None):
            raise Exception("Cannot delete n. Instead, create a new mesh")

        change['value'] = np.array(change['value'], dtype=int).ravel()
        if len(change['value']) > 3:
            raise Exception("Dimensions of {}, which is higher than 3 are not "
                            "supported".format(change['value']))

        if np.any(change['previous'] != properties.undefined):
            # can't change dimension of the mesh
            if len(change['previous']) != len(change['value']):
                raise Exception(
                    "Cannot change dimensionality of the mesh. Expected {} "
                    "dimensions, got {} dimensions".format(
                        len(change['previous']), len(change['value'])))

            # check that if h has been set, sizes still agree
            if getattr(self, 'h', None) is not None and len(self.h) > 0:
                for i in range(len(change['value'])):
                    if len(self.h[i]) != change['value'][i]:
                        raise Exception(
                            "Mismatched shape of n. Expected {}, len(h[{}]), got "
                            "{}".format(len(self.h[i]), i, change['value'][i]))

            # check that if nodes have been set for curvi mesh, sizes still
            # agree
            if (getattr(self, 'nodes', None) is not None
                    and len(self.nodes) > 0):
                for i in range(len(change['value'])):
                    if self.nodes[0].shape[i] - 1 != change['value'][i]:
                        raise Exception(
                            "Mismatched shape of n. Expected {}, len(nodes[{}]), "
                            "got {}".format(self.nodes[0].shape[i] - 1, i,
                                            change['value'][i]))

    @properties.validator('x0')
    def _check_x0(self, change):
        if not (not isinstance(change['value'], properties.utils.Sentinel)
                and change['value'] is not None):
            raise Exception("n must be set prior to setting x0")

        if len(self._n) != len(change['value']):
            raise Exception(
                "Dimension mismatch. x0 has length {} != len(n) which is "
                "{}".format(len(x0), len(n)))

    @property
    def dim(self):
        """The dimension of the mesh (1, 2, or 3).

        Returns
        -------
        int
            dimension of the mesh
        """
        return len(self._n)

    @property
    def nC(self):
        """Total number of cells in the mesh.

        Returns
        -------
        int
            number of cells in the mesh

        Example
        -------
        .. plot::
            :include-source:

            import discretize
            import numpy as np
            mesh = discretize.TensorMesh([np.ones(n) for n in [2,3]])
            mesh.plotGrid(centers=True, showIt=True)

            print(mesh.nC)
        """
        return int(self._n.prod())

    @property
    def nN(self):
        """Total number of nodes

        Returns
        -------
        int
            number of nodes in the mesh

        Example
        -------
        .. plot::
            :include-source:

            import discretize
            import numpy as np
            mesh = discretize.TensorMesh([np.ones(n) for n in [2,3]])
            mesh.plotGrid(nodes=True, showIt=True)

            print(mesh.nN)
        """
        return int((self._n + 1).prod())

    @property
    def nEx(self):
        """Number of x-edges

        Returns
        -------
        nEx : int

        """
        return int((self._n + np.r_[0, 1, 1][:self.dim]).prod())

    @property
    def nEy(self):
        """Number of y-edges

        Returns
        -------
        nEy : int


        """
        if self.dim < 2:
            return None
        return int((self._n + np.r_[1, 0, 1][:self.dim]).prod())

    @property
    def nEz(self):
        """Number of z-edges

        Returns
        -------
        nEz : int


        """
        if self.dim < 3:
            return None
        return int((self._n + np.r_[1, 1, 0][:self.dim]).prod())

    @property
    def vnE(self):
        """Total number of edges in each direction

        Returns
        -------
        vnE : numpy.ndarray = [nEx, nEy, nEz], (dim, )

        .. plot::
            :include-source:

            import discretize
            import numpy as np
            M = discretize.TensorMesh([np.ones(n) for n in [2,3]])
            M.plotGrid(edges=True, showIt=True)
        """
        return np.array(
            [x for x in [self.nEx, self.nEy, self.nEz] if x is not None],
            dtype=int)

    @property
    def nE(self):
        """Total number of edges.

        Returns
        -------
        nE : int = sum([nEx, nEy, nEz])

        """
        return int(self.vnE.sum())

    @property
    def nFx(self):
        """Number of x-faces

        :rtype: int
        :return: nFx
        """
        return int((self._n + np.r_[1, 0, 0][:self.dim]).prod())

    @property
    def nFy(self):
        """Number of y-faces

        :rtype: int
        :return: nFy
        """
        if self.dim < 2:
            return None
        return int((self._n + np.r_[0, 1, 0][:self.dim]).prod())

    @property
    def nFz(self):
        """Number of z-faces

        :rtype: int
        :return: nFz
        """
        if self.dim < 3:
            return None
        return int((self._n + np.r_[0, 0, 1][:self.dim]).prod())

    @property
    def vnF(self):
        """Total number of faces in each direction

        :rtype: numpy.ndarray
        :return: [nFx, nFy, nFz], (dim, )

        .. plot::
            :include-source:

            import discretize
            import numpy as np
            M = discretize.TensorMesh([np.ones(n) for n in [2,3]])
            M.plotGrid(faces=True, showIt=True)
        """
        return np.array(
            [x for x in [self.nFx, self.nFy, self.nFz] if x is not None],
            dtype=int)

    @property
    def nF(self):
        """Total number of faces.

        :rtype: int
        :return: sum([nFx, nFy, nFz])

        """
        return int(self.vnF.sum())

    @property
    def normals(self):
        """Face Normals

        :rtype: numpy.ndarray
        :return: normals, (sum(nF), dim)
        """
        if self.dim == 2:
            nX = np.c_[np.ones(self.nFx), np.zeros(self.nFx)]
            nY = np.c_[np.zeros(self.nFy), np.ones(self.nFy)]
            return np.r_[nX, nY]
        elif self.dim == 3:
            nX = np.c_[np.ones(self.nFx),
                       np.zeros(self.nFx),
                       np.zeros(self.nFx)]
            nY = np.c_[np.zeros(self.nFy),
                       np.ones(self.nFy),
                       np.zeros(self.nFy)]
            nZ = np.c_[np.zeros(self.nFz),
                       np.zeros(self.nFz),
                       np.ones(self.nFz)]
            return np.r_[nX, nY, nZ]

    @property
    def tangents(self):
        """Edge Tangents

        :rtype: numpy.ndarray
        :return: normals, (sum(nE), dim)
        """
        if self.dim == 2:
            tX = np.c_[np.ones(self.nEx), np.zeros(self.nEx)]
            tY = np.c_[np.zeros(self.nEy), np.ones(self.nEy)]
            return np.r_[tX, tY]
        elif self.dim == 3:
            tX = np.c_[np.ones(self.nEx),
                       np.zeros(self.nEx),
                       np.zeros(self.nEx)]
            tY = np.c_[np.zeros(self.nEy),
                       np.ones(self.nEy),
                       np.zeros(self.nEy)]
            tZ = np.c_[np.zeros(self.nEz),
                       np.zeros(self.nEz),
                       np.ones(self.nEz)]
            return np.r_[tX, tY, tZ]

    def projectFaceVector(self, fV):
        """Given a vector, fV, in cartesian coordinates, this will project
        it onto the mesh using the normals

        :param numpy.ndarray fV: face vector with shape (nF, dim)
        :rtype: numpy.ndarray
        :return: projected face vector, (nF, )

        """
        if not isinstance(fV, np.ndarray):
            raise Exception('fV must be an ndarray')
        if not (len(fV.shape) == 2 and fV.shape[0] == self.nF
                and fV.shape[1] == self.dim):
            raise Exception('fV must be an ndarray of shape (nF x dim)')
        return np.sum(fV * self.normals, 1)

    def projectEdgeVector(self, eV):
        """Given a vector, eV, in cartesian coordinates, this will project
        it onto the mesh using the tangents

        :param numpy.ndarray eV: edge vector with shape (nE, dim)
        :rtype: numpy.ndarray
        :return: projected edge vector, (nE, )

        """
        if not isinstance(eV, np.ndarray):
            raise Exception('eV must be an ndarray')
        if not (len(eV.shape) == 2 and eV.shape[0] == self.nE
                and eV.shape[1] == self.dim):
            raise Exception('eV must be an ndarray of shape (nE x dim)')
        return np.sum(eV * self.tangents, 1)

    def save(self, filename='mesh.json', verbose=False):
        """
        Save the mesh to json
        :param str file: filename for saving the casing properties
        :param str directory: working directory for saving the file
        """

        f = os.path.abspath(filename)  # make sure we are working with abs path
        with open(f, 'w') as outfile:
            json.dump(self.serialize(), outfile)

        if verbose is True:
            print('Saved {}'.format(f))

        return f

    def copy(self):
        """
        Make a copy of the current mesh
        """
        return properties.copy(self)

    axis_u = properties.Vector3(
        'Vector orientation of u-direction. For more details see the docs for the :attr:`~discretize.base.BaseMesh.rotation_matrix` property.',
        default='X',
        length=1)
    axis_v = properties.Vector3(
        'Vector orientation of v-direction. For more details see the docs for the :attr:`~discretize.base.BaseMesh.rotation_matrix` property.',
        default='Y',
        length=1)
    axis_w = properties.Vector3(
        'Vector orientation of w-direction. For more details see the docs for the :attr:`~discretize.base.BaseMesh.rotation_matrix` property.',
        default='Z',
        length=1)

    @properties.validator
    def _validate_orientation(self):
        """Check if axes are orthogonal"""
        if not (np.abs(self.axis_u.dot(self.axis_v) < 1e-6)
                and np.abs(self.axis_v.dot(self.axis_w) < 1e-6)
                and np.abs(self.axis_w.dot(self.axis_u) < 1e-6)):
            raise ValueError('axis_u, axis_v, and axis_w must be orthogonal')
        return True

    @property
    def reference_is_rotated(self):
        """True if the axes are rotated from the traditional <X,Y,Z> system
        with vectors of :math:`(1,0,0)`, :math:`(0,1,0)`, and :math:`(0,0,1)`
        """
        if (np.allclose(self.axis_u,
                        (1, 0, 0)) and np.allclose(self.axis_v, (0, 1, 0))
                and np.allclose(self.axis_w, (0, 0, 1))):
            return False
        return True

    @property
    def rotation_matrix(self):
        """Builds a rotation matrix to transform coordinates from their coordinate
        system into a conventional cartesian system. This is built off of the
        three `axis_u`, `axis_v`, and `axis_w` properties; these mapping
        coordinates use the letters U, V, and W (the three letters preceding X,
        Y, and Z in the alphabet) to define the projection of the X, Y, and Z
        durections. These UVW vectors describe the placement and transformation
        of the mesh's coordinate sytem assuming at most 3 directions.

        Why would you want to use these UVW mapping vectors the this
        `rotation_matrix` property? They allow us to define the realationship
        between local and global coordinate systems and provide a tool for
        switching between the two while still maintaing the connectivity of the
        mesh's cells. For a visual example of this, please see the figure in the
        docs for the :class:`~discretize.mixins.vtkModule.InterfaceVTK`.
        """
        return np.array([self.axis_u, self.axis_v, self.axis_w])

    reference_system = properties.String(
        'The type of coordinate reference frame. Can take on the values ' +
        'cartesian, cylindrical, or spherical. Abbreviations of these are allowed.',
        default='cartesian',
        change_case='lower',
    )

    @properties.validator
    def _validate_reference_system(self):
        """Check if the reference system is of a known type."""
        choices = ['cartesian', 'cylindrical', 'spherical']
        # Here are a few abbreviations that users can harnes
        abrevs = {
            'car': choices[0],
            'cart': choices[0],
            'cy': choices[1],
            'cyl': choices[1],
            'sph': choices[2],
        }
        # Get the name and fix it if it is abbreviated
        self.reference_system = abrevs.get(self.reference_system,
                                           self.reference_system)
        if self.reference_system not in choices:
            raise ValueError('Coordinate system ({}) unknown.'.format(
                self.reference_system))
        return True
Example #27
0
 class HasVec3(properties.HasProperties):
     vec3 = properties.Vector3('simple vector')
class ElementSurfaceGrid(_BaseElementSurface):
    """Surface element with geometry defined by a grid

    The grid is defined by two axes and cell spacing along these
    axes.
    """

    SUB_TYPE = 'surfacegrid'

    origin = properties.Vector3(
        'Grid origin, where axis_u and axis_v vectors extend from', )
    tensor_u = properties.List(
        'Grid cell widths, u-direction',
        properties.Float('', min=0),
        max_length=10000,
        coerce=True,
    )
    tensor_v = properties.List(
        'Grid cell widths, v-direction',
        properties.Float('', min=0),
        max_length=10000,
        coerce=True,
    )
    axis_u = properties.Vector3(
        'Vector orientation of u-direction',
        length=1,
    )
    axis_v = properties.Vector3(
        'Vector orientation of v-direction',
        length=1,
    )
    offset_w = Pointer(
        'Node offset perpendicular to the two axes; this must be an array '
        'of shape len(tensor_u)+1 x len(tensor_v)+1, flattened with '
        'row-major order',
        Array,
        required=False,
    )

    @property
    def num_nodes(self):
        """Number of nodes (vertices)"""
        try:
            return (len(self.tensor_u) + 1) * (len(self.tensor_v) + 1)
        except (AttributeError, IndexError, TypeError):
            return None

    @property
    def num_cells(self):
        """Number of cells (faces)"""
        try:
            return len(self.tensor_u) * len(self.tensor_v)
        except (AttributeError, IndexError, TypeError):
            return None

    @properties.validator
    def _validate_geometry(self):
        """Ensure offset_w shape is consistent with tensor lengths"""
        if self.offset_w is None or isinstance(self.offset_w, string_types):
            return True
        if len(self.offset_w.shape) != 1:
            raise properties.ValidationError(
                message='offset_w must be 1D array, not of shape {}'.format(
                    self.offset_w.shape),
                reason='invalid',
                prop='offset_w',
                instance=self,
            )
        if self.offset_w.shape[0] != self.num_nodes:
            raise properties.ValidationError(
                message=(
                    'Length of offset_w, {zlen}, must equal number of nodes, '
                    '{nnode}'.format(
                        zlen=self.offset_w.shape[0],
                        nnode=self.num_nodes,
                    )),
                reason='invalid',
                prop='offset_w',
                instance=self,
            )
        return True

    def to_omf(self):
        self.validate()
        omf_grid_surface = omf.SurfaceElement(
            name=self.name or '',
            description=self.description or '',
            geometry=omf.SurfaceGridGeometry(
                origin=self.origin,
                tensor_u=self.tensor_u,
                tensor_v=self.tensor_v,
                axis_u=self.axis_u,
                axis_v=self.axis_v,
            ),
            data=[
                attr.to_omf(cell_location='faces') for attr in self.data
                if not isinstance(attr, TextureProjection)
            ],
            textures=[
                tex.to_omf() for tex in self.data
                if isinstance(tex, TextureProjection)
            ],
            color=self.defaults.color.value,
        )
        if self.offset_w is not None:
            omf_grid_surface.offset_w = self.offset_w.array
        return omf_grid_surface
Example #29
0
class MagDipole(BaseTDEMSrc):

    moment = properties.Float("dipole moment of the transmitter",
                              default=1.0,
                              min=0.0)
    mu = properties.Float("permeability of the background",
                          default=mu_0,
                          min=0.0)
    orientation = properties.Vector3("orientation of the source",
                                     default="Z",
                                     length=1.0,
                                     required=True)
    location = LocationVector("location of the source",
                              default=np.r_[0.0, 0.0, 0.0],
                              shape=(3, ))
    loc = deprecate_property(location,
                             "loc",
                             new_name="location",
                             removal_version="0.15.0")

    def __init__(self, receiver_list=None, **kwargs):
        kwargs.pop("srcType", None)
        BaseTDEMSrc.__init__(self,
                             receiver_list=receiver_list,
                             srcType="inductive",
                             **kwargs)

    def _srcFct(self, obsLoc, coordinates="cartesian"):
        if getattr(self, "_dipole", None) is None:
            self._dipole = MagneticDipoleWholeSpace(
                mu=self.mu,
                orientation=self.orientation,
                location=self.loc,
                moment=self.moment,
            )
        return self._dipole.vector_potential(obsLoc, coordinates=coordinates)

    def _aSrc(self, prob):
        coordinates = "cartesian"
        if prob._formulation == "EB":
            gridX = prob.mesh.gridEx
            gridY = prob.mesh.gridEy
            gridZ = prob.mesh.gridEz

        elif prob._formulation == "HJ":
            gridX = prob.mesh.gridFx
            gridY = prob.mesh.gridFy
            gridZ = prob.mesh.gridFz

        if prob.mesh._meshType == "CYL":
            coordinates = "cylindrical"
            if prob.mesh.isSymmetric:
                return self._srcFct(gridY)[:, 1]

        ax = self._srcFct(gridX, coordinates)[:, 0]
        ay = self._srcFct(gridY, coordinates)[:, 1]
        az = self._srcFct(gridZ, coordinates)[:, 2]
        a = np.concatenate((ax, ay, az))

        return a

    def _getAmagnetostatic(self, prob):
        if prob._formulation == "EB":
            return prob.mesh.faceDiv * prob.MfMuiI * prob.mesh.faceDiv.T
        else:
            raise NotImplementedError(
                "Solving the magnetostatic problem for the initial fields "
                "when a permeable model is considered has not yet been "
                "implemented for the HJ formulation. "
                "See: https://github.com/simpeg/simpeg/issues/680")

    def _rhs_magnetostatic(self, prob):
        if getattr(self, "_hp", None) is None:
            if prob._formulation == "EB":
                bp = prob.mesh.edgeCurl * self._aSrc(prob)
                self._MfMuip = prob.mesh.getFaceInnerProduct(1.0 / self.mu)
                self._MfMuipI = prob.mesh.getFaceInnerProduct(1.0 / self.mu,
                                                              invMat=True)
                self._hp = self._MfMuip * bp
            else:
                raise NotImplementedError(
                    "Solving the magnetostatic problem for the initial fields "
                    "when a permeable model is considered has not yet been "
                    "implemented for the HJ formulation. "
                    "See: https://github.com/simpeg/simpeg/issues/680")

        if prob._formulation == "EB":
            return -prob.mesh.faceDiv * (
                (prob.MfMuiI - self._MfMuipI) * self._hp)
        else:
            raise NotImplementedError(
                "Solving the magnetostatic problem for the initial fields "
                "when a permeable model is considered has not yet been "
                "implemented for the HJ formulation. "
                "See: https://github.com/simpeg/simpeg/issues/680")

    def _phiSrc(self, prob):
        Ainv = prob.Solver(self._getAmagnetostatic(prob))  # todo: store these
        rhs = self._rhs_magnetostatic(prob)
        Ainv.clean()
        return Ainv * rhs

    def _bSrc(self, prob):
        if prob._formulation == "EB":
            C = prob.mesh.edgeCurl

        elif prob._formulation == "HJ":
            C = prob.mesh.edgeCurl.T

        return C * self._aSrc(prob)

    def bInitial(self, prob):

        if self.waveform.hasInitialFields is False:
            return Zero()

        if np.all(prob.mu == self.mu):
            return self._bSrc(prob)

        else:
            if prob._formulation == "EB":
                hs = prob.mesh.faceDiv.T * self._phiSrc(prob)
                ht = self._hp + hs
                return prob.MfMuiI * ht
            else:
                raise NotImplementedError

    def hInitial(self, prob):

        if self.waveform.hasInitialFields is False:
            return Zero()
        # if prob._formulation == 'EB':
        #     return prob.MfMui * self.bInitial(prob)
        # elif prob._formulation == 'HJ':
        #     return prob.MeMuI * self.bInitial(prob)
        return 1.0 / self.mu * self.bInitial(prob)

    def s_m(self, prob, time):
        if self.waveform.hasInitialFields is False:
            return Zero()
        return Zero()

    def s_e(self, prob, time):
        C = prob.mesh.edgeCurl
        b = self._bSrc(prob)

        if prob._formulation == "EB":
            MfMui = prob.mesh.getFaceInnerProduct(1.0 / self.mu)

            if self.waveform.hasInitialFields is True and time < prob.time_steps[
                    1]:
                if prob._fieldType == "b":
                    return Zero()
                elif prob._fieldType == "e":
                    # Compute s_e from vector potential
                    return C.T * (MfMui * b)
            else:
                return C.T * (MfMui * b) * self.waveform.eval(time)

        elif prob._formulation == "HJ":

            h = 1.0 / self.mu * b

            if self.waveform.hasInitialFields is True and time < prob.time_steps[
                    1]:
                if prob._fieldType == "h":
                    return Zero()
                elif prob._fieldType == "j":
                    # Compute s_e from vector potential
                    return C * h
            else:
                return C * h * self.waveform.eval(time)
Example #30
0
class MagDipole(BaseTDEMSrc):

    moment = properties.Float("dipole moment of the transmitter",
                              default=1.,
                              min=0.)
    mu = properties.Float("permeability of the background",
                          default=mu_0,
                          min=0.)
    orientation = properties.Vector3("orientation of the source",
                                     default='Z',
                                     length=1.,
                                     required=True)

    def __init__(self, rxList, **kwargs):
        # assert(self.orientation in ['X', 'Y', 'Z']), (
        #     "Orientation (right now) doesn't actually do anything! The methods"
        #     " in SrcUtils should take care of this..."
        #     )
        # self.integrate = False
        BaseTDEMSrc.__init__(self, rxList, srcType="inductive", **kwargs)

    @properties.validator('orientation')
    def _warn_non_axis_aligned_sources(self, change):
        value = change['value']
        axaligned = [
            True for vec in
            [np.r_[1., 0., 0.], np.r_[0., 1., 0.], np.r_[0., 0., 1.]]
            if np.all(value == vec)
        ]
        if len(axaligned) != 1:
            warnings.warn('non-axes aligned orientations {} are not rigorously'
                          ' tested'.format(value))

    def _srcFct(self, obsLoc, component):
        return MagneticDipoleVectorPotential(self.loc,
                                             obsLoc,
                                             component,
                                             mu=self.mu,
                                             moment=self.moment)

    def _aSrc(self, prob):
        if prob._formulation == 'EB':
            gridX = prob.mesh.gridEx
            gridY = prob.mesh.gridEy
            gridZ = prob.mesh.gridEz

        elif prob._formulation == 'HJ':
            gridX = prob.mesh.gridFx
            gridY = prob.mesh.gridFy
            gridZ = prob.mesh.gridFz

        if prob.mesh._meshType is 'CYL':
            if not prob.mesh.isSymmetric:
                raise NotImplementedError(
                    'Non-symmetric cyl mesh not implemented yet!')
            a = self._srcFct(gridY, 'y')

        else:
            ax = self._srcFct(gridX, 'x')
            ay = self._srcFct(gridY, 'y')
            az = self._srcFct(gridZ, 'z')
            a = np.concatenate((ax, ay, az))

        return a

    def _getAmagnetostatic(self, prob):
        if prob._formulation == 'EB':
            return prob.mesh.faceDiv * prob.MfMuiI * prob.mesh.faceDiv.T
        else:
            raise NotImplementedError(
                "Solving the magnetostatic problem for the initial fields "
                "when a permeable model is considered has not yet been "
                "implemented for the HJ formulation. "
                "See: https://github.com/simpeg/simpeg/issues/680")

    def _rhs_magnetostatic(self, prob):
        if getattr(self, '_hp', None) is None:
            if prob._formulation == 'EB':
                bp = prob.mesh.edgeCurl * self._aSrc(prob)
                self._MfMuip = prob.mesh.getFaceInnerProduct(1. / self.mu)
                self._MfMuipI = prob.mesh.getFaceInnerProduct(1. / self.mu,
                                                              invMat=True)
                self._hp = self._MfMuip * bp
            else:
                raise NotImplementedError(
                    "Solving the magnetostatic problem for the initial fields "
                    "when a permeable model is considered has not yet been "
                    "implemented for the HJ formulation. "
                    "See: https://github.com/simpeg/simpeg/issues/680")

        if prob._formulation == 'EB':
            return -prob.mesh.faceDiv * (
                (prob.MfMuiI - self._MfMuipI) * self._hp)
        else:
            raise NotImplementedError(
                "Solving the magnetostatic problem for the initial fields "
                "when a permeable model is considered has not yet been "
                "implemented for the HJ formulation. "
                "See: https://github.com/simpeg/simpeg/issues/680")

    def _phiSrc(self, prob):
        Ainv = prob.Solver(self._getAmagnetostatic(prob))
        rhs = self._rhs_magnetostatic(prob)
        return Ainv * rhs

    def _bSrc(self, prob):
        if prob._formulation == 'EB':
            C = prob.mesh.edgeCurl

        elif prob._formulation == 'HJ':
            C = prob.mesh.edgeCurl.T

        return C * self._aSrc(prob)

    def bInitial(self, prob):

        if self.waveform.hasInitialFields is False:
            return Zero()

        if np.all(prob.mu == self.mu):
            return self._bSrc(prob)

        else:
            if prob._formulation == 'EB':
                hs = prob.mesh.faceDiv.T * self._phiSrc(prob)
                ht = self._hp + hs
                return prob.MfMuiI * ht
            else:
                raise NotImplementedError

    def hInitial(self, prob):

        if self.waveform.hasInitialFields is False:
            return Zero()
        # if prob._formulation == 'EB':
        #     return prob.MfMui * self.bInitial(prob)
        # elif prob._formulation == 'HJ':
        #     return prob.MeMuI * self.bInitial(prob)
        return 1. / self.mu * self.bInitial(prob)

    def s_m(self, prob, time):
        if self.waveform.hasInitialFields is False:
            return Zero()
        return Zero()

    def s_e(self, prob, time):
        C = prob.mesh.edgeCurl
        b = self._bSrc(prob)

        if prob._formulation == 'EB':
            MfMui = prob.mesh.getFaceInnerProduct(1. / self.mu)

            if self.waveform.hasInitialFields is True and time < prob.timeSteps[
                    1]:
                if prob._fieldType == 'b':
                    return Zero()
                elif prob._fieldType == 'e':
                    # Compute s_e from vector potential
                    return C.T * (MfMui * b)
            else:
                return C.T * (MfMui * b) * self.waveform.eval(time)

        elif prob._formulation == 'HJ':

            h = 1. / self.mu * b

            if self.waveform.hasInitialFields is True and time < prob.timeSteps[
                    1]:
                if prob._fieldType == 'h':
                    return Zero()
                elif prob._fieldType == 'j':
                    # Compute s_e from vector potential
                    return C * h
            else:
                return C * h * self.waveform.eval(time)