Input Data


In [1]:
from salib import extend, NBImporter
from Tables import Table, DataSource
from Nodes import Node
from Members import Member
from LoadSets import LoadSet, LoadCombination
from NodeLoads import makeNodeLoad
from MemberLoads import makeMemberLoad
from collections import OrderedDict, defaultdict
import numpy as np


Compiling notebook file 'Tables.ipynb' to python.
Compiling notebook file 'Nodes.ipynb' to python.
Compiling notebook file 'Members.ipynb' to python.
Compiling notebook file 'MemberLoads.ipynb' to python.
Compiling notebook file 'LoadSets.ipynb' to python.
Compiling notebook file 'NodeLoads.ipynb' to python.

In [2]:
from Frame2D_Base import Frame2D


Compiling notebook file 'Frame2D_Base.ipynb' to python.

In [3]:
@extend
class Frame2D:
    
    COLUMNS_xxx = [] # list of column names for table 'xxx'
        
    def get_table(self,tablename,extrasok=False,optional=False):
        columns = getattr(self,'COLUMNS_'+tablename)
        t = DataSource.read_table(tablename,columns=columns,optional=optional)
        return t

Test Frame

Nodes

Table nodes (file nodes.csv) provides the $x$-$y$ coordinates of each node. Other columns, such as the $z$- coordinate are optional, and ignored if given.


In [4]:
%%Table nodes
NODEID,X,Y,Z
A,0.,0.,5000.
B,0,4000,5000
C,8000,4000,5000
D,8000,0,5000

In [5]:
@extend
class Frame2D:
    
    COLUMNS_nodes = ['NODEID','X','Y']
        
    def input_nodes(self):
        node_table = self.get_table('nodes')
        for ix,r in node_table.iterrows():
            if r.NODEID in self.nodes:
                raise Exception('Multiply defined node: {}'.format(r.NODEID))
            n = Node(r.NODEID,r.X,r.Y)
            self.nodes[n.id] = n
        self.rawdata.nodes = node_table
            
    def get_node(self,id):
        try:
            return self.nodes[id]
        except KeyError:
            raise Exception('Node not defined: {}'.format(id))

In [6]:
##test:
f = Frame2D()

In [7]:
##test:
f.input_nodes()

In [8]:
##test:
f.nodes


Out[8]:
OrderedDict([('A', Node("A",0.0,0.0)),
             ('B', Node("B",0.0,4000.0)),
             ('C', Node("C",8000.0,4000.0)),
             ('D', Node("D",8000.0,0.0))])

In [9]:
##test:
f.get_node('C')


Out[9]:
Node("C",8000.0,4000.0)

Supports

Table supports (file supports.csv) specifies the support fixity, by indicating the constrained direction for each node. There can be 1, 2 or 3 constraints, selected from the set 'FX', 'FY' or 'MZ', in any order for each constrained node. Directions not mentioned are 'free' or unconstrained.


In [10]:
%%Table supports
NODEID,C0,C1,C2
A,FX,FY,MZ
D,FX,FY

In [11]:
def isnan(x):
    if x is None:
        return True
    try:
        return np.isnan(x)
    except TypeError:
        return False

In [12]:
@extend
class Frame2D:
    
    COLUMNS_supports = ['NODEID','C0','C1','C2']
    
    def input_supports(self):
        table = self.get_table('supports')
        for ix,row in table.iterrows():
            node = self.get_node(row.NODEID)
            for c in [row.C0,row.C1,row.C2]:
                if not isnan(c):
                    node.add_constraint(c)
        self.rawdata.supports = table

In [13]:
##test:
f.input_supports()

In [14]:
##test:
vars(f.get_node('D'))


Out[14]:
{'constraints': {'FX', 'FY'},
 'dofnums': [None, None, None],
 'id': 'D',
 'x': 8000.0,
 'y': 0.0}

Members

Table members (file members.csv) specifies the member incidences. For each member, specify the id of the nodes at the 'j-' and 'k-' ends. These ends are used to interpret the signs of various values.


In [15]:
%%Table members
MEMBERID,NODEJ,NODEK
AB,A,B
BC,B,C
CD,C,D

