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 MemberLoads import makeMemberLoad
from collections import OrderedDict, defaultdict
import numpy as np


Compiling notebook 'Tables.ipynb' to 'Tables.py'.

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,optional=False):
        columns = getattr(self,'COLUMNS_'+tablename)
        t = Table(tablename,columns=columns,optional=optional)
        t.read(optional=optional)
        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

Test Frame

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,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


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]:
@sl.extend(Frame2D)
class Frame2D:
    
    COLUMNS_supports = ('NODEID','C0','C1','C2')
    
    def install_supports(self):
        table = self.get_table('supports')
        for ix,row in table.data.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.install_supports()

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


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

Members


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

In [16]:
@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 [17]:
##test:
f.install_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)")),
             ('DC',
              Member("DC","Node("D",8000.0,0.0)","Node("C",8000.0,4000.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


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

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

In [21]:
##test:
f.install_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,
 'fefsl': None,
 'id': 'AB',
 'mefs': None,
 'nodej': Node("A",0.0,0.0),
 'nodek': Node("B",0.0,4000.0),
 'releases': {'MZK'}}

Properties

If the SST module is loadable, member properties may be specified by giving steel shape designations (such as 'W310x97') in the member properties data. If the module is not available, you may still give $A$ and $I_x$ directly (it only tries to lookup the properties if these two are not provided).


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,,
DC,,

In [25]:
@sl.extend(Frame2D)
class Frame2D:
    
    COLUMNS_properties = ('MEMBERID','SIZE','IX','A')
    
    def install_properties(self):
        table = self.get_table('properties')
        table = self.fill_properties(table)
        for ix,row in table.data.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):
        data = table.data
        prev = None
        for ix,row in data.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
                        data.loc[ix,'IX'] = Ix
                    if isnan(row.A):
                        nf += 1
                        data.loc[ix,'A'] = A
            elif isnan(row.SIZE):
                data.loc[ix,'SIZE'] = '' if nf == 0 else prev
            prev = data.loc[ix,'SIZE']
        table.data = data.fillna(method='ffill')
        return table

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

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


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

Node Loads


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

In [29]:
@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 [30]:
##test:
f.install_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)

Member Loads


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

In [33]:
@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 [34]:
##test:
f.install_member_loads()

In [35]:
##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,P=-200000,a=5000.0) 1.0 EF(0.0,63281.25,140625000.0,0.0,136718.75,-234375000.0)

Load Combinations


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

In [37]:
@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 [38]:
##test:
f.install_load_combinations()

In [39]:
##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 [40]:
@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 [41]:
##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

Accumulated Cell Data


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


Out[42]:
{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',
 u'properties': u'MEMBERID,SIZE,IX,A\nBC,W460x106,,\nAB,W310x97,,\nDC,,',
 u'releases': u'MEMBERID,RELEASE\nAB,MZK',
 u'supports': u'NODEID,C0,C1,C2\nA,FX,FY,MZ\nD,FX,FY'}

Input Everything


In [43]:
@sl.extend(Frame2D)
class Frame2D:
    
    def install_all(self):
        self.install_nodes()
        self.install_supports()
        self.install_members()
        self.install_releases()
        self.install_properties()
        self.install_node_loads()
        self.install_member_loads()
        self.install_load_combinations()

In [44]:
f = Frame2D(dsname='frame-6b')
f.install_all()

Number the DOFs


In [45]:
@sl.extend(Frame2D)
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 [46]:
##test:
f.number_dofs()

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


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

Display Nodes


In [48]:
def prhead(txt,ul='='):
    """Print a heading and underline it."""
    print()
    print(txt)
    if ul:
        print(ul*(len(txt)//len(ul)))
    print()

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

    def print_nodes(self,precision=0,printdof=False):
        prhead('Nodes:')
        print('Node          X         Y  Constraints  DOF #s')
        print('----      -----     -----  -----------  ------')
        for nid,node in self.nodes.items():
            ct = ','.join(sorted(node.constraints,key=lambda t: Node.DIRECTIONS[t]))
            dt = ','.join([str(x) for x in node.dofnums])
            print('{:<5s}{:>10.{precision}f}{:>10.{precision}f}  {:<11s}  {}'\
                  .format(nid,node.x,node.y,ct,dt,precision=precision))
        if not printdof:
            return
        print()
        print('DOF#   Node  Dirn')
        print('----   ----  ----')
        for i in range(len(self.dofdesc)):
            node,dirn = self.dofdesc[i]
            print('{:>4d}   {:<4s}  {}'.format(i,node.id,dirn))

In [50]:
##test:
f.print_nodes(printdof=True)


Nodes:
======

Node          X         Y  Constraints  DOF #s
----      -----     -----  -----------  ------
A             0         0  FX,FY,MZ     7,8,9
B             0      4000               0,1,2
C          8000      4000               3,4,5
D          8000         0  FX,FY        10,11,6

DOF#   Node  Dirn
----   ----  ----
   0   B     FX
   1   B     FY
   2   B     MZ
   3   C     FX
   4   C     FY
   5   C     MZ
   6   D     MZ
   7   A     FX
   8   A     FY
   9   A     MZ
  10   D     FX
  11   D     FY

Display Members


In [51]:
@sl.extend(Frame2D)
class Frame2D:
    
    def print_members(self,precision=1):
        prhead('Members:')
        print('Member   Node-J  Node-K    Length       dcx       dcy  Size                Ix           A  Releases')
        print('------   ------  ------    ------   -------   -------  --------      --------       -----  --------')
        for mid,memb in self.members.items():
            nj = memb.nodej
            nk = memb.nodek
            rt = ','.join(sorted(memb.releases,key=lambda t: Member.RELEASES[t]))
            print('{:<7s}  {:<6s}  {:<6s}  {:>8.{precision}f}  {:>8.5f}  {:>8.5f}  {:<10s}  {:>10g}  {:>10g}  {}'\
                  .format(memb.id,nj.id,nk.id,memb.L,memb.dcx,memb.dcy,str(memb.size),memb.Ix,memb.A,rt,precision=precision))

In [52]:
##test:
f.print_members()


Members:
========

Member   Node-J  Node-K    Length       dcx       dcy  Size                Ix           A  Releases
------   ------  ------    ------   -------   -------  --------      --------       -----  --------
AB       A       B         4000.0   0.00000   1.00000  W310x97       2.22e+08       12300  MZK
BC       B       C         8000.0   1.00000   0.00000  W460x106      4.88e+08       13500  
DC       D       C         4000.0   0.00000   1.00000                2.22e+08       12300  

Display loads


In [53]:
@sl.extend(Frame2D)
class Frame2D:
    
    def print_loads(self,precision=0):
        
        prhead('Node Loads:')
        if self.nodeloads:
            print('Type  Node      FX          FY          MZ')
            print('----  ----  ----------  ----------  ----------')
            for lname,node,load in self.nodeloads:
                print('{:<4s}  {:<4s}  {:>10.{precision}f}  {:>10.{precision}f}  {:>10.{precision}f}'
                      .format(lname,node.id,load.fx,load.fy,load.mz,precision=precision))
        else:
            print(" - - - none - - -")

        prhead('Member Loads:')
        if self.memberloads:
            print('Type  Member  Load')
            print('----  ------  ----------------')
            for lname,memb,load in self.memberloads:
                print("{:<4s}  {:<6s}  {}".format(lname,memb.id,load))
        else:
            print(" - - - none - - -")

        prhead("Load Combinations:")
        if self.loadcombinations:
            print('Combo  Type  Factor')
            print('-----  ----  ------')
            prev = None
            for cname,lname,f in self.loadcombinations:
                cn = ' '*(len(prev)//2)+'"' if cname == prev else cname
                print("{:<5s}  {:<4s}  {:>6.2f}".format(cn,lname,f))
                prev = cname
        else:
            print(" - - - none - - -")

In [54]:
##test:
f.print_loads()


Node Loads:
===========

Type  Node      FX          FY          MZ
----  ----  ----------  ----------  ----------
wind  B        -200000           0           0

Member Loads:
=============

Type  Member  Load
----  ------  ----------------
live  BC      UDL(L=8000.0,w=-50)
live  BC      PL(L=8000.0,P=-200000,a=5000.0)

Load Combinations:
==================

Combo  Type  Factor
-----  ----  ------
one    live    1.50
 "     wind    1.75

In [61]:
@sl.extend(Frame2D)
class Frame2D:
    
    def print_input(self):
        
        prhead('Frame '+str(self.dsname)+':')
        print()
        print('              # of nodal degrees of freedom:',self.ndof)
        print('  # of constrained nodal degrees of freedom:',self.ncons)
        print('# of unconstrained nodal degrees of freedom:',self.nfree,' (= degree of kinematic indeterminacy)')
        m = len(self.members)
        r = self.ncons
        j = len(self.nodes)
        c = len(self.rawdata.releases)
        print()
        print('                               # of members:',m)
        print('                             # of reactions:',r)
        print('                                 # of nodes:',j)
        print('                            # of conditions:',c)
        print('             degree of static indeterminacy:',(3*m+r)-(3*j+c))
        print('\n')

        self.print_nodes()
        print('\n')
        self.print_members()
        print('\n')
        self.print_loads()

In [62]:
##test:
f.print_input()


Frame frame-6b:
===============


              # of nodal degrees of freedom: 12
  # of constrained nodal degrees of freedom: 5
# of unconstrained nodal degrees of freedom: 7  (= degree of kinematic indeterminacy)

                               # of members: 3
                             # of reactions: 5
                                 # of nodes: 4
                            # of conditions: 1
             degree of static indeterminacy: 1



Nodes:
======

Node          X         Y  Constraints  DOF #s
----      -----     -----  -----------  ------
A             0         0  FX,FY,MZ     7,8,9
B             0      4000               0,1,2
C          8000      4000               3,4,5
D          8000         0  FX,FY        10,11,6



Members:
========

Member   Node-J  Node-K    Length       dcx       dcy  Size                Ix           A  Releases
------   ------  ------    ------   -------   -------  --------      --------       -----  --------
AB       A       B         4000.0   0.00000   1.00000  W310x97       2.22e+08       12300  MZK
BC       B       C         8000.0   1.00000   0.00000  W460x106      4.88e+08       13500  
DC       D       C         4000.0   0.00000   1.00000                2.22e+08       12300  



Node Loads:
===========

Type  Node      FX          FY          MZ
----  ----  ----------  ----------  ----------
wind  B        -200000           0           0

Member Loads:
=============

Type  Member  Load
----  ------  ----------------
live  BC      UDL(L=8000.0,w=-50)
live  BC      PL(L=8000.0,P=-200000,a=5000.0)

Load Combinations:
==================

Combo  Type  Factor
-----  ----  ------
one    live    1.50
 "     wind    1.75

In [ ]: