Milestone 1 - input of nodes, members, loads and load combos.

2-Dimensional Frame Analysis - Version 04

This program performs an elastic analysis of 2-dimensional structural frames. It has the following features:

  1. Input is provided by a set of CSV files (and cell-magics exist so you can specifiy the CSV data in a notebook cell). See the example below for an, er, example.
  2. Handles concentrated forces on nodes, and concentrated forces, concentrated moments, and linearly varying distributed loads applied transversely anywhere along the member (i.e., there is as yet no way to handle longitudinal load components).
  3. It handles fixed, pinned, roller supports and member end moment releases (internal pins). The former are handled by assigning free or fixed global degrees of freedom, and the latter are handled by adjusting the member stiffness matrix.
  4. It has the ability to handle named sets of loads with factored combinations of these.
  5. The DOF #'s are assigned by the program, with the fixed DOF #'s assigned after the non-fixed. The equilibrium equation is then partitioned for solution. Among other advantages, this means that support settlement could be easily added (there is no UI for that, yet).
  6. A non-linear analysis can be performed using the P-Delta method (fake shears are computed at column ends due to the vertical load acting through horizontal displacement differences, and these shears are applied as extra loads to the nodes).
  7. A full non-linear (2nd order) elastic analysis will soon be available by forming the equilibrium equations on the deformed structure. This is very easy to add, but it hasn't been done yet. Shouldn't be too long.
  8. There is very little no documentation below, but that will improve, slowly.

In [1]:
from __future__ import print_function

In [2]:
import salib as sl
sl.import_notebooks()
from Tables import Table
from Nodes import Node
from Members import Member
from LoadSets import LoadSet, LoadCombination
from NodeLoads import makeNodeLoad
from FixedEndForces import makeMemberLoad
from collections import OrderedDict, defaultdict

In [3]:
class Object(object):
    pass

class Frame2D(object):
    
    def __init__(self,dsname=None):
        self.dsname = dsname
        self.rawdata = Object()
        self.nodes = OrderedDict()
        self.members = OrderedDict()
        self.nodeloads = LoadSet()
        self.memberloads = LoadSet()
        self.loadcombinations = LoadCombination()
        #self.dofdesc = []
        #self.nodeloads = defaultdict(list)
        #self.membloads = defaultdict(list)
        self.ndof = 0
        self.nfree = 0
        self.ncons = 0
        self.R = None
        self.D = None
        self.PDF = None    # P-Delta forces
        
    COLUMNS_xxx = [] # list of column names for table 'xxx'
        
    def get_table(self,tablename,extrasok=False):
        columns = getattr(self,'COLUMNS_'+tablename)
        t = Table(tablename,columns=columns)
        t.read()
        reqdl= columns
        reqd = set(reqdl)
        prov = set(t.columns)
        if reqd-prov:
            raise Exception('Columns missing {} for table "{}". Required columns are: {}'\
                            .format(list(reqd-prov),tablename,reqdl))
        if not extrasok:
            if prov-reqd:
                raise Exception('Extra columns {} for table "{}". Required columns are: {}'\
                               .format(list(prov-reqd),tablename,reqdl))
        return t

Nodes


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]:
@sl.extend(Frame2D)
class Frame2D:
    
    COLUMNS_nodes = ('NODEID','X','Y')
        
    def install_nodes(self):
        node_table = self.get_table('nodes')
        for ix,r in node_table.data.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.install_nodes()

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


Out[8]:
OrderedDict([('A', Node("A",0,0)),
             ('B', Node("B",0,4000)),
             ('C', Node("C",8000,4000)),
             ('D', Node("D",8000,0))])

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


Out[9]:
Node("C",8000,4000)

Members


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

In [11]:
@sl.extend(Frame2D)
class Frame2D:
    
    COLUMNS_members = ('MEMBERID','NODEJ','NODEK')
    
    def install_members(self):
        table = self.get_table('members')
        for ix,m in table.data.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 [12]:
##test:
f.install_members()
f.members


Out[12]:
OrderedDict([('AB', Member("AB","Node("A",0,0)","Node("B",0,4000)")),
             ('BC', Member("BC","Node("B",0,4000)","Node("C",8000,4000)")),
             ('DC', Member("DC","Node("D",8000,0)","Node("C",8000,4000)"))])

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


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

Node Loads


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

In [15]:
@sl.extend(Frame2D)
class Frame2D:
    
    COLUMNS_node_loads = ('LOAD','NODEID','DIRN','F')
    
    def install_node_loads(self):
        table = self.get_table('node_loads')
        dirns = ['FX','FY','FZ']
        for ix,row in table.data.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)))
            l = makeNodeLoad({row.DIRN:row.F})
            self.nodeloads.append(row.LOAD,n,l)
        self.rawdata.node_loads = table

In [16]:
##test:
f.install_node_loads()

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


Node("B",0,4000) NodeLoad(-200000.0,0.0,0.0) 1.0 NodeLoad(-200000.0,0.0,0.0)

Member Loads


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

In [19]:
@sl.extend(Frame2D)
class Frame2D:
    
    COLUMNS_member_loads = ('LOAD','MEMBERID','TYPE','W1','W2','A','B','C')
    
    def install_member_loads(self):
        table = self.get_table('member_loads')
        for ix,row in table.data.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 [20]:
##test:
f.install_member_loads()

In [21]:
##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.667,0.0,200000.0,-266666666.667)
BC PL(L=8000.0,W1=-200000,a=5000.0) 1.0 EF(0.0,63281.25,140625000.0,0.0,136718.75,-234375000.0)

Load Combinations


In [22]:
%%Table load_combinations
COMBO,LOAD,FACTOR
One,Live,1.5
One,Wind,1.75

In [23]:
@sl.extend(Frame2D)
class Frame2D:
    
    COLUMNS_load_combinations = ('COMBO','LOAD','FACTOR')
    
    def install_load_combinations(self):
        table = self.get_table('load_combinations')
        for ix,row in table.data.iterrows():
            self.loadcombinations.append(row.COMBO,row.LOAD,row.FACTOR)
        self.rawdata.load_combinations = table

In [24]:
##test:
f.install_load_combinations()

In [25]:
##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,W1=-200000,a=5000.0) 1.5 EF(0.0,94921.875,210937500.0,0.0,205078.125,-351562500.0)

Load Iterators


In [26]:
@sl.extend(Frame2D)
class Frame2D:

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

In [27]:
##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,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,W1=-200000,a=5000.0) 1.5 EF(0.0,94921.875,210937500.0,0.0,205078.125,-351562500.0)

Accumulated Cell Data


In [28]:
##test:
Table.CELLDATA


Out[28]:
{u'load_combinations': u'COMBO,LOAD,FACTOR\nOne,Live,1.5\nOne,Wind,1.75',
 u'member_loads': u'LOAD,MEMBERID,TYPE,W1,W2,A,B,C\nLive,BC,UDL,-50,,,,\nLive,BC,PL,-200000,,5000',
 u'members': u'MEMBERID,NODEJ,NODEK\nAB,A,B\nBC,B,C\nDC,D,C',
 u'node_loads': u'LOAD,NODEID,DIRN,F\nWind,B,FX,-200000.',
 u'nodes': u'NODEID,X,Y,Z\nA,0,0,5000\nB,0,4000,5000\nC,8000,4000,5000\nD,8000,0,5000'}