"""
Reference-physical domain mappings.
"""
import numpy as nm
from sfepy.base.base import Struct
from sfepy.discrete.common.extmods.cmapping import CMapping
[docs]
class PyCMapping(Struct):
    """
    Class for storing mapping data. Primary data in numpy arrays.
    Data for C functions translated to FMFields and embedded in CMapping.
    """
    def __init__(self, bf, det, volume, bfg, normal, dim):
        self.bf = bf
        self.det = det
        self.volume = volume
        self.bfg = bfg
        self.normal = normal
        self.cmap = CMapping(bf, det, volume, bfg, normal, dim)
        n_el, n_qp = det.shape[:2]
        n_ep = bf.shape[3]
        self.n_el = n_el
        self.n_qp = n_qp
        self.dim = dim
        self.n_ep = n_ep
[docs]
    def integrate(self, out, field, mode=0):
        dim = field.shape[2]
        if mode < 3 or dim == 1:
            out[:] = nm.sum(field * self.det, axis=1)[:, None, :, :]
            if mode == 1:
                out /= self.volume
        elif dim == (self.tdim + 1) and self.normal is not None:
            out[:] = nm.dot(field, self.normal) * self.det / self.volume
        return 0 
 
[docs]
class PhysicalQPs(Struct):
    """
    Physical quadrature points in a region.
    """
    def __init__(self, num=0):
        Struct.__init__(self, num=num, shape=(0, 0, 0))
        self.values = nm.empty(self.shape, dtype=nm.float64)
[docs]
    def get_shape(self, rshape):
        """
        Get shape from raveled shape.
        """
        n_qp = self.shape[1]
        if n_qp > 0:
            if rshape[0] == 1:
                shape = (0, n_qp) + rshape[1:] # Constant parameter.
            elif (rshape[0] // n_qp) * n_qp != rshape[0]:
                raise ValueError('incompatible shapes! (n_qp: %d, %s)'
                                 % (n_qp, rshape))
            else:
                shape = (rshape[0] // n_qp, n_qp) + rshape[1:]
        else:
            shape = (rshape[0], 0, 0, 0)
        return shape 
 
[docs]
class Mapping(Struct):
    """
    Base class for mappings.
    """
[docs]
    @staticmethod
    def from_args(region, kind='v'):
        """
        Create mapping from reference to physical entities in a given
        region, given the integration kind ('v' or 's').
        This mapping can be used to compute the physical quadrature
        points.
        Parameters
        ----------
        region : Region instance
            The region defining the entities.
        kind : 'v' or 's'
            The kind of the entities: 'v' - cells, 's' - facets.
        Returns
        -------
        mapping : FEMapping or IGMapping instance
            The requested mapping.
        """
        from sfepy.discrete.fem.domain import FEDomain
        from sfepy.discrete.iga.domain import IGDomain
        if isinstance(region.domain, FEDomain):
            from sfepy.discrete.fem.mappings import FEMapping
            coors = region.domain.get_mesh_coors()
            if kind == 's':
                coors = coors[region.vertices]
            if kind == 'v':
                cells = region.get_cells()
                conn, gel = region.domain.get_conn(ret_gel=True,
                                                   tdim=region.tdim,
                                                   cells=region.cells)
                mapping = FEMapping(coors, conn, gel=gel)
            elif kind == 's':
                from sfepy.discrete.fem.fe_surface import FESurface
                aux, gel = FESurface.from_region('aux', region, ret_gel=True)
                mapping = FEMapping(coors, aux.leconn, gel=gel)
        elif isinstance(region.domain, IGDomain):
            from sfepy.discrete.iga.mappings import IGMapping
            mapping = IGMapping(region.domain, region.cells)
        else:
            raise ValueError('unknown domain class! (%s)' % type(region.domain))
        return mapping 
 
[docs]
def get_physical_qps(region, integral, map_kind=None):
    """
    Get physical quadrature points corresponding to the given region
    and integral.
    """
    phys_qps = PhysicalQPs()
    if map_kind is None:
        map_kind = 'v' if region.can_cells else 's'
    gmap = Mapping.from_args(region, map_kind)
    gel = gmap.get_geometry()
    if isinstance(gel, dict):
        qp_coors = {}
        for k, v in gel.items():
            qp, _ = integral.get_qp(v.name)
            qp_coors[k] = qp
    else:
        qp_coors, _ = integral.get_qp(gel.name)
    qps = gmap.get_physical_qps(qp_coors)
    n_el, n_qp = qps.shape[0], qps.shape[1]
    phys_qps.num = n_el * n_qp
    phys_qps.shape = qps.shape
    qps.shape = (phys_qps.num, qps.shape[2])
    phys_qps.values = qps
    return phys_qps 
[docs]
def get_mapping_data(name, field, integral, region=None, integration='volume'):
    """
    General helper function for accessing reference mapping data.
    Get data attribute `name` from reference mapping corresponding to
    `field` in `region` in quadrature points of the given `integral` and
    `integration` type.
    Parameters
    ----------
    name : str
        The reference mapping attribute name.
    field : Field instance
        The field defining the reference mapping.
    integral : Integral instance
        The integral defining quadrature points.
    region : Region instance, optional
        If given, use the given region instead of `field` region.
    integration : one of ('volume', 'surface', 'surface_extra')
        The integration type.
    Returns
    -------
    data : array
        The required data merged for all element groups.
    Notes
    -----
    Assumes the same element geometry in all element groups of the field!
    """
    data = None
    if region is None:
        region = field.region
    geo, _ = field.get_mapping(region, integral, integration)
    data = getattr(geo, name)
    return data 
[docs]
def get_jacobian(field, integral, region=None, integration='volume'):
    """
    Get the jacobian of reference mapping corresponding to `field`.
    Parameters
    ----------
    field : Field instance
        The field defining the reference mapping.
    integral : Integral instance
        The integral defining quadrature points.
    region : Region instance, optional
        If given, use the given region instead of `field` region.
    integration : one of ('volume', 'surface', 'surface_extra')
        The integration type.
    Returns
    -------
    jac : array
        The jacobian merged for all element groups.
    See Also
    --------
    get_mapping_data
    Notes
    -----
    Assumes the same element geometry in all element groups of the field!
    """
    jac = get_mapping_data('det', field, integral, region=region,
                           integration=integration)
    return jac 
[docs]
def get_normals(field, integral, region):
    """
    Get the normals of element faces in `region`.
    Parameters
    ----------
    field : Field instance
        The field defining the reference mapping.
    integral : Integral instance
        The integral defining quadrature points.
    region : Region instance
        The given of the element faces.
    Returns
    -------
    normals : array
        The normals merged for all element groups.
    See Also
    --------
    get_mapping_data
    Notes
    -----
    Assumes the same element geometry in all element groups of the field!
    """
    normals = get_mapping_data('normal', field, integral, region=region,
                               integration='surface')
    return normals