In [16]:
@extend
class Frame2D:
    
    COLUMNS_members = ['MEMBERID','NODEJ','NODEK']
    
    def input_members(self):
        table = self.get_table('members')
        for ix,m in table.iterrows():
            if m.MEMBERID in self.members:
                raise Exception('Multiply defined member: {}'.format(m.MEMBERID))
            memb = Member(m.MEMBERID,self.get_node(m.NODEJ),self.get_node(m.NODEK))
            self.members[memb.id] = memb
        self.rawdata.members = table
            
    def get_member(self,id):
        try:
            return self.members[id]
        except KeyError:
            raise Exception('Member not defined: {}'.format(id))

In [17]:
##test:
f.input_members()
f.members


Out[17]:
OrderedDict([('AB', Member("AB","Node("A",0.0,0.0)","Node("B",0.0,4000.0)")),
             ('BC',
              Member("BC","Node("B",0.0,4000.0)","Node("C",8000.0,4000.0)")),
             ('CD',
              Member("CD","Node("C",8000.0,4000.0)","Node("D",8000.0,0.0)"))])

In [18]:
##test:
m = f.get_member('BC')
m.id, m.L, m.dcx, m.dcy


Out[18]:
('BC', 8000.0, 1.0, 0.0)

Releases

Table releases (file releases.csv) is optional and specifies internal force releases in some members. Currently only moment releases at the 'j-' end ('MZJ') and 'k-' end ('MZK') are supported. These specify that the internal bending moment at those locations are zero. You can only specify one release per line, but you can have more than one line for a member.


In [19]:
%%Table releases
MEMBERID,RELEASE
AB,MZK
CD,MZJ

In [20]:
@extend
class Frame2D:
    
    COLUMNS_releases = ['MEMBERID','RELEASE']
    
    def input_releases(self):
        table = self.get_table('releases',optional=True)
        for ix,r in table.iterrows():
            memb = self.get_member(r.MEMBERID)
            memb.add_release(r.RELEASE)
        self.rawdata.releases = table

In [21]:
##test:
f.input_releases()

In [22]:
##test:
vars(f.get_member('AB'))


Out[22]:
{'A': None,
 'Ix': None,
 'KG': None,
 'KL': None,
 'L': 4000.0,
 'Tm': None,
 'dcx': 0.0,
 'dcy': 1.0,
 'id': 'AB',
 'nodej': Node("A",0.0,0.0),
 'nodek': Node("B",0.0,4000.0),
 'releases': {'MZK'}}

Properties

Table properties (file properties.csv) specifies the member properties for each member. If the 'SST' library is available, you may specify the size of the member by using the designation of a shape in the CISC Structural Section Tables. If either IX or A is missing, it is retreived using the sst library using the provided size. If the values on any line are missing, they are copied from the line above.


In [23]:
try:
    from sst import SST
    __SST = SST()
    get_section = __SST.section
except ImportError:
    def get_section(dsg,fields):
        raise ValueError('Cannot lookup property SIZE because SST is not available.  SIZE = {}'.format(dsg))
        ##return [1.] * len(fields.split(',')) # in case you want to do it that way

In [24]:
%%Table properties
MEMBERID,SIZE,IX,A
BC,W460x106,,
AB,W310x97,,
CD,,

In [25]:
@extend
class Frame2D:
    
    COLUMNS_properties = ['MEMBERID','SIZE','IX','A']
    
    def input_properties(self):
        table = self.get_table('properties')
        table = self.fill_properties(table)
        for ix,row in table.iterrows():
            memb = self.get_member(row.MEMBERID)
            memb.size = row.SIZE
            memb.Ix = row.IX
            memb.A = row.A
        self.rawdata.properties = table
        
    def fill_properties(self,table):
        prev = None
        for ix,row in table.iterrows():
            nf = 0
            if type(row.SIZE) in [type(''),type(u'')]:
                if isnan(row.IX) or isnan(row.A):
                    Ix,A = get_section(row.SIZE,'Ix,A')
                    if isnan(row.IX):
                        nf += 1
                        table.loc[ix,'IX'] = Ix
                    if isnan(row.A):
                        nf += 1
                        table.loc[ix,'A'] = A
            elif isnan(row.SIZE):
                table.loc[ix,'SIZE'] = '' if nf == 0 else prev
            prev = table.loc[ix,'SIZE']
        table = table.fillna(method='ffill')
        return table

