Source code for pyNastran.bdf.bdfInterface.getCard

# pylint: disable=E1101,C0103,C0111
from __future__ import (nested_scopes, generators, division, absolute_import,
                        print_function, unicode_literals)
from six import string_types, iteritems
#import sys
from numpy import ndarray

from pyNastran.bdf.deprecated import GetMethodsDeprecated
from pyNastran.bdf.cards.nodes import SPOINT


class GetMethods(GetMethodsDeprecated):
    def __init__(self):
        pass

    def get_node_ids_with_element(self, eid, msg=''):
        return self.get_node_ids_with_elements([eid], msg=msg)

    def get_node_ids_with_elements(self, eids, msg=''):
        """
        Get the node IDs associated with a list of element IDs

        :param self: the BDF object
        :param eids: list of element ID
        :param msg:  a additional message to print out if an element is not found
        :returns node_ids: set of node IDs

        For example

        .. code-block:: python

          >>> eids = [1, 2, 3]  # list of elements with pid=1
          >>> msg = ' which are required for pid=1'
          >>> node_ids = bdf.get_node_ids_with_elements(eids, msg=msg)
        """
        nids2 = set([])
        for eid in eids:
            element = self.Element(eid, msg=msg)
            self.log.debug("element.pid = %s" % (element.pid))
            nids = set(element.nodeIDs())
            nids2.update(nids)
        return nids2

    def Node(self, nid, allowEmptyNodes=False, msg=''):
        if (nid == 0 or nid is None) and allowEmptyNodes:
            return None
        elif nid in self.nodes:
            return self.nodes[nid]
        elif self.spoints and nid in self.spoints.spoints:
            return SPOINT(nid)
        else:
            assert isinstance(nid, int), 'nid should be an integer; not %s' % type(nid)
            raise RuntimeError('nid=%s is not a GRID or SPOINT%s' % (nid, msg))

    def Nodes(self, nids, allowEmptyNodes=False, msg=''):
        """
        Returns a series of node objects given a list of node IDs
        """
        nodes = []
        for nid in nids:
            nodes.append(self.Node(nid, allowEmptyNodes, msg))
        return nodes

    #--------------------
    # ELEMENT CARDS

    def get_element_ids_list_with_pids(self, pids):
        """
        Gets all the element IDs with a specific property ID.

        :param self: the BDF object
        :param pids: list of property ID
        :returns element_ids: as a list

        For example, we want to get all the element ids with ``pids=[1, 2, 3]``

        .. code-block:: python

          model = BDF()
          model.read_bdf(bdf_filename)
          pids = [1, 2, 3]
          eids_list = model.get_element_ids_with_pids(pids, mode='list')
        """
        assert isinstance(pids, list), pids
        eids2 = []
        for eid, element in sorted(iteritems(self.elements)):
            pid = element.Pid()
            if pid in pids:
                eids2.append(eid)
        return eids2

    def get_element_ids_dict_with_pids(self, pids):
        """
        Gets all the element IDs with a specific property ID.

        :param self: the BDF object
        :param pids: list of property ID
        :returns element_ids: as a dictionary of lists by property

        For example, we want all the elements with ``pids=[4, 5, 6]``,
        but we want them in separate groups

        .. code-block:: python

          model = BDF()
          model.read_bdf(bdf_filename)
          pids = [4, 5, 6]
          eids_dict = model.get_element_ids_with_pids(pids, mode='dict')
        """
        assert isinstance(pids, list), pids
        eids2 = {}
        for pid in pids:
            eids2[pid] = []
        for eid, element in iteritems(self.elements):
            try:
                pid = element.Pid()
                if pid in pids:
                    eids2[pid].append(eid)
            except AttributeError:
                #eids2[0].append(eid)
                pass
        return eids2

    def get_node_id_to_element_ids_map(self):
        """
        Returns a dictionary that maps a node ID to a list of elemnents

        .. todo:: support 0d or 1d elements
        .. todo:: support elements with missing nodes
                  (e.g. CQUAD8 with missing nodes)
        """
        nidToElementsMap = {}
        for nid in self.nodes:  # initalize the mapper
            nidToElementsMap[nid] = []

        if self.spoints:  # SPOINTs
            for nid in sorted(self.spoints.spoints):  # SPOINTs
                nidToElementsMap[nid] = []

        for (eid, element) in iteritems(self.elements):  # load the mapper
            try:
                # not supported for 0-D and 1-D elements
                nids = element.nodeIDs()
                for nid in nids:  # (e.g. CQUAD8 with missing node)
                    nidToElementsMap[nid].append(eid)
            except:
                pass
        return nidToElementsMap

    def get_property_id_to_element_ids_map(self):
        """
        Returns a dictionary that maps a property ID to a list of elemnents
        """
        pidToEidsMap = {}
        pids = self.property_ids
        for pid in pids:
            pidToEidsMap[pid] = []

        for eid in self.element_ids:
            element = self.Element(eid)

            if hasattr(element, 'pid'):
                pid = element.Pid()
                if pid == 0:  # CONM2
                    continue
                pidToEidsMap[pid].append(eid)
        return pidToEidsMap

    def get_material_id_to_property_ids_map(self):
        """
        Returns a dictionary that maps a material ID to a list of properties

        :returns mid_to_pids_map: the mapping

        .. code-block:: python

          >>> mid_to_pid_map = get_material_id_to_property_ids_map()
          >>> mid = 1
          >>> pids = get_material_id_to_property_ids_map[mid]
          >>> pids
          [1, 2, 3]

        .. note:: all properties require an mid to be counted (except for
                  PCOMP, which has multiple mids)
        """
        mid_to_pids_map = {}
        for mid in self.get_material_ids():
            mid_to_pids_map[mid] = []

        for pid in self.property_ids:
            prop = self.Property(pid)
            if prop.type in ['PCOMP', 'PCOMPG']:
                mids = prop.Mids()

                for mid in mids:
                    if pid not in mid_to_pids_map[mid]:
                        mid_to_pids_map[mid].append(pid)
                    else:  # PCOMP
                        if hasattr(prop, 'mid') and prop.Mid() in mids:
                            if pid not in mid_to_pids_map[mid]:
                                mid_to_pids_map[mid].append(pid)
            else:
                if hasattr(prop, 'Mids'):
                    mids = prop.Mids()
                else:
                    mids = [prop.Mid()]

                for mid in mids:
                    mid_to_pids_map[mid].append(pid)
        return mid_to_pids_map

    def Element(self, eid, msg=''):
        try:
            return self.elements[eid]
        except KeyError:
            raise KeyError('eid=%s not found%s.  Allowed elements=%s'
                           % (eid, msg, self.elements.keys()))

    def Elements(self, eids, msg=''):
        elements = []
        for eid in eids:
            elements.append(self.Element(eid, msg))
        return elements

    def Mass(self, eid, msg=''):
        try:
            return self.masses[eid]
        except KeyError:
            raise KeyError('eid=%s not found%s.  Allowed masses=%s'
                           % (eid, msg, self.masses.keys()))

    def RigidElement(self, eid, msg=''):
        try:
            return self.rigidElements[eid]
        except KeyError:
            raise KeyError('eid=%s not found%s.  Allowed rigidElements=%s'
                           % (eid, msg, self.rigidElements.keys()))

    #--------------------
    # PROPERTY CARDS

    def Property(self, pid, msg=''):
        try:
            return self.properties[pid]
        except KeyError:
            raise KeyError('pid=%s not found%s.  Allowed Pids=%s'
                           % (pid, msg, self.property_ids))

    def Properties(self, pids, msg=''):
        properties = []
        for pid in pids:
            properties.append(self.Property(pid, msg))
        return properties

    def PropertyMass(self, pid, msg=''):
        try:
            return self.properties_mass[pid]
        except KeyError:
            raise KeyError('pid=%s not found%s.  Allowed Mass Pids=%s'
                           % (pid, msg, self.mass_property.keys()))

    def Phbdy(self, pid, msg=''):
        try:
            return self.phbdys[pid]
        except KeyError:
            raise KeyError('pid=%s not found%s.  Allowed PHBDY Pids=%s'
                           % (pid, msg, self.phbdys.keys()))

    #--------------------
    # MATERIAL CARDS

    def get_structural_material_ids(self):
        return self.materials.keys()

    def get_material_ids(self):
        return self.materials.keys() + self.thermalMaterials.keys()

    def get_thermal_material_ids(self):
        return self.thermalMaterials.keys()

    def Material(self, mid, msg=''):
        if mid in self.materials:
            return self.materials[mid]
        elif mid in self.thermalMaterials:
            return self.thermalMaterials[mid]
        else:
            msg = '\n' + msg
            raise KeyError('Invalid Material ID:  mid=%s%s' % (mid, msg))

    def StructuralMaterial(self, mid, msg=''):
        try:
            mat = self.materials[mid]
        except KeyError:
            msg = '\n' + msg
            raise KeyError('Invalid Structural Material ID:  mid=%s%s' % (mid, msg))
        return mat

    def ThermalMaterial(self, mid, msg=''):
        try:
            mat = self.thermalMaterials[mid]
        except KeyError:
            msg = '\n' + msg
            raise KeyError('Invalid Thermal Material ID:  mid=%s%s' % (mid, msg))
        return mat

    def HyperelasticMaterial(self, mid, msg=''):
        try:
            mat = self.hyperelasticMaterials[mid]
        except KeyError:
            msg = '\n' + msg
            raise KeyError('Invalid Hyperelastic Material ID:  mid=%s%s' % (mid, msg))
        return mat

    def Materials(self, mids, msg=''):
        materials = []
        for mid in mids:
            materials.append(self.Material(mid, msg))
        return materials

    #--------------------
    # LOADS

    def Load(self, sid, msg=''):
        assert isinstance(sid, int), 'sid=%s is not an integer\n' % sid
        if sid in self.loads:
            load = self.loads[sid]
        else:
            raise KeyError('cannot find LoadID=%r%s.\nLoadIDs=%s\n' % (sid, msg, sorted(self.loads.keys())))
        return load

    def DLoad(self, sid, msg=''):
        assert isinstance(sid, int), 'sid=%s is not an integer\n' % sid
        if sid in self.dloads:
            load = self.dloads[sid]
        else:
            raise KeyError('cannot find DLoadID=%r%s.\nDLoadIDs=%s\n' % (sid, msg, sorted(self.dloads.keys())))
        return load

    def get_dload_entries(self, sid, msg=''):
        assert isinstance(sid, int), 'sid=%s is not an integer\n' % sid
        if sid in self.dload_entries:
            load = self.dload_entries[sid]
        else:
            raise KeyError('cannot find DLoad Entry ID=%r%s.\nDLoadEntryIDs=%s\n' % (sid, msg, sorted(self.dload_entries.keys())))
        return load

    #--------------------
    # SPCs

    def SPC(self, conid, msg=''):
        assert isinstance(conid, int), 'conid=%s is not an integer\n' % conid
        if conid in self.spcs:
            constraint = self.spcs[conid]
        else:
            raise KeyError('cannot find ConstraintID=%r.\n%s' % (conid, msg))
        return constraint

    #--------------------
    # COORDINATES CARDS
    def Coord(self, cid, msg=''):
        try:
            return self.coords[cid]
        except KeyError:
            raise KeyError('cid=%s not found%s.  Allowed Cids=%s'
                           % (cid, msg, self.coord_ids))

    #--------------------
    # AERO CARDS

    def AEList(self, aelist, msg=''):
        try:
            return self.aelists[aelist]
        except KeyError:
            raise KeyError('aelist=%s not found%s.  Allowed AELIST=%s'
                           % (aelist, msg, self.aelists.keys()))

    def AEFact(self, aefact, msg=''):
        try:
            return self.aefacts[aefact]
        except KeyError:
            raise KeyError('aefact=%s not found%s.  Allowed AEFACT=%s'
                           % (aefact, msg, self.aefacts.keys()))

    def Aero(self, acsid, msg=''):
        try:
            return self.aero[acsid]
        except KeyError:
            raise KeyError('acsid=%s not found%s.  Allowed AERO=%s'
                           % (acsid, msg, self.aero.keys()))

    def Aeros(self, acsid, msg=''):
        try:
            return self.aeros[acsid]
        except KeyError:
            raise KeyError('acsid=%s not found%s.  Allowed AEROS=%s'
                           % (acsid, msg, self.aeros.keys()))

    def Spline(self, eid, msg=''):
        try:
            return self.splines[eid]
        except KeyError:
            raise KeyError('eid=%s not found%s.  Allowed SPLINEx=%s'
                           % (eid, msg, self.splines.keys()))

    def CAero(self, eid, msg=''):
        try:
            return self.caeros[eid]
        except KeyError:
            raise KeyError('eid=%s not found%s.  Allowed CAEROx=%s'
                           % (eid, msg, self.caero_ids))

    def PAero(self, pid, msg=''):
        try:
            return self.paeros[pid]
        except KeyError:
            raise KeyError('pid=%s not found%s.  Allowed PAEROx=%s'
                           % (pid, msg, self.paeros.keys()))

    def Gust(self, sid, msg=''):
        try:
            return self.gusts[sid]
        except KeyError:
            raise KeyError('sid=%s not found%s.  Allowed GUSTs=%s'
                           % (sid, msg, self.gusts.keys()))

    #--------------------
    # AERO CONTROL SURFACE CARDS
    def AEStat(self, aid, msg=''):
        try:
            return self.aestats[aid]
        except KeyError:
            raise KeyError('aid=%s not found%s.  Allowed AESTATs=%s'
                           % (aid, msg, self.aestats.keys()))

    def AELink(self, linkID, msg=''):
        try:
            return self.aelinks[linkID]
        except KeyError:
            raise KeyError('linkID=%s not found%s.  Allowed AELINKs=%s'
                           % (linkID, msg, self.aelinks.keys()))

    def AEParam(self, aid, msg=''):
        try:
            return self.aeparams[aid]
        except KeyError:
            raise KeyError('aid=%s not found%s.  Allowed AEPARMs=%s'
                           % (aid, msg, self.aeparams.keys()))

    #--------------------
    # FLUTTER CARDS

    def FLFACT(self, sid, msg=''):
        try:
            return self.flfacts[sid]
        except KeyError:
            raise KeyError('sid=%s not found%s.  Allowed FLFACTs=%s'
                           % (sid, msg, self.flfacts.keys()))

    def Flutter(self, fid, msg=''):
        try:
            return self.flutters[fid]
        except KeyError:
            raise KeyError('fid=%s not found%s.  Allowed FLUTTERs=%s'
                           % (fid, msg, self.flutters.keys()))

    #--------------------
    # OPTIMIZATION CARDS

    def DConstr(self, oid, msg=''):
        try:
            return self.dconstrs[oid]
        except KeyError:
            raise KeyError('oid=%s not found%s.  Allowed DCONSTRs=%s'
                           % (oid, msg, self.dconstrs.keys()))

    def Desvar(self, oid, msg=''):
        try:
            return self.desvars[oid]
        except KeyError:
            raise KeyError('oid=%s not found%s.  Allowed DESVARs=%s'
                           % (oid, msg, self.desvars.keys()))

    def DDVal(self, oid, msg=''):
        try:
            return self.ddvals[oid]
        except KeyError:
            raise KeyError('oid=%s not found%s.  Allowed DDVALs=%s'
                           % (oid, msg, self.ddvals.keys()))

    #--------------------
    # SET CARDS

    def Set(self, sid, msg=''):
        try:
            return self.sets[sid]
        except KeyError:
            raise KeyError('sid=%s not found%s.  Allowed SETx=%s'
                           % (sid, msg, self.sets.keys()))

    def SetSuper(self, seid, msg=''):
        try:
            return self.setsSuper[seid]
        except KeyError:
            raise KeyError('seid=%s not found%s.  Allowed SETx=%s'
                           % (seid, msg, self.setsSuper.keys()))

    #--------------------
    # METHOD CARDS
    def Method(self, sid, msg=''):
        try:
            return self.methods[sid]
        except KeyError:
            raise KeyError('sid=%s not found%s.  Allowed METHODs=%s'
                           % (sid, msg, self.methods.keys()))

    def CMethod(self, sid, msg=''):
        try:
            return self.cmethods[sid]
        except KeyError:
            raise KeyError('sid=%s not found%s.  Allowed CMETHODs=%s'
                           % (sid, msg, self.cmethods.keys()))

    #--------------------
    # TABLE CARDS
    def Table(self, tid, msg=''):
        try:
            return self.tables[tid]
        except KeyError:
            raise KeyError('tid=%s not found%s.  Allowed TABLEs=%s'
                           % (tid, msg, self.tables.keys()))

    def RandomTable(self, tid, msg=''):
        try:
            return self.randomTables[tid]
        except KeyError:
            raise KeyError('tid=%s not found%s.  Allowed TABLEs=%s'
                           % (tid, msg, self.randomTables.keys()))

    #--------------------
    # NONLINEAR CARDS

    def NLParm(self, nid, msg=''):
        try:
            return self.nlparms[nid]
        except KeyError:
            raise KeyError('nid=%s not found%s.  Allowed NLPARMs=%s'
                           % (nid, msg, self.nlparms.keys()))

    #--------------------
    # MATRIX ENTRY CARDS
    def DMIG(self, dname, msg=''):
        try:
            return self.dmig[dname]
        except KeyError:
            raise KeyError('dname=%s not found%s.  Allowed DMIGs=%s'
                           % (dname, msg, self.dmig.keys()))

    def get_x_associated_with_y(self, xdict, xkeys, ykeys, stop_on_failure=True):
        """
        Get the range of sub-properties of a card.

        .. note::
           Assumes you're taking a single path through the cards.
           You could probably explicitly code these queries faster, but
           this method has a lot of flexibility with very little user code.

        :param self:
            the BDF object
        :param xdict:
            the BDF attribute that should be querried
            (e.g. self.elements)
        :param xkeys:
            the list of object keys that should be stepped through
            associated with xdict
            (e.g. eids=[1, 2, 3])
        :param ykeys:
            the list of response keys that should be stepped through
            (e.g. ['pid', 'mid', 'mid'])
        :param stop_on_failure:
            Should an error be raised if there is an invalid key?
            For example, get all material used by elements, but don't crash
            on CONRODs.
        :returns results:
            The set of all values used

        .. code-block:: python

          # Get nodes associated with eid=[1, 2, 3]
          nodes = self.get_x_associated_with_y(
              self.elements, [1, 2, 3], ['nodes'])

          # Get node IDs associated with eid=[1, 2, 3]
          nodesIDs = self.get_x_associated_with_y(
              self.elements, [1, 2, 3], ['nodes', 'nid'])

          # Get coord IDs associated with eid=[1, 2, 3]
          coordIDs = self.get_x_associated_with_y(
              self.elements, [1, 2, 3], ['nodes', 'cp', 'cid'])

          # Get properties associated with eid=[1, 2, 3]
          properties = self.get_x_associated_with_y(
              self.elements, [1, 2, 3], ['pid'])

          # Get materials associated with eid=[1, 2, 3]
          materials = self.get_x_associated_with_y(
              self.elements, [1, 2, 3], ['pid', 'mid'])

          # Get material IDs associated with eid=[1, 2, 3]
          materialIDs = self.get_x_associated_with_y(
              self.elements, [1, 2, 3], ['pid', 'mid', 'mid'])

          # Get the values for Young's Modulus
          E = self.get_x_associated_with_y(
              self.elements, None, ['pid', 'mid', 'e'])
        """
        assert isinstance(xdict, dict), type(xdict)
        assert self._xref == True, self._xref

        if isinstance(xkeys, list) or isinstance(xkeys, tuple):
            pass
        elif isinstance(xkeys, ndarray):
            assert len(xkeys.shape) == 1, xkeys.shape
            assert isinstance(xkeys[0], int), type(xkeys[0])
        elif xkeys is None:
            xkeys = xdict.iterkeys()
        else:
            raise RuntimeError('invalid type; type(xkeys)=%r' % type(xkeys))

        assert isinstance(ykeys[0], string_types), ykeys

        out_set = set([])
        for xkey in xkeys:
            xi = xdict[xkey]
            _getattr(out_set, xi, ykeys, len(ykeys)-1, stop_on_failure)
        return out_set

    def __test_method(self):
        # getElementsAssociatedWithMaterialIDs(self):
        #getElementIDsAssociatedWithPropertyIDs
        #getPropertyIDsAssociatedWithMaterialIDs
        #get_x_associated_with_y(self, mids, 'pid', yname='mid')

        #CTETRA   1       1       8       13      67      33
        #CTETRA   2       1       8       7       62      59
        #CTETRA   3       1       8       45      58      66
        #nids 7 8 13 33 45 58 59 62 66 67

        cps = self.get_x_associated_with_y(
            self.elements, [1, 2, 3], ['nodes', 'cp', 'cast'], stop_on_failure=False)
        #print('*cps', cps)

        nids2 = self.get_x_associated_with_y(self.elements, [1, 2, 3], ['nodes', 'nid'])
        nids2 = list(nids2)
        nids2.sort()
        #print('*nids2', nids2)

        mids = self.get_x_associated_with_y(
            self.elements, [1, 2, 3, 4, 5], ['pid', 'mid'])
        #print('*mids', mids)

        mids2 = self.get_x_associated_with_y(
            self.elements, None, ['pid', 'mid', 'mid'])

        E = self.get_x_associated_with_y(
            self.elements, None, ['pid', 'mid', 'e'])
        #print('*E', E)

        #get_nodes_associated_with_elements
        #nids = get_x_associated_with_y(self, self.elements, 'nodes', 'nid')
        #pids = get_x_associated_with_y(self, self.elements, 'pid', 'pid')
        #mids = get_x_associated_with_y(self, self.properties, 'mid', 'mid')


