# coding: utf-8
"""
All dynamic loads are defined in this file. This includes:
* ACSRCE
* DLOAD
* TLOAD1
* TLOAD2
* RLOAD1
* RLOAD2
"""
from __future__ import annotations
import warnings
from typing import Union, TYPE_CHECKING
import numpy as np
from pyNastran.utils.numpy_utils import integer_types
from pyNastran.bdf import MAX_INT
from pyNastran.bdf.field_writer_8 import set_blank_if_default
from pyNastran.bdf.bdf_interface.assign_type import (
integer, double_or_blank, integer_string_or_blank,
integer_double_or_blank, double)
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.cards.loads.loads import DynamicLoad, LoadCombination, BaseCard
if TYPE_CHECKING: # pragma: no cover
from pyNastran.bdf.bdf import BDF
from pyNastran.bdf.bdf_interface.bdf_card import BDFCard
[docs]
class ACSRCE(BaseCard):
r"""
Defines acoustic source as a function of power vs. frequency.
+--------+-----+----------+---------------+-----------------+-------+-----+---+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
+========+=====+==========+===============+=================+=======+=====+===+
| ACSRCE | SID | EXCITEID | DELAYI/DELAYR | DPHASEI/DPHASER | TP/RP | RHO | B |
+--------+-----+----------+---------------+-----------------+-------+-----+---+
..math ::
C = \sqrt(B ⁄ ρ)
Source Strength = {A} * 1/(2πf) * \sqrt( 8πC P(f) / ρ) ^ (ei(θ + 2πfτ))
"""
type = 'ACSRCE'
[docs]
@classmethod
def _init_from_empty(cls):
sid = 1
excite_id = 2
rho = 3.
b = 5.
return ACSRCE(sid, excite_id, rho, b,
delay=0, dphase=0, power=0, comment='')
def __init__(self, sid: int, excite_id: int, rho: float, b: float,
delay: Union[int, float]=0,
dphase: Union[int, float]=0,
power: Union[int, float]=0, comment=''):
"""
Creates an ACSRCE card
Parameters
----------
sid : int
load set id number (referenced by DLOAD)
excite_id : int
Identification number of a DAREA or SLOAD entry that lists
each degree of freedom to apply the excitation and the
corresponding scale factor, A, for the excitation
rho : float
Density of the fluid
b : float
Bulk modulus of the fluid
delay : int; default=0
Time delay, τ.
dphase : int / float; default=0
the dphase; if it's 0/blank there is no phase lag
float : delay in units of time
int : delay id
power : int; default=0
Power as a function of frequency, P(f).
float : value of P(f) used over all frequencies for all
degrees of freedom in EXCITEID entry.
int : TABLEDi entry that defines P(f) for all degrees of
freedom in EXCITEID entry.
comment : str; default=''
a comment for the card
"""
if comment:
self.comment = comment
self.sid = sid
self.excite_id = excite_id
self.delay = delay
self.dphase = dphase
self.power = power
self.rho = rho
self.b = b
self.power_ref = None
self.sloads_ref = None
self.delay_ref = None
self.dphase_ref = None
#self.dphases_ref = None
#self.delays_ref = None
[docs]
@classmethod
def add_card(cls, card: BDFCard, comment: str=''):
"""
Adds a ACSRCE card from ``BDF.add_card(...)``
Parameters
----------
card : BDFCard()
a BDFCard object
comment : str; default=''
a comment for the card
"""
sid = integer(card, 1, 'sid')
excite_id = integer(card, 2, 'excite_id') # DAREA, FBALOAD, SLOAD
delay = integer_double_or_blank(card, 3, 'delay', default=0) # DELAY, FBADLAY
dphase = integer_double_or_blank(card, 4, 'dphase', default=0) # DPHASE, FBAPHAS
power = integer_double_or_blank(card, 5, 'power/tp/rp', default=0) # TABLEDi/power
rho = double(card, 6, 'rho')
b = double(card, 7, 'bulk modulus')
assert len(card) <= 8, 'len(ACSRCE card) = %i\n%s' % (len(card), card)
return ACSRCE(sid, excite_id, rho, b,
delay=delay, dphase=dphase, power=power, comment=comment)
[docs]
def cross_reference(self, model: BDF) -> None:
"""
Cross links the card so referenced cards can be extracted directly
Parameters
----------
model : BDF()
the BDF object
"""
cmsg = ', which is required by ACSRCE=%s' % (self.sid)
# TODO: excite_id = DAREA, FBALOAD, SLOAD
sloads_ref = {}
lseqs_ref = {}
for load_id, loads in model.loads.items():
for load in loads:
if load.type == 'SLOAD':
#if load_id not in sloads_ref:
#sloads_ref[load_id] = []
for nid in load.node_ids:
sloads_ref[(load_id, nid, 0)] = load
elif load.type == 'LSEQ':
load_idi = load.lid_ref[0].sid
#print(load)
#print(load.lid)
excite_idi = load.excite_id
#print('load_idi = %s' % load_idi)
#print('excite_id = %s' % excite_idi)
assert load_idi not in lseqs_ref
lseqs_ref[load_idi] = load
if sloads_ref:
self.sloads_ref = sloads_ref
sload_keys = list(sloads_ref.keys())
#print('sload_keys =', sload_keys)
else:
sload_keys = []
if self.excite_id not in model.dareas and self.excite_id not in lseqs_ref:
darea_keys = list(model.dareas.keys())
dphase_keys = list(model.dphases.keys())
delay_keys = list(model.delays.keys())
msg = 'excite_id=%s delay=%s dphase=%s\n' % (
self.excite_id, self.delay, self.dphase)
msg += ' darea_keys=%s\n' % darea_keys
msg += ' sloads(load_id, nid, comp)=%s\n' % sload_keys
msg += ' dphases(sid)=%s\n' % dphase_keys
msg += ' delays(delay_id)=%s\n' % delay_keys
#raise RuntimeError(msg)
#print(msg)
if isinstance(self.delay, integer_types) and self.delay > 0:
delays_ref = {}
for sload_key in sload_keys:
nid = sload_key[1]
delay_key = (self.delay, nid, 0)
delays_ref[sload_key] = model.DELAY(self.delay, msg=cmsg)
if delays_ref:
self.delay_ref = delays_ref
if isinstance(self.dphase, integer_types) and self.dphase > 0:
dphases_ref = {}
for sload_key in sload_keys:
nid = sload_key[1]
dphase_key = (self.dphase, nid, 0)
dphases_ref[sload_key] = model.DPHASE(self.dphase, msg=cmsg)
if dphases_ref:
self.dphase_ref = dphases_ref
if isinstance(self.power, integer_types) and self.power > 0:
self.power_ref = model.TableD(self.power, msg=cmsg)
#load_ids2 = []
#for load_id in self.load_ids:
#load_id2 = model.DLoad(load_id, consider_dload_combinations=False, msg=msg)
#load_ids2.append(load_id2)
#self.load_ids = load_ids2
#self.load_ids_ref = self.load_ids
[docs]
def uncross_reference(self) -> None:
"""Removes cross-reference links"""
self.power = self.Power()
self.dphase = self.DPhase()
self.delay = self.Delay()
#self.sloads = self.
#self.tb = self.Tb()
#self.tp = self.Tp()
#self.delay = self.delay_id
#if self.tb > 0:
#del self.tb_ref
#if self.tp > 0:
#del self.tp_ref
self.power_ref = None
self.sloads_ref = None
self.delay_ref = None
self.dphase_ref = None
#self.dphases_ref = None
#self.delays_ref = None
[docs]
def safe_cross_reference(self, model: BDF, xref_errors):
return self.cross_reference(model)
#def uncross_reference(self) -> None:
#self.load_ids = [self.LoadID(load) for load in self.load_ids]
#del self.load_ids_ref
[docs]
def Delay(self):
if self.delay_ref is not None:
return next(self.delay_ref.values()).sid
elif self.delay in [0, 0.0]:
return 0
else:
return self.delay
[docs]
def DPhase(self):
if self.dphase_ref is not None:
return next(self.delay_ref.values()).tid
elif self.dphase in [0, 0.0]:
return 0
else:
return self.dphase
[docs]
def Power(self):
if self.power_ref is not None:
return self.power_ref.tid
return self.power
[docs]
def get_load_at_freq(self, freq: float) -> float:
r"""
..math ::
C = \sqrt(B ⁄ ρ)
Source_strength = {A} * 1/(2πf) * \sqrt( 8πC P(f) / ρ) ^ (ei(θ + 2πfτ))
"""
C = np.sqrt(self.b / self.rho)
ei = np.exp(1) * 1.j
A = 0.0
pi = np.pi
if self.delay in [0, 0.]:
tau = 0.
else:
#print('delay\n', self.delay_ref)
tau = self.delay_ref.value
Pf = self.power_ref.interpolate(freq)
if self.dphase in [0, 0.]:
theta = 0.
else:
#print('dphase\n', self.dphase_ref)
theta = self.dphase_ref.interpolate(freq)
omega = 2.* pi * freq
strength = A / omega * np.sqrt(8*pi*C*Pf / self.rho) ** (ei*(theta + omega*tau))
return 0.0
[docs]
def raw_fields(self):
list_fields = ['ACSRCE', self.sid, self.excite_id, self.Delay(), self.DPhase(),
self.Power(), self.rho, self.b]
return list_fields
[docs]
def repr_fields(self):
return self.raw_fields()
[docs]
def write_card(self, size: int=8, is_double: bool=False) -> str:
card = self.raw_fields()
if size == 16:
return self.comment + print_card_16(card)
return self.comment + print_card_8(card)
[docs]
class DLOAD(LoadCombination):
"""
+-------+-----+----+------+----+----+----+----+----+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
+=======+=====+====+======+====+====+====+====+====+
| DLOAD | SID | S | S1 | L1 | S2 | L2 | S3 | L3 |
+-------+-----+----+------+----+----+----+----+----+
| | S4 | L4 | etc. | | | | | |
+-------+-----+----+------+----+----+----+----+----+
"""
type = 'DLOAD'
[docs]
@classmethod
def _init_from_empty(cls):
sid = 1
scale = 1.
scale_factors = [1., 2.]
load_ids = [1, 2]
return DLOAD(sid, scale, scale_factors, load_ids, comment='')
def __init__(self, sid, scale, scale_factors, load_ids, comment=''):
"""
Creates a DLOAD card
Parameters
----------
sid : int
Load set identification number. See Remarks 1. and 4. (Integer > 0)
scale : float
Scale factor. See Remarks 2. and 8. (Real)
Si : list[float]
Scale factors. See Remarks 2., 7. and 8. (Real)
load_ids : list[int]
Load set identification numbers of RLOAD1, RLOAD2, TLOAD1,
TLOAD2, and ACSRCE entries. See Remarks 3 and 7. (Integer > 0)
comment : str; default=''
a comment for the card
"""
LoadCombination.__init__(self, sid, scale, scale_factors, load_ids,
comment=comment)
[docs]
def cross_reference(self, model: BDF) -> None:
"""
Cross links the card so referenced cards can be extracted directly
Parameters
----------
model : BDF()
the BDF object
"""
dload_ids2 = []
msg = ', which is required by DLOAD=%s' % (self.sid)
for dload_id in self.load_ids:
dload_id2 = model.DLoad(dload_id, consider_dload_combinations=False, msg=msg)
dload_ids2.append(dload_id2)
self.load_ids_ref = dload_ids2
[docs]
def safe_cross_reference(self, model: BDF, xref_errors, debug=True):
dload_ids2 = []
msg = ', which is required by DLOAD=%s' % (self.sid)
for dload_id in self.load_ids:
try:
dload_id2 = model.DLoad(dload_id, consider_dload_combinations=False, msg=msg)
except KeyError:
if debug:
msg = 'Couldnt find dload_id=%i, which is required by %s=%s' % (
dload_id, self.type, self.sid)
model.log.warning(msg)
continue
dload_ids2.append(dload_id2)
self.load_ids_ref = dload_ids2
[docs]
def uncross_reference(self) -> None:
"""Removes cross-reference links"""
self.load_ids = [self.LoadID(dload) for dload in self.get_load_ids()]
self.load_ids_ref = None
[docs]
def raw_fields(self):
list_fields = ['DLOAD', self.sid, self.scale]
for (scale_factor, load_id) in zip(self.scale_factors, self.get_load_ids()):
list_fields += [scale_factor, self.LoadID(load_id)]
return list_fields
[docs]
def repr_fields(self):
return self.raw_fields()
[docs]
def write_card(self, size: int=8, is_double: bool=False) -> str:
card = self.raw_fields()
if size == 16 or max(self.get_load_ids()) > MAX_INT:
return self.comment + print_card_16(card)
return self.comment + print_card_8(card)
[docs]
class RLOAD1(DynamicLoad):
r"""
Defines a frequency-dependent dynamic load of the form
for use in frequency response problems.
.. math::
\left\{ P(f) \right\} = \left\{A\right\} [ C(f)+iD(f)]
e^{ i \left\{\theta - 2 \pi f \tau \right\} }
+--------+-----+----------+-------+--------+----+----+------+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
+========+=====+==========+=======+========+====+====+======+
| RLOAD1 | SID | EXCITEID | DELAY | DPHASE | TC | TD | TYPE |
+--------+-----+----------+-------+--------+----+----+------+
| RLOAD1 | 5 | 3 | | | 1 | | |
+--------+-----+----------+-------+--------+----+----+------+
NX allows DELAY and DPHASE to be floats
"""
type = 'RLOAD1'
_properties = ['delay_id', 'dphase_id']
[docs]
@classmethod
def _init_from_empty(cls):
sid = 1
excite_id = 1
return RLOAD1(sid, excite_id, delay=0, dphase=0, tc=0, td=0, Type='LOAD', comment='')
def __init__(self, sid, excite_id, delay=0, dphase=0, tc=0, td=0, Type='LOAD', comment=''):
"""
Creates an RLOAD1 card, which defines a frequency-dependent load
based on TABLEDs.
Parameters
----------
sid : int
load id
excite_id : int
node id where the load is applied
delay : int/float; default=None
the delay; if it's 0/blank there is no delay
float : delay in units of time
int : delay id
dphase : int/float; default=None
the dphase; if it's 0/blank there is no phase lag
float : delay in units of time
int : delay id
tc : int/float; default=0
TABLEDi id that defines C(f) for all degrees of freedom in
EXCITEID entry
td : int/float; default=0
TABLEDi id that defines D(f) for all degrees of freedom in
EXCITEID entry
Type : int/str; default='LOAD'
the type of load
0/LOAD
1/DISP
2/VELO
3/ACCE
4, 5, 6, 7, 12, 13 - MSC only
comment : str; default=''
a comment for the card
"""
DynamicLoad.__init__(self)
if comment:
self.comment = comment
Type = update_loadtype(Type)
self.sid = sid
self.excite_id = excite_id
self.delay = delay
self.dphase = dphase
self.tc = tc
self.td = td
self.Type = Type
assert sid > 0, self
self.tc_ref = None
self.td_ref = None
self.delay_ref = None
self.dphase_ref = None
[docs]
def validate(self):
msg = ''
is_failed = False
if self.tc > 0 or self.td > 0:
msg += 'either RLOAD1 TC or TD > 0; tc=%s td=%s\n' % (self.tc, self.td)
try:
self.Type = fix_loadtype_rload1(self.Type)
except RuntimeError:
msg += f'invalid RLOAD1 type Type={self.Type}\n'
is_failed = True
if is_failed:
msg += str(self)
raise RuntimeError(msg)
assert self.sid > 0, self.sid
[docs]
@classmethod
def add_card(cls, card, comment=''):
"""
Adds a RLOAD1 card from ``BDF.add_card(...)``
Parameters
----------
card : BDFCard()
a BDFCard object
comment : str; default=''
a comment for the card
"""
sid = integer(card, 1, 'sid')
excite_id = integer(card, 2, 'excite_id')
delay = integer_double_or_blank(card, 3, 'delay', default=0)
dphase = integer_double_or_blank(card, 4, 'dphase', default=0)
tc = integer_double_or_blank(card, 5, 'tc', default=0)
td = integer_double_or_blank(card, 6, 'td', default=0)
Type = integer_string_or_blank(card, 7, 'Type', default='LOAD')
assert len(card) <= 8, f'len(RLOAD1 card) = {len(card):d}\ncard={card}'
return RLOAD1(sid, excite_id, delay, dphase, tc, td, Type, comment=comment)
[docs]
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 RLOAD1 sid=%s' % (self.sid)
_cross_reference_excite_id(self, model, msg)
if isinstance(self.tc, integer_types) and self.tc:
self.tc_ref = model.TableD(self.tc, msg=msg)
if isinstance(self.td, integer_types) and self.td:
self.td_ref = model.TableD(self.td, msg=msg)
if isinstance(self.delay, integer_types) and self.delay > 0:
self.delay_ref = model.DELAY(self.delay_id, msg=msg)
if isinstance(self.dphase, integer_types) and self.dphase > 0:
self.dphase_ref = model.DPHASE(self.dphase, msg=msg)
[docs]
def safe_cross_reference(self, model: BDF, xref_errors, ):
msg = ', which is required by RLOAD1 sid=%s' % (self.sid)
_cross_reference_excite_id(self, model, msg)
if isinstance(self.tc, integer_types) and self.tc:
self.tc_ref = model.TableD(self.tc, msg=msg)
if isinstance(self.td, integer_types) and self.td:
self.td_ref = model.TableD(self.td, msg=msg)
if isinstance(self.delay, integer_types) and self.delay > 0:
self.delay_ref = model.DELAY(self.delay_id, msg=msg)
if isinstance(self.dphase, integer_types) and self.dphase > 0:
self.dphase_ref = model.DPHASE(self.dphase, msg=msg)
[docs]
def uncross_reference(self) -> None:
"""Removes cross-reference links"""
self.tc = self.Tc()
self.td = self.Td()
self.delay = self.delay_id
self.dphase = self.dphase_id
self.tc_ref = None
self.td_ref = None
self.delay_ref = None
self.dphase_ref = None
[docs]
def get_loads(self):
return [self]
[docs]
def Tc(self):
if self.tc_ref is not None:
return self.tc_ref.tid
elif self.tc in [0, 0.0, None]:
return 0
return self.tc
[docs]
def Td(self):
if self.td_ref is not None:
return self.td_ref.tid
elif self.td in [0, 0.0, None]:
return 0
return self.td
@property
def delay_id(self):
if self.delay_ref is not None:
return self.delay_ref.sid
elif self.delay in [0, 0., None]:
return 0
return self.delay
@property
def dphase_id(self):
if self.dphase_ref is not None:
return self.dphase_ref.sid
elif self.dphase in [0, 0.0, None]:
return 0
return self.dphase
[docs]
def get_table_at_freq(self, freq: np.ndarray, table_id: Union[int, float], tabled_ref: TABLED2):
if isinstance(table_id, float):
c = table_id
elif table_id == 0 or table_id is None:
c = 0.
else:
c = tabled_ref.interpolate(freq)
return c
[docs]
def get_phase_at_freq(self, freq: np.ndarray) -> float:
if isinstance(self.dphase, float):
dphase = np.radians(self.dphase)
elif self.dphase == 0:
dphase = 0.0
else:
nids, comps, dphases = self.dphase_ref.get_dphase_at_freq(freq)
assert len(dphases) == 1, 'dphases=%s\n%s' % (dphases, self.dphase_ref)
dphase = dphases[0]
return dphase
[docs]
def get_delay_at_freq(self, freq: np.ndarray) -> np.ndarray:
if isinstance(self.delay, float):
tau = self.delay
elif self.delay == 0:
tau = 0.0
else:
nids, comps, taus = self.delay_ref.get_delay_at_freq(freq)
assert len(taus) == 1, 'taus=%s\n%s' % (taus, self.delay_ref)
tau = taus[0]
return tau
[docs]
def get_load_at_freq(self, freq, scale=1., fdtype='float64'):
# A = 1. # points to DAREA or SPCD
freq = np.asarray(freq, dtype=fdtype)
freq = np.atleast_1d(freq)
c = self.get_table_at_freq(freq, self.tc, self.tc_ref)
d = self.get_table_at_freq(freq, self.td, self.td_ref)
phase = self.get_phase_at_freq(freq)
tau = self.get_delay_at_freq(freq)
out = (c + 1.j * d) * np.exp(phase - 2 * np.pi * freq * tau)
return out
[docs]
def raw_fields(self):
list_fields = ['RLOAD1', self.sid, self.excite_id, self.delay_id, self.dphase_id,
self.Tc(), self.Td(), self.Type]
return list_fields
[docs]
def repr_fields(self):
Type = set_blank_if_default(self.Type, 'LOAD')
list_fields = ['RLOAD1', self.sid, self.excite_id, self.delay_id, self.dphase_id,
self.Tc(), self.Td(), Type]
return list_fields
[docs]
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)
if is_double:
return self.comment + print_card_double(card)
return self.comment + print_card_16(card)
[docs]
def _cross_reference_excite_id_backup(self, model, msg): # pragma: no cover
"""not quite done...not sure how to handle the very odd xref
EXCITEID may refer to one or more static load entries (FORCE, PLOADi, GRAV, etc.).
"""
excite_id_ref = []
case_control = model.case_control_deck
if case_control is not None:
#print('cc = %r' % case_control)
for key, subcase in sorted(model.case_control_deck.subcases.items()):
#print(subcase, type(subcase))
#if 'LOADSET' in subcase:
#lseq_id = subcase['LOADSET'][0]
#lseq = model.Load(lseq_id, consider_load_combinations=False, msg=msg)[0]
#self.excite_id_ref = lseq
##self.dload_id = lseq.
#if 'DLOAD' in subcase:
if self.excite_id in model.loads:
# FORCE, FORCE1, FORCE2, PLOAD4, GRAV
# changes the magnitudes of the load, not the direction
model.log.debug('excite_id load = %s' % self.excite_id)
#print(' dloads =', list(model.dloads.keys()))
#print(' dareas =', list(model.dareas.keys()))
excite_id_ref += model.loads[self.excite_id]
if self.excite_id in model.dareas:
model.log.debug('excite_id darea = %s' % self.excite_id)
darea_ref = model.DAREA(self.excite_id, msg=msg)
excite_id_ref.append(darea_ref)
if self.excite_id in model.dload_entries:
# this is probably wrong...
# it was added to pass TestLoads.test_loads_nonlinear_thermal1, but
# I think QVECT should be in self.loads, not self.dload_entries...
model.log.debug('excite_id dload_entries = %s' % self.excite_id)
excite_id_ref += model.dload_entries
# what about TEMPBC?
#else:
#msg = ('LOADSET and DLOAD are not found in the case control deck\n%s' %
#str(model.case_control_deck))
#raise RuntimeError(msg)
#else:
#model.log.warning('could not find excite_id=%i for\n%s' % (self.excite_id, str(self)))
#self.excite_id_ref = model.DAREA(self.excite_id, msg=msg)
if len(excite_id_ref) == 0:
print('excite_id = %s' % self.excite_id)
print(' loads =', list(model.loads.keys()))
print(' dareas =', list(model.dareas.keys()))
print(' dloads =', list(model.dloads.keys()))
print(' dload_entries =', list(model.dload_entries.keys()))
model.log.warning('could not find excite_id=%i for\n%s' % (self.excite_id, str(self)))
raise RuntimeError('could not find excite_id=%i for\n%s' % (self.excite_id, str(self)))
[docs]
def get_lseqs_by_excite_id(model, excite_id):
from collections import defaultdict
# get the lseqs that correspond to the correct EXCITE_ID id
lseq_sids = defaultdict(list)
for sid, loads in model.load_combinations.items():
for load in loads:
if load.type == 'LSEQ':
if excite_id == load.excite_id:
#print(load)
lseq_sids[sid].append(load)
#for sid, loads in lseqs.items():
#print(sid, loads)
return lseq_sids
[docs]
def _cross_reference_excite_id(self, model, msg):
"""not quite done...not sure how to handle the very odd xref
EXCITEID may refer to one or more static load entries (FORCE, PLOADi, GRAV, etc.).
"""
#print('*' * 80)
lseq_sids = get_lseqs_by_excite_id(model, self.excite_id)
# find all the LOADSETs in the model
# LOADSETs reference LSEQs by sid
valid_lseqs = []
if lseq_sids:
# get the sid for the LSEQ
case_control = model.case_control_deck
if case_control is not None:
#print('cc = %r' % case_control)
for key, subcase in sorted(model.case_control_deck.subcases.items()):
if 'LOADSET' in subcase:
lseq_sid = subcase['LOADSET'][0]
if lseq_sid in lseq_sids:
model.log.debug('adding LOADSET = %i' % lseq_sid)
valid_lseqs.append(lseq_sid)
if valid_lseqs:
valid_lseqs = list(set(valid_lseqs))
valid_lseqs.sort()
#assert len(valid_lseqs) == 1, 'valid_lseqs=%s' % valid_lseqs
#print('valid_lseqs =', valid_lseqs)
# can Case Control LOADSET be substituted for Case Control DLOAD id?
excite_id_ref = []
if self.excite_id in model.loads:
# FORCE, FORCE1, FORCE2, PLOAD4, GRAV
# changes the magnitudes of the load, not the direction
model.log.debug('excite_id load = %s' % self.excite_id)
#print(' dloads =', list(model.dloads.keys()))
#print(' dareas =', list(model.dareas.keys()))
excite_id_ref += model.loads[self.excite_id]
if self.excite_id in model.dareas:
model.log.debug('excite_id darea = %s' % self.excite_id)
darea_ref = model.DAREA(self.excite_id, msg=msg)
excite_id_ref.append(darea_ref)
if self.excite_id in model.bcs:
# CONV, TEMPBC
model.log.debug('excite_id bcs = %s' % self.excite_id)
excite_id_ref = model.bcs[self.excite_id]
if self.excite_id in model.dload_entries: # this is probably wrong...
# this is probably wrong...
# it was added to pass TestLoads.test_loads_nonlinear_thermal1, but
# I think QVECT should be in self.loads, not self.dload_entries...
model.log.debug('excite_id dload_entries = %s' % self.excite_id)
excite_id_ref += model.dload_entries
if self.excite_id in model.load_combinations: # this should be right...
# C:\NASA\m4\formats\git\examples\move_tpl\nlstrs2.op2
model.log.debug('excite_id load_combinations = %s' % self.excite_id)
excite_id_ref = model.load_combinations[self.excite_id]
# handles LSEQ
if valid_lseqs:
for lseq_sid in valid_lseqs:
excite_id_ref += lseq_sids[lseq_sid]
# what about SPCD?
if len(excite_id_ref) == 0:
print(model.get_bdf_stats())
print('excite_id = %s' % self.excite_id)
print(' loads =', list(model.loads.keys()))
print(' dareas =', list(model.dareas.keys()))
print(' bcs =', list(model.bcs.keys()))
print(' dloads =', list(model.dloads.keys()))
print(' dload_entries =', list(model.dload_entries.keys()))
print(' load_combinations =', list(model.load_combinations.keys())) # what about LSEQ
if lseq_sids:
sids = list(lseq_sids.keys())
print(' lseq_excite_ids=%s; lseq_sids=%s; valid_lseqs=%s' % (
self.excite_id, sids, valid_lseqs))
else:
print(' lseq_sids = []')
model.log.warning('could not find excite_id=%i for\n%s' % (self.excite_id, str(self)))
raise RuntimeError('could not find excite_id=%i for\n%s' % (self.excite_id, str(self)))
[docs]
class RLOAD2(DynamicLoad):
r"""
Defines a frequency-dependent dynamic load of the form
for use in frequency response problems.
.. math:: \left\{ P(f) \right\} = \left\{A\right\} * B(f)
e^{ i \left\{ \phi(f) + \theta - 2 \pi f \tau \right\} }
+--------+-----+----------+-------+--------+----+----+------+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
+========+=====+==========+=======+========+====+====+======+
| RLOAD2 | SID | EXCITEID | DELAY | DPHASE | TB | TP | TYPE |
+--------+-----+----------+-------+--------+----+----+------+
| RLOAD2 | 5 | 3 | | | 1 | | |
+--------+-----+----------+-------+--------+----+----+------+
NX allows DELAY and DPHASE to be floats
"""
type = 'RLOAD2'
_properties = ['delay_id', 'dphase_id']
[docs]
@classmethod
def _init_from_empty(cls):
sid = 1
excite_id = 1
return RLOAD2(sid, excite_id, delay=0, dphase=0, tb=0, tp=0, Type='LOAD', comment='')
# P(f) = {A} * B(f) * e^(i*phi(f), + theta - 2*pi*f*tau)
def __init__(self, sid, excite_id, delay=0, dphase=0, tb=0, tp=0, Type='LOAD', comment=''):
"""
Creates an RLOAD2 card, which defines a frequency-dependent load
based on TABLEDs.
Parameters
----------
sid : int
load id
excite_id : int
node id where the load is applied
delay : int/float; default=None
the delay; if it's 0/blank there is no delay
float : delay in units of time
int : delay id
dphase : int/float; default=None
the dphase; if it's 0/blank there is no phase lag
float : dphase in units of degrees
int : dphase id
tb : int/float; default=0
TABLEDi id that defines B(f) for all degrees of freedom in
EXCITEID entry
tp : int/float; default=0
TABLEDi id that defines phi(f) for all degrees of freedom in
EXCITEID entry
Type : int/str; default='LOAD'
the type of load
0/LOAD
1/DISP
2/VELO
3/ACCE
4, 5, 6, 7, 12, 13 - MSC only
comment : str; default=''
a comment for the card
"""
DynamicLoad.__init__(self)
if comment:
self.comment = comment
Type = update_loadtype(Type)
self.sid = sid
self.excite_id = excite_id
self.delay = delay
self.dphase = dphase
self.tb = tb
self.tp = tp
self.Type = Type
self.tb_ref = None
self.tp_ref = None
self.delay_ref = None
self.dphase_ref = None
#@property
#def Type(self):
#"""gets the load_type"""
#return self.load_type
#@Type.setter
#def Type(self, load_type):
#"""sets the load_type"""
#self.load_type = load_type
[docs]
def validate(self):
msg = ''
is_failed = False
if self.tb > 0 or self.tp > 0:
msg += 'either RLOAD2 TB or TP > 0; tb=%s tp=%s\n' % (self.tb, self.tp)
try:
self.Type = fix_loadtype_rload2(self.Type)
except RuntimeError:
msg += f'invalid RLOAD2 type Type={self.Type}\n'
is_failed = True
if is_failed:
msg += str(self)
raise RuntimeError(msg)
assert self.sid > 0, self.sid
[docs]
@classmethod
def add_card(cls, card, comment=''):
"""
Adds a RLOAD2 card from ``BDF.add_card(...)``
Parameters
----------
card : BDFCard()
a BDFCard object
comment : str; default=''
a comment for the card
"""
sid = integer(card, 1, 'sid')
excite_id = integer(card, 2, 'excite_id')
delay = integer_double_or_blank(card, 3, 'delay', default=0)
dphase = integer_double_or_blank(card, 4, 'dphase', default=0)
tb = integer_double_or_blank(card, 5, 'tb', default=0)
tp = integer_double_or_blank(card, 6, 'tp', default=0)
Type = integer_string_or_blank(card, 7, 'Type', default='LOAD')
assert len(card) <= 8, f'len(RLOAD2 card) = {len(card):d}\ncard={card}'
return RLOAD2(sid, excite_id, delay, dphase, tb, tp, Type, comment=comment)
[docs]
def get_damping_at_freq(self, freq: np.ndarray) -> float:
if isinstance(self.tb, float):
b = self.tb
elif self.tb == 0 or self.tb is None:
b = 0.0
else:
b = self.tb_ref.interpolate(freq)
return b
[docs]
def get_phi_at_freq(self, freq: np.ndarray) -> float:
if isinstance(self.tp, float):
p = self.tp
elif self.tp == 0 or self.tp is None:
p = 0.0
else:
p = self.tp_ref.interpolate(freq)
return p
[docs]
def get_phase_at_freq(self, freq: np.ndarray) -> float:
if isinstance(self.dphase, float):
dphase = np.radians(self.dphase)
elif self.dphase == 0 or self.dphase is None:
dphase = 0.0
else:
warnings.warn('RLOAD2 doesnt support DPHASE...')
nids, comps, dphases = self.dphase_ref.get_dphase_at_freq(freq)
assert len(dphases) == 1, 'dphases=%s\n%s' % (dphases, self.dphase_ref)
dphase = dphases[0]
return dphase
[docs]
def get_delay_at_freq(self, freq: np.ndarray) -> np.ndarray:
if isinstance(self.delay, float):
tau = self.delay
elif self.delay == 0 or self.delay is None:
tau = 0.0
else:
warnings.warn('RLOAD2 doesnt support DELAY...')
nids, comps, taus = self.delay_ref.get_delay_at_freq(freq)
assert len(taus) == 1, 'taus=%s\n%s' % (taus, self.delay_ref)
tau = taus[0]
return tau
[docs]
def get_load_at_freq(self, freq, scale=1., fdtype='complex128'):
# A = 1. # points to DAREA or SPCD
freq = np.asarray(freq, dtype=fdtype)
freq = np.atleast_1d(freq)
b = self.get_damping_at_freq(freq)
p = self.get_phi_at_freq(freq)
phase = self.get_phase_at_freq(freq)
tau = self.get_delay_at_freq(freq)
try:
out = b * np.exp(1.j * p + phase - 2 * np.pi * freq * tau)
except TypeError:
print('b =', b)
print('p =', p)
print('phase =', phase)
print('freq =', freq)
print('tau =', tau)
raise
return out
[docs]
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 RLOAD2=%s' % (self.sid)
_cross_reference_excite_id(self, model, msg)
if isinstance(self.tb, integer_types) and self.tb:
self.tb_ref = model.TableD(self.tb, msg=msg)
if isinstance(self.tp, integer_types) and self.tp:
self.tp_ref = model.TableD(self.tp, msg=msg)
if isinstance(self.delay, integer_types) and self.delay > 0:
self.delay_ref = model.DELAY(self.delay, msg=msg)
if isinstance(self.dphase, integer_types) and self.dphase > 0:
self.dphase_ref = model.DPHASE(self.dphase, msg=msg)
[docs]
def safe_cross_reference(self, model: BDF, xref_errors, ):
msg = ', which is required by RLOAD2=%s' % (self.sid)
_cross_reference_excite_id(self, model, msg)
if isinstance(self.tb, integer_types) and self.tb:
self.tb_ref = model.TableD(self.tb, msg=msg)
if isinstance(self.tp, integer_types) and self.tp:
self.tp_ref = model.TableD(self.tp, msg=msg)
if isinstance(self.delay, integer_types) and self.delay > 0:
self.delay_ref = model.DELAY(self.delay, msg=msg)
if isinstance(self.dphase, integer_types) and self.dphase > 0:
self.dphase_ref = model.DPHASE(self.dphase, msg=msg)
[docs]
def uncross_reference(self) -> None:
"""Removes cross-reference links"""
self.tb = self.Tb()
self.tp = self.Tp()
self.delay = self.delay_id
self.dphase = self.dphase_id
self.tb_ref = None
self.tp_ref = None
self.delay_ref = None
self.dphase_ref = None
[docs]
def get_loads(self):
return [self]
[docs]
def LoadID(self):
return self.sid
[docs]
def Tb(self):
if self.tb_ref is not None:
return self.tb_ref.tid
elif self.tb == 0:
return 0
return self.tb
[docs]
def Tp(self):
if self.tp_ref is not None:
return self.tp_ref.tid
elif self.tp == 0:
return 0
return self.tp
@property
def delay_id(self):
if self.delay_ref is not None:
return self.delay_ref.sid
elif self.delay == 0:
return 0
return self.delay
@property
def dphase_id(self):
if self.dphase_ref is not None:
return self.dphase_ref.sid
elif self.dphase == 0:
return 0
return self.dphase
[docs]
def raw_fields(self):
list_fields = ['RLOAD2', self.sid, self.excite_id, self.delay_id, self.dphase_id,
self.Tb(), self.Tp(), self.Type]
return list_fields
[docs]
def repr_fields(self):
Type = set_blank_if_default(self.Type, 0.0)
list_fields = ['RLOAD2', self.sid, self.excite_id, self.delay_id, self.dphase_id,
self.Tb(), self.Tp(), Type]
return list_fields
[docs]
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)
if is_double:
return self.comment + print_card_double(card)
return self.comment + print_card_16(card)
[docs]
class TLOAD1(DynamicLoad):
r"""
Transient Response Dynamic Excitation, Form 1
Defines a time-dependent dynamic load or enforced motion of the form:
.. math::
\left\{ P(t) \right\} = \left\{ A \right\} \cdot F(t-\tau)
for use in transient response analysis.
MSC 20005.2
+--------+-----+----------+-------+------+-----+-----+-----+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
+========+=====+==========+=======+======+=====+=====+=====+
| TLOAD1 | SID | EXCITEID | DELAY | TYPE | TID | US0 | VS0 |
+--------+-----+----------+-------+------+-----+-----+-----+
NX 11
+--------+-----+----------+-------+------+-----+
| 1 | 2 | 3 | 4 | 5 | 6 |
+========+=====+==========+=======+======+=====+
| TLOAD1 | SID | EXCITEID | DELAY | TYPE | TID |
+--------+-----+----------+-------+------+-----+
"""
type = 'TLOAD1'
_properties = ['delay_id']
[docs]
@classmethod
def _init_from_empty(cls):
sid = 1
excite_id = 1
tid = 1
return TLOAD1(sid, excite_id, tid, delay=0, Type='LOAD', us0=0.0, vs0=0.0, comment='')
def __init__(self, sid: int, excite_id: int,
tid: Union[int, float], delay: Union[int, float]=0,
Type: str='LOAD',
us0: float=0.0, vs0: float=0.0, comment: str=''):
"""
Creates a TLOAD1 card, which defines a time-dependent load
based on a DTABLE.
Parameters
----------
sid : int
load id
excite_id : int
node id where the load is applied
tid : int / float
TABLEDi id that defines F(t) for all degrees of freedom in
EXCITEID entry
delay : int/float; default=None
the delay; if it's 0/blank there is no delay
float : delay in units of time
int : delay id
Type : int/str; default='LOAD'
the type of load
0/LOAD
1/DISP
2/VELO
3/ACCE
4, 5, 6, 7, 12, 13 - MSC only
us0 : float; default=0.
Factor for initial displacements of the enforced degrees-of-freedom
MSC only
vs0 : float; default=0.
Factor for initial velocities of the enforced degrees-of-freedom
MSC only
comment : str; default=''
a comment for the card
"""
DynamicLoad.__init__(self)
if delay is None:
delay = 0
Type = update_loadtype(Type)
if comment:
self.comment = comment
#: load ID
self.sid = sid
#: Identification number of DAREA or SPCD entry set or a thermal load
#: set (in heat transfer analysis) that defines {A}. (Integer > 0)
self.excite_id = excite_id
#: If it is a non-zero integer, it represents the
#: identification number of DELAY Bulk Data entry that defines .
#: If it is real, then it directly defines the value of that will
#: be used for all degrees-of-freedom that are excited by this
#: dynamic load entry. See also Remark 9. (Integer >= 0,
#: real or blank)
self.delay = delay
#: Defines the type of the dynamic excitation. (LOAD,DISP, VELO, ACCE)
self.Type = Type
#: Identification number of TABLEDi entry that gives F(t). (Integer > 0)
self.tid = tid
#: Factor for initial displacements of the enforced degrees-of-freedom.
#: (Real; Default = 0.0)
self.us0 = us0
#: Factor for initial velocities of the enforced degrees-of-freedom.
#: (Real; Default = 0.0)
self.vs0 = vs0
self.tid_ref = None
self.delay_ref = None
[docs]
def validate(self):
self.Type = fix_loadtype_tload1(self.Type)
assert self.sid > 0, self.sid
[docs]
@classmethod
def add_card(cls, card, comment=''):
"""
Adds a TLOAD1 card from ``BDF.add_card(...)``
Parameters
----------
card : BDFCard()
a BDFCard object
comment : str; default=''
a comment for the card
"""
sid = integer(card, 1, 'sid')
excite_id = integer(card, 2, 'excite_id')
delay = integer_double_or_blank(card, 3, 'delay', default=0)
Type = integer_string_or_blank(card, 4, 'Type', 'LOAD')
tid = integer_double_or_blank(card, 5, 'tid', default=0)
us0 = double_or_blank(card, 6, 'us0', default=0.0)
vs0 = double_or_blank(card, 7, 'vs0', default=0.0)
assert len(card) <= 8, f'len(TLOAD1 card) = {len(card):d}\ncard={card}'
return TLOAD1(sid, excite_id, tid, delay=delay, Type=Type, us0=us0, vs0=vs0, comment=comment)
[docs]
def get_loads(self):
return [self]
[docs]
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 TLOAD1=%s' % (self.sid)
_cross_reference_excite_id(self, model, msg)
if isinstance(self.tid, integer_types) and self.tid:
self.tid_ref = model.TableD(self.tid, msg=msg)
if isinstance(self.delay, integer_types) and self.delay > 0:
self.delay_ref = model.DELAY(self.delay, msg=msg)
[docs]
def safe_cross_reference(self, model: BDF, debug=True):
msg = ', which is required by TLOAD1=%s' % (self.sid)
_cross_reference_excite_id(self, model, msg)
if isinstance(self.tid, integer_types) and self.tid:
#try:
self.tid_ref = model.TableD(self.tid, msg=msg)
#except
if isinstance(self.delay, integer_types) and self.delay > 0:
self.delay_ref = model.DELAY(self.delay_id, msg=msg)
[docs]
def uncross_reference(self) -> None:
"""Removes cross-reference links"""
self.tid = self.Tid()
self.delay = self.delay_id
self.tid_ref = None
self.delay_ref = None
[docs]
def Tid(self) -> Union[int, float]:
if self.tid_ref is not None:
return self.tid_ref.tid
elif self.tid == 0 or self.tid is None:
return 0
else:
return self.tid
@property
def delay_id(self) -> Union[int, float]:
if self.delay_ref is not None:
return self.delay_ref.sid
elif self.delay == 0 or self.delay is None:
return 0
return self.delay
[docs]
def get_delay_at_time(self, time: np.ndarray) -> float:
if isinstance(self.delay, float):
tau = self.delay
elif self.delay == 0 or self.delay is None:
tau = 0.0
else:
warnings.warn('TLOAD1 doesnt support DELAY...')
tau = self.delay_ref.get_delay_at_time(time)
return tau
[docs]
def get_load_at_time(self, time: np.ndarray, scale: float=1.,
fdtype: str='float64') -> np.ndarray:
# A = 1. # points to DAREA or SPCD
time = np.asarray(time, dtype=fdtype)
time = np.atleast_1d(time)
tau = self.get_delay_at_time(time)
i = np.where(time - tau < 0)[0]
time2 = time - tau
time2[i] = 0.
response = np.zeros(time.shape, dtype=time.dtype)
if isinstance(self.tid, integer_types):
response = self.tid_ref.interpolate(time2)
else:
# float
response += self.tid
is_spcd = False
if self.Type == 'VELO' and is_spcd:
response[0] = self.us0
if self.Type == 'ACCE' and is_spcd:
response[0] = self.vs0
assert len(response) == len(time)
response[i] = 0.
return response * scale
[docs]
def raw_fields(self):
list_fields = ['TLOAD1', self.sid, self.excite_id, self.delay_id, self.Type,
self.Tid(), self.us0, self.vs0]
return list_fields
[docs]
def repr_fields(self):
us0 = set_blank_if_default(self.us0, 0.0)
vs0 = set_blank_if_default(self.vs0, 0.0)
list_fields = ['TLOAD1', self.sid, self.excite_id, self.delay_id, self.Type,
self.Tid(), us0, vs0]
return list_fields
[docs]
def write_card(self, size: int=8, is_double: bool=False) -> str:
card = self.repr_fields()
if size == 8:
if max(self.sid, self.excite_id, self.delay_id, self.Tid()) > MAX_INT:
return self.comment + print_card_16(card)
return self.comment + print_card_8(card)
if is_double:
return self.comment + print_card_double(card)
return self.comment + print_card_16(card)
[docs]
def fix_loadtype_tload1(load_type: Union[int, str]) -> str:
"""
4 FLOW boundary condition on the face of an Eulerian solid element (SOL 700 only).
5 Displacement of SPH elements before activation by a FLOWSPH
boundary condition (SOL 700 only).
6 Velocity of SPH elements before activation by a FLOWSPH
boundary condition (SOL 700 only).
7 Acceleration of SPH elements before activation by a FLOWSPH
boundary condition (SOL 700 only)
12 Velocity of the center of gravity of a rigid body (SOL 700 only)
13 Force or moment on the center of gravity of a rigid body (SOL 700 only).
"""
if load_type in {0, 'L', 'LO', 'LOA', 'LOAD'}:
load_type = 'LOAD'
elif load_type in {1, 'D', 'DI', 'DIS', 'DISP'}:
load_type = 'DISP'
elif load_type in {2, 'V', 'VE', 'VEL', 'VELO'}:
load_type = 'VELO'
elif load_type in {3, 'A', 'AC', 'ACC', 'ACCE'}:
load_type = 'ACCE'
elif load_type in {4, 5, 6, 7, 12, 13}: # MSC-only
pass
else:
msg = 'invalid TLOAD1 type Type={load_type!r}'
raise AssertionError(msg)
return load_type
[docs]
def fix_loadtype_tload2(load_type: Union[int, str]) -> str:
if load_type in {0, 'L', 'LO', 'LOA', 'LOAD'}:
load_type = 'LOAD'
elif load_type in {1, 'D', 'DI', 'DIS', 'DISP'}:
load_type = 'DISP'
elif load_type in {2, 'V', 'VE', 'VEL', 'VELO'}:
load_type = 'VELO'
elif load_type in {3, 'A', 'AC', 'ACC', 'ACCE'}:
load_type = 'ACCE'
elif load_type in {5, 6, 7, 12, 13}: # MSC only
pass
else:
msg = f'invalid TLOAD2 type Type={load_type!r}'
raise RuntimeError(msg)
return load_type
[docs]
def fix_loadtype_rload1(load_type: Union[int, str]) -> str:
if load_type in {0, 'L', 'LO', 'LOA', 'LOAD'}:
load_type = 'LOAD'
elif load_type in {1, 'D', 'DI', 'DIS', 'DISP'}:
load_type = 'DISP'
elif load_type in {2, 'V', 'VE', 'VEL', 'VELO'}:
load_type = 'VELO'
elif load_type in {3, 'A', 'AC', 'ACC', 'ACCE'}:
load_type = 'ACCE'
elif load_type in {'FORCE'}:
load_type = 'FORCE'
else:
msg = f'invalid RLOAD1 type; load_type={load_type!r}\n'
raise RuntimeError(msg)
return load_type
[docs]
def fix_loadtype_rload2(load_type: Union[int, str]) -> str:
if load_type in {0, 'L', 'LO', 'LOA', 'LOAD'}:
load_type = 'LOAD'
elif load_type in {1, 'D', 'DI', 'DIS', 'DISP'}:
load_type = 'DISP'
elif load_type in {2, 'V', 'VE', 'VEL', 'VELO'}:
load_type = 'VELO'
elif load_type in {3, 'A', 'AC', 'ACC', 'ACCE'}:
load_type = 'ACCE'
else:
msg = f'invalid RLOAD2 type; load_type={load_type!r}\n'
raise RuntimeError(msg)
return load_type
[docs]
class TLOAD2(DynamicLoad):
r"""
Transient Response Dynamic Excitation, Form 1
Defines a time-dependent dynamic load or enforced motion of the form:
.. math::
\left\{ P(t) \right\} = \left\{ A \right\} e^(C*t) cos(2 \pi f t + \phi)
P(t) = 0 (t<T1+tau or t > T2+tau)
P(t) = {A} * t^b * e^(C*t) * cos(2*pi*f*t + phase) (T1+tau <= t <= T2+tau)
for use in transient response analysis.
MSC 2016.1
+--------+-----+----------+-------+------+-----+-----+--------+---------+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
+========+=====+==========+=======+======+=====+=====+========+=========+
| TLOAD2 | SID | EXCITEID | DELAY | TYPE | T1 | T2 | FREQ | PHASE |
+--------+-----+----------+-------+------+-----+-----+--------+---------+
| | C | B | US0 | VS0 | | | | |
+--------+-----+----------+-------+------+-----+-----+--------+---------+
NX 11
+--------+-----+----------+-------+------+-----+-----+--------+---------+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
+========+=====+==========+=======+======+=====+=====+========+=========+
| TLOAD2 | SID | EXCITEID | DELAY | TYPE | T1 | T2 | FREQ | PHASE |
+--------+-----+----------+-------+------+-----+-----+--------+---------+
| | C | B | | | | | | |
+--------+-----+----------+-------+------+-----+-----+--------+---------+
"""
type = 'TLOAD2'
_properties = ['delay_id']
[docs]
@classmethod
def _init_from_empty(cls):
sid = 1
excite_id = 1
return TLOAD2(sid, excite_id, delay=0, Type='LOAD', T1=0., T2=None,
frequency=0., phase=0., c=0., b=0., us0=0., vs0=0., comment='')
def __init__(self, sid: int,
excite_id: int,
delay: Union[int, float]=0,
Type: str='LOAD',
T1: float=0.,
T2: Optional[float]=None,
frequency: float=0.,
phase: Union[int, float]=0.,
c: float=0.,
b: float=0.,
us0: float=0.,
vs0: float=0., comment: str=''):
"""
Creates a TLOAD2 card, which defines a exponential time dependent
load based on constants.
Parameters
----------
sid : int
load id
excite_id : int
node id where the load is applied
delay : int/float; default=None
the delay; if it's 0/blank there is no delay
float : delay in units of time
int : delay id
Type : int/str; default='LOAD'
the type of load
0/LOAD
1/DISP
2/VELO
3/ACCE
4, 5, 6, 7, 12, 13 - MSC only
T1 : float; default=0.
time constant (t1 > 0.0)
times below this are ignored
T2 : float; default=None
time constant (t2 > t1)
times above this are ignored
frequency : float; default=0.
Frequency in cycles per unit time.
phase : float; default=0.
Phase angle in degrees.
c : float; default=0.
Exponential coefficient.
b : float; default=0.
Growth coefficient.
us0 : float; default=0.
Factor for initial displacements of the enforced degrees-of-freedom
MSC only
vs0 : float; default=0.
Factor for initial velocities of the enforced degrees-of-freedom
MSC only
comment : str; default=''
a comment for the card
"""
DynamicLoad.__init__(self)
if comment:
self.comment = comment
#if T2 is None:
#T2 = T1
Type = update_loadtype(Type)
#: load ID
#: SID must be unique for all TLOAD1, TLOAD2, RLOAD1, RLOAD2, and ACSRCE entries.
self.sid = sid
self.excite_id = excite_id
self.delay = delay
#: Defines the type of the dynamic excitation. (Integer; character
#: or blank; Default = 0)
self.Type = Type
#: Time constant. (Real >= 0.0)
self.T1 = T1
#: Time constant. (Real; T2 > T1)
self.T2 = T2
#: Frequency in cycles per unit time. (Real >= 0.0; Default = 0.0)
self.frequency = frequency
#: Phase angle in degrees. (Real; Default = 0.0)
self.phase = phase
#: Exponential coefficient. (Real; Default = 0.0)
self.c = c
#: Growth coefficient. (Real; Default = 0.0)
self.b = b
#: Factor for initial displacements of the enforced degrees-of-freedom.
#: (Real; Default = 0.0)
self.us0 = us0
#: Factor for initial velocities of the enforced degrees-of-freedom
#: (Real; Default = 0.0)
self.vs0 = vs0
self.delay_ref = None
self.dphase_ref = None
[docs]
def validate(self):
self.Type = fix_loadtype_tload2(self.Type)
assert self.sid > 0, self.sid
[docs]
@classmethod
def add_card(cls, card, comment=''):
"""
Adds a TLOAD2 card from ``BDF.add_card(...)``
Parameters
----------
card : BDFCard()
a BDFCard object
comment : str; default=''
a comment for the card
"""
sid = integer(card, 1, 'sid')
excite_id = integer(card, 2, 'excite_id')
delay = integer_double_or_blank(card, 3, 'delay', default=0)
Type = integer_string_or_blank(card, 4, 'Type', default='LOAD')
T1 = double_or_blank(card, 5, 'T1', default=0.0)
T2 = double_or_blank(card, 6, 'T2', default=T1)
frequency = double_or_blank(card, 7, 'frequency', default=0.)
phase = double_or_blank(card, 8, 'phase', default=0.)
c = double_or_blank(card, 9, 'c', default=0.)
b = double_or_blank(card, 10, 'b', default=0.)
us0 = double_or_blank(card, 11, 'us0', default=0.)
vs0 = double_or_blank(card, 12, 'vs0', default=0.)
assert len(card) <= 13, f'len(TLOAD2 card) = {len(card):d}\ncard={card}'
return TLOAD2(sid, excite_id, delay, Type, T1, T2, frequency, phase,
c, b, us0, vs0, comment=comment)
[docs]
def get_delay_at_time(self, time: np.ndarray) -> float:
if isinstance(self.delay, float):
tau = self.delay
elif self.delay == 0 or self.delay is None:
tau = 0.0
else:
delay_ref = self.delay_ref
assert delay_ref is not None, self.delay_ref
nodes = delay_ref.nodes
delays = delay_ref.delays
components = delay_ref.components
warnings.warn('TLOAD2 doesnt support DELAY; setting tau=0.')
tau = 0.
#tau = self.delay_ref.get_delay_at_time(time)
return tau
[docs]
def get_phase_at_time(self, time: np.ndarray) -> float:
if isinstance(self.phase, integer_types):
dphase_ref = self.dphase_ref
assert dphase_ref is not None, dphase_ref
nodes = dphase_ref.nodes
phases = dphase_ref.phase_leads
components = dphase_ref.components
warnings.warn('TLOAD2 doesnt support DPHASE; setting phase=0.')
p = 0.
#p = np.radians(dphase_ref.phase)
else:
p = np.radians(self.phase)
return p
[docs]
def get_load_at_time(self, time, scale=1., fdtype='float64'):
time = np.asarray(time, dtype=fdtype)
time = np.atleast_1d(time)
# A = 1. # points to DAREA or SPCD
#xy = array(self.tid.table.table)
#x = xy[:, 0]
#y = xy[:, 1]
#assert x.shape == y.shape, 'x.shape=%s y.shape=%s' % (str(x.shape), str(y.shape))
#f = interp1d(x, y)
tau = self.get_delay_at_time(time)
t1 = self.T1 + tau
if self.T2 is not None:
t2 = self.T2 + tau
i = np.where(t1 <= time)[0]
j = np.where(time[i] <= t2)[0]
i = i[j]
else:
i = np.where(t1 <= time)[0]
freq = self.frequency
p = self.get_phase_at_time(time)
f = np.zeros(time.shape, dtype=time.dtype)
f[i] = scale * time[i] ** self.b * np.exp(self.c * time[i]) * np.cos(2 * np.pi * freq * time[i] + p)
is_spcd = False
#resp = f
if self.Type == 'VELO' and is_spcd:
f[0] = self.us0
if self.Type == 'ACCE' and is_spcd:
f[0] = self.vs0
return f
[docs]
def get_loads(self):
return [self]
[docs]
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 TLOAD2 sid=%s' % (self.sid)
_cross_reference_excite_id(self, model, msg)
if isinstance(self.delay, integer_types) and self.delay > 0:
self.delay_ref = model.DELAY(self.delay_id, msg=msg)
if isinstance(self.phase, integer_types) and self.phase > 0:
self.dphase_ref = model.DPHASE(self.phase, msg=msg)
# TODO: excite_id
[docs]
def safe_cross_reference(self, model: BDF, xref_errors, debug=True):
msg = ', which is required by TLOAD2 sid=%s' % (self.sid)
_cross_reference_excite_id(self, model, msg)
if isinstance(self.delay, integer_types) and self.delay > 0:
self.delay_ref = model.DELAY(self.delay_id, msg=msg)
# TODO: excite_id
[docs]
def uncross_reference(self) -> None:
"""Removes cross-reference links"""
self.delay = self.delay_id
self.delay_ref = None
@property
def delay_id(self) -> Union[int, float]:
if self.delay_ref is not None:
return self.delay_ref.sid
elif self.delay == 0 or self.delay is None:
return 0
return self.delay
@property
def dphase_id(self) -> Union[int, float]:
if self.dphase_ref is not None:
return self.dphase_ref.sid
elif self.phase == 0 or self.phase is None:
return 0
return self.phase
[docs]
def raw_fields(self):
list_fields = ['TLOAD2', self.sid, self.excite_id, self.delay_id, self.Type,
self.T1, self.T2, self.frequency, self.dphase_id, self.c, self.b,
self.us0, self.vs0]
return list_fields
[docs]
def repr_fields(self):
frequency = set_blank_if_default(self.frequency, 0.0)
phase = set_blank_if_default(self.phase, 0.0)
c = set_blank_if_default(self.c, 0.0)
b = set_blank_if_default(self.b, 0.0)
us0 = set_blank_if_default(self.us0, 0.0)
vs0 = set_blank_if_default(self.vs0, 0.0)
list_fields = ['TLOAD2', self.sid, self.excite_id, self.delay_id, self.Type,
self.T1, self.T2, frequency, phase, c, b, us0, vs0]
return list_fields
[docs]
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)
if is_double:
return self.comment + print_card_double(card)
return self.comment + print_card_16(card)
[docs]
def update_loadtype(load_type):
if load_type in [0, 'L', 'LO', 'LOA', 'LOAD']:
load_type = 'LOAD'
elif load_type in [1, 'D', 'DI', 'DIS', 'DISP']:
load_type = 'DISP'
elif load_type in [2, 'V', 'VE', 'VEL', 'VELO']:
load_type = 'VELO'
elif load_type in [3, 'A', 'AC', 'ACC', 'ACCE']:
load_type = 'ACCE'
return load_type