code: # pylint: disable=C0103,R0902,R0904,R0914
  """
  All dynamic control cards are defined in this file.  This includes:
  
   * FREQ
   * FREQ1
   * FREQ2 (not implemented)
   * FREQ3
   * FREQ4
   * FREQ5 (not implemented)
   * NLPCI
   * NLPARM
   * TSTEP
   * TSTEPNL
  
  All cards are BaseCard objects.
  
  """
  from math import log, exp, ceil
  import numpy as np
  from numpy import unique, hstack
  
  from pyNastran.utils.numpy_utils import integer_types
  from pyNastran.bdf.field_writer_8 import set_blank_if_default
  from pyNastran.bdf.cards.base_card import BaseCard
  from pyNastran.bdf.bdf_interface.assign_type import (
      integer, integer_or_blank, double, double_or_blank,
      string_or_blank, blank, fields, components_or_blank
  )
  from pyNastran.bdf.field_writer_8 import print_card_8
  from pyNastran.bdf.field_writer_16 import print_card_16
  if TYPE_CHECKING:  # pragma: no cover
      from pyNastran.bdf.bdf import BDF
  
  
  class DELAY(BaseCard):
      type = 'DELAY'
  
      def __init__(self, sid, nodes, components, delays, comment=''):
          """
          +-------+-----+-----------+-----+--------+------+-----+--------+-----+
          |   1   |  2  |     3     |  4  |   5    |  6   |  7  |   8    |  9  |
          +=======+=====+===========+=====+========+======+=====+========+=====+
          | DELAY | SID | POINT ID1 | C1  |   T1   | P2   | C2  |   T2   |     |
          +-------+-----+-----------+-----+--------+------+-----+--------+-----+
          """
          if comment:
              self.comment = comment
  
          #: Identification number of DELAY entry. (Integer > 0)
          self.sid = sid
          #: Grid, extra, or scalar point identification number. (Integer > 0)
          self.nodes = nodes
          #: Component number. (Integers 1 through 6 for grid points; zero or blank for extra
          #: or scalar points)
          self.components = components
          #: Time delay (tau) for designated point Pi and component Ci. (Real)
          self.delays = delays
  
      @classmethod
      def add_card(cls, card, comment=''):
          """
          Adds a DELAY card from ``BDF.add_card(...)``
  
          Parameters
          ----------
          card : BDFCard()
              a BDFCard object
          comment : str; default=''
              a comment for the card
          """
          sid = integer(card, 1, 'sid')
          nodes = [integer(card, 2, 'node')]
          components = [integer(card, 3, 'components')]
          delays = [double_or_blank(card, 4, 'delay')]
          assert components[0] in [0, 1, 2, 3, 4, 5, 6], components
          if card.field(5):
              nodes.append(integer(card, 5, 'node'))
              components.append(integer(card, 6, 'components'))
              delays.append(double_or_blank(card, 7, 'delay'))
              assert components[1] in [0, 1, 2, 3, 4, 5, 6], components
          return DELAY(sid, nodes, components, delays, comment=comment)
  
      def add(self, delay):
          assert self.sid == delay.sid, 'sid=%s delay.sid=%s' % (self.sid, delay.sid)
          if delay.comment:
              if hasattr('_comment'):
                  self._comment += delay.comment
              else:
                  self._comment = delay.comment
          self.nodes += delay.nodes
          self.components += delay.components
          self.delays += delay.delays
  
      def get_delay_at_freq(self, freq):
          return self.nodes, self.components, self.delays
  
      #def cross_reference(self, model: BDF) -> None:
          #"""
          #Cross links the card so referenced cards can be extracted directly
  
          #Parameters
          #----------
          #model : BDF()
              #the BDF object
          #"""
          #msg = ', which is required by DELAY sid=%s' % self.sid
          #self.nodes_ref = model.Node(self.node_ids, msg=msg)
  
      #@property
      #def node_id1(self):
          #if isinstance(self.nodes[0], integer_types):
              #return self.nodes[0]
          #return self.nodes_ref[0].nid
  
      #@property
      #def node_id2(self):
          #if isinstance(self.nodes[1], integer_types):
              #return self.nodes[1]
          #return self.nodes_ref[1].nid
  
      @property
      def node_ids(self):
          node_ids = [self.node_id1]
          if len(self.components) == 2:
              node_ids.append(self.node_id2)
          return node_ids
  
      def raw_fields(self):
          list_fields = ['DELAY', self.sid]
          for nid, comp, delay in zip(self.node_ids, self.components, self.delays):
              if isinstance(nid, integer_types):
                  nidi = nid
              else:
                  nidi = nid.nid
              list_fields += [nidi, comp, delay]
          return list_fields
  
      def write_card(self, size: int=8, is_double: bool=False) -> str:
          msg = self.comment
          node_ids = self.node_ids
          if size == 8:
              for nid, comp, delay in zip(node_ids, self.components, self.delays):
                  msg += print_card_8(['DELAY', self.sid, nid, comp, delay])
          else:
              for nid, comp, delay in zip(node_ids, self.components, self.delays):
                  msg += print_card_16(['DELAY', self.sid, nid, comp, delay])
          return msg
  
  
  class DPHASE(BaseCard):
      type = 'DPHASE'
  
      def __init__(self, sid, nodes, components, phase_leads, comment=''):
          """
          +--------+-----+-----------+-----+------+------+-----+-----+-----+
          |   1    |  2  |     3     |  4  |  5   |  6   |  7  |  8  |  9  |
          +========+=====+===========+=====+======+======+=====+=====+=====+
          | DPHASE | SID | POINT ID1 | C1  | TH1  |  P2  | C2  | TH2 |     |
          +--------+-----+-----------+-----+------+------+-----+-----+-----+
          """
          if comment:
              self.comment = comment
          self.sid = sid
          self.nodes = nodes
          self.components = components
          self.phase_leads = phase_leads
  
      @classmethod
      def add_card(cls, card, comment=''):
          """
          Adds a DPHASE card from ``BDF.add_card(...)``
  
          Parameters
          ----------
          card : BDFCard()
              a BDFCard object
          comment : str; default=''
              a comment for the card
          """
          sid = integer(card, 1, 'sid')
          nodes = [integer(card, 2, 'node')]
          components = [integer(card, 3, 'components')]
          phase_leads = [double_or_blank(card, 4, 'phase_lead')]
          assert components[0] in [0, 1, 2, 3, 4, 5, 6], components
          if card.field(5):
              nodes.append(integer(card, 5, 'node'))
              components.append(integer(card, 6, 'components'))
              phase_leads.append(double_or_blank(card, 7, 'phase_lead'))
              assert components[1] in [0, 1, 2, 3, 4, 5, 6], components
          return DPHASE(sid, nodes, components, phase_leads, comment=comment)
  
      def add(self, dphase):
          assert self.sid == dphase.sid, 'sid=%s dphase.sid=%s' % (self.sid, dphase.sid)
          if dphase.comment:
              if hasattr('_comment'):
                  self._comment += dphase.comment
              else:
                  self._comment = dphase.comment
          self.nodes += dphase.nodes
          self.components += dphase.components
          self.phase_leads += dphase.phase_leads
  
      #def cross_reference(self, model: BDF) -> None:
          #"""
          #Cross links the card so referenced cards can be extracted directly
  
          #Parameters
          #----------
          #model : BDF()
              #the BDF object
          #"""
          #msg = ', which is required by DPHASE sid=%s' % self.sid
          #self.nodes_ref = model.Nodes(self.node_ids, msg=msg)
  
      #@property
      #def node_id1(self):
          #if isinstance(self.nodes[0], integer_types):
              #return self.nodes[0]
          #return self.nodes_ref[0].nid
  
      #@property
      #def node_id2(self):
          #if isinstance(self.nodes[1], integer_types):
              #return self.nodes[1]
          #return self.nodes_ref[1].nid
  
      @property
      def node_ids(self):
          node_ids = [self.node_id1]
          if len(self.components) == 2:
              node_ids.append(self.node_id2)
          return node_ids
  
      def raw_fields(self):
          list_fields = ['DPHASE', self.sid]
          for nid, comp, delay in zip(self.nodes, self.components, self.phase_leads):
              if isinstance(nid, integer_types):
                  nidi = nid
              else:
                  nidi = nid.nid
              list_fields += [nidi, comp, delay]
          return list_fields
  
      def write_card(self, size: int=8, is_double: bool=False) -> str:
          msg = self.comment
          node_ids = self.node_ids
          if size == 8:
              for nid, comp, delay in zip(node_ids, self.components, self.phase_leads):
                  msg += print_card_8(['DPHASE', self.sid, nid, comp, delay])
          else:
              for nid, comp, delay in zip(node_ids, self.components, self.phase_leads):
                  msg += print_card_16(['DPHASE', self.sid, nid, comp, delay])
          return msg
  
  
  class FREQ(BaseCard):
      """
      Defines a set of frequencies to be used in the solution of frequency
      response problems.
  
      +------+-----+-----+-----+------+-----+-----+-----+-----+
      |  1   |  2  |  3  |  4  |  5   |  6  |  7  |  8  |  9  |
      +======+=====+=====+=====+======+=====+=====+=====+=====+
      | FREQ | SID | F1  | F2  | etc. |     |     |     |     |
      +------+-----+-----+-----+------+-----+-----+-----+-----+
      """
      type = 'FREQ'
  
      def __init__(self, sid, freqs, comment=''):
          if comment:
              self.comment = comment
          self.sid = sid
          self.freqs = np.unique(freqs)
  
      @classmethod
      def add_card(cls, card, comment=''):
          """
          Adds a FREQ card from ``BDF.add_card(...)``
  
          Parameters
          ----------
          card : BDFCard()
              a BDFCard object
          comment : str; default=''
              a comment for the card
          """
          sid = integer(card, 1, 'sid')
          freqs = fields(double, card, 'freq', i=2, j=len(card))
          return FREQ(sid, freqs, comment=comment)
  
      def get_freqs(self):
          return self.freqs
  
      def add_frequencies(self, freqs):
          """
          Combines the frequencies from 1 FREQx object with another.
          All FREQi entries with the same frequency set identification numbers
          will be used. Duplicate frequencies will be ignored.
  
          Parameters
          ----------
          freqs : ???
              the frequencies for a FREQx object
          """
          #print("self.freqs = ",self.freqs)
          #print("freqs = ",freqs)
          self.freqs = unique(hstack([self.freqs, freqs]))
  
      def add_frequency_object(self, freq):
          """
          :param freq: a FREQx object
  
          .. seealso:: :func:`addFrequencies`
          """
          self.add_frequencies(freq.freqs)
  
      def raw_fields(self):
          list_fields = ['FREQ', self.sid] + list(self.freqs)
          return list_fields
  
      def write_card(self, size: int=8, is_double: bool=False) -> str:
          card = self.repr_fields()
          if size == 8:
              return self.comment + print_card_8(card)
          return self.comment + print_card_16(card)
  
  
  class FREQ1(FREQ):
      """
      Defines a set of frequencies to be used in the solution of frequency
      response problems by specification of a starting frequency, frequency
      increment, and the number of increments desired.
  
      +-------+-----+-----+-----+-----+-----+-----+-----+-----+
      |   1   |  2  | 3   |  4  |  5  |  6  |  7  |  8  |  9  |
      +=======+=====+=====+=====+=====+=====+=====+=====+=====+
      | FREQ1 | SID |  F1 | DF  | NDF |     |     |     |     |
      +-------+-----+-----+-----+-----+-----+-----+-----+-----+
  
      .. note:: this card rewrites as a FREQ card
      """
      type = 'FREQ1'
  
      def __init__(self, sid, f1, df, ndf, comment=''):
          if comment:
              self.comment = comment
          self.sid = sid
          self.f1 = f1
          self.df = df
          self.ndf = ndf
  
          freqs = []
          for i in range(ndf):
              freqs.append(f1 + i * df)
          self.freqs = unique(freqs)
  
      @classmethod
      def add_card(cls, card, comment=''):
          """
          Adds a FREQ1 card from ``BDF.add_card(...)``
  
          Parameters
          ----------
          card : BDFCard()
              a BDFCard object
          comment : str; default=''
              a comment for the card
          """
          sid = integer(card, 1, 'sid')
          f1 = double_or_blank(card, 2, 'f1', 0.0)
          df = double(card, 3, 'df')
          ndf = integer_or_blank(card, 4, 'ndf', 1)
          assert len(card) <= 5, 'len(FREQ card) = %i\ncard=%s' % (len(card), card)
          return FREQ1(sid, f1, df, ndf, comment=comment)
  
      def write_card(self, size: int=8, is_double: bool=False) -> str:
          card = self.repr_fields()
          if size == 8:
              return self.comment + print_card_8(card)
          return self.comment + print_card_16(card)
  
  
  class FREQ2(FREQ):
      """
      Defines a set of frequencies to be used in the solution of frequency
      response problems by specification of a starting frequency, final
      frequency, and the number of logarithmic increments desired.
  
      +-------+-----+-----+-----+-----+-----+-----+-----+-----+
      |   1   |  2  | 3   |  4  |  5  |  6  |  7  |  8  |  9  |
      +=======+=====+=====+=====+=====+=====+=====+=====+=====+
      | FREQ2 | SID |  F1 | F2  | NDF |     |     |     |     |
      +-------+-----+-----+-----+-----+-----+-----+-----+-----+
  
      .. note:: this card rewrites as a FREQ card
      """
      type = 'FREQ2'
  
      def __init__(self, sid, f1, f2, ndf=1, comment=''):
          if comment:
              self.comment = comment
          self.sid = sid
          self.f1 = f1
          self.f2 = f2
          self.ndf = ndf
  
          d = 1. / ndf * log(f2 / f1)
          freqs = []
          for i in range(ndf):
              freqs.append(f1 * exp(i * d))  # 0 based index
          self.freqs = np.unique(freqs)
  
      @classmethod
      def add_card(cls, card, comment=''):
          """
          Adds a FREQ2 card from ``BDF.add_card(...)``
  
          Parameters
          ----------
          card : BDFCard()
              a BDFCard object
          comment : str; default=''
              a comment for the card
          """
          sid = integer(card, 1, 'sid')
          f1 = double(card, 2, 'f1')  # default=0.0 ?
          f2 = double(card, 3, 'f2')
          ndf = integer_or_blank(card, 4, 'nf', 1)
          assert len(card) <= 5, 'len(FREQ2 card) = %i\ncard=%s' % (len(card), card)
          return FREQ2(sid, f1, f2, ndf, comment=comment)
          #return FREQ(sid, freqs, comment=comment)
  
  
  class FREQ3(FREQ):
      """
      +-------+-----+------+-------+--------+-----+---------+
      |   1   |  2  |   3  |   4   |    5   |  6  |    7    |
      +=======+=====+======+=======+========+=====+=========+
      | FREQ3 | SID |  F1  |  F2   |  TYPE  | NEF | CLUSTER |
      +-------+-----+------+-------+--------+-----+---------+
      | FREQ3 |  6  | 20.0 | 200.0 | LINEAR | 10  |   2.0   |
      +-------+-----+------+-------+--------+-----+---------+
      """
      type = 'FREQ3'
  
      def __init__(self, f1, f2=None, Type='LINEAR', nef=10, cluster=1.0, comment=''):
          if comment:
              self.comment = comment
          if f2 is None:
              f2 = f1
          self.sid = sid
          self.f1 = f1
          self.f2 = f2
          self.Type = Type
          self.nef = nef
          self.cluster = cluster
  
      @classmethod
      def add_card(cls, card, comment=''):
          sid = integer(card, 1, 'sid')
          f1 = double(card, 1, 'f1')
          f2 = integer_or_blank(card, 1, 'f2', f1)
          Type = string_or_blank(card, 1, 'Type', 'LINEAR')
          nef = integer_or_blank(card, 1, 'nef', 10)
          cluster = double_or_blank(card, 1, 'cluster', 1.0)
  
          return FREQ3(sid, f1, f2, Type, nef, cluster, comment='')
  
      def raw_fields(self):
          return ['FREQ3', self.sid, self.f1, self.f2, self.Type, self.nef, self.cluster]
  
      def write_card(self, size: int=8, is_double: bool=False) -> str:
          card = self.repr_fields()
          if size == 8:
              return self.comment + print_card_8(card)
          return self.comment + print_card_16(card)
  
  
  class FREQ4(FREQ):
      """
      Defines a set of frequencies used in the solution of modal frequency
      response problems by specifying the amount of 'spread' around each natural
      frequency and the number of equally spaced excitation frequencies within
      the spread.
  
      +-------+-----+-----+-----+------+-----+-----+-----+-----+
      |   1   |  2  | 3   |  4  |  5   |  6  |  7  |  8  |  9  |
      +=======+=====+=====+=====+======+=====+=====+=====+=====+
      | FREQ4 | SID |  F1 | F2  | FSPD | NFM |     |     |     |
      +-------+-----+-----+-----+------+-----+-----+-----+-----+
  
      .. note:: this card rewrites as a FREQ card
      .. todo:: not done...
      """
      type = 'FREQ4'
  
      def __init__(self, sid, f1, f2, fspread, nfm, comment=''):
          if comment:
              self.comment = comment
          self.sid = sid
          self.f1 = f1
          self.f2 = f2
          self.fspread = fspread
          self.nfm = nfm
  
      @classmethod
      def add_card(cls, card, comment=''):
          """
          Adds a FREQ4 card from ``BDF.add_card(...)``
  
          Parameters
          ----------
          card : BDFCard()
              a BDFCard object
          comment : str; default=''
              a comment for the card
          """
          sid = integer(card, 1, 'sid')
          f1 = double_or_blank(card, 2, 'f1', 0.0)
          f2 = double_or_blank(card, 3, 'f2', 1.e20)
          fspread = double_or_blank(card, 4, 'fspd', 0.1)
          nfm = integer_or_blank(card, 5, 'nfm', 3)
          assert len(card) <= 6, 'len(FREQ card) = %i\ncard=%s' % (len(card), card)
          return FREQ4(sid, f1, f2, fspread, nfm, comment=comment)
  
      def raw_fields(self):
          list_fields = ['FREQ4', self.sid, self.f1, self.f2, self.fspread,
                         self.nfm]
          return list_fields
  
      def repr_fields(self):
          return self.raw_fields()
  
      def write_card(self, size: int=8, is_double: bool=False) -> str:
          card = self.repr_fields()
          if size == 8:
              return self.comment + print_card_8(card)
          return self.comment + print_card_16(card)
  
  
  #class FREQ5(FREQ):
      #type = 'FREQ5'
  
      #def __init__(self, card=None, data=None, comment=''):
          #if comment:
              # self.comment = comment
          #raise NotImplementedError()
  
      #def write_card(self, size: int=8, is_double: bool=False) -> str:
          #card = self.repr_fields()
          #if size == 8:
              #return self.comment + print_card_8(card)
          #return self.comment + print_card_16(card)
  
  
  class NLPARM(BaseCard):
      """
      Defines a set of parameters for nonlinear static analysis iteration
      strategy.
  
      +--------+--------+------+------+---------+-------+---------+---------+--------+
      |    1   |  2     |  3   |  4   |    5    |   6   |   7     |  8      |   9    |
      +========+========+======+======+=========+=======+=========+=========+========+
      | NLPARM |   ID   | NINC |  DT  | KMETHOD | KSTEP | MAXITER |  CONV   | INTOUT |
      +--------+--------+------+------+---------+-------+---------+---------+--------+
      |        |  ESPU  | EPSP | EPSW | MAXDIV  | MAXQN | MAXLS   | FSTRESS | LSTOL  |
      +--------+--------+------+------+---------+-------+---------+---------+--------+
      |        | MAXBIS |      |      |         | MAXR  |         | RTOLB   | CONV   |
      +--------+--------+------+------+---------+-------+---------+---------+--------+
      """
      type = 'NLPARM'
  
      def __init__(self, nlparm_id, ninc=10, dt=0.0, kmethod='AUTO', kstep=5,
                   max_iter=25, conv='PW', int_out='NO',
                   eps_u=0.01, eps_p=0.01, eps_w=0.01, max_div=3, max_qn=None, max_ls=4,
                   fstress=0.2, ls_tol=0.5, max_bisect=5, max_r=20., rtol_b=20., comment=''):
          if comment:
              self.comment = comment
          self.nlparm_id = nlparm_id
          self.ninc = ninc
          self.dt = dt
          self.kmethod = kmethod
          self.kstep = kstep
          self.max_iter = max_iter
          self.conv = conv
          self.int_out = int_out
  
          # line 2
          self.eps_p = eps_p
          self.eps_u = eps_u
          self.eps_w = eps_w
          self.max_div = max_div
          self.max_qn = max_qn
          self.max_ls = max_ls
          self.fstress = fstress
          self.ls_tol = ls_tol
  
          # line 3
          self.max_bisect = max_bisect
          self.max_r = max_r
          self.rtol_b = rtol_b
  
          if self.max_qn is None:
              if kmethod == 'PFNT':
                  self.max_qn = 0
              else:
                  self.max_qn = max_iter
  
      @classmethod
      def add_card(cls, card, comment=''):
          """
          Adds a NLPARM card from ``BDF.add_card(...)``
  
          Parameters
          ----------
          card : BDFCard()
              a BDFCard object
          comment : str; default=''
              a comment for the card
          """
          nlparm_id = integer(card, 1, 'nlparm_id')
          ninc = integer_or_blank(card, 2, 'ninc', 10)
          dt = double_or_blank(card, 3, 'dt', 0.0)
          kmethod = string_or_blank(card, 4, 'kmethod', 'AUTO')
          kstep = integer_or_blank(card, 5, 'kstep', 5)
          max_iter = integer_or_blank(card, 6, 'max_iter', 25)
          conv = string_or_blank(card, 7, 'conv', 'PW')
          int_out = string_or_blank(card, 8, 'intOut', 'NO')
  
          # line 2
          eps_u = double_or_blank(card, 9, 'eps_u', 0.01)
          eps_p = double_or_blank(card, 10, 'eps_p', 0.01)
          eps_w = double_or_blank(card, 11, 'eps_w', 0.01)
          max_div = integer_or_blank(card, 12, 'max_div', 3)
  
          if kmethod == 'PFNT':
              max_qn = integer_or_blank(card, 13, 'max_qn', 0)
          else:
              max_qn = integer_or_blank(card, 13, 'max_qn', max_iter)
  
          max_ls = integer_or_blank(card, 14, 'max_ls', 4)
          fstress = double_or_blank(card, 15, 'fstress', 0.2)
          ls_tol = double_or_blank(card, 16, 'ls_tol', 0.5)
  
          # line 3
          max_bisect = integer_or_blank(card, 17, 'max_bisect', 5)
          max_r = double_or_blank(card, 21, 'max_r', 20.)
          rtol_b = double_or_blank(card, 23, 'rtol_b', 20.)
          assert len(card) <= 24, 'len(NLPARM card) = %i\ncard=%s' % (len(card), card)
          return NLPARM(nlparm_id, ninc, dt, kmethod, kstep, max_iter, conv,
                        int_out, eps_u, eps_p, eps_w, max_div,
                        max_qn, max_ls, fstress,
                        ls_tol, max_bisect, max_r,
                        rtol_b, comment=comment)
  
      @classmethod
      def add_op2_data(cls, data, comment=''):
          """
          Adds a NLPARM card from the OP2
  
          Parameters
          ----------
          data : List[varies]
              a list of fields defined in OP2 format
          comment : str; default=''
              a comment for the card
          """
          (nlparm_id, ninc, dt, kmethod, kstep, max_iter, conv, int_out, eps_u, eps_p,
           eps_w, max_div, max_qn, max_ls, fstress, ls_tol, max_bisect, max_r,
           rtol_b) = data
  
          if kmethod == 1:
              kmethod = 'AUTO'
          elif kmethod == 2:
              kmethod = 'ITER'
          elif kmethod == 4:
              kmethod = 'SEMI'
          elif kmethod == 3:
              kmethod = 'ADAPT'
          else:
              msg = 'nlparm_id=%s kmethod=%r data=%s' % (nlparm_id, kmethod, data)
              raise NotImplementedError(msg)
  
          if conv == 1:
              conv = 'W'
          elif conv == 2:
              conv = 'P'
          elif conv == 3:
              conv = 'PW'
          elif conv == 4:
              conv = 'U'
          elif conv == 5:
              conv = 'UW'
          elif conv == 6:
              conv = 'UP'
          elif conv == 7:
              conv = 'UPW'
          else:
              msg = 'nlparm_id=%s conv=%r data=%s' % (nlparm_id, conv, data)
              raise NotImplementedError(msg)
  
          if int_out == 0:
              int_out = 'NO'
          elif int_out == 1:
              int_out = 'YES'
          elif int_out == 2:
              int_out = 'ALL'
          else:
              msg = 'nlparm_id=%s int_out=%r data=%s' % (nlparm_id, int_out, data)
              raise NotImplementedError(msg)
          return NLPARM(nlparm_id, ninc, dt, kmethod, kstep, max_iter, conv,
                        int_out, eps_u, eps_p, eps_w, max_div,
                        max_qn, max_ls, fstress,
                        ls_tol, max_bisect, max_r,
                        rtol_b, comment=comment)
  
      def raw_fields(self):
          list_fields = ['NLPARM', self.nlparm_id, self.ninc, self.dt, self.kmethod,
                         self.kstep, self.max_iter, self.conv, self.int_out, self.eps_u,
                         self.eps_p, self.eps_w, self.max_div, self.max_qn, self.max_ls,
                         self.fstress, self.ls_tol, self.max_bisect, None, None, None,
                         self.max_r, None, self.rtol_b]
          return list_fields
  
      def repr_fields(self):
          ninc = set_blank_if_default(self.ninc, 10)
          dt = set_blank_if_default(self.dt, 0.0)
          kmethod = set_blank_if_default(self.kmethod, 'AUTO')
          kstep = set_blank_if_default(self.kstep, 5)
          max_iter = set_blank_if_default(self.max_iter, 25)
          conv = set_blank_if_default(self.conv, 'PW')
          int_out = set_blank_if_default(self.int_out, 'NO')
          eps_u = set_blank_if_default(self.eps_u, 0.01)
          eps_p = set_blank_if_default(self.eps_p, 0.01)
          eps_w = set_blank_if_default(self.eps_w, 0.01)
          max_div = set_blank_if_default(self.max_div, 3)
          max_qn = set_blank_if_default(self.max_qn, self.max_iter)
          max_ls = set_blank_if_default(self.max_ls, 4)
          fstress = set_blank_if_default(self.fstress, 0.2)
          ls_tol = set_blank_if_default(self.ls_tol, 0.5)
          max_bisect = set_blank_if_default(self.max_bisect, 5)
          max_r = set_blank_if_default(self.max_r, 20.)
          rtol_b = set_blank_if_default(self.rtol_b, 20.)
  
          list_fields = ['NLPARM', self.nlparm_id, ninc, dt, kmethod, kstep, max_iter,
                         conv, int_out, eps_u, eps_p, eps_w, max_div, max_qn, max_ls,
                         fstress, ls_tol, max_bisect, None, None, None, max_r, None,
                         rtol_b]
          return list_fields
  
      def write_card(self, size: int=8, is_double: bool=False) -> str:
          card = self.repr_fields()
          if size == 8:
              return self.comment + print_card_8(card) # having trouble with double precision...
          return self.comment + print_card_16(card)
  
  
  class NLPCI(BaseCard):
      type = 'NLPCI'
  
      def __init__(self, nlpci_id, Type='CRIS', minalr=0.25, maxalr=4.,
                   scale=0., desiter=12, mxinc=20, comment=''):
          if comment:
              self.comment = comment
          self.nlpci_id = nlpci_id
          self.Type = Type
          self.minalr = minalr
          self.maxalr = maxalr
          self.scale = scale
          self.desiter = desiter
          self.mxinc = mxinc
  
      @classmethod
      def add_card(cls, card, comment=''):
          """
          Adds a NLPCI card from ``BDF.add_card(...)``
  
          Parameters
          ----------
          card : BDFCard()
              a BDFCard object
          comment : str; default=''
              a comment for the card
          """
          nlpci_id = integer(card, 1, 'nlpci_id')
          Type = string_or_blank(card, 2, 'Type', 'CRIS')
          minalr = double_or_blank(card, 3, 'minalr', 0.25)
          maxalr = double_or_blank(card, 4, 'maxalr', 4.0)
          scale = double_or_blank(card, 5, 'scale', 0.0)
          blank(card, 6, 'blank')
          desiter = integer_or_blank(card, 7, 'desiter', 12)
          mxinc = integer_or_blank(card, 8, 'mxinc', 20)
          return NLPCI(nlpci_id, Type=Type, minalr=minalr, maxalr=maxalr,
                       scale=scale, desiter=desiter, mxinc=mxinc, comment=comment)
  
      def raw_fields(self):
          list_fields = ['NLPCI', self.nlpci_id, self.Type, self.minalr,
                         self.maxalr, self.scale, None, self.desiter, self.mxinc]
          return list_fields
  
      def repr_fields(self):
          #minalr = set_blank_if_default(self.minalr, 0.25)
          return self.raw_fields()
  
      def write_card(self, size: int=8, is_double: bool=False) -> str:
          card = self.repr_fields()
          if size == 8:
              return self.comment + print_card_8(card)
          return self.comment + print_card_16(card)
  
  
  class TF(BaseCard):
      """
      Defines a dynamic transfer function of the form:
          (B0 + B1 p + B2 *p2)*ud  sum(A0_i + A1_i*p + A2_i*p2)*ui = 0
  
      +----+-----+-----+------+------+------+--------+----+----+
      |  1 |  2  |  3  |   4  |   5  |   6  |    7   |  8 |  9 |
      +====+=====+=====+======+======+======+========+====+====+
      | TF | SID | GD  |  CD  |  B0  |  B1  |   B2   |    |    |
      +----+-----+-----+------+------+------+--------+----+----+
      |    | G_1 | C_1 | A0_1 | A1_1 | A2_1 |  etc.  |    |    |
      +----+-----+-----+------+------+------+--------+----+----+
  
      """
      type = 'TF'
      def __init__(self, sid, nid0, c, b0, b1, b2, nids, components, a, comment=''):
          if comment:
              self.comment = comment
          self.sid = sid
          self.nid0 = nid0
          self.c = c
          self.b0 = b0
          self.b1 = b1
          self.b2 = b2
          self.nids = nids
          self.components = components
          self.a = a
  
      def validate(self):
          pass
          #assert len(self.grids1) > 0, 'ngrids1=%s\n%s' % (len(self.grids1), str(self))
  
      #def cross_reference(self, model: BDF) -> None:
          #pass
  
      @classmethod
      def add_card(cls, card, comment=''):
          """
          Adds a TF card from ``BDF.add_card(...)``
  
          Parameters
          ----------
          card : BDFCard()
              a BDFCard object
          comment : str; default=''
              a comment for the card
          """
          sid = integer(card, 1, 'sid')
          nid0 = integer(card, 2, 'nid0')
          # component 0 means an SPOINT/EPOINT
          c = components_or_blank(card, 3, 'components_0', 0)
          b0 = double_or_blank(card, 4, 'b0', 0.)
          b1 = double_or_blank(card, 5, 'b1', 0.)
          b2 = double_or_blank(card, 6, 'b2', 0.)
  
          nfields = len(card) - 9
          nrows = nfields // 8
          if nfields % 8 > 0:
              nrows += 1
  
          nids = []
          components = []
          a = []
          for irow in range(nrows):
              j = irow * 8 + 9
              #ifield = irow + 1
              nid = integer(card, j, 'grid_%i' % (irow + 1))
              component = components_or_blank(card, j + 1, 'components_%i' % (irow + 1), 0)
              a0 = double_or_blank(card, j + 2, 'a0_%i' % (irow + 1), 0.)
              a1 = double_or_blank(card, j + 3, 'a1_%i' % (irow + 1), 0.)
              a2 = double_or_blank(card, j + 4, 'a2_%i' % (irow + 1), 0.)
              nids.append(nid)
              components.append(component)
              a.append([a0, a1, a2])
          return TF(sid, nid0, c, b0, b1, b2, nids, components, a,
                    comment=comment)
  
      def raw_fields(self):
          list_fields = ['TF', self.sid, self.nid0, self.c, self.b0, self.b1, self.b2, None, None]
          for grid, c, (a0, a1, a2) in zip(self.nids, self.components, self.a):
              list_fields += [grid, c, a0, a1, a2, None, None, None]
          return list_fields
  
      def write_card(self, size: int=8, is_double: bool=False) -> str:
          # double precision?
          card = self.repr_fields()
          if size == 8:
              return self.comment + print_card_8(card)
          return self.comment + print_card_16(card)
  
  
  class TSTEP(BaseCard):
      """
      Transient Time Step
      Defines time step intervals at which a solution will be generated and
      output in transient analysis.
  
      +-------+------+------+------+------+-----+-----+-----+-----+
      |   1   |   2  |  3   |  4   |  5   |  6  |  7  |  8  |  9  |
      +=======+======+======+======+======+=====+=====+=====+=====+
      | TSTEP | SID  |  N1  | DT1  | NO1  |     |     |     |     |
      +-------+------+------+------+------+-----+-----+-----+-----+
      |       |      |  N2  | DT2  | NO2  |     |     |     |     |
      +-------+------+------+------+------+-----+-----+-----+-----+
      |       |      | etc. |      |      |     |     |     |     |
      +-------+------+------+------+------+-----+-----+-----+-----+
  
      +-------+------+------+------+------+-----+-----+-----+-----+
      |   1   |   2  |  3   |  4   |  5   |  6  |  7  |  8  |  9  |
      +=======+======+======+======+======+=====+=====+=====+=====+
      | TSTEP | 101  | 9000 | .001 | 9000 |     |     |     |     |
      +-------+------+------+------+------+-----+-----+-----+-----+
      |       |      | 1000 | .001 | 1    |     |     |     |     |
      +-------+------+------+------+------+-----+-----+-----+-----+
      """
      type = 'TSTEP'
  
      def __init__(self, sid, N, DT, NO, comment=''):
          """
          Creates a TSTEP card
  
          Parameters
          ----------
          sid : int
              the time step id
          N : List[int/None]
              ???
          DT : List[float/None]
              ???
          NO : List[int/None]
              ???
          comment : str; default=''
              a comment for the card
          """
          if comment:
              self.comment = comment
          self.sid = sid
          #: Number of time steps of value DTi. (Integer > 1)
          self.N = N
          #: Time increment (float)
          self.DT = DT
          #: Skip factor for output. Every NOi-th step will be saved for output (default=1)
          self.NO = NO
  
      def validate(self):
          assert len(self.N) == len(self.DT), 'N=%s DT=%s' % (self.N, self.DT)
          assert len(self.N) == len(self.NO), 'N=%s NO=%s' % (self.N, self.NO)
  
      @classmethod
      def add_card(cls, card, comment=''):
          """
          Adds a TSTEP card from ``BDF.add_card(...)``
  
          Parameters
          ----------
          card : BDFCard()
              a BDFCard object
          comment : str; default=''
              a comment for the card
          """
          sid = integer(card, 1, 'sid')
          N = []
          DT = []
          NO = []
  
          nrows = int(ceil((len(card) - 1.) / 8.))
          for i in range(nrows):
              n = 8 * i + 1
              ni = integer_or_blank(card, n + 1, 'N' + str(i), 1)
              dt = double_or_blank(card, n + 2, 'dt' + str(i), 0.)
              no = integer_or_blank(card, n + 3, 'NO' + str(i), 1)
              N.append(ni)
              DT.append(dt)
              NO.append(no)
          return TSTEP(sid, N, DT, NO, comment=comment)
  
      def raw_fields(self):
          list_fields = ['TSTEP', self.sid]
          for (N, dt, no) in zip(self.N, self.DT, self.NO):
              list_fields += [N, dt, no, None, None, None, None, None]
          return list_fields
  
      def repr_fields(self):
          return self.raw_fields()
  
      def write_card(self, size: int=8, is_double: bool=False) -> str:
          card = self.repr_fields()
          if size == 8:
              return self.comment + print_card_8(card)
          return self.comment + print_card_16(card)
  
  
  class TSTEPNL(BaseCard):
      """
      Defines parametric controls and data for nonlinear transient structural or
      heat transfer analysis. TSTEPNL is intended for SOLs 129, 159, and 600.
      Parameters for Nonlinear Transient Analysis.
  
      +---------+--------+--------+-------+--------+--------+-------+---------+------+
      |    1    |   2    |   3    |   4   |   5    |   6    |   7   |    8    |  9   |
      +=========+========+========+=======+========+========+=======+=========+======+
      | TSTEPNL |   ID   |  NDT   |  DT   |   NO   | METHOD | KSTEP | MAXITER | CONV |
      +---------+--------+--------+-------+--------+--------+-------+---------+------+
      |         |  ESPU  |  EPSP  |  EPSW | MAXDIV | MAXQN  | MAXLS | FSTRESS |      |
      +---------+--------+--------+-------+--------+--------+-------+---------+------+
      |         | MAXBIS | ADJUST | MSTEP |   RB   |  MAXR  | UTOL  | RTOLB   |      |
      +---------+--------+--------+-------+--------+--------+-------+---------+------+
  
      method = None for NX, but apparently TSTEP as well, which is not in the QRG
      """
      type = 'TSTEPNL'
      allowed_methods = ['AUTO', 'ITER', 'ADAPT', 'SEMI', 'FNT', 'PFNT', # MSC
                         'TSTEP'] # NX
  
      def __init__(self, sid, ndt, dt, no, method='ADAPT', kstep=None,
                   max_iter=10, conv='PW', eps_u=1.e-2, eps_p=1.e-3,
                   eps_w=1.e-6, max_div=2, max_qn=10, max_ls=2,
                   fstress=0.2, max_bisect=5, adjust=5, mstep=None,
                   rb=0.6, max_r=32., utol=0.1, rtol_b=20.,
                   min_iter=None, comment=''):
          """
          Creates a TSTEPNL card
  
          Parameters
          ----------
          sid : int
              the time step id
          ndt : ???
              ???
          dt : ???
              ???
          no : ???
              ???
          eps_u : float; default=1.e-2
              ???
          eps_p : float; default=1.e-3
              ???
          eps_w : float; default=1.e-6
              ???
          max_div : int; default=2
              ???
          max_qn : int; default=10
              ???
          max_ls : int; default=2
              ???
          fstress : float; default=0.2
              ???
          max_bisect : int; default=5
              ???
          adjust : int; default=5
              ???
          mstep : int; default=None
              ???
          rb : float; default=0.6
              ???
          max_r = float; default=32.
              ???
          utol = float; default=0.1
              ???
          rtol_b = float; default=20.
              ???
          min_iter : int; default=None
              not listed in all QRGs
          comment : str; default=''
              a comment for the card
          """
          if comment:
              self.comment = comment
  
          # line 1
          self.sid = sid
          self.ndt = ndt
          self.dt = dt
          self.no = no
          self.method = method
          self.kstep = kstep
          self.max_iter = max_iter
          self.conv = conv
  
          self.eps_u = eps_u
          self.eps_p = eps_p
          self.eps_w = eps_w
          self.max_div = max_div
          self.max_qn = max_qn
          self.max_ls = max_ls
          self.fstress = fstress
  
          # line 3
          self.max_bisect = max_bisect
          self.adjust = adjust
          self.mstep = mstep
          self.rb = rb
          self.max_r = max_r
          self.utol = utol
          self.rtol_b = rtol_b
          self.min_iter = min_iter
          assert self.ndt >= 3
          assert self.dt > 0.
  
      def validate(self):
          if self.method not in self.allowed_methods:
              msg = 'method=%r allowed_methods=[%s]' % (
                  self.method, ', '.join(self.allowed_methods))
              raise ValueError(msg)
  
      @classmethod
      def add_card(cls, card, comment=''):
          """
          Adds a TSTEPNL card from ``BDF.add_card(...)``
  
          Parameters
          ----------
          card : BDFCard()
              a BDFCard object
          comment : str; default=''
              a comment for the card
          """
          sid = integer(card, 1, 'sid')
          ndt = integer(card, 2, 'ndt')
          dt = double(card, 3, 'dt')
          no = integer_or_blank(card, 4, 'no', 1)
  
          #: .. note:: not listed in all QRGs
          method = string_or_blank(card, 5, 'method', 'ADAPT')
          if method == 'ADAPT':
              kstep = integer_or_blank(card, 6, 'kStep', 2)
          elif method == 'ITER':
              kstep = integer_or_blank(card, 6, 'kStep', 10)
          elif method in ['AUTO', 'TSTEP', 'SEMI']:
              kstep = None
              #kstep = blank(card, 6, 'kStep') #: .. todo:: not blank
          else:
              msg = 'invalid TSTEPNL Method.  method=%r; allowed_methods=[%s]' % (
                  method, ', '.join(cls.allowed_methods))
              raise RuntimeError(msg)
          max_iter = integer_or_blank(card, 7, 'maxIter', 10)
          conv = string_or_blank(card, 8, 'conv', 'PW')
  
          # line 2
          eps_u = double_or_blank(card, 9, 'epsU', 1.E-2)
          eps_p = double_or_blank(card, 10, 'epsP', 1.E-3)
          eps_w = double_or_blank(card, 11, 'epsW', 1.E-6)
          max_div = integer_or_blank(card, 12, 'maxDiv', 2)
          max_qn = integer_or_blank(card, 13, 'maxQn', 10)
          max_ls = integer_or_blank(card, 14, 'MaxLs', 2)
          fstress = double_or_blank(card, 15, 'fStress', 0.2)
  
          # line 3
          max_bisect = integer_or_blank(card, 17, 'maxBisect', 5)
          adjust = integer_or_blank(card, 18, 'adjust', 5)
          mstep = integer_or_blank(card, 19, 'mStep')
          rb = double_or_blank(card, 20, 'rb', 0.6)
          max_r = double_or_blank(card, 21, 'maxR', 32.)
          utol = double_or_blank(card, 22, 'uTol', 0.1)
          rtol_b = double_or_blank(card, 23, 'rTolB', 20.)
  
          # not listed in all QRGs
          min_iter = integer_or_blank(card, 24, 'minIter')
          assert len(card) <= 25, 'len(TSTEPNL card) = %i\ncard=%s' % (len(card), card)
          return TSTEPNL(
              sid, ndt, dt, no, method, kstep, max_iter, conv,
              eps_u, eps_p, eps_w, max_div, max_qn, max_ls, fstress,
              max_bisect, adjust, mstep, rb, max_r, utol, rtol_b, min_iter,
              comment=comment)
  
      @classmethod
      def add_op2_data(cls, data, comment=''):
          """
          Adds a TSTEPNL card from the OP2
  
          Parameters
          ----------
          data : List[varies]
              a list of fields defined in OP2 format
          comment : str; default=''
              a comment for the card
          """
          (sid, ndt, dt, no, method, kstep, max_iter, conv, eps_u, eps_p, eps_w,
           max_div, max_qn, max_ls, fstress, max_bisect,
           adjust, mstep, rb, max_r, utol, rtol_b) = data
  
          if method == 1:
              method = 'AUTO'
          elif method == 3:
              method = 'ADAPT'
          else:
              raise NotImplementedError('tstepnl=%s method=%r data=%s' % (sid, method, data))
  
          if conv == 3:
              conv = 'PW'
          elif conv == 4:
              conv = 'U'
          #elif conv == 3:
              #conv = 'ADAPT'
          else:
              raise NotImplementedError('tstepnl=%s conv=%r data=%s' % (sid, conv, data))
  
          min_iter = None  # not listed in DMAP 2005
          return TSTEPNL(
              sid, ndt, dt, no, method, kstep, max_iter, conv,
              eps_u, eps_p, eps_w, max_div, max_qn, max_ls, fstress,
              max_bisect, adjust, mstep, rb, max_r, utol, rtol_b, min_iter,
              comment=comment)
          #self.sid = sid
          #self.ndt = ndt
          #self.dt = dt
          #self.no = no
          #self.method = method
          #self.kStep = kStep
          #self.maxIter = maxIter
          #self.conv = conv
  
          ## line 2
          #self.epsU = epsU
          #self.epsP = epsP
          #self.epsW = epsW
          #self.maxDiv = maxDiv
          #self.maxQn = maxQn
          #self.MaxLs = maxLs
          #self.fStress = fStress
  
          ## line 3
          #self.maxBisect = maxBisect
          #self.adjust = adjust
          #self.mStep = mStep
          #self.rb = rb
          #self.maxR = maxR
          #self.uTol = uTol
          #self.rTolB = rTolB
  
      def raw_fields(self):
          list_fields = ['TSTEPNL', self.sid, self.ndt, self.dt, self.no,
                         self.method, self.kstep, self.max_iter, self.conv, self.eps_u,
                         self.eps_p, self.eps_w, self.max_div, self.max_qn, self.max_ls,
                         self.fstress, None, self.max_bisect, self.adjust, self.mstep,
                         self.rb, self.max_r, self.utol, self.rtol_b, self.min_iter]
          return list_fields
  
      def repr_fields(self):
          #no = set_blank_if_default(self.no,1)
          no = self.no
          method = set_blank_if_default(self.method, 'ADAPT')
  
          kstep = self.kstep
          #if self.method == 'ADAPT':
              #kStep = set_blank_if_default(self.kStep, 2)
          #elif self.method == 'ITER':
              #kStep = set_blank_if_default(self.kStep, 10)
          #else:
              #msg = 'invalid TSTEPNL Method.  method=|%s|' %(self.method)
              #raise RuntimeError(msg)
  
          #maxIter = set_blank_if_default(self.maxIter, 10)
          conv = set_blank_if_default(self.conv, 'PW')
  
          eps_u = set_blank_if_default(self.eps_u, 1e-2)
          eps_p = set_blank_if_default(self.eps_p, 1e-3)
          eps_w = set_blank_if_default(self.eps_w, 1e-6)
          max_div = set_blank_if_default(self.max_div, 2)
          max_qn = set_blank_if_default(self.max_qn, 10)
          max_ls = set_blank_if_default(self.max_ls, 2)
          fstress = set_blank_if_default(self.fstress, 0.2)
  
          max_bisect = set_blank_if_default(self.max_bisect, 5)
          adjust = set_blank_if_default(self.adjust, 5)
          rb = set_blank_if_default(self.rb, 0.6)
          max_r = set_blank_if_default(self.max_r, 32.)
          utol = set_blank_if_default(self.utol, 0.1)
          rtol_b = set_blank_if_default(self.rtol_b, 20.)
  
          list_fields = ['TSTEPNL', self.sid, self.ndt, self.dt, no, method,
                         kstep, self.max_iter, conv, eps_u, eps_p, eps_w, max_div, max_qn,
                         max_ls, fstress, None, max_bisect, adjust, self.mstep, rb,
                         max_r, utol, rtol_b, self.min_iter]
          return list_fields
  
      def write_card(self, size: int=8, is_double: bool=False) -> str:
          card = self.repr_fields()
          if size == 8:
              return self.comment + print_card_8(card)
          return self.comment + print_card_16(card)
   # pylint: disable=R0902,R0904,R0914
  from math import sin, cos, radians, atan2, sqrt, degrees
  from itertools import count
  from typing import Tuple # , TYPE_CHECKING
  
  import numpy as np
  from numpy import array, zeros
  from scipy.sparse import coo_matrix  # type: ignore
  
  from pyNastran.utils.numpy_utils import integer_types
  from pyNastran.bdf.cards.base_card import BaseCard
  from pyNastran.bdf.field_writer_8 import print_card_8
  from pyNastran.bdf.field_writer_16 import print_card_16
  from pyNastran.bdf.field_writer_double import print_card_double
  
  from pyNastran.bdf.bdf_interface.assign_type import (
      integer, integer_or_blank, double, string, string_or_blank,
      parse_components, interpret_value, integer_double_string_or_blank)
  
  
  class DTI(BaseCard):
      """
      +-----+-------+-----+------+-------+--------+------+-------------+
      |  1  |   2   |  3  |   4  |   5   |    6   |   7  |       8     |
      +=====+=======+=====+======+=======+========+======+=============+
      | DTI | UNITS | "1" | MASS | FORCE | LENGTH | TIME |   STRESS    |
      +-----+-------+-----+------+-------+--------+------+-------------+
  
      MSC
  
      +-----+-------+-----+------+-------+--------+------+-------------+
      |  1  |   2   |  3  |   4  |   5   |    6   |   7  |       8     |
      +=====+=======+=====+======+=======+========+======+=============+
      | DTI | UNITS | "1" | MASS | FORCE | LENGTH | TIME | TEMPERATURE |
      +-----+-------+-----+------+-------+--------+------+-------------+
  
      NX
      """
      type = 'DTI'
      #_properties = ['shape', 'ifo', 'is_real', 'is_complex', 'is_polar', 'matrix_type', 'tin_dtype', 'tout_dtype']
  
      @classmethod
      def _init_from_empty(cls):
          name = 'name'
          fields = []
          return DTI(name, fields, comment='')
  
      def _finalize_hdf5(self, encoding):
          """hdf5 helper function"""
          keys, values = self.fields
  
          # nan != nan
          values = [value if value == value else None for value in values]
          values_str = [value.decode(encoding) if isinstance(value, bytes) else value
                        for value in values]
          #values = [valuei.decode(encoding) if isinstance(valuei, bytes) else (
          #    None if np.isnan(valuei) else valuei)
          #          for valuei in values]
          self.fields = {key : value for key, value in zip(keys, values_str)}
  
      @classmethod
      def export_to_hdf5(cls, h5_file, model, encoding):
          """exports the elements in a vectorized way"""
          from pyNastran.bdf.bdf_interface.hdf5_exporter import _export_list
          for name, dti in sorted(model.dti.items()):
              if name == 'UNITS':
                  i = 0
                  for key, value in sorted(dti.fields.items()):
                      #print(key, value)
                      h5_group = h5_file.create_group(str(key))
                      if value is None:
                          h5_group.create_dataset(str(i), data=np.nan)
                      else:
                          h5_group.create_dataset(str(i), data=value)
                      i += 1
                  #fields = {
                      #'mass' : mass,
                      #'force' : force,
                      #'length' : length,
                      #'time' : time,
                      #'temp_stress' : temp_stress
                  #}
              else:
                  for irecord, fields in sorted(dti.fields.items()):
                      #h5_group = h5_file.create_group(str(irecord))
                      attr = 'irecord=%s' % irecord
                      namei = str(irecord)
                      values = fields
                      _export_list(h5_file, attr, namei, values, encoding)
                      #print(h5_group)
                      #print(irecord, fields)
  
      def __init__(self, name, fields, comment=''):
          """
          Creates a DTI card
  
          Parameters
          ----------
          name : str
              UNITS
          fields : List[varies]
              the fields
          comment : str; default=''
              a comment for the card
          """
          if comment:
              self.comment = comment
          self.name = name
          self.fields = fields
          assert len(fields) > 0, fields
  
      @classmethod
      def add_card(cls, card, comment):
          """
          Adds a DTI card from ``BDF.add_card(...)``
  
          Parameters
          ----------
          card : BDFCard()
              a BDFCard object
          comment : str; default=''
              a comment for the card
  
          """
          name = string(card, 1, 'name')
          if name == 'UNITS':
              integer(card, 2, '1')
              mass = string(card, 3, 'mass')
              force = string(card, 4, 'force')
              length = string(card, 5, 'length')
              time = string(card, 6, 'time')
              temp_stress = string_or_blank(card, 7, 'stress/temperature')
              fields = {
                  'mass' : mass,
                  'force' : force,
                  'length' : length,
                  'time' : time,
                  'temp_stress' : temp_stress
              }
          else:
              fields = []
              #field2 = card[2]
  
              list_fields = []
              irecord = integer(card, 2, 'record')
              if irecord == 0:
                  for i in range(3, len(card)):
                      val = integer_double_string_or_blank(
                          card, i, 'T%i' % (i-1), default=32767)
                      list_fields.append(val)
              else:
                  for i in range(3, len(card)):
                      val = integer_double_string_or_blank(
                          card, i, 'T%i' % (i-1), default=None)
                      list_fields.append(val)
              fields = {irecord: list_fields,}
          return DTI(name, fields, comment=comment)
  
      def raw_fields(self):
          if self.name == 'UNITS':
              mass = self.fields['mass']
              force = self.fields['force']
              length = self.fields['length']
              time = self.fields['time']
              temp_stress = self.fields['temp_stress']
              list_fields = ['DTI', self.name, '1', mass, force, length, time, temp_stress]
          else:
              list_fields = []
              for irecord, fields in sorted(self.fields.items()):
                  nfields = len(fields)
                  list_fields += ['DTI', self.name] + fields
                  nleftover = nfields % 8
                  if nleftover:
                      list_fields += [None] * nleftover
          return list_fields
  
      def write_card(self, size: int=8, is_double: bool=False) -> str:
          if self.name == 'UNITS':
              card = self.repr_fields()
              return self.comment + print_card_8(card)
  
          msg = self.comment
          for irecord, fields in sorted(self.fields.items()):
              list_fields = ['DTI', self.name, irecord, ] + fields
              msg += print_card_8(list_fields)
          return msg
  
  
  class NastranMatrix(BaseCard):
      """
      Base class for the DMIG, DMIJ, DMIJI, DMIK matrices
      """
      def _finalize_hdf5(self, encoding):
          """hdf5 helper function"""
          self.finalize()
  
      def __init__(self, name, matrix_form, tin, tout, polar, ncols,
                   GCj, GCi, Real, Complex=None, comment='', finalize=True):
          """
          Creates a NastranMatrix
  
          Parameters
          ----------
          name : str
              the name of the matrix
          matrix_form : int
              matrix shape
              4=Lower Triangular
              5=Upper Triangular
              6=Symmetric
              8=Identity (m=nRows, n=m)
          tin : int
              matrix input precision
              1=Real, Single Precision
              2=Real, Double Precision
              3=Complex, Single Precision
              4=Complex, Double Precision
          tout : int
              matrix output precision
              0=same as tin
              1=Real, Single Precision
              2=Real, Double Precision
              3=Complex, Single Precision
              4=Complex, Double Precision
          polar : int; default=0
              Input format of Ai, Bi
              Integer=blank or 0 indicates real, imaginary format
              Integer > 0 indicates amplitude, phase format
          ncols : int
              ???
          GCj  : List[(node, dof)]
              the jnode, jDOFs
          GCi  : List[(node, dof)]
              the inode, iDOFs
          Real : List[float]
              The real values
          Complex : List[float]; default=None
              The complex values (if the matrix is complex)
          comment : str; default=''
              a comment for the card
  
          """
          if comment:
              self.comment = comment
          if Complex is None:
              Complex = []
          if tout is None:
              tout = 0
  
          polar = _set_polar(polar)
  
          if matrix_form not in [1, 2, 4, 5, 6, 8, 9]:
              msg = (
                  'matrix_form=%r must be [1, 2, 4, 5, 6, 8, 9]\n'
                  '  1: Square\n'
                  '  2: Rectangular\n'
                  #'  4: Lower Triangular\n'
                  #'  5: Upper Triangular\n'
                  '  6: Symmetric\n'
                  #'  8: Identity (m=nRows, n=m)\n'
                  '  9: Rectangular\n' % matrix_form)
              raise ValueError(msg)
          self.name = name
  
          #: 4-Lower Triangular; 5=Upper Triangular; 6=Symmetric; 8=Identity (m=nRows, n=m)
          self.matrix_form = matrix_form
  
          #: 1-Real, Single Precision; 2=Real,Double Precision;
          #  3=Complex, Single; 4=Complex, Double
          self.tin = tin
  
          #: 0-Set by cell precision
          self.tout = tout
  
          #: Input format of Ai, Bi. (Integer=blank or 0 indicates real, imaginary format;
          #: Integer > 0 indicates amplitude, phase format.)
          self.polar = polar
  
          self.ncols = ncols
          self.GCj = GCj
          self.GCi = GCi
  
          self.Real = Real
          if len(Complex) or self.is_complex:
              self.Complex = Complex
              assert self.tin in [3, 4], 'tin=%r and must 3 or 4 to be complex' % self.tin
              assert self.tout in [0, 3, 4], 'tin=%r and must 0, 3 or 4 to be complex' % self.tout
          assert isinstance(matrix_form, integer_types), 'matrix_form=%r type=%s' % (matrix_form, type(matrix_form))
          assert not isinstance(matrix_form, bool), 'matrix_form=%r type=%s' % (matrix_form, type(matrix_form))
          if finalize:
              self.finalize()
  
      @classmethod
      def add_card(cls, card, comment=''):
          """
          Adds a NastranMatrix (DMIG, DMIJ, DMIK, DMIJI) card from ``BDF.add_card(...)``
  
          Parameters
          ----------
          card : BDFCard()
              a BDFCard object
          comment : str; default=''
              a comment for the card
  
          """
          name = string(card, 1, 'name')
          #zero
  
          matrix_form = integer(card, 3, 'ifo')
          tin = integer(card, 4, 'tin')
          tout = integer_or_blank(card, 5, 'tout', 0)
          polar = integer_or_blank(card, 6, 'polar', 0)
          if matrix_form == 1: # square
              ncols = integer_or_blank(card, 8, 'matrix_form=%s; ncol' % matrix_form)
          elif matrix_form == 6: # symmetric
              ncols = integer_or_blank(card, 8, 'matrix_form=%s; ncol' % matrix_form)
          elif matrix_form in [2, 9]: # rectangular
              ncols = integer(card, 8, 'matrix_form=%s; ncol' % (matrix_form))
          else:
              # technically right, but nulling this will fix bad decks
              #self.ncols = blank(card, 8, 'matrix_form=%s; ncol' % self.matrix_form)
  
              msg = (
                  '%s name=%r matrix_form=%r is not supported.  Valid forms:\n'
                  '  4=Lower Triangular\n'
                  '  5=Upper Triangular\n'
                  '  6=Symmetric\n'
                  '  8=Identity (m=nRows, n=m)\n' % (cls.type, name, matrix_form)
              )
              raise NotImplementedError(msg)
  
          GCj = []
          GCi = []
          Real = []
          Complex = []
          return cls(name, matrix_form, tin, tout, polar, ncols,
                     GCj, GCi, Real, Complex, comment=comment, finalize=False)
  
      @property
      def matrix_type(self):
          """gets the matrix type"""
          if not isinstance(self.matrix_form, integer_types):
              msg = 'ifo must be an integer; matrix_form=%r type=%s name=%s' % (
                  self.matrix_form, type(self.matrix_form), self.name)
              raise TypeError(msg)
          if isinstance(self.matrix_form, bool):
              msg = 'matrix_form must not be a boolean; matrix_form=%r type=%s name=%s' % (
                  self.matrix_form, type(self.matrix_form), self.name)
              raise TypeError(msg)
  
          if self.matrix_form == 1:
              matrix_type = 'square'
          elif self.matrix_form == 6:
              matrix_type = 'symmetric'
          elif self.matrix_form in [2, 9]:
              matrix_type = 'rectangular'
          else:
              # technically right, but nulling this will fix bad decks
              #self.ncols = blank(card, 8, 'matrix_form=%s; ncol' % self.matrix_form)
              raise NotImplementedError('%s matrix_form=%r is not supported' % (
                  self.type, self.matrix_form))
          return matrix_type
  
      def finalize(self):
          """converts the lists into numpy arrays"""
          self.GCi = np.asarray(self.GCi)
          self.GCj = np.asarray(self.GCj)
          self.Real = np.asarray(self.Real)
          if self.is_complex:
              self.Complex = np.asarray(self.Complex)
  
      @property
      def shape(self):
          """gets the matrix shape"""
          if self.matrix_form in [1, 6]: # square, symmetric
              if self.ncols is not None:
                  shape = (self.ncols, self.ncols)
              else:
                  nrows, ncols = get_row_col_map(
                      self, self.GCi, self.GCj, self.matrix_form)[:2]
                  shape = (nrows, ncols)
          elif self.matrix_form in [2, 9]:
              raise NotImplementedError('need to pull the nrows after reading in everything')
              #shape = (self.ncols, self.ncols)
          else:
              raise NotImplementedError('matrix_form=%s' % self.matrix_form)
          return shape
  
      def _add_column(self, card, comment=''):
          """adds an additional column entry to the matrix"""
          if comment:
              if hasattr(self, '_comment'):
                  self.comment += comment
              else:
                  self.comment = comment
  
          name = string(card, 1, 'name')
          if name == 'UACCEL':
              self._add_column_uaccel()
              return
  
          Gj = integer(card, 2, 'Gj')
          # Cj = integer(card, 3, 'Cj')
          Cj = integer_or_blank(card, 3, 'Cj', 0)
          #Cj = parse_components(card, 3, 'Cj')
          assert 0 <= Cj <= 6, 'C%i must be between [0, 6]; Cj=%s' % (0, Cj)
  
          nfields = len(card)
          #print("nfields = %i" % nfields)
          #print("card[5:] =", card[5:])
          #print("(nfields - 5) %% 4 = %i" % ((nfields - 5) % 4))
  
          nloops = (nfields - 5) // 4
          if (nfields - 5) % 4 in [2, 3]:  # real/complex
              nloops += 1
          #assert nfields <= 8,'nfields=%s' % nfields
          #print("nloops = %i" % nloops)
          assert nloops > 0, 'nloops=%s' % nloops
  
          for i in range(nloops):
              self.GCj.append((Gj, Cj))
  
          if self.is_complex:
              if self.is_polar:
                  for i in range(nloops):
                      n = 5 + 4 * i
                      Gi = integer(card, n, 'Gi')
                      # Ci = integer(card, n + 1, 'Ci')
                      Ci = integer_or_blank(card, n + 1, 'Ci', 0)
                      #Ci = parse_components(card, n + 1, 'Ci')
                      assert 0 <= Ci <= 6, 'C%i must be between [0, 6]; Ci=%s' % (i + 1, Ci)
                      self.GCi.append((Gi, Ci))
                      magi = double(card, n + 2, 'ai')
                      phasei = double(card, n + 3, 'bi')
                      reali = magi * cos(radians(phasei))
                      complexi = magi * sin(radians(phasei))
                      self.Real.append(reali)
                      self.Complex.append(complexi)
              else:
                  for i in range(nloops):
                      n = 5 + 4 * i
                      Gi = integer(card, n, 'Gi')
                      # Ci = integer(card, n + 1, 'Ci')
                      Ci = integer_or_blank(card, n + 1, 'Ci', 0)
                      #Ci = parse_components(card, n + 1, 'Ci')
                      assert 0 <= Ci <= 6, 'C%i must be between [0, 6]; Ci=%s' % (i + 1, Ci)
                      self.GCi.append((Gi, Ci))
                      reali = double(card, n + 2, 'real')
                      complexi = double(card, n + 3, 'complex')
                      self.Real.append(reali)
                      self.Complex.append(complexi)
          else:
              # real
              for i in range(nloops):
                  n = 5 + 4 * i
                  Gi = integer(card, n, 'Gi')
                  # Ci = integer(card, n + 1, 'Ci')
                  Ci = integer_or_blank(card, n + 1, 'Ci', 0)
                  #Ci = parse_components(card, n + 1, 'Ci')
                  assert 0 <= Ci <= 6, 'C%i must be between [0, 6]; Ci=%s' % (i + 1, Ci)
                  reali = double(card, n + 2, 'real')
                  self.GCi.append((Gi, Ci))
                  self.Real.append(reali)
                  #print("GC=%s,%s real=%s" % (Gi, Ci, reali))
  
          msg = '(len(GCj)=%s len(GCi)=%s' % (len(self.GCj), len(self.GCi))
          assert len(self.GCj) == len(self.GCi), msg
          #if self.is_complex:
              #self.Complex(double(card, v, 'complex')
  
      def get_matrix(self, is_sparse=False, apply_symmetry=True):
          """
          Builds the Matrix
  
          Parameters
          ----------
          is_sparse : bool; default=False
              should the matrix be returned as a sparse matrix.
              Slower for dense matrices.
          apply_symmetry : bool; default=True
              If the matrix is symmetric (ifo=6), returns a symmetric matrix.
              Supported as there are symmetric matrix routines.
  
          Returns
          -------
          M : numpy.ndarray or scipy.coomatrix
              the matrix
          rows : dict[int] = [int, int]
              dictionary of keys=rowID, values=(Grid,Component) for the matrix
          cols: dict[int] = [int, int]
              dictionary of keys=columnID, values=(Grid,Component) for the matrix
  
          .. warning:: is_sparse=True WILL fail
  
          """
          return get_matrix(self, is_sparse=is_sparse, apply_symmetry=apply_symmetry)
  
      @property
      def is_real(self):
          """real vs. complex attribute"""
          return not self.is_complex
  
      @property
      def is_complex(self):
          """real vs. complex attribute"""
          if self.tin in [1, 2]: # real
              return False
          elif self.tin in [3, 4]: # complex
              return True
          msg = ('Matrix %r must have a value of TIN = [1, 2, 3, 4].\n'
                 'TIN defines the type (real, complex) '
                 'of the matrix.  TIN=%r.\n'
                 '  TIN=1,2 -> real\n'
                 '  TIN=3,4 -> complex' % (self.name, self.tin))
          raise ValueError(msg)
  
      @property
      def is_polar(self):
          """
          Used by:
            - DMIG
            - DMIJ
            - DMIJI
            - DMIK
  
          Not used by:
            - DMI
            - DMIAX
            - DMIG, UACCEL
            - DMIGOUT
            - DMIGROT
  
          """
          if self.polar == 0: # real, imag
              return False
          elif self.polar == 1: # mag, phase
              return True
          elif self.polar is None:
              return False
          msg = ('Matrix %r must have a value of POLAR = [0, 1].\n'
                 'POLAR defines the type (real/imag or mag/phase) complex) '
                 'of the matrix.  POLAR=%r.' % (self.name, self.polar))
          raise ValueError(msg)
  
      @property
      def tin_dtype(self):
          """gets the input dtype"""
          return _get_dtype(self.is_complex, self.tin)
  
      @property
      def tout_dtype(self):
          """gets the output dtype"""
          return _get_dtype(self.is_complex, self.tout)
  
      def __repr__(self):
          return self.write_card(size=8, is_double=False)
  
      def fill_in_default_components(self, model):
          for i, (Gi, Ci) in enumerate(self.GCi):
              if Ci is None:
                  node = model.nodes[Gi]
                  if node.type == 'GRID':
                      msg = ('Ci on DMIG card must be 1, 2, 3, 4, 5, or 6; '
                             'Node=%i (GRID); Ci=%s' % (Gi, Ci))
                      raise RuntimeError(msg)
                  elif node.type in ['SPOINT', 'EPOINT']:
                      Ci = 0
                  else:
                      raise NotImplementedError(node)
                  self.GCi[i] = [Gi, Ci]
  
          for i, (Gj, Cj) in enumerate(self.GCj):
              if Cj is None:
                  node = model.nodes[Gj]
                  if node.type == 'GRID':
                      msg = ('Cj on DMIG card must be 1, 2, 3, 4, 5, or 6; '
                             'Node=%i (GRID); Cj=%s' % (Gj, Cj))
                      raise RuntimeError(msg)
                  elif node.type in ['SPOINT', 'EPOINT']:
                      Cj = 0
                  else:
                      raise NotImplementedError(node)
                  self.GCj[i] = [Gj, Cj]
          return
  
      def write_card(self, size: int=8, is_double: bool=False) -> str:
          if self.tin in [1, 3]:
              is_double = False
          elif self.tin in [2, 4]:
              is_double = True
              size = 16
          else:
              raise RuntimeError('tin=%r must be 1, 2, 3, or 4' % self.tin)
  
          assert isinstance(self.GCi, (list, np.ndarray)), 'type(GCi)=%s' % type(self.GCi)
          assert isinstance(self.GCj, (list, np.ndarray)), 'type(GCj)=%s' % type(self.GCj)
          assert isinstance(self.Real, (list, np.ndarray)), 'type(Real)=%s' % type(self.Real)
          #assert isinstance(self.GCi[0], (list, np.ndarray)), 'type(GCi[0])=%s' % type(self.GCi[0])
          #assert isinstance(self.GCj[0], (list, np.ndarray)), 'type(GCj[0])=%s' % type(self.GCj[0])
  
          msg = '\n$' + '-' * 80
          msg += '\n$ %s Matrix %s\n' % (self.type, self.name)
          list_fields = [self.type, self.name, 0, self.matrix_form, self.tin,
                         self.tout, self.polar, None, self.ncols]
          if size == 8:
              msg += print_card_8(list_fields)
          else:
              msg += print_card_16(list_fields)
  
          if self.is_complex:
              if self.is_polar:
                  for (GCi, GCj, reali, complexi) in zip(self.GCi, self.GCj, self.Real, self.Complex):
                      magi = sqrt(reali**2 + complexi**2)
                      if reali == 0.0:
                          phasei = 0.0
                      else:
                          phasei = degrees(atan2(complexi, reali))
                      list_fields = [self.type, self.name, GCj[0], GCj[1],
                                     None, GCi[0], GCi[1], magi, phasei]
                      if size == 8:
                          msg += print_card_8(list_fields)
                      elif is_double:
                          msg += print_card_double(list_fields)
                      else:
                          msg += print_card_16(list_fields)
              else:
                  for (GCi, GCj, reali, complexi) in zip(self.GCi, self.GCj, self.Real, self.Complex):
                      list_fields = [self.type, self.name, GCj[0], GCj[1],
                                     None, GCi[0], GCi[1], reali, complexi]
                      if size == 8:
                          msg += print_card_8(list_fields)
                      elif is_double:
                          msg += print_card_double(list_fields)
                      else:
                          msg += print_card_16(list_fields)
          else:
              for (GCi, GCj, reali) in zip(self.GCi, self.GCj, self.Real):
                  list_fields = [self.type, self.name, GCj[0], GCj[1],
                                 None, GCi[0], GCi[1], reali, None]
                  if size == 8:
                      msg += print_card_8(list_fields)
                  elif is_double:
                      msg += print_card_double(list_fields)
                  else:
                      msg += print_card_16(list_fields)
  
          #msg += '\n\nGCi[0]=%s\n' % self.GCi[0]
          #msg += 'GCj[0]=%s\n' % self.GCj[0]
          #msg += 'Real[0]=%s\n' % self.Real[0]
          #assert isinstance(self.GCi[0], (list, np.ndarray)), msg
          #assert isinstance(self.GCj[0], (list, np.ndarray)), msg
          #assert isinstance(self.Real[0], (list, np.ndarray)), msg
  
          return msg
  
  
  class DMIG_UACCEL(BaseCard):
      """
      Direct Matrix Input of Enforced Static Acceleration
      Defines rigid body accelerations in the basic coordinate system.
  
      +------+--------+-----+-----+-----+-----+-----+-------+-------+
      |   1  |   2    |  3  |  4  |  5  |  6  |  7  |   8   |       |
      +======+========+=====+=====+=====+=====+=====+=======+=======+
      | DMIG | UACCEL | "0" | "9" | TIN |     |     |       | NCOL  |
      +------+--------+-----+-----+-----+-----+-----+-------+-------+
      | DMIG | UACCEL |  L  |     |     |  G1 | C1  |  X1   |       |
      +------+--------+-----+-----+-----+-----+-----+-------+-------+
      |      |   G2   |  C2 | X2  |     |  G3 | C3  |  X3   |       |
      +------+--------+-----+-----+-----+-----+-----+-------+-------+
  
      +------+--------+-----+-----+-----+-----+-----+-------+-------+
      | DMIG | UACCEL |  0  |  9  |  1  |     |     |       |   4   |
      +------+--------+-----+-----+-----+-----+-----+-------+-------+
      | DMIG | UACCEL |  2  |     |     |  2  |  3  | 386.4 |       |
      +------+--------+-----+-----+-----+-----+-----+-------+-------+
      | DMIG | UACCEL |  3  |     |     |  2  |  4  |  3.0  |       |
      +------+--------+-----+-----+-----+-----+-----+-------+-------+
      | DMIG | UACCEL |  4  |     |     |  2  |  6  |  1.0  |       |
      +------+--------+-----+-----+-----+-----+-----+-------+-------+
      """
      type = 'DMIG'
      name = 'UACCEL'
      def __init__(self, tin, ncol, load_sequences, comment=''):
          if comment:
              self.comment = comment
          self.tin = tin
          self.ncol = ncol
          self.load_sequences = load_sequences
          #print(str(self))
  
      @classmethod
      def export_to_hdf5(cls, h5_file, model, encoding):
          _export_dmig_to_hdf5(h5_file, model, model.dmig, encoding)
  
      @classmethod
      def add_card(cls, card, comment=''):
          """
          Adds a DMIG,UACCEL card from ``BDF.add_card(...)``
  
          Parameters
          ----------
          card : BDFCard()
              a BDFCard object
          comment : str; default=''
              a comment for the card
  
          """
          tin = integer(card, 4, 'tin')
          ncol = integer_or_blank(card, 8, 'ncol')
          return DMIG_UACCEL(tin, ncol, load_sequences={}, comment=comment)
  
      def _add_column(self, card, comment=''):
          if comment:
              if hasattr(self, '_comment'):
                  self.comment += comment
              else:
                  self.comment = comment
          load_seq = integer(card, 2, 'load_seq')
  
          i = 0
          ifield = 5
          self.load_sequences[load_seq] = []
          assert len(card) >= 8, 'len=%s card=%s' % (len(card), card)
          while ifield < len(card):
              g1 = integer(card, ifield, 'nid%d' % i)
              c1 = parse_components(card, ifield+1, 'c%d' % i)
              x1 = double(card, ifield+2, 'x%d' % i)
              #assert len(card) <= 8, 'len=%s card=%s' % (len(card), card)
              gcx = [g1, c1, x1]
              self.load_sequences[load_seq].append(gcx)
              ifield += 4
              i += 1
  
  
      @staticmethod
      def finalize():
          """a passer method"""
          pass
  
      def raw_fields(self):
          list_fields = [
              'DMIG', 'UACCEL', 0, 9, self.tin, None, None, None, self.ncol
          ]
          for lseq, ncx in sorted(self.load_sequences.items()):
              list_fields += [lseq, None, None]
              for ncxi in ncx:
                  list_fields += ncxi
             #for (nid, comp, xi) in ncx:
          #print('list_fields= %s' % list_fields)
          self.write_card()
          return list_fields
  
      def write_card(self, size: int=8, is_double: bool=False) -> str:
          if self.tin in [1, 3]:
              is_double = False
              msg = self.write_card_8()
          elif self.tin in [2, 4]:
              is_double = True
              size = 16
              msg = self.write_card_16()
          else:
              raise RuntimeError('tin=%r must be 1, 2, 3, or 4' % self.tin)
          return msg
  
      def write_card_8(self):
          """writes the card in small field format"""
          return self._write_card(print_card_8)
  
      def write_card_16(self):
          """writes the card in small large format"""
          return self._write_card(print_card_16)
  
      def _write_card(self, func):
          """writes the card"""
          msg = '\n$' + '-' * 80
          msg += '\n$ DMIG Matrix UACCEL\n'
          list_fields = [
              'DMIG', 'UACCEL', 0, 9, self.tin, None, None, None, self.ncol,
          ]
          msg += func(list_fields)
  
          for lseq, ncx in sorted(self.load_sequences.items()):
              list_fields = ['DMIG', 'UACCEL']
              list_fields += [lseq, None, None]
              for ncxi in ncx:
                  list_fields += ncxi + [None]
              list_fields.pop()
              msg += func(list_fields)
          #print(msg)
          #if self.is_complex:
              #msg += self._get_complex_fields(func)
          #else:
              #msg += self._get_real_fields(func)
          return msg
  
      def __repr__(self):
          return self.write_card(size=8)
  
  class DMIG(NastranMatrix):
      """
      Defines direct input matrices related to grid, extra, and/or scalar points.
      The matrix is defined by a single header entry and one or more column
      entries. A column entry is required for each column with nonzero elements.
  
      +------+------+----+-----+-----+------+-------+----+------+
      |   1  |  2   | 3  |  4  |  5  |   6  |   7   | 8  |  9   |
      +======+======+====+=====+=====+======+=======+====+======+
      | DMIG | NAME | 0  | IFO | TIN | TOUT | POLAR |    | NCOL |
      +------+------+----+-----+-----+------+-------+----+------+
      | DMIG | NAME | GJ | CJ  |     |  G1  |  C1   | A1 |  B1  |
      +------+------+----+-----+-----+------+-------+----+------+
      |      |  G2  | C2 | A2  |  B2 |      |       |    |      |
      +------+------+----+-----+-----+------+-------+----+------+
      """
      type = 'DMIG'
      _properties = ['is_real', 'is_complex', 'is_polar', 'matrix_type', 'shape',
                     'tin_dtype', 'tout_dtype']
  
      #@classmethod
      #def _init_from_empty(cls):
          #name = 'name'
          #ifo = 1
          #tin = 1
          #tout = 1
          #polar = 0
          #ncols = 1
          #GCj = []
          #GCi = []
          #Real = []
          #return DMIG(name, ifo, tin, tout, polar, ncols, GCj, GCi, Real,
                      #Complex=None, comment='', finalize=True)
  
      @classmethod
      def export_to_hdf5(cls, h5_file, model, encoding):
          _export_dmig_to_hdf5(h5_file, model, model.dmig, encoding)
  
      def __init__(self, name, ifo, tin, tout, polar, ncols,
                   GCj, GCi, Real, Complex=None, comment='', finalize=True):
          """
          Creates a DMIG card
  
          Parameters
          ----------
          name : str
              the name of the matrix
          ifo : int
              matrix shape
              4=Lower Triangular
              5=Upper Triangular
              6=Symmetric
              8=Identity (m=nRows, n=m)
          tin : int
              matrix input precision
              1=Real, Single Precision
              2=Real, Double Precision
              3=Complex, Single Precision
              4=Complex, Double Precision
          tout : int
              matrix output precision
              0=same as tin
              1=Real, Single Precision
              2=Real, Double Precision
              3=Complex, Single Precision
              4=Complex, Double Precision
          polar : int; default=0
              Input format of Ai, Bi
              Integer=blank or 0 indicates real, imaginary format
              Integer > 0 indicates amplitude, phase format
          ncols : int
              ???
          GCj  : List[(node, dof)]
              the [jnode, jDOFs]
          GCi  : List[(node, dof)]
              the inode, iDOFs
          Real : List[float]
              The real values
          Complex : List[float]; default=None
              The complex values (if the matrix is complex)
          comment : str; default=''
              a comment for the card
  
          """
          NastranMatrix.__init__(self, name, ifo, tin, tout, polar, ncols,
                                 GCj, GCi, Real, Complex, comment=comment,
                                 finalize=finalize)
  
  
  class DMIAX(BaseCard):
      """
      Direct Matrix Input for Axisymmetric Analysis
  
      Defines axisymmetric (fluid or structure) related direct input matrix
      terms.  The matrix is defined by a single header entry and one or
      more column entries. Only one header entry is required. A column
      entry is required for each column with nonzero elements.
  
      +-------+------+----+--------+------+--------+-------+----+------+
      |   1   |  2   | 3  |    4   |   5  |   6    |   7   | 8  |  9   |
      +=======+======+====+========+======+========+=======+====+======+
      | DMIAX | NAME | 0  |   IFO  | TIN  |  TOUT  |       |    |      |
      +-------+------+----+--------+------+--------+-------+----+------+
  
      +-------+------+----+--------+------+--------+-------+----+------+
      |   1   |  2   | 3  |    4   |   5  |   6    |   7   | 8  |  9   |
      +=======+======+====+========+======+========+=======+====+======+
      | DMIAX | NAME | GJ |   CJ   |  NJ  |        |       |    |      |
      +-------+------+----+--------+------+--------+-------+----+------+
      |       |  G1  | C1 |   N1   |  A1  |   B1   |       |    |      |
      +-------+------+----+--------+------+--------+-------+----+------+
      |       |  G2  | C2 |  etc.  |      |        |       |    |      |
      +-------+------+----+--------+------+--------+-------+----+------+
  
      +-------+------+----+--------+------+--------+-------+----+------+
      |   1   |  2   | 3  |    4   |   5  |   6    |   7   | 8  |  9   |
      +=======+======+====+========+======+========+=======+====+======+
      | DMIAX | B2PP | 0  |   1    |  3   |        |       |    |      |
      +-------+------+----+--------+------+--------+-------+----+------+
      | DMIAX | B2PP | 32 |        |      |        |       |    |      |
      +-------+------+----+--------+------+--------+-------+----+------+
      |       | 1027 | 3  | 4.25+6 |      | 2.27+3 |       |    |      |
      +-------+------+----+--------+------+--------+-------+----+------+
  
      """
      type = 'DMIAX'
  
      def __init__(self, name, matrix_form, tin, tout, ncols,
                   GCNj, GCNi, Real, Complex=None, comment=''):
          """
          Creates a DMIAX card
  
          Parameters
          ----------
          name : str
              the name of the matrix
          matrix_form : int
              matrix shape
              1=Square
              2=General Rectangular
              6=Symmetric
          tin : int
              matrix input precision
              1=Real, Single Precision
              3=Complex, Single Precision
          tout : int
              matrix output precision
              1=Real, Single Precision
              2=Real, Double Precision
              3=Complex, Single Precision
              4=Complex, Double Precision
          GCNj  : List[(node, dof, harmonic_number)]???
              the jnode, jDOFs
          GCNi  : List[(node, dof, harmonic_number)]???
              the inode, iDOFs
          Real : List[float]???
              The real values
          Complex : List[float]???; default=None
              The complex values (if the matrix is complex)
          comment : str; default=''
              a comment for the card
  
          """
          ncols = None
  
          if comment:
              self.comment = comment
  
          if Complex is None:
              Complex = []
  
          if tout is None:
              tout = 0
  
          self.name = name
  
          #: ifo/4-Lower Triangular; 5=Upper Triangular; 6=Symmetric; 8=Identity (m=nRows, n=m)
          self.matrix_form = matrix_form
  
          #: 1-Real, Single Precision; 2=Real,Double Precision;
          #  3=Complex, Single; 4=Complex, Double
          self.tin = tin
  
          #: 0-Set by cell precision
          self.tout = tout
  
          self.ncols = ncols
          self.GCNj = GCNj
          self.GCNi = GCNi
  
          self.Real = Real
          if len(Complex) or self.is_complex:
              self.Complex = Complex
              if matrix_form not in [1]:  #4, 5, 6, 8
                  msg = (
                      f'{self.type} name={name!r} matrix_form={matrix_form!r} '
                      'must be [1, 2, 6]\n'
                      '  1: Square\n'
                      '  2: General Rectangular\n'
                      '  4: Lower Triangular\n'
                      '  5: Upper Triangular\n'
                      '  6: Symmetric\n'
                      '  8: Identity (m=nRows, n=m)\n')
                  raise ValueError(msg)
  
          assert isinstance(matrix_form, integer_types), 'matrix_form=%r type=%s' % (matrix_form, type(matrix_form))
          assert not isinstance(matrix_form, bool), 'matrix_form=%r type=%s' % (matrix_form, type(matrix_form))
  
      def finalize(self):
          """converts the lists into numpy arrays"""
          return
          #self.GCi = np.asarray(self.GCi)
          #self.GCj = np.asarray(self.GCj)
          self.Real = np.asarray(self.Real)
          if self.is_complex:
              self.Complex = np.asarray(self.Complex)
  
      @classmethod
      def export_to_hdf5(cls, h5_file, model, encoding):
          _export_dmiax_to_hdf5(h5_file, model, model.dmiax, encoding)
  
      @property
      def is_real(self):
          """is the matrix real?"""
          if self.tin in [1, 2]:
              return True
          return False
  
      @property
      def is_complex(self):
          """is the matrix complex"""
          return not self.is_real
  
      @property
      def is_polar(self):
          """is the matrix polar (vs real/imag)?"""
          return False
  
      @property
      def tin_dtype(self):
          """gets the input dtype"""
          return _get_dtype(self.is_complex, self.tin)
  
      @property
      def tout_dtype(self):
          """gets the output dtype"""
          return _get_dtype(self.is_complex, self.tout)
  
      @property
      def matrix_type(self):
          """gets the matrix type"""
          if not isinstance(self.matrix_form, integer_types):
              msg = 'ifo must be an integer; matrix_form=%r type=%s name=%s' % (
                  self.matrix_form, type(self.matrix_form), self.name)
              raise TypeError(msg)
          if isinstance(self.matrix_form, bool):
              msg = 'matrix_form must not be a boolean; matrix_form=%r type=%s name=%s' % (
                  self.matrix_form, type(self.matrix_form), self.name)
              raise TypeError(msg)
  
          if self.matrix_form == 1:
              matrix_type = 'square'
          #elif self.matrix_form == 6:
              #matrix_type = 'symmetric'
          #elif self.matrix_form in [2, 9]:
              #matrix_type = 'rectangular'
          else:
              # technically right, but nulling this will fix bad decks
              #self.ncols = blank(card, 8, 'matrix_form=%s; ncol' % self.matrix_form)
              raise NotImplementedError(f'{self.type} matrix_form={self.matrix_form} '
                                        'is not supported')
          return matrix_type
  
      @classmethod
      def add_card(cls, card, comment=''):
          """
          Adds a NastranMatrix (DMIAX) card from ``BDF.add_card(...)``
  
          Parameters
          ----------
          card : BDFCard()
              a BDFCard object
          comment : str; default=''
              a comment for the card
  
          """
          name = string(card, 1, 'name')
          #zero
  
          matrix_form = integer(card, 3, 'ifo')
          tin = integer(card, 4, 'tin')
          tout = integer_or_blank(card, 5, 'tout', 0)
          if matrix_form == 1: # square
              ncols = integer_or_blank(card, 8, 'matrix_form=%s; ncol' % matrix_form)
          elif matrix_form == 6: # symmetric
              ncols = integer_or_blank(card, 8, 'matrix_form=%s; ncol' % matrix_form)
          elif matrix_form in [2, 9]: # rectangular
              ncols = integer(card, 8, 'matrix_form=%s; ncol' % matrix_form)
          else:
              # technically right, but nulling this will fix bad decks
              #self.ncols = blank(card, 8, 'matrix_form=%s; ncol' % self.matrix_form)
              raise NotImplementedError('matrix_form=%s is not supported' % matrix_form)
  
          GCj = []
          GCi = []
          Real = []
          Complex = []
          return DMIAX(name, matrix_form, tin, tout, ncols,
                       GCj, GCi, Real, Complex, comment=comment)
  
      def _add_column(self, card, comment=''):
          if comment:
              if hasattr(self, '_comment'):
                  self.comment += comment
              else:
                  self.comment = comment
  
          unused_name = string(card, 1, 'name')
  
          Gj = integer(card, 2, 'Gj')
          # Cj = integer(card, 3, 'Cj')
          Cj = integer_or_blank(card, 3, 'Cj', 0)
          #Cj = parse_components(card, 3, 'Cj')
          Nj = integer_or_blank(card, 4, 'Nj')
  
          assert 0 <= Cj <= 6, 'C%i must be between [0, 6]; Cj=%s' % (0, Cj)
  
          nfields = len(card)
          #print("nfields = %i" % nfields)
          #print("card[5:] =", card[5:])
          #print("(nfields - 5) %% 4 = %i" % ((nfields - 5) % 4))
  
          nloops = (nfields - 8) // 8
          if nfields - 8 % 8:
              nloops += 1
          #assert nfields <= 8,'nfields=%s' % nfields
          #print("nloops = %i" % nloops)
          assert nloops > 0, 'nloops=%s' % nloops
  
          self.GCNj.append((Gj, Cj, Nj))
          GCNi = []
          self.GCNi.append(GCNi)
          if self.is_complex:
              for i in range(nloops):
                  #print(dir(card))
                  n = 9 + 8 * i
                  Gi = integer(card, n, f'Gi{i}')
                  # Ci = integer(card, n + 1, 'Ci')
                  Ci = integer_or_blank(card, n + 1, f'Ci{i}', 0)
                  #Ci = parse_components(card, n + 1, 'Ci')
                  Ni = integer_or_blank(card, n + 2, f'Ni{i}')
  
                  assert 0 <= Ci <= 6, 'C%i must be between [0, 6]; Ci=%s' % (i + 1, Ci)
                  GCNi.append((Gi, Ci, Ni))
                  reali = double(card, n + 3, 'real')
                  complexi = double(card, n + 4, 'complex')
                  self.Real.append(reali)
                  self.Complex.append(complexi)
          else:
              # real
              for i in range(nloops):
                  n = 9 + 9 * i
                  Gi = integer(card, n, 'Gi')
                  # Ci = integer(card, n + 1, 'Ci')
                  Ci = integer_or_blank(card, n + 1, 'Ci', 0)
                  #Ci = parse_components(card, n + 1, 'Ci')
                  Ni = integer(card, n + 2, 'Ni')
  
                  assert 0 <= Ci <= 6, 'C%i must be between [0, 6]; Ci=%s' % (i + 1, Ci)
                  reali = double(card, n + 3, 'real')
                  GCNi.append((Gi, Ci, Ni))
                  self.Real.append(reali)
                  #print("GC=%s,%s real=%s" % (Gi, Ci, reali))
  
          msg = '(len(GCNj)=%s len(GCNi)=%s' % (len(self.GCNj), len(self.GCNi))
          assert len(self.GCNj) == len(self.GCNi), msg
          #if self.is_complex:
              #self.Complex(double(card, v, 'complex')
  
      def raw_fields(self):
          list_fields = [
              'DMIAX', self.name, 0, self.matrix_form, self.tin, None, None, None, self.ncols,
          ]
          k = 0
          if self.is_real:
              for i, GCNj in enumerate(self.GCNj):
                  gj, cj, nj = GCNj
                  list_fields += ['DMIAX', self.name, gj, cj, nj, None, None, None, None]
                  for unused_j, GCNi in enumerate(self.GCNi[i]):
                      gi, ci, ni = GCNi
                      reali = self.Real[k]
                      list_fields += [gi, ci, ni, reali, None, None, None, None]
                      k += 1
          else:
              for i, GCNj in enumerate(self.GCNj):
                  gj, cj, nj = GCNj
                  list_fields += ['DMIAX', self.name, gj, cj, nj, None, None, None, None]
                  for unused_j, GCNi in enumerate(self.GCNi[i]):
                      gi, ci, ni = GCNi
                      reali = self.Real[k]
                      imagi = self.Complex[k]
                      list_fields += [gi, ci, ni, reali, imagi, None, None, None, None]
                      k += 1
  
          self.write_card()
          return list_fields
  
      def write_card(self, size: int=8, is_double: bool=False) -> str:
          if self.tin in [1, 3]:
              is_double = False
              msg = self.write_card_8()
          elif self.tin in [2, 4]:
              is_double = True
              size = 16
              msg = self.write_card_16()
          else:
              raise RuntimeError('tin=%r must be 1, 2, 3, or 4' % self.tin)
          return msg
  
      def write_card_8(self):
          """writes the card in small field format"""
          return self._write_card(print_card_8)
  
      def write_card_16(self):
          """writes the card in small large format"""
          return self._write_card(print_card_16)
  
      def _write_card(self, func):
          """writes the card"""
          msg = '\n$' + '-' * 80
          msg += f'\n$ DMIAX Matrix {self.name}\n'
          list_fields = [
              'DMIAX', self.name, 0, self.matrix_form, self.tin, None, None, None, self.ncols,
          ]
          msg += func(list_fields)
          k = 0
          assert len(self.GCNj) > 0, self.get_stats()
          assert len(self.GCNi) > 0, self.get_stats()
          if self.is_real:
              for i, GCNj in enumerate(self.GCNj):
                  gj, cj, nj = GCNj
                  list_fields = ['DMIAX', self.name, gj, cj, nj, None, None, None, None]
                  for unused_j, GCNi in enumerate(self.GCNi[i]):
                      gi, ci, ni = GCNi
                      reali = self.Real[k]
                      list_fields += [gi, ci, ni, reali, None, None, None, None]
                      k += 1
                  msg += func(list_fields)
          else:
              for i, GCNj in enumerate(self.GCNj):
                  gj, cj, nj = GCNj
                  list_fields = ['DMIAX', self.name, gj, cj, nj, None, None, None, None]
                  for unused_j, GCNi in enumerate(self.GCNi[i]):
                      gi, ci, ni = GCNi
                      reali = self.Real[k]
                      imagi = self.Complex[k]
                      list_fields += [gi, ci, ni, reali, imagi, None, None, None]
                      k += 1
                  msg += func(list_fields)
          return msg
  
      def __repr__(self):
          return self.write_card(size=8)
  
  class DMIJ(NastranMatrix):
      """
      Direct Matrix Input at js-Set of the Aerodynamic Mesh
      Defines direct input matrices related to collation degrees-of-freedom
      (js-set) of aerodynamic mesh points for CAERO1, CAERO3, CAERO4 and CAERO5
      and for the slender body elements of CAERO2. These include W2GJ, FA2J and
      input pressures and downwashes associated with AEPRESS and AEDW entries.
      The matrix is described by a single header entry and one or more column
      entries. A column entry is required for each column with nonzero elements.
      For entering data for the interference elements of a CAERO2, use DMIJI
      or DMI.
  
      """
      type = 'DMIJ'
      _properties = ['shape', 'ifo', 'is_real', 'is_complex', 'is_polar', 'matrix_type',
                     'tin_dtype', 'tout_dtype']
  
      @classmethod
      def _init_from_empty(cls):
          name = 'name'
          ifo = 1
          tin = 1
          tout = 1
          polar = 0
          ncols = 1
          GCj = []
          GCi = []
          Real = []
          return DMIJ(name, ifo, tin, tout, polar, ncols, GCj, GCi, Real,
                      Complex=None, comment='', finalize=True)
  
      @classmethod
      def export_to_hdf5(cls, h5_file, model, encoding):
          _export_dmig_to_hdf5(h5_file, model, model.dmij, encoding)
  
      def __init__(self, name, matrix_form, tin, tout, polar, ncols,
                   GCj, GCi, Real, Complex=None, comment='',
                   finalize=True):
          """
          Creates a DMIJ card
  
          Parameters
          ----------
          name : str
              the name of the matrix
          matrix_form : int
              matrix shape
              4=Lower Triangular
              5=Upper Triangular
              6=Symmetric
              8=Identity (m=nRows, n=m)
          tin : int
              matrix input precision
              1=Real, Single Precision
              2=Real, Double Precision
              3=Complex, Single Precision
              4=Complex, Double Precision
          tout : int
              matrix output precision
              0=same as tin
              1=Real, Single Precision
              2=Real, Double Precision
              3=Complex, Single Precision
              4=Complex, Double Precision
          polar : int; default=0
              Input format of Ai, Bi
              Integer=blank or 0 indicates real, imaginary format
              Integer > 0 indicates amplitude, phase format
          ncols : int
              ???
          GCj  : List[(node, dof)]???
              the jnode, jDOFs
          GCi  : List[(node, dof)]???
              the inode, iDOFs
          Real : List[float]???
              The real values
          Complex : List[float]???; default=None
              The complex values (if the matrix is complex)
          comment : str; default=''
              a comment for the card
  
          """
          NastranMatrix.__init__(self, name, matrix_form, tin, tout, polar, ncols,
                                 GCj, GCi, Real, Complex, comment=comment,
                                 finalize=finalize)
  
  
  class DMIJI(NastranMatrix):
      """
      Direct Matrix Input at js-Set of the Interference Body
      Defines direct input matrices related to collation degrees-of-freedom
      (js-set) of aerodynamic mesh points for the interference elements of CAERO2.
      These include W2GJ, FA2J and input pressures and downwashes associated with
      AEPRESS and AEDW entries. The matrix is described by a single header entry
      and one or more column entries. A column entry is required for each column
      with nonzero elements.  For entering data for the slender elements of a
      CAERO2, or a CAERO1, 3, 4 or 5 use DMIJ or DMI.
  
      """
      type = 'DMIJI'
      _properties = ['shape', 'ifo', 'is_real', 'is_complex', 'is_polar', 'matrix_type',
                     'tin_dtype', 'tout_dtype']
  
      #@classmethod
      #def _init_from_empty(cls):
          #name = 'name'
          #ifo = 1
          #tin = 1
          #tout = 1
          #polar = 0
          #ncols = 1
          #GCj = []
          #GCi = []
          #Real = []
          #return DMIJI(name, ifo, tin, tout, polar, ncols, GCj, GCi, Real,
                       #Complex=None, comment='', finalize=True)
  
      @classmethod
      def export_to_hdf5(cls, h5_file, model, encoding):
          _export_dmig_to_hdf5(h5_file, model, model.dmiji, encoding)
  
      def __init__(self, name, ifo, tin, tout, polar, ncols,
                   GCj, GCi, Real, Complex=None, comment='', finalize=True):
          """
          Creates a DMIJI card
  
          Parameters
          ----------
          name : str
              the name of the matrix
          ifo : int
              matrix shape
              4=Lower Triangular
              5=Upper Triangular
              6=Symmetric
              8=Identity (m=nRows, n=m)
          tin : int
              matrix input precision
              1=Real, Single Precision
              2=Real, Double Precision
              3=Complex, Single Precision
              4=Complex, Double Precision
          tout : int
              matrix output precision
              0=same as tin
              1=Real, Single Precision
              2=Real, Double Precision
              3=Complex, Single Precision
              4=Complex, Double Precision
          polar : int; default=0
              Input format of Ai, Bi
              Integer=blank or 0 indicates real, imaginary format
              Integer > 0 indicates amplitude, phase format
          ncols : int
              ???
          GCj  : List[(node, dof)]???
              the jnode, jDOFs
          GCi  : List[(node, dof)]???
              the inode, iDOFs
          Real : List[float]???
              The real values
          Complex : List[float]???; default=None
              The complex values (if the matrix is complex)
          comment : str; default=''
              a comment for the card
  
          """
          NastranMatrix.__init__(self, name, ifo, tin, tout, polar, ncols,
                                 GCj, GCi, Real, Complex, comment=comment,
                                 finalize=finalize)
  
  
  class DMIK(NastranMatrix):
      """
      Direct Matrix Input at ks-Set of the Aerodynamic Mesh
      Defines direct input matrices related to physical (displacement)
      degrees-of-freedom (ks-set) of aerodynamic grid points. These include WKK,
      WTFACT and input forces associated with AEFORCE entries. The matrix is
      described by a single header entry and one or more column entries. A column
      entry is required for each column with nonzero elements.
  
      +------+-------+----+-----+-----+------+-------+----+------+
      |   1  |   2   | 3  |  4  |  5  |   6  |   7   | 8  |  9   |
      +======+=======+====+=====+=====+======+=======+====+======+
      | DMIK | NAME  | 0  | IFO | TIN | TOUT | POLAR |    | NCOL |
      +------+-------+----+-----+-----+------+-------+----+------+
      | DMIK | NAME  | GJ | CJ  |     |  G1  |  C1   | A1 |  B1  |
      +------+-------+----+-----+-----+------+-------+----+------+
      |      |  G2   | C2 | A2  |  B2 |      |       |    |      |
      +------+-------+----+-----+-----+------+-------+----+------+
      | DMIK | ALPH1 | 0  |  9  |  2  |  0   |   1   |    |      |
      +------+-------+----+-----+-----+------+-------+----+------+
      | DMIK | ALPH1 | 1  |  1  |  1  |  1   |  1.0  |    |      |
      +------+-------+----+-----+-----+------+-------+----+------+
      |      |   2   | 1  | 1.0 |     |      |       |    |      |
      +------+-------+----+-----+-----+------+-------+----+------+
      """
      type = 'DMIK'
      _properties = ['shape', 'ifo', 'is_real', 'is_complex', 'is_polar', 'matrix_type',
                     'tin_dtype', 'tout_dtype']
  
      #@classmethod
      #def _init_from_empty(cls):
          #name = 'name'
          #ifo = 1
          #tin = 1
          #tout = 1
          #polar = 0
          #ncols = 1
          #GCj = []
          #GCi = []
          #Real = []
          #return DMIK(name, ifo, tin, tout, polar, ncols, GCj, GCi, Real,
                      #Complex=None, comment='', finalize=True)
  
      @classmethod
      def export_to_hdf5(cls, h5_file, model, encoding):
          _export_dmig_to_hdf5(h5_file, model, model.dmik, encoding)
  
      def __init__(self, name, ifo, tin, tout, polar, ncols,
                   GCj, GCi, Real, Complex=None, comment='', finalize=True):
          """
          Creates a DMIK card
  
          Parameters
          ----------
          name : str
              the name of the matrix
          ifo : int
              matrix shape
              4=Lower Triangular
              5=Upper Triangular
              6=Symmetric
              8=Identity (m=nRows, n=m)
          tin : int
              matrix input precision
              1=Real, Single Precision
              2=Real, Double Precision
              3=Complex, Single Precision
              4=Complex, Double Precision
          tout : int
              matrix output precision
              0=same as tin
              1=Real, Single Precision
              2=Real, Double Precision
              3=Complex, Single Precision
              4=Complex, Double Precision
          polar : int; default=0
              Input format of Ai, Bi
              Integer=blank or 0 indicates real, imaginary format
              Integer > 0 indicates amplitude, phase format
          ncols : int
              ???
          GCj  : List[(node, dof)]
              the jnode, jDOFs
          GCi  : List[(node, dof)]
              the inode, iDOFs
          Real : List[float]
              The real values
          Complex : List[float]; default=None
              The complex values (if the matrix is complex)
          comment : str; default=''
              a comment for the card
  
          """
          NastranMatrix.__init__(self, name, ifo, tin, tout, polar, ncols,
                                 GCj, GCi, Real, Complex, comment=comment,
                                 finalize=finalize)
  
  
  class DMI(NastranMatrix):
      """
      +------+-------+------+------+---------+----------+-----------+-----------+------+
      |  1   |   2   |  3   |   4  |    5    |    6     |     7     | 8         |  9   |
      +======+=======+======+======+=========+==========+===========+===========+======+
      | DMI  |  NAME |  0   | FORM |   TIN   |   TOUT   |           |     M     |  N   |
      +------+-------+------+------+---------+----------+-----------+-----------+------+
      | DMI  |  NAME |  J   |  I1  | A(I1,J) |  A(I1,J) | A(I1+1,J) | A(I1+2,J) | etc. |
      +------+-------+------+------+---------+----------+-----------+-----------+------+
      |      |  I2   | etc. |      |         |          |           |           |      |
      +------+-------+------+------+---------+----------+-----------+-----------+------+
      """
      type = 'DMI'
      _properties = ['shape', 'ifo', 'is_real', 'is_complex', 'is_polar', 'matrix_type',
                     'tin_dtype', 'tout_dtype']
  
      @classmethod
      def _init_from_empty(cls):
          name = 'name'
          matrix_form = 8
          tin = 1
          tout = 1
          nrows = 5
          ncols = 5
          GCj = []
          GCi = []
          Real = []
          return DMI(name, matrix_form, tin, tout, nrows, ncols, GCj, GCi, Real,
                     Complex=None, comment='', finalize=False)
  
      @classmethod
      def export_to_hdf5(cls, h5_file, model, encoding):
          _export_dmig_to_hdf5(h5_file, model, model.dmi, encoding)
  
      def __init__(self, name, matrix_form, tin, tout, nrows, ncols,
                   GCj, GCi, Real, Complex=None, comment='', finalize=True):
          #NastranMatrix.__init__(self, name, ifo, tin, tout, polar, ncols,
                                 #GCj, GCi, Real, Complex, comment='')
          if comment:
              self.comment = comment
  
          if Complex is None:
              Complex = []
  
          if tout is None:
              tout = 0
  
          if matrix_form not in [1, 2, 3, 4, 5, 6, 8]:
              msg = (
                  '%s name=%r matrix_form=%r must be [1, 2, 3, 4, 5, 6, 8]\n'
                  '  1: Square\n'
                  '  2: Rectangular\n'
                  '  3: Diagonal matrix (M=number of rows, N=1)\n'
                  '  4: Lower Triangular\n'
                  '  5: Upper Triangular\n'
                  '  6: Symmetric\n'
                  '  8: Identity (m=nRows, n=m)\n'
                  #'  9: Rectangular\n'
                  % (self.type, name, matrix_form))
              raise ValueError(msg)
  
          self.name = name
          self.matrix_form = matrix_form
          self.tin = tin
          self.tout = tout
          self.nrows = nrows
          self.ncols = ncols
          self.GCi = GCi
          self.GCj = GCj
          self.Real = Real
          if len(Complex) or self.is_complex:
              self.Complex = Complex
          if finalize:
              self.finalize()
  
      #@property
      #def form(self):
          #"""gets the matrix_form"""
          #self.deprecated('form', 'matrix_form', '1.1')
          #return self.matrix_form
  
      #@form.setter
      #def form(self, matrix_form):
          #"""sets the matrix_form"""
          #self.deprecated('form', 'matrix_form', '1.1')
          #self.matrix_form = matrix_form
  
      @classmethod
      def add_card(cls, card, comment=''):
          """
          Adds a DMI card from ``BDF.add_card(...)``
  
          Parameters
          ----------
          card : BDFCard()
              a BDFCard object
          comment : str; default=''
              a comment for the card
  
          """
          name = string(card, 1, 'name')
          #zero
  
          #: Form of the matrix:  1=Square (not symmetric); 2=Rectangular;
          #: 3=Diagonal (m=nRows,n=1);  4=Lower Triangular; 5=Upper Triangular;
          #: 6=Symmetric; 8=Identity (m=nRows, n=m)
          matrix_form = integer(card, 3, 'form')
  
          #: 1-Real, Single Precision; 2=Real,Double Precision;
          #: 3=Complex, Single; 4=Complex, Double
          tin = integer(card, 4, 'tin')
  
          #: 0-Set by cell precision
          tout = integer_or_blank(card, 5, 'tout', 0)
  
          nrows = integer(card, 7, 'nrows')
          ncols = integer(card, 8, 'ncols')
  
          assert len(card) == 9, 'len(DMI card) = %i\ncard=%s' % (len(card), card)
  
          GCj = []
          GCi = []
          Real = []
          Complex = []
          return DMI(name, matrix_form, tin, tout, nrows, ncols,
                     GCj, GCi, Real, Complex, comment=comment, finalize=False)
  
      def finalize(self):
          self.GCi = np.asarray(self.GCi)
          self.GCj = np.asarray(self.GCj)
          self.Real = np.asarray(self.Real)
          if self.is_complex:
              self.Complex = np.asarray(self.Complex)
  
      @property
      def matrix_type(self):
          """
          gets the matrix type
  
          1 Square matrix (not symmetric)
          2 General rectangular matrix
          3 Diagonal matrix (M=number of rows, N = 1)
          #4 Lower triangular factor
          #5 Upper triangular factor
          6 Symmetric matrix
          8 Identity matrix (M=number of rows, N = M)
          """
          if not isinstance(self.matrix_form, integer_types):
              msg = 'ifo must be an integer; matrix_form=%r type=%s name=%s' % (
                  self.matrix_form, type(self.matrix_form), self.name)
              raise TypeError(msg)
          if isinstance(self.matrix_form, bool):
              msg = 'matrix_form must not be a boolean; matrix_form=%r type=%s name=%s' % (
                  self.matrix_form, type(self.matrix_form), self.name)
              raise TypeError(msg)
  
          if self.matrix_form == 1:
              matrix_type = 'square'
          elif self.matrix_form == 2: # 9 ???
              matrix_type = 'rectangular'
          elif self.matrix_form == 3:
              matrix_type = 'diagonal'
          elif self.matrix_form == 6:
              matrix_type = 'symmetric'
          elif self.matrix_form == 9:
              matrix_type = 'identity'
          else:
              raise NotImplementedError('%s matrix_form=%r is not supported' % (
                  self.type, self.matrix_form))
          return matrix_type
  
      @property
      def is_polar(self):
          if self.tin in [1, 2]:
              is_polar = False
          elif self.tin in [3, 4]:
              is_polar = False # TODO: could be wrong...
          else:
              raise NotImplementedError('nrows=%s ncols=%s' % (self.nrows, self.ncols))
          return is_polar
  
      @property
      def shape(self):
          return (self.nrows, self.ncols)
  
      @property
      def ifo(self):
          """
          ifo
          #: 4-Lower Triangular; 5=Upper Triangular; 6=Symmetric; 8=Identity (m=nRows, n=m)
  
          #: Form of the matrix:  1=Square (not symmetric); 2=Rectangular;
          #: 3=Diagonal (m=nRows,n=1);  4=Lower Triangular; 5=Upper Triangular;
          #: 6=Symmetric; 8=Identity (m=nRows, n=m)
          self.matrix_form = integer(card, 3, 'matrix_form')
  
          """
          return self.matrix_form
          #if self.nrows == self.ncols:
              ## symmetric
              #ifo = 6
          ##elif self.nrows > 1 and self.ncols > 1:
              ##ifo = 2
          #else:
              #raise NotImplementedError('matrix_form=%r nrows=%s ncols=%s' % (
                  #self.matrix_form, self.nrows, self.ncols))
          #return ifo
  
      def _add_column(self, card, comment=''):
          """
          .. todo:: support comment
          """
          if self.is_complex:
              self._read_complex(card)
          else:
              self._read_real(card)
  
      def _read_real(self, card):
          """reads a real DMI column"""
          # column number
          j = integer(card, 2, 'icol')
  
          # counter
          i = 0
          fields = [interpret_value(field, card) for field in card[3:]]
  
          # Real, starts at A(i1,j), goes to A(i2,j) in a column
          while i < len(fields):
              i1 = fields[i]
              if isinstance(i1, integer_types):
                  i += 1
                  is_done_reading_floats = False
                  while not is_done_reading_floats and i < len(fields):
                      real_value = fields[i]
                      if isinstance(real_value, integer_types):
                          is_done_reading_floats = True
                      elif isinstance(real_value, float):
                          #print('adding j=%s i1=%s val=%s' % (j, i1, real_value))
                          self.GCj.append(j)
                          self.GCi.append(i1)
                          self.Real.append(real_value)
                          i += 1
                          i1 += 1
                      else:
                          real_value = self.Real[-1]
                          end_i = fields[i + 1]
                          for ii in range(i1, end_i + 1):
                              #print('adding j=%s i1=%s val=%s' % (j, ii, real_value))
                              self.GCj.append(j)
                              self.GCi.append(ii)
                              self.Real.append(real_value)
                          i += 1
                          is_done_reading_floats = True
  
      def _read_complex(self, card):
          """reads a complex DMI column"""
          #msg = 'complex matrices not supported in the DMI reader...'
          #raise NotImplementedError(msg)
          # column number
          j = integer(card, 2, 'icol')
          # counter
          i = 0
          fields = [interpret_value(field, card) for field in card[3:]]
          # Complex, starts at A(i1,j)+imag*A(i1,j), goes to A(i2,j) in a column
          if 0: # pragma: no cover
              is_real = True
              gci = None
              for field in fields:
                  if isinstance(field, integer_types):
                      gci = field
                  elif isinstance(field, float):
                      if is_real:
                          real = field
                      else:
                          self.GCj.append(j)
                          self.GCi.append(gci)
                          self.Real.append(real)
                          self.Complex.append(field)
                      is_real = not is_real
  
          while i < len(fields):
              i1 = fields[i]
              assert isinstance(i1, int), card
              i += 1
              is_done_reading_floats = False
              while not is_done_reading_floats and i < len(fields):
                  value = fields[i]
                  #print("i=%s len(fields)=%s value=%s" % (
                      #i, len(fields), value))
                  if isinstance(value, integer_types):
                      is_done_reading_floats = True
                  elif isinstance(value, float):
                      complex_value = fields[i + 1]
                      assert isinstance(complex_value, float), card
                      self.GCj.append(j)
                      self.GCi.append(i1)
                      self.Real.append(value)
                      self.Complex.append(complex_value)
                      i += 2
                  else:
                      raise NotImplementedError()
  
      @property
      def is_real(self):
          """real vs. complex attribute"""
          return not self.is_complex
  
      @property
      def is_complex(self):
          """real vs. complex attribute"""
          if self.tin in [3, 4]:
              return True
          return False
  
      def raw_fields(self):
          """
          .. warning:: All the writers are bad because Nastran insists on
                        making columns a single DMI card.  This makes
                        writing a card much harder, so there are a lot of
                        NotImplementedErrors floating about.
  
                        This is an invalid method, but is not disabled
                        because it's currently needed for checking results
  
          """
          list_fields = ['DMI', self.name, 0, self.matrix_form, self.tin,
                         self.tout, None, self.nrows, self.ncols]
  
          if self.is_complex:
              for (gci, gcj, reali, imagi) in zip(self.GCi, self.GCj, self.Real, self.Complex):
                  list_fields += ['DMI', self.name, gcj, gci, reali, imagi]
          else:
              for (gci, gcj, reali) in zip(self.GCi, self.GCj, self.Real):
                  list_fields += ['DMI', self.name, gcj, gci, reali]
          return list_fields
  
      def write_card_8(self):
          """writes the card in single precision"""
          return self._write_card(print_card_8)
  
      def _get_real_fields(self, func):
          msg = ''
          uGCj = np.unique(self.GCj)
          for gcj in uGCj:
              i = np.where(gcj == self.GCj)[0]
              gcis = self.GCi[i]
              reals = self.Real[i]
              isort = np.argsort(gcis)
              list_fields = ['DMI', self.name, gcj]
  
              # will always write the first one
              gci_last = -1
              for gci, real in zip(gcis[isort], reals[isort]):
                  if gci == gci_last + 1:
                      pass
                  else:
                      list_fields.append(gci)
                  list_fields.append(real)
                  gci_last = gci
              msg += func(list_fields)
          return msg
  
      def _get_complex_fields(self, func):
          msg = ''
          uGCj = np.unique(self.GCj)
          for gcj in uGCj:
              i = np.where(gcj == self.GCj)[0]
              gcis = self.GCi[i]
              reals = self.Real[i]
              complexs = self.Complex[i]
              isort = np.argsort(gcis)
              list_fields = ['DMI', self.name, gcj]
  
              # will always write the first one
              gci_last = -10
              #print('gcis=%s \nreals=%s \ncomplexs=%s' % (
                  #gcis[isort], reals[isort], complexs[isort]))
              if max(gcis) == min(gcis):
                  list_fields += [gcis[0]]
                  for reali, complexi in zip(reals, complexs):
                      list_fields.extend([reali, complexi])
                  msg += func(list_fields)
              else:
                  #print(f'list_fields0 = {list_fields}')
                  for i, gci, reali, complexi in zip(count(), gcis[isort], reals[isort], complexs[isort]):
                      #print('B', gci, reali, complexi, gci_last)
                      if gci != gci_last + 1 and i != 0:
                          pass
                      else:
                          list_fields.append(gci)
                      list_fields.append(reali)
                      list_fields.append(complexi)
                      gci_last = gci
                  #print(f'list_fields = {list_fields}')
                  msg += func(list_fields)
          return msg
  
      def get_matrix(self, is_sparse=False, apply_symmetry=True):
          """
          Builds the Matrix
  
          Parameters
          ----------
          is_sparse : bool; default=False
              should the matrix be returned as a sparse matrix.
              Slower for dense matrices.
          apply_symmetry : bool; default=True
              If the matrix is symmetric (ifo=6), returns a symmetric matrix.
              Supported as there are symmetric matrix routines.
  
          Returns
          -------
          M : numpy.ndarray or scipy.coomatrix
              the matrix
          rows : dict[int] = [int, int]
              dictionary of keys=rowID, values=(Grid,Component) for the matrix
          cols: dict[int] = [int, int]
              dictionary of keys=columnID, values=(Grid,Component) for the matrix
  
          .. warning:: is_sparse=True WILL fail
  
          """
          return get_dmi_matrix(self, is_sparse=is_sparse, apply_symmetry=apply_symmetry)
  
      def write_card_16(self):
          """writes the card in single precision"""
          return self._write_card(print_card_16)
  
      def write_card_double(self):
          """writes the card in double precision"""
          return self._write_card(print_card_16)
  
      def _write_card(self, func):
          """writes the card in single/double precision"""
          msg = '\n$' + '-' * 80
          msg += '\n$ %s Matrix %s\n' % ('DMI', self.name)
          list_fields = ['DMI', self.name, 0, self.matrix_form, self.tin,
                         self.tout, None, self.nrows, self.ncols]
          msg += print_card_8(list_fields)
  
          if self.is_complex:
              msg += self._get_complex_fields(func)
          else:
              msg += self._get_real_fields(func)
          return msg
  
      def write_card(self, size: int=8, is_double: bool=False) -> str:
          if size == 8:
              return self.write_card_8()
          if is_double:
              return self.write_card_double()
          return self.write_card_16()
  
      def __repr__(self):
          """
          .. todo:: support shortened output format.  There's a very low 1000
                    DMI cap, I assume this is entries and not matrices.
  
          """
          return self.write_card(size=8, is_double=False)
  
  
  def get_row_col_map(matrix, GCi, GCj, ifo):
      ndim = len(GCi.shape)
      #print('ndim=%s' % ndim)
      #print('GCj=%s' % GCj)
      #print('GCi=%s' % GCi)
      if ndim == 1:
          rows, cols, rows_reversed, cols_reversed = _get_row_col_map_1d(matrix, GCi, GCj, ifo)
      else:
          rows, cols, rows_reversed, cols_reversed = _get_row_col_map_2d(matrix, GCi, GCj, ifo)
  
      nrows = len(rows)
      ncols = len(cols)
      assert nrows > 0, 'nrows=%s' % nrows
      assert ncols > 0, 'ncols=%s' % ncols
      return nrows, ncols, ndim, rows, cols, rows_reversed, cols_reversed
  
  def _get_row_col_map_1d(matrix, GCi, GCj, ifo):
      """helper for ``get_row_col_map``"""
      rows = {}
      rows_reversed = {}
  
      cols = {}
      cols_reversed = {}
      i = 0
      #nrows = np.unique(GCi)
      #ncols = np.unique(GCj)
      for gci in GCi:
          if gci not in rows:
              rows[gci] = i
              rows_reversed[i] = gci
              i += 1
  
      if ifo == 6:
          # symmetric
          #print(GCj)
          for gcj in GCj:
              if gcj not in rows:
                  #print('row.gcj = %s' % str(gcj))
                  rows[gcj] = i
                  rows_reversed[i] = gcj
                  i += 1
          cols = rows
          cols_reversed = rows_reversed
      else:
          j = 0
          for gcj in GCj:
              if gcj not in cols:
                  cols[gcj] = j
                  cols_reversed[j] = gcj
                  j += 1
      return rows, cols, rows_reversed, cols_reversed
  
  def _get_row_col_map_2d(matrix, GCi, GCj, ifo):
      """helper for ``get_row_col_map``"""
      rows = {}
      rows_reversed = {}
  
      cols = {}
      cols_reversed = {}
      #print('i0=%s j0=%s' % (i, j))
      #nrows = len(GCi)
      #ncols = len(GCj)
      #rows_array = np.zeros((nrows, 2), dtype='int32')
      #cols_array = np.zeros((ncols, 2), dtype='int32')
      #for i, (nid, comp) in enumerate(GCi):
          ##print('i=%s nid=%s comp=%s nrows=%s rows_array.shape=%s' % (
              ##i, nid, comp, nrows, str(rows_array.shape)))
          #rows_array[i, :] = [nid, comp]
      #print('rows_array = \n%s' % rows_array)
  
      #for j, (nid, comp) in enumerate(GCj):
          #cols_array[j, :] = [nid, comp]
      #print('cols_array = \n%s' % cols_array)
  
      #print(GCi)
      #print(GCj)
      i = 0
      for (nid, comp) in GCi:
          gci = (nid, comp)
          if gci not in rows:
              #print('row.gci = %s' % str(gci))
              rows[gci] = i
              rows_reversed[i] = gci
              i += 1
      if ifo == 6:
          # symmetric
          for (nid, comp) in GCj:
              gcj = (nid, comp)
              if gcj not in rows:
                  #print('row.gcj = %s' % str(gcj))
                  rows[gcj] = i
                  rows_reversed[i] = gcj
                  i += 1
          cols = rows
          cols_reversed = rows_reversed
      else:
          j = 0
          for (nid, comp) in GCj:
              gcj = (nid, comp)
              if gcj not in cols:
                  #print('col.gcj = %s' % str(gcj))
                  cols[gcj] = j
                  cols_reversed[j] = gcj
                  j += 1
      return rows, cols, rows_reversed, cols_reversed
  
  def _fill_sparse_matrix(matrix, nrows, ncols):
      """helper method for get_matrix"""
      GCj = array(matrix.GCj, dtype='int32') - 1
      GCi = array(matrix.GCi, dtype='int32') - 1
      reals = array(matrix.Real, dtype='float32')
  
      # TODO: matrix size:  is this correct?
      nrows = max(GCi) + 1
      ncols = max(GCj) + 1
  
      dtype = _get_dtype(matrix.is_complex, matrix.tin)
      # TODO: no check for symmetry
      # TODO: no check for dtype
      if matrix.is_complex:
          complexs = array(matrix.Complex, dtype='float32')
          data = array([reals, complexs]).astype(complex)
      else:
          data = reals
  
      if matrix.matrix_form in [1, 6]:
          nrows = max(nrows, ncols)
          ncols = nrows
  
      #A = coo_matrix( (entries,(rows,cols)),shape=(nrows,ncols),dtype=dtype) # test
      sparse_matrix = coo_matrix((data, (matrix.GCi, matrix.GCj)),
                                 shape=(nrows, ncols), dtype=dtype)
      #sparse_matrix = coo_matrix( (data,(matrix.GCi,matrix.GCj)),shape=(i,j)) # old
      #sparse_matrix = coo_matrix( (data,(matrix.GCi,matrix.GCj)),shape=(nrows,ncols))
      #print(sparse_matrix.toarray())
      #print(sparse_matrix)
      return sparse_matrix
  
  
  def _fill_dense_rectangular_matrix(self, nrows, ncols, ndim, rows, cols, apply_symmetry):
      """helper method for get_matrix"""
      is_sparse = False
      if self.is_complex:
          dense_mat = zeros((nrows, ncols), dtype='complex128')
          if self.matrix_form == 6 and apply_symmetry:  # symmetric
              for (gcj, gci, reali, complexi) in zip(self.GCj, self.GCi,
                                                     self.Real, self.Complex):
                  i = rows[(gci[0], gci[1])]
                  j = cols[(gcj[0], gcj[1])]
                  dense_mat[i, j] = complex(reali, complexi)
                  dense_mat[j, i] = complex(reali, complexi)
          else:
              for (gcj, gci, reali, complexi) in zip(self.GCj, self.GCi,
                                                     self.Real, self.Complex):
                  i = rows[(gci[0], gci[1])]
                  j = cols[(gcj[0], gcj[1])]
                  dense_mat[i, j] = complex(reali, complexi)
      else:
          dense_mat = zeros((nrows, ncols), dtype='float64')
          if self.matrix_form == 6 and apply_symmetry:  # symmetric
              try:
                  for (gcj, gci, reali) in zip(self.GCj, self.GCi, self.Real):
                      i = rows[(gci[0], gci[1])]
                      j = cols[(gcj[0], gcj[1])]
                      dense_mat[i, j] = reali
                      dense_mat[j, i] = reali
              except IndexError:
                  msg = ('name=%s ndim=%s i=%s j=%s matrix_type=%s '
                         'is_polar=%s ncols=%s M.shape=%s\n' % (
                             self.name, ndim, i, j, self.matrix_type,
                             self.is_polar, self.ncols, dense_mat.shape))
                  msg += 'Rows:\n'
                  for i, row in enumerate(rows):
                      msg += 'i=%s row=%s\n' % (i, row)
                  raise RuntimeError(msg)
          else:
              try:
                  for (gcj, gci, reali) in zip(self.GCj, self.GCi, self.Real):
                      i = rows[(gci[0], gci[1])]
                      j = cols[(gcj[0], gcj[1])]
                      dense_mat[i, j] = reali
              except KeyError:
                  msg = ('name=%s ndim=%s gci=%s gcj=%s matrix_type=%s '
                         'is_polar=%s is_sparse=%s ncols=%s M.shape=%s\n\n' % (
                             self.name, ndim, str(gci), str(gcj), self.matrix_type,
                             self.is_polar, is_sparse, self.ncols, dense_mat.shape))
  
                  gci2 = (gci[0], gci[1])
                  gcj2 = (gcj[0], gcj[1])
                  if gci2 in rows:
                      msg += 'gci/row_key=%s found\n' % str(gci2)
                  else:
                      msg += 'gci/row_key=%s not found\n' % str(gci2)
                      msg += 'Rows:\n'
                      for i, row in enumerate(rows):
                          msg += '  i=%s row=%s\n' % (i, row)
  
                  if gcj2 in cols:
                      msg += '\ngcj/col_key=%s found\n' % str(gcj2)
                  else:
                      msg += '\ngcj/col_key=%s not found\n' % str(gcj2)
                      msg += 'Cols:\n'
                      for j, col in enumerate(cols):
                          msg += '  j=%s row=%s\n' % (j, col)
  
                  msg += '\n'
                  print(msg)
                  raise KeyError(msg)
              except IndexError:
                  msg = ('name=%s ndim=%s i=%s j=%s matrix_type=%s '
                         'is_polar=%s is_sparse=%s ncols=%s M.shape=%s\n' % (
                             self.name, ndim, i, j, self.matrix_type,
                             self.is_polar, is_sparse, self.ncols, dense_mat.shape))
                  msg += 'Rows:\n'
                  for i, row in enumerate(rows):
                      msg += '  i=%s row=%s\n' % (i, row)
  
                  msg += '\nCols:\n'
                  for j, row in enumerate(cols):
                      msg += '  j=%s row=%s\n' % (j, col)
                  raise RuntimeError(msg)
      return dense_mat
  
  
  def _fill_dense_column_matrix(self, nrows, ncols, ndim, rows, cols, apply_symmetry):
      """helper method for get_matrix"""
      is_sparse = False
      if self.is_complex:
          dense_mat = zeros((nrows, ncols), dtype='complex128')
          if self.matrix_form == 6 and apply_symmetry:  # symmetric
              assert nrows == ncols, 'nrows=%s ncols=%s' % (nrows, ncols)
              for (gcj, gci, reali, complexi) in zip(self.GCj, self.GCi,
                                                     self.Real, self.Complex):
                  i = rows[gci]
                  j = cols[gcj]
                  dense_mat[i, j] = complex(reali, complexi)
                  dense_mat[j, i] = complex(reali, complexi)
          elif self.matrix_form == 2:  # rectangular
              assert nrows == ncols, 'nrows=%s ncols=%s' % (nrows, ncols)
              for (gcj, gci, reali, complexi) in zip(self.GCj, self.GCi,
                                                     self.Real, self.Complex):
                  i = rows[gci]
                  j = cols[gcj]
                  dense_mat[i, j] = complex(reali, complexi)
          else:
              for (gcj, gci, reali, complexi) in zip(self.GCj, self.GCi,
                                                     self.Real, self.Complex):
                  i = rows[gci]
                  j = cols[gcj]
      else:
          #print('nrows=%s ncols=%s' % (nrows, ncols))
          dense_mat = zeros((nrows, ncols), dtype='float64')
          if self.matrix_form == 6 and apply_symmetry:  # symmetric
              assert nrows == ncols, 'nrows=%s ncols=%s' % (nrows, ncols)
              for (gcj, gci, reali) in zip(self.GCj, self.GCi, self.Real):
                  i = rows[gci]
                  j = cols[gcj]
                  dense_mat[i, j] = reali
                  dense_mat[j, i] = reali
          else:
              try:
                  for (gcj, gci, reali) in zip(self.GCj, self.GCi, self.Real):
                      i = rows[gci]
                      j = cols[gcj]
                      dense_mat[i, j] = reali
              except IndexError:
                  msg = ('name=%s ndim=%s i=%s j=%s matrix_type=%s '
                         'is_polar=%s is_sparse=%s ncols=%s M.shape=%s\n' % (
                             self.name, ndim, i, j, self.matrix_type,
                             self.is_polar, is_sparse, self.ncols, dense_mat.shape))
                  msg += 'Rows:\n'
                  for i, row in enumerate(rows):
                      msg += '  i=%s row=%s\n' % (i, row)
                  raise RuntimeError(msg)
      return dense_mat
  
  def get_dmi_matrix(matrix: DMI, is_sparse: bool=False,
                     apply_symmetry: bool=True) -> Tuple[np.array, None, None]:
      """
      Builds the Matrix
  
      Parameters
      ----------
      is_sparse : bool
          should the matrix be returned as a sparse matrix (default=True).
          Slower for dense matrices.
      apply_symmetry: bool
          If the matrix is symmetric (matrix_form=6), returns a symmetric matrix.
          Supported as there are symmetric matrix routines.
          TODO: unused...
  
      Returns
      -------
      M : ndarray
          the matrix
      rows : None
          unused
      cols : None
          unused
  
      .. warning:: is_sparse=True WILL fail
  
      """
      ifo = matrix.ifo
      GCj = array(matrix.GCj, dtype='int32') - 1
      GCi = array(matrix.GCi, dtype='int32') - 1
  
      dtype = matrix.tin_dtype
  
      if matrix.is_complex:
          data = matrix.Real + matrix.Complex * 1j
      else:
          data = matrix.Real
  
      if ifo == 2:
          # rectangular
          nrows = matrix.nrows
          ncols = matrix.ncols
  
          M = coo_matrix((data, (GCi, GCj)),
                         shape=(nrows, ncols), dtype=dtype)
          if not is_sparse:
              M = M.toarray()
  
      else:
          nrows = matrix.nrows
          ncols = matrix.ncols
          if ifo == 6:
              nrows = max(nrows, ncols)
              ncols = nrows
          M = coo_matrix((data, (GCi, GCj)),
                         shape=(nrows, ncols), dtype=dtype)
          if not is_sparse:
              M = M.toarray()
      #else:
          #ifo : int
          #    matrix shape
          #    4=Lower Triangular
          #    5=Upper Triangular
          #    6=Symmetric
          #    8=Identity (m=nRows, n=m)
          #raise RuntimeError(matrix.get_stats())
      return M, None, None
  
  def get_matrix(self, is_sparse=False, apply_symmetry=True):
      """
      Builds the Matrix
  
      Parameters
      ----------
      is_sparse : bool
          should the matrix be returned as a sparse matrix (default=True).
          Slower for dense matrices.
      apply_symmetry: bool
          If the matrix is symmetric (matrix_form=6), returns a symmetric matrix.
          Supported as there are symmetric matrix routines.
          TODO: unused...
  
      Returns
      -------
      M : ndarray
          the matrix
      rows : Dict[(nid, nid)] = float
          dictionary of keys=rowID,    values=(Grid,Component) for the matrix
      cols : Dict[](int, int)] = float
          dictionary of keys=columnID, values=(Grid,Component) for the matrix
  
      .. warning:: is_sparse=True WILL fail
  
      """
      nrows, ncols, ndim, rows, cols, rows_reversed, cols_reversed = get_row_col_map(
          self, self.GCi, self.GCj, self.matrix_form)
      #print('rows = ', rows)
      #print('cols = ', cols)
      #print('i=%s j=%s' % (i, j))
      #nrows = len(rows2)
      #ncols = len(cols2)
  
      #A = ss.lil_matrix((3,3), dtype='d') # double precision
      #rows = []
      #cols = []
      #data = []
      #for i in range(3):
         #for j in range(3):
             #k = float((i+1)*(j+1))
             #rows.append(i)
             #cols.append(j)
             #data.append(k)
             #A[i,j] = k
  
      #is_sparse = False
      if is_sparse:
          M = _fill_sparse_matrix(self, nrows, ncols)
          return M
      else:
          if ndim == 1:
              M = _fill_dense_column_matrix(self, nrows, ncols, ndim, rows, cols, apply_symmetry)
          else:
              M = _fill_dense_rectangular_matrix(self, nrows, ncols, ndim, rows, cols, apply_symmetry)
  
          #print(M)
          return (M, rows_reversed, cols_reversed)
  
  
  def _export_dmig_to_hdf5(h5_file, model, dict_obj, encoding):
      """export dmigs, dmij, dmiji, dmik, dmi"""
      for name, dmig in dict_obj.items():
          dmig_group = h5_file.create_group(name)
          dmig_group.create_dataset('tin', data=dmig.tin)
  
          if hasattr(dmig, 'tout'):
              dmig_group.create_dataset('tout', data=dmig.tout)
  
          if dmig.type == 'DMIG' and name == 'UACCEL':
              if dmig.ncol is not None:
                  dmig_group.create_dataset('ncol', data=dmig.ncol)
              #load_seq_group = dmig_group.create_group('load_sequences')
  
              nids = []
              dofs = []
              values = []
              for lseq, ncx in sorted(dmig.load_sequences.items()):
                  lseq_group = dmig_group.create_group(str(lseq))
                  #list_fields += [lseq, None, None]
                  for (nid, dof, value) in ncx:
                      nids.append(nid)
                      dofs.append(int(dof))
                      values.append(value)
  
                      #print('nids =', nids)
                      #print('dofs =', dofs)
                      #print('values =', values)
                  lseq_group.create_dataset('nids', data=nids)
                  lseq_group.create_dataset('dofs', data=dofs)
                  lseq_group.create_dataset('values', data=values)
          else:
              if hasattr(dmig, 'nrows') and dmig.nrows is not None:
                  dmig_group.create_dataset('nrows', data=dmig.nrows)
              if dmig.ncols is not None:
                  dmig_group.create_dataset('ncols', data=dmig.ncols)
              if hasattr(dmig, 'polar'):
                  dmig_group.create_dataset('polar', data=dmig.polar)
  
              dmig_group.create_dataset('matrix_form', data=dmig.matrix_form)
              dmig_group.create_dataset('tin_dtype', data=dmig.tin_dtype)
              dmig_group.create_dataset('tout_dtype', data=dmig.tout_dtype)
  
              dmig_group.create_dataset('matrix_type', data=dmig.matrix_type)
              dmig_group.create_dataset('is_complex', data=dmig.is_complex)
              dmig_group.create_dataset('is_real', data=dmig.is_real)
              dmig_group.create_dataset('is_polar', data=dmig.is_polar)
  
              dmig_group.create_dataset('GCi', data=dmig.GCi)
              dmig_group.create_dataset('GCj', data=dmig.GCj)
              dmig_group.create_dataset('Real', data=dmig.Real)
              if hasattr(dmig, 'Complex') and dmig.Complex is not None:
                  dmig_group.create_dataset('Complex', data=dmig.Complex)
  
  
  def _export_dmiax_to_hdf5(h5_file, model, dict_obj, encoding):
      """export dmiax"""
      for name, dmiax in dict_obj.items():
          #print(f'exporting {dmiax.type} name={name!r}')
          dmiax_group = h5_file.create_group(name)
          dmiax_group.create_dataset('tin', data=dmiax.tin)
  
          if hasattr(dmiax, 'tout'):
              dmiax_group.create_dataset('tout', data=dmiax.tout)
  
          if hasattr(dmiax, 'nrows') and dmiax.nrows is not None:
              dmiax_group.create_dataset('nrows', data=dmiax.nrows)
          if dmiax.ncols is not None:
              dmiax_group.create_dataset('ncols', data=dmiax.ncols)
          if hasattr(dmiax, 'polar'):
              dmiax_group.create_dataset('polar', data=dmiax.polar)
  
          dmiax_group.create_dataset('matrix_form', data=dmiax.matrix_form)
          dmiax_group.create_dataset('tin_dtype', data=dmiax.tin_dtype)
          dmiax_group.create_dataset('tout_dtype', data=dmiax.tout_dtype)
  
          dmiax_group.create_dataset('matrix_type', data=dmiax.matrix_type)
          dmiax_group.create_dataset('is_complex', data=dmiax.is_complex)
          dmiax_group.create_dataset('is_real', data=dmiax.is_real)
          dmiax_group.create_dataset('is_polar', data=dmiax.is_polar)
  
          gcnj = []
          j_none_flags = []
  
          gcni = []
          i_none_flags = []
          for j, GCNj in enumerate(dmiax.GCNj):
              gj, cj, nj = GCNj
              is_none_flag_j = False
              if nj is None:
                  nj = 0
                  is_none_flag_j = True
              j_none_flags.append(is_none_flag_j)
              gcnj.append((gj, cj, nj))
              for unused_i, GCNi in enumerate(dmiax.GCNi[j]):
                  gi, ci, ni = GCNi
                  is_none_flag_i = False
                  if ni is None:
                      ni = 0
                      is_none_flag_i = True
                  i_none_flags.append(is_none_flag_i)
                  gcni.append((gi, ci, ni, j))
  
          dmiax_group.create_dataset('GCNi_j', data=gcni)
          dmiax_group.create_dataset('GCNj', data=gcnj)
          dmiax_group.create_dataset('i_none_flags', data=i_none_flags)
          dmiax_group.create_dataset('j_none_flags', data=j_none_flags)
  
          dmiax_group.create_dataset('Real', data=dmiax.Real)
          if hasattr(dmiax, 'Complex') and dmiax.Complex is not None:
              dmiax_group.create_dataset('Complex', data=dmiax.Complex)
  
  def _set_polar(polar):
      if polar in [None, 0, False]:
          polar = 0
      elif polar in [1, True]:
          polar = 1
      else:
          raise ValueError('polar=%r and must be 0 or 1' % polar)
      return polar
  
  def _get_dtype(is_complex, type_flag):
      if type_flag == 1:
          dtype = 'float32'
      elif type_flag == 2:
          dtype = 'float64'
      elif type_flag == 3:
          dtype = 'complex64'
      elif type_flag == 4:
          dtype = 'complex128'
      elif type_flag == 0:
          if is_complex:
              dtype = 'complex128'
          else:
              dtype = 'float64'
      else:
          raise RuntimeError("invalid option for matrix format")
      return dtype
  
  
apis: numpy.hstack numpy.unique
apis: numpy.zeros numpy.argsort numpy.asarray scipy.sparse.coo_matrix numpy.array numpy.where numpy.unique