def _getattr(out_set, xi, xkeys, nlevels_left=0, stop_on_failure=True):
    """
    Recursive method to help get_x_associated_with_y get the value of xkeys
    for the given variable xi

    :param out_set:
        the SET of all outputs that will be filled and implicitly returned
    :type out_set:
        SET

    :param xi:
        the current variable being iterated over
    :type xi:
        BDF BaseCard

    :param xkeys:
        the variables left to iterate over
    :type xkeys:
        LIST of STRINGS

    :param nlevels_left:
        the number of levels still to iterate over
    :type nlevels_left:
        INT >= 0

    :param stop_on_failure:
        should the code crash if a certain xkey cannot be found
    :type stop_on_failure:
        bool
    """
    try:
        y = getattr(xi, xkeys[0])
    except AttributeError:
        if stop_on_failure:
            raise
        return

    if nlevels_left:
        if isinstance(y, list):
            # handles nodes
            for yi in y:
                _getattr(out_set, yi, xkeys[1:], nlevels_left-1, stop_on_failure=stop_on_failure)
        else:
            _getattr(out_set, y, xkeys[1:], nlevels_left-1, stop_on_failure=stop_on_failure)
    else:
        # handles nodes
        if isinstance(y, list):
            for yi in y:
                out_set.add(yi)
        else:
            out_set.add(y)