In [26]:
##test:
f.input_properties()

In [27]:
##test:
vars(f.get_member('CD'))


Out[27]:
{'A': 12300.0,
 'Ix': 222000000.0,
 'KG': None,
 'KL': None,
 'L': 4000.0,
 'Tm': None,
 'dcx': 0.0,
 'dcy': -1.0,
 'id': 'CD',
 'nodej': Node("C",8000.0,4000.0),
 'nodek': Node("D",8000.0,0.0),
 'releases': {'MZJ'},
 'size': ''}

Node Loads

Table node_loads (file node_loads.csv) specifies the forces applied directly to the nodes. DIRN (direction) may be one of 'FX,FY,MZ'. 'LOAD' is an identifier of the kind of load being applied and F is the value of the load, normally given as a service or specified load. A later input table will specify load combinations and factors.


In [28]:
%%Table node_loads
LOAD,NODEID,DIRN,F
Wind,B,FX,-200000.

In [29]:
@extend
class Frame2D:
    
    COLUMNS_node_loads = ['LOAD','NODEID','DIRN','F']
    
    def input_node_loads(self):
        table = self.get_table('node_loads')
        dirns = ['FX','FY','FZ']
        for ix,row in table.iterrows():
            n = self.get_node(row.NODEID)
            if row.DIRN not in dirns:
                raise ValueError("Invalid node load direction: {} for load {}, node {}; must be one of '{}'"
                                .format(row.DIRN, row.LOAD, row.NODEID, ', '.join(dirns)))
            if row.DIRN in n.constraints:
                raise ValueError("Constrained node {} {} must not have load applied."
                                .format(row.NODEID,row.DIRN))
            l = makeNodeLoad({row.DIRN:row.F})
            self.nodeloads.append(row.LOAD,n,l)
        self.rawdata.node_loads = table

In [30]:
##test:
f.input_node_loads()

In [31]:
##test:
for o,l,fact in f.nodeloads.iterloads('Wind'):
    print(o,l,fact,l*fact)


Node("B",0.0,4000.0) NodeLoad(-200000.0,0.0,0.0) 1.0 NodeLoad(-200000.0,0.0,0.0)

Support Displacements

Table support_displacements (file support_displacements.csv) is optional and specifies imposed displacements of the supports. DIRN (direction) is one of 'DX, DY, RZ'. LOAD is as for Node Loads, above.

Of course, in this example the frame is statically determinate and so the support displacement will have no effect on the reactions or member end forces.


In [32]:
%%Table support_displacements
LOAD,NODEID,DIRN,DELTA
Other,A,DY,-10

In [33]:
@extend
class Frame2D:
    
    COLUMNS_support_displacements = ['LOAD','NODEID','DIRN','DELTA']
    
    def input_support_displacements(self):
        table = self.get_table('support_displacements',optional=True)
        forns = {'DX':'FX','DY':'FY','RZ':'MZ'}
        for ix,row in table.iterrows():
            n = self.get_node(row.NODEID)
            if row.DIRN not in forns:
                raise ValueError("Invalid support displacements direction: {} for load {}, node {}; must be one of '{}'"
                                .format(row.DIRN, row.LOAD, row.NODEID, ', '.join(forns.keys())))
            fd = forns[row.DIRN]
            if fd not in n.constraints:
                raise ValueError("Support displacement, load: '{}'  node: '{}'  dirn: '{}' must be for a constrained node."
                                .format(row.LOAD,row.NODEID,row.DIRN))
            l = makeNodeLoad({fd:row.DELTA})
            self.nodedeltas.append(row.LOAD,n,l)
        self.rawdata.support_displacements = table

In [34]:
##test:
f.input_support_displacements()

In [35]:
##test:
list(f.nodedeltas)[0]


Out[35]:
('other', Node("A",0.0,0.0), NodeLoad(0.0,-10.0,0.0))

Member Loads

Table member_loads (file member_loads.csv) specifies loads acting on members. Current types are PL (concentrated transverse, ie point load), CM (concentrated moment), UDL (uniformly distributed load over entire span), LVL (linearly varying load over a portion of the span) and PLA (point load applied parallel to member coincident with centroidal axis). Values W1 and W2 are loads or load intensities and A, B, and C are dimensions appropriate to the kind of load.


In [36]:
%%Table member_loads
LOAD,MEMBERID,TYPE,W1,W2,A,B,C
Live,BC,UDL,-50,,,,
Live,BC,PL,-200000,,5000

In [37]:
@extend
class Frame2D:
    
    COLUMNS_member_loads = ['LOAD','MEMBERID','TYPE','W1','W2','A','B','C']
    
    def input_member_loads(self):
        table = self.get_table('member_loads')
        for ix,row in table.iterrows():
            m = self.get_member(row.MEMBERID)
            l = makeMemberLoad(m.L,row)
            self.memberloads.append(row.LOAD,m,l)
        self.rawdata.member_loads = table

In [38]:
##test:
f.input_member_loads()

In [39]:
##test:
for o,l,fact in f.memberloads.iterloads('Live'):
    print(o.id,l,fact,l.fefs()*fact)


BC UDL(L=8000.0,w=-50) 1.0 EF(0.0,200000.0,266666666.66666666,0.0,200000.0,-266666666.66666666)
BC PL(L=8000.0,P=-200000,a=5000.0) 1.0 EF(0.0,63281.25,140625000.0,0.0,136718.75,-234375000.0)

Load Combinations

Table load_combinations (file load_combinations.csv) is optional and specifies factored combinations of loads. By default, there is always a load combination called all that includes all loads with a factor of 1.0. A frame solution (see below) indicates which CASE to use.


In [40]:
%%Table load_combinations
CASE,LOAD,FACTOR
One,Live,1.5
One,Wind,1.75

In [41]:
@extend
class Frame2D:
    
    COLUMNS_load_combinations = ['CASE','LOAD','FACTOR']
    
    def input_load_combinations(self):
        table = self.get_table('load_combinations',optional=True)
        if len(table) > 0:
            for ix,row in table.iterrows():
                self.loadcombinations.append(row.CASE,row.LOAD,row.FACTOR)
        if 'all' not in self.loadcombinations:
            all = self.nodeloads.names.union(self.memberloads.names)
            all = self.nodedeltas.names.union(all)
            for l in all:
                self.loadcombinations.append('all',l,1.0)
        self.rawdata.load_combinations = table

In [42]:
##test:
f.input_load_combinations()

In [43]:
##test:
for o,l,fact in f.loadcombinations.iterloads('One',f.nodeloads):
    print(o.id,l,fact)
for o,l,fact in f.loadcombinations.iterloads('One',f.memberloads):
    print(o.id,l,fact,l.fefs()*fact)


B NodeLoad(-200000.0,0.0,0.0) 1.75
BC UDL(L=8000.0,w=-50) 1.5 EF(0.0,300000.0,400000000.0,0.0,300000.0,-400000000.0)
BC PL(L=8000.0,P=-200000,a=5000.0) 1.5 EF(0.0,94921.875,210937500.0,0.0,205078.125,-351562500.0)

Load Iterators


In [44]:
@extend
class Frame2D:

    def iter_nodeloads(self,casename):
        for o,l,f in self.loadcombinations.iterloads(casename,self.nodeloads):
            yield o,l,f
    
    def iter_nodedeltas(self,casename):
        for o,l,f in self.loadcombinations.iterloads(casename,self.nodedeltas):
            yield o,l,f
    
    def iter_memberloads(self,casename):
        for o,l,f in self.loadcombinations.iterloads(casename,self.memberloads):
            yield o,l,f

In [45]:
##test:
for o,l,fact in f.iter_nodeloads('One'):
    print(o.id,l,fact)
for o,l,fact in f.iter_memberloads('One'):
    print(o.id,l,fact)


B NodeLoad(-200000.0,0.0,0.0) 1.75
BC UDL(L=8000.0,w=-50) 1.5
BC PL(L=8000.0,P=-200000,a=5000.0) 1.5

Number the DOFs


In [46]:
@extend
class Frame2D:
    
    def number_dofs(self):
        self.ndof = (3*len(self.nodes))
        self.ncons = sum([len(node.constraints) for node in self.nodes.values()])
        self.nfree = self.ndof - self.ncons
        ifree = 0
        icons = self.nfree
        self.dofdesc = [None] * self.ndof
        for node in self.nodes.values():
            for dirn,ix in node.DIRECTIONS.items():
                if dirn in node.constraints:
                    n = icons
                    icons += 1
                else:
                    n = ifree
                    ifree += 1
                node.dofnums[ix] = n
                self.dofdesc[n] = (node,dirn)

In [47]:
##test:
f.number_dofs()
f.ndof, f.ncons, f.nfree


Out[47]:
(12, 5, 7)

In [48]:
##test:
f.dofdesc


Out[48]:
[(Node("B",0.0,4000.0), 'FX'),
 (Node("B",0.0,4000.0), 'FY'),
 (Node("B",0.0,4000.0), 'MZ'),
 (Node("C",8000.0,4000.0), 'FX'),
 (Node("C",8000.0,4000.0), 'FY'),
 (Node("C",8000.0,4000.0), 'MZ'),
 (Node("D",8000.0,0.0), 'MZ'),
 (Node("A",0.0,0.0), 'FX'),
 (Node("A",0.0,0.0), 'FY'),
 (Node("A",0.0,0.0), 'MZ'),
 (Node("D",8000.0,0.0), 'FX'),
 (Node("D",8000.0,0.0), 'FY')]

In [49]:
##test:
f.get_node('D').dofnums


Out[49]:
[10, 11, 6]

Input Everything


In [50]:
@extend
class Frame2D:
    
    def input_all(self):
        self.input_nodes()
        self.input_supports()
        self.input_members()
        self.input_releases()
        self.input_properties()
        self.input_node_loads()
        self.input_support_displacements()
        self.input_member_loads()
        self.input_load_combinations()
        self.input_finish()
        
    def input_finish(self):
        self.number_dofs()

In [51]:
##test:
f.reset()
f.input_all()

Input From Files


In [52]:
##test:
f.reset()
DataSource.set_source('frame-1')
f.input_all()

In [53]:
##test:
vars(f.rawdata)


Out[53]:
{'load_combinations':   CASE  LOAD  FACTOR
 0  One  Live    1.50
 1  One  Wind    1.75,
 'member_loads':    LOAD MEMBERID TYPE      W1  W2       A   B   C
 0  Live       BC  UDL     -50 NaN     NaN NaN NaN
 1  Live       BC   PL -200000 NaN  5000.0 NaN NaN,
 'members':   MEMBERID NODEJ NODEK
 0       AB     A     B
 1       BC     B     C
 2       CD     C     D,
 'node_loads':    LOAD NODEID DIRN         F
 0  Wind      B   FX -200000.0,
 'nodes':   NODEID       X       Y
 0      A     0.0     0.0
 1      B     0.0  4000.0
 2      C  8000.0  4000.0
 3      D  8000.0     0.0,
 'properties':   MEMBERID      SIZE           IX        A
 0       BC  W460x106  488000000.0  13500.0
 1       AB   W310x97  222000000.0  12300.0
 2       CD            222000000.0  12300.0,
 'releases':   MEMBERID RELEASE
 0       AB     MZK
 1       CD     MZJ,
 'support_displacements':     LOAD NODEID DIRN  DELTA
 0  Other      A   DY    -10,
 'supports':   NODEID  C0  C1   C2
 0      A  FX  FY   MZ
 1      D  FX  FY  NaN}

In [54]:
##test:
f.rawdata.nodes


Out[54]:
NODEID X Y
0 A 0.0 0.0
1 B 0.0 4000.0
2 C 8000.0 4000.0
3 D 8000.0 0.0

In [55]:
##test:
f.members


Out[55]:
OrderedDict([('AB', Member("AB","Node("A",0.0,0.0)","Node("B",0.0,4000.0)")),
             ('BC',
              Member("BC","Node("B",0.0,4000.0)","Node("C",8000.0,4000.0)")),
             ('CD',
              Member("CD","Node("C",8000.0,4000.0)","Node("D",8000.0,0.0)"))])

In [56]:
##test:
DataSource.DATASOURCE.celldata


Out[56]:
{}

In [57]:
##test:
DataSource.DATASOURCE.tables


Out[57]:
{}

In [ ]: