Exemple #1
0
class LoadCase(object):
    """
    A class representing a structural load case.

    Parameters
    ----------
    name : str
        The name of the load case.
    node_loads : list
        A list of :class:`.NodeLoad` to apply with the load case.
    elem_loads : list
        A list of :class:`.ElementLoad` to apply with the load case.
    """
    # Custom properties
    name = propy.str_property('name')

    def __init__(self, name, node_loads=[], elem_loads=[]):
        self.name = name
        self.node_loads = node_loads
        self.elem_loads = elem_loads

    __repr__ = propy.repr_method('name', 'node_loads', 'elem_loads')

    def set_nodes(self, ndict):
        """
        Sets the node references for all node loads assigned to the load case.

        Parameters
        ----------
        ndict : dict
            A dictionary mapping node names to node objects.
        """
        for n in self.node_loads:
            n.set_node(ndict)

    def set_elements(self, edict):
        """
        Sets the element references for all element loads assigned to the load
        case.

        Parameters
        ----------
        edict : dict
            A dictionary mapping element names to element objects.
        """
        for e in self.elem_loads:
            e.set_element(edict)
Exemple #2
0
class Material(object):
    """
    A class representing an engineered material.

    Parameters
    ----------
    name : str
        The name of the material.
    elasticity : float
        The modulus of elasticity.
    rigidity : float
        The modulus of rigidity.
    """
    # Custom properties
    name = propy.str_property('name')

    def __init__(self, name, elasticity, rigidity=0):
        self.name = name
        self.elasticity = elasticity
        self.rigidity = rigidity

    __repr__ = propy.repr_method('name', 'elasticity', 'rigidity')
Exemple #3
0
class ElementGroup(object):
    """
    A class representing a group of element properties.

    Parameters
    ----------
    name : str
        The name of the group.
    section : :class:`.CrossSection`
        The group cross section.
    material : :class:`.Material`
        The group material.
    """
    # Custom properties
    name = propy.str_property('name')

    def __init__(self, name, section, material):
        self.name = name
        self.section = section
        self.material = material

    __repr__ = propy.repr_method('name', 'section', 'material')
Exemple #4
0
class ElementLoad(np.ndarray):
    """
    A class representing an element load.

    Parameters
    ----------
    element : str
        The name of the element to which the loads are applied.
    fx, fy, fz : float
        The global forces applied to the element.
    mx, my, mz : float
        The global moments applied to the element.
    ix, : float
        The distance from the i node at where the loads are applied.
    dx : float
        The distance from the ix position toward the j node over which
        the loads are applied.
    """
    # Custom properties
    element = propy.str_property('element')
    fx = propy.index_property(0)
    fy = propy.index_property(1)
    fz = propy.index_property(2)
    mx = propy.index_property(3)
    my = propy.index_property(4)
    mz = propy.index_property(5)
    _element_ref = propy.weakref_property('_element_ref')

    def __new__(cls, element, fx=0, fy=0, fz=0, mx=0, my=0, mz=0, ix=0, dx=-1):
        obj = np.array([fx, fy, fz, mx, my, mz], dtype='float').view(cls)
        obj.element = element
        obj.ix = ix
        obj.dx = dx
        return obj

    def __array_finalize__(self, obj):
        if obj is None: return
        self.element = getattr(obj, 'element', '')
        self.ix = getattr(obj, 'ix', 0)
        self.dx = getattr(obj, 'dx', 0)
        self._element_ref = None

    def __repr__(self):
        s = [
            'element={!r}'.format(self.element), 'forces={!r}'.format(
                (self.fx, self.fy, self.fz)), 'moments={!r}'.format(
                    (self.mx, self.my, self.mz)), 'ix={!r}'.format(self.ix),
            'dx={!r}'.format(self.dx)
        ]

        return '{}({})'.format(type(self).__name__, ', '.join(s))

    def forces(self):
        """Returns the force vector."""
        return self[:3]

    def moments(self):
        """Returns the moment vector."""
        return self[3:6]

    def get_element(self):
        """Gets the referenced element."""
        if self._element_ref is None:
            raise ValueError('Element has not been set.')
        return self._element_ref

    def set_element(self, edict):
        """
        Sets the element reference.

        Parameters
        ----------
        edict : dict
            A dictionary mapping node names to node objects.
        """
        self._element_ref = edict[self.element]

    def local_reactions(self, di=(0, 0, 0), dj=(0, 0, 0)):
        """
        Returns the local end reactions for the element.

        Parameters
        ----------
        di, dj : array
            The deflections at the i and j ends of the element.
        """
        di, dj = np.asarray(di), np.asarray(dj)
        e = self.get_element()
        xi, xj = e.get_nodes()

        dx, dy, dz = (xj - xi) + (dj - di)
        fx, fy, fz = self.forces()
        mx, my, mz = self.moments()

        r = local_reactions(fx, fy, fz, mx, my, mz, dx, dy, dz, e.roll,
                            self.ix, self.dx, e.imx_free, e.imy_free,
                            e.imz_free, e.jmx_free, e.jmy_free, e.jmz_free)

        return r

    def global_reactions(self, di=(0, 0, 0), dj=(0, 0, 0)):
        """
        Returns the global end reactions for the element.

        Parameters
        ----------
        di, dj : array
            The deflections at the i and j ends of the element.
        """
        di, dj = np.asarray(di), np.asarray(dj)
        e = self.get_element()
        t = e.transformation_matrix(di, dj)
        q = self.local_reactions(di, dj)
        return t.T.dot(q)
Exemple #5
0
class Alignment(object):
    """
    A class representing a survey alignment.

    Parameters
    ----------
    name : str
        Name of alignment.
    pis : list
        A list of :class:`.PI`.
    stakes : list
        A list of :class:`.SurveyStake`.
    grid : float
        The grid size used for spatial hash generation.
    view_offset : float
        The offset beyond which points will be ignored when generating station
        coordinates from global coordinates.
    view_margin : float
        The station margin at the beginning and end of the alignment. Beyond
        this threshold, generated station coordinates from global coordinates
        will be ignored.

    Examples
    --------
    .. plot:: ../examples/survey/alignment_ex1.py
        :include-source:
    """
    BISC_TOL = 1e-4  # Bisector station tolerance

    # Custom properties
    name = propy.str_property('name')

    def __init__(self,
                 name,
                 pis=[],
                 stakes=[],
                 grid=10,
                 view_offset=15,
                 view_margin=15):
        self.name = name
        self.pis = pis
        self.stakes = stakes
        self.grid = grid
        self.view_offset = view_offset
        self.view_margin = view_margin

    __repr__ = propy.repr_method('name', 'grid', 'view_offset', 'view_margin',
                                 'pis', 'stakes')

    def set_stake_xy(self):
        """
        Sets the xy coordinates for all station stakes assigned to the
        alignment.
        """
        obj = []
        p = []

        for x in self.stakes:
            if x._type == 'station':
                obj.append(x)
                p.append((x.station, x.offset, x.rotation))

        p = np.array(p)
        c, s = np.cos(p[:, 2]), np.sin(p[:, 2])
        c, s = np.column_stack([c, -s]), np.column_stack([s, c])

        b = self.coordinates(p[:, 0])
        p = self.coordinates(p[:, :2])
        p -= b

        c = np.einsum('ij,ij->i', p, c)
        s = np.einsum('ij,ij->i', p, s)
        p = np.column_stack([c, s])
        p += b

        for a, b in zip(obj, p):
            a[:2] = b

    def pi_coordinates(self):
        """
        Returns an array of PI coordinates of shape (N, 3).
        """
        if not self.pis:
            return np.zeros((0, 3), dtype='float')
        return np.array(self.pis, dtype='float')

    def pi_radii(self):
        """
        Returns an array of PI horizontal curve radii of shape (N,).
        """
        return np.array([x.radius for x in self.pis], dtype='float')

    def azimuths(self):
        """
        Returns an array of alignment azimuths in the shape (N,). Each element
        of the array corresponds to a PI index and represents the azimuth of
        the alignment ahead of that PI.
        """
        if not self.pis:
            return np.zeros(0, dtype='float')

        elif len(self.pis) == 1:
            return np.zeros(1, dtype='float')

        x = self.pi_coordinates()
        dx = x[1:, :2] - x[:-1, :2]
        az = np.arctan2(dx[:, 0], dx[:, 1])
        az = np.append(az, az[-1])

        return np.asarray(az, dtype='float')

    def deflection_angles(self):
        """
        Returns an array of PI deflection angles in the shape (N,). The angle
        is negative for turns to the left and positive for turns to the right.
        """
        if not self.pis:
            return np.zeros(0, dtype='float')

        elif len(self.pis) == 1:
            return np.zeros(1, dtype='float')

        az = self.azimuths()
        da = az[1:] - az[:-1]
        i = (np.abs(da) > np.pi)
        da[i] -= 2 * np.pi * np.sign(da[i])
        da = np.insert(da, 0, 0)

        return np.asarray(da, dtype='float')

    def tangent_ordinates(self):
        """
        Returns an array of tangent ordinates corresponding to each PI
        in the shape (N,). This value is the horizontal distance between
        the PI and PC and PI and PT.
        """
        r = self.pi_radii()
        da = self.deflection_angles()
        return r * np.abs(np.tan(da / 2))

    def curve_lengths(self):
        """
        Returns an array of horizontal curve lengths corresponding to each PI
        in teh shape (N,). This value is the station distance between the
        PC and PT.
        """
        r = self.pi_radii()
        da = self.deflection_angles()
        return r * np.abs(da)

    def middle_ordinates(self):
        """
        Returns an array of middle ordinate distances corresponding to each PI
        in the shape (N,). This value is the horizontal distance between the
        MPC and midpoint of the chord line between the PC and PT.
        """
        r = self.pi_radii()
        da = np.abs(self.deflection_angles())
        return r * (1 - np.cos(da / 2))

    def external_ordinates(self):
        """
        Returns an array of external ordinates corresponding to each PI
        in the shape (N,). This is the horizontal distance between the
        MPC and PI.
        """
        r = self.pi_radii()
        da = self.deflection_angles()
        return r * np.abs(np.tan(da / 2) * np.tan(da / 4))

    def chord_distances(self):
        """
        Returns an array of chord distances corresponding to each PI
        in teh shape (N,). This is the straight line horizontal distance
        between the PC and PT.
        """
        r = self.pi_radii()
        da = np.abs(self.deflection_angles())
        return 2 * r * np.sin(da / 2)

    def pt_coordinates(self):
        """
        Returns an array of (x, y) coordinates for the Point of Tangents (PT)
        in the shape (N, 2).
        """
        if not self.pis:
            return np.zeros((0, 3), dtype='float')

        pi = self.pi_coordinates()
        az = self.azimuths()
        t = self.tangent_ordinates()
        t = np.expand_dims(t, 1)
        uv = np.column_stack([np.sin(az), np.cos(az)])
        pt = pi[:, :2] + t * uv

        return np.asarray(pt, dtype='float')

    def pc_coordinates(self):
        """
        Returns an array of (x, y) coordinates for the Point of Curves (PC)
        in the shape (N, 2).
        """
        if not self.pis:
            return np.zeros((0, 3), dtype='float')

        pi = self.pi_coordinates()
        az = self.azimuths()
        da = self.deflection_angles()
        t = self.tangent_ordinates()
        t = np.expand_dims(t, 1)
        az -= da
        uv = np.column_stack([np.sin(az), np.cos(az)])
        pc = pi[:, :2] - t * uv

        return np.asarray(pc, dtype='float')

    def mpc_coordinates(self):
        """
        Returns an array of (x, y) coordinates for the Midpoint of Curves (MPC)
        in the shape (N, 2).
        """
        if not self.pis:
            return np.zeros((0, 3), dtype='float')

        pi = self.pi_coordinates()
        az = self.azimuths()
        da = self.deflection_angles()
        e = self.external_ordinates()
        az += (np.pi - da) / 2
        da = np.expand_dims(da, 1)
        e = np.expand_dims(e, 1)
        uv = np.column_stack([np.sin(az), np.cos(az)])
        mpc = pi[:, :2] + np.sign(da) * e * uv

        return np.asarray(mpc, dtype='float')

    def rp_coordinates(self):
        """
        Returns an array of (x, y) coordinates for the Radius Points (RP)
        in the shape (N, 2).
        """
        if not self.pis:
            return np.zeros((0, 3), dtype='float')

        pi = self.pi_coordinates()
        az = self.azimuths()
        da = self.deflection_angles()
        e = self.external_ordinates()
        e = np.expand_dims(e, 1)
        r = self.pi_radii()
        r = np.expand_dims(r, 1)
        az += (np.pi - da) / 2
        uv = np.column_stack([np.sin(az), np.cos(az)])
        da = np.expand_dims(da, 1)
        rp = pi[:, :2] + np.sign(da) * (e + r) * uv

        return np.asarray(rp, dtype='float')

    def pt_stations(self):
        """
        Returns an array of (x, y) coordinates for the Point of Tangents (PT)
        in the shape (N, 2).
        """
        if not self.pis:
            return np.zeros(0, dtype='float')

        x = self.pi_coordinates()
        tan = self.tangent_ordinates()
        dist = np.linalg.norm(x[:-1, :2] - x[1:, :2], axis=1)
        dist = np.insert(dist, 0, 0)
        dist += self.curve_lengths() - tan
        sta = np.cumsum(dist)
        sta[1:] -= np.cumsum(tan[:-1])

        return np.asarray(sta, dtype='float')

    def pc_stations(self):
        """
        Returns an array of stations for the Point of Curves (PC) in the
        shape (N,).
        """
        if not self.pis:
            return np.zeros(0, dtype='float')

        sta = self.pt_stations() - self.curve_lengths()
        return np.asarray(sta, dtype='float')

    def mpc_stations(self):
        """
        Returns an array of stations for the Midpoint of Curves (MPC)
        in the shape (N,).
        """
        return 0.5 * (self.pt_stations() + self.pc_stations())

    def poc_transforms(self):
        """
        Returns the POC transforms in the shape (N, 2, 2). These transforms
        project (x, y) global coordinates to (offset, station) station
        coordinates relative to the PI angle bisector.
        """
        az = self.azimuths()
        da = self.deflection_angles()
        l = az - da / 2
        t = l + np.pi / 2
        t = np.column_stack([np.sin(t), np.cos(t), np.sin(l), np.cos(l)])

        return t.reshape(t.shape[0], 2, 2)

    def pot_transforms(self):
        """
        Returns the POT transforms in the shape (N, 2, 2). These transforms
        project (x, y) global coordinates to (offset, station) station
        coordinates relative to the tangent line between PI's.
        """
        l = self.azimuths()
        t = l + np.pi / 2
        t = np.column_stack([np.sin(t), np.cos(t), np.sin(l), np.cos(l)])
        return t.reshape(t.shape[0], 2, 2)

    def segment_indices(self, stations):
        """
        Determines the segment type and PI indices corresponding to the
        specified stations. Returns an array of shape (N, 2). The first column
        of the array contains 1 if the station is located along an alignment
        tangent or 2 if the station is located on a horizontal curve or
        alignment bisector. The second column contains the index corresponding
        to the PI where the point is located.

        Parameters
        ----------
        stations : array
            An array of stations of shape (N,).
        """
        sta = np.asarray(stations)
        pc_sta = self.pc_stations()
        pt_sta = self.pt_stations()
        s = SpatialHash(np.expand_dims(sta, 1), self.grid)

        # Set values beyond alignment limits
        r = np.zeros((sta.shape[0], 2), dtype='int')
        r[sta < 0] = 1, 0
        r[sta > pt_sta[-1]] = 1, pt_sta.shape[0] - 1

        # POT segments
        ah = np.expand_dims(pc_sta[1:], 1)
        bk = np.expand_dims(pt_sta[:-1], 1)

        for i, (a, b) in enumerate(zip(ah, bk)):
            f = s.query_range(b, a, 0)
            r[f] = 1, i

        # POC segments
        f = (self.curve_lengths() == 0)
        pc_sta[f] -= Alignment.BISC_TOL
        pt_sta[f] += Alignment.BISC_TOL

        ah = np.expand_dims(pt_sta[1:-1], 1)
        bk = np.expand_dims(pc_sta[1:-1], 1)

        for i, (a, b) in enumerate(zip(ah, bk)):
            f = s.query_range(b, a, 0)
            r[f] = 2, i + 1

        return r

    def _pot_coordinates(self, result, seg, sta_coords):
        """
        Assigns the POT coordinates for :meth:`.coordinates`.

        Parameters
        ----------
        result : array
            The array to which the results will be added.
        seg : array
            The segment indices array.
        sta_coords : array
            An array of station coordinates of shape (N, 2).
        """
        f = (seg[:, 0] == 1)

        if not f.any():
            return

        sta = np.expand_dims(sta_coords[f, 0], 1)
        off = np.expand_dims(sta_coords[f, 1], 1)

        i = seg[f, 1]
        t = self.pot_transforms()[i]
        tx, ty = t[:, 0], t[:, 1]
        pt_coord = self.pt_coordinates()[i]
        pt_sta = np.expand_dims(self.pt_stations()[i], 1)

        result[f] = tx * off + ty * (sta - pt_sta) + pt_coord

    def _poc_bisc_coordinates(self, result, seg, sta_coords):
        """
        Assigns the POC bisector coordinates for :meth:`.coordinates`.

        Parameters
        ----------
        result : array
            The array to which the results will be added.
        seg : array
            The segment indices array.
        sta_coords : array
            An array of station coordinates of shape (N, 2).
        """
        f = (seg[:, 0] == 2) & (self.curve_lengths() == 0)[seg[:, 1]]

        if not f.any():
            return

        off = np.expand_dims(sta_coords[f, 1], 1)

        i = seg[f, 1]
        tx = self.poc_transforms()[i, 0]
        rp_coord = self.rp_coordinates()[i]

        result[f] = tx * off + rp_coord

    def _poc_curve_coordinates(self, result, seg, sta_coords):
        """
        Assigns the POC curve coordinates for :meth:`.coordinates`.

        Parameters
        ----------
        result : array
            The array to which the results will be added.
        seg : array
            The segment indices array.
        sta_coords : array
            An array of station coordinates of shape (N, 2).
        """
        l = self.curve_lengths()
        f = (seg[:, 0] == 2) & (l != 0)[seg[:, 1]]

        if not f.any():
            return

        sta = sta_coords[f, 0]
        off = sta_coords[f, 1]

        i = seg[f, 1]
        tx = self.poc_transforms()[i, 0]
        mpc_sta = self.mpc_stations()[i]
        rp_coord = self.rp_coordinates()[i]
        da = self.deflection_angles()[i]
        r = np.expand_dims(self.pi_radii()[i], 1)

        beta = da * (mpc_sta - sta) / l[i]
        c, s = np.cos(beta), np.sin(beta)
        c, s = np.column_stack([c, -s]), np.column_stack([s, c])

        c = np.einsum('ij,ij->i', tx, c)
        s = np.einsum('ij,ij->i', tx, s)

        tx = np.column_stack([c, s])
        da = np.sign(np.expand_dims(da, 1))
        off = np.expand_dims(off, 1)

        result[f] = tx * (off - da * r) + rp_coord

    def coordinates(self, sta_coords):
        """
        Returns the (x, y) or (x, y, z) global coordinates corresponding
        to the input station coordinates. Result is in the shape of (N, 2)
        of (N, 3).

        Parameters
        ----------
        sta_coords : array
            An array of (station), (station, offset), or (station, offset, z)
            coordinates of the shape (N,), (N, 2) or (N, 3).
        """
        sta_coords = np.asarray(sta_coords)

        # If shape is (N,), add zero offsets
        if len(sta_coords.shape) == 1:
            sta_coords = np.column_stack(
                [sta_coords, np.zeros(sta_coords.shape[0])])

        result = np.zeros((sta_coords.shape[0], 2), dtype='float')
        seg = self.segment_indices(sta_coords[:, 0])

        self._pot_coordinates(result, seg, sta_coords)
        self._poc_bisc_coordinates(result, seg, sta_coords)
        self._poc_curve_coordinates(result, seg, sta_coords)

        # Add z coordinate to result if available
        if sta_coords.shape[1] == 3:
            result = np.column_stack([result, sta_coords[:, 2]])

        return np.asarray(result, dtype='float')

    def _pot_station_coordinates(self, result, spatial_hash, coords):
        """
        Adds the POT station coordinates within the view.

        Parameters
        ----------
        result : dict
            The dictionary to which the results will be added.
        spatial_hash : array
            The spatial hash.
        coords : array
            An array of coordinates of shape (N, 2) or (N, 3).
        """
        t = self.pot_transforms()
        pt_sta = self.pt_stations()
        pt_coord = self.pt_coordinates()

        bk = self.pt_coordinates()[:-1]
        ah = self.pc_coordinates()[1:]

        if t.shape[0] > 0:
            bk[0] -= self.view_margin * t[0, 1]
            ah[-1] += self.view_margin * t[-1, 1]

        for i, (a, b) in enumerate(zip(ah, bk)):
            f = spatial_hash.query_range(b, a, self.view_offset)

            if f.shape[0] == 0:
                continue

            delta = coords[f, :2] - pt_coord[i]
            sta = np.dot(delta, t[i, 1]) + pt_sta[i]
            off = np.dot(delta, t[i, 0])

            if coords.shape[1] == 3:
                p = np.column_stack([sta, off, coords[f, 2]])
            else:
                p = np.column_stack([sta, off])

            for n, m in enumerate(f):
                if m not in result:
                    result[m] = []
                result[m].append(p[n])

    def _poc_station_coordinates(self, result, spatial_hash, coords):
        """
        Adds the POC station coordinates within the view.

        Parameters
        ----------
        result : dict
            The dictionary to which the results will be added.
        spatial_hash : array
            The spatial hash.
        coords : array
            An array of coordinates of shape (N, 2) or (N, 3).
        """
        l = self.curve_lengths()
        t = self.poc_transforms()
        da = self.deflection_angles()
        pc_sta = self.pc_stations()
        pt_sta = self.pt_stations()
        rp_coord = self.rp_coordinates()
        pt_coord = self.pt_coordinates()

        for i in range(1, len(self.pis) - 1):
            r = self.pis[i].radius
            ro = r + self.view_offset
            ri = max(r - self.view_offset, 0)
            f = spatial_hash.query_point(rp_coord[i], ro, ri)

            if f.shape[0] == 0:
                continue

            if l[i] == 0:
                # Angle bisector
                delta = coords[f, :2] - pt_coord[i]
                sta = np.dot(delta, t[i, 1]) + pt_sta[i]
                off = np.dot(delta, t[i, 0])

                g = ((np.abs(off) <= self.view_offset)
                     & (sta >= pt_sta[i] - Alignment.BISC_TOL)
                     & (sta <= pt_sta[i] + Alignment.BISC_TOL))
            else:
                # Horizontal curve
                delta = pt_coord[i] - rp_coord[i]
                delta = np.arctan2(delta[0], delta[1])
                p = coords[f, :2] - rp_coord[i]
                delta -= np.arctan2(p[:, 0], p[:, 1])

                sta = pt_sta[i] - (l[i] / da[i]) * delta
                off = np.sign(da[i]) * (r - np.linalg.norm(p, axis=1))

                g = (sta >= pc_sta[i]) & (sta <= pt_sta[i])

            if coords.shape[1] == 3:
                p = np.column_stack([sta, off, coords[f, 2]])[g]
            else:
                p = np.column_stack([sta, off])[g]

            for n, m in enumerate(f[g]):
                if m not in result:
                    result[m] = []
                result[m].append(p[n])

    def station_coordinates(self, coordinates):
        """
        Finds the (station, offset) or (station, offset, z) coordinates
        for the input global coordinates. Returns a dictionary of point
        indices with arrays of shape (N, 2) or (N, 3). If a point index
        is not in the dictionary, then no points are located along
        the alignment within the view threshold.

        Parameters
        ----------
        coordinates : array
            An array of (x, y) or (x, y, z) global coordinates in the shape
            (N, 2) or (N, 3).
        """
        coordinates = np.asarray(coordinates)
        s = SpatialHash(coordinates[:, :2], self.grid)
        result = {}

        self._pot_station_coordinates(result, s, coordinates)
        self._poc_station_coordinates(result, s, coordinates)

        for k, x in result.items():
            result[k] = np.array(x, dtype='float')

        return result

    def plot_plan(self, ax=None, step=1, symbols={}):
        """
        Plots a the plan view for the alignment.

        Parameters
        ----------
        ax : :class:`matplotlib.axes.Axes`
            The axex to which to add the plot. If None, a new figure and axes
            will be created.
        step : float
            The step interval to use for plotting points along horizontal
            curves.
        symbols : dict
            A dictionary of symbols to use for the plot. The following keys
            are used:

                * `pi`: PI point symbol, default is 'r.'
                * `rp`: RP point symbol, default is 'c.'
                * `pc`: PC point symbol, default is 'b.'
                * `pt`: PT point symbol, default is 'b.'
                * `alignment`: Alignment lines, default is 'b-'
                * `stakes`: Stake symbols, default is 'rx'

        Examples
        --------
        .. plot:: ../examples/survey/alignment_ex1.py
            :include-source:
        """
        if ax is None:
            x = self.pi_coordinates()[:, :2]
            mx = x.max(axis=0)
            c = 0.5 * (mx + x.min(axis=0))
            r = 1.1 * (np.max(mx - c) + self.view_offset + self.view_margin)
            xlim, ylim = np.column_stack([c - r, c + r])

            fig = plt.figure()
            ax = fig.add_subplot(111,
                                 title=self.name,
                                 xlim=xlim,
                                 ylim=ylim,
                                 xlabel='X',
                                 ylabel='Y',
                                 aspect='equal')
            ax.grid('major', alpha=0.2)

        sym = dict(pi='r.',
                   rp='c.',
                   pc='b.',
                   pt='b.',
                   alignment='b-',
                   stakes='rx')
        sym.update(symbols)

        pt = self.pt_coordinates()
        pc = self.pc_coordinates()

        if sym['alignment'] is not None:
            for a, b in zip(pt[:-1], pc[1:]):
                x = np.array([a, b])
                ax.plot(x[:, 0], x[:, 1], sym['alignment'])

            for a, b in zip(self.pt_stations(), self.pc_stations()):
                if a != b:
                    n = int(np.ceil((a - b) / step))
                    sta = np.linspace(b, a, n)
                    x = self.coordinates(sta)
                    ax.plot(x[:, 0], x[:, 1], sym['alignment'])

        if sym['pi'] is not None:
            x = self.pi_coordinates()
            ax.plot(x[:, 0], x[:, 1], sym['pi'])

        if sym['rp'] is not None:
            x = self.rp_coordinates()
            ax.plot(x[:, 0], x[:, 1], sym['rp'])

        if sym['pt'] is not None:
            ax.plot(pt[:, 0], pt[:, 1], sym['pt'])

        if sym['pc'] is not None:
            ax.plot(pc[:, 0], pc[:, 1], sym['pc'])

        if sym['stakes'] is not None and len(self.stakes) > 0:
            self.set_stake_xy()
            x = np.array(self.stakes)
            ax.plot(x[:, 0], x[:, 1], sym['stakes'])

        return ax
Exemple #6
0
class Element(object):
    """
    A class representing a structural element.

    Parameters
    ----------
    name : str
        A unique name for the element.
    inode, jnode : str
        The names of the nodes at the i and j ends of the element.
    group : :class:`.ElementGroup`
        The group assigned to the element.
    symmetry : {None, 'x', 'y', 'xy'}
        The symmetry of the element.
    roll : float
        The counter clockwise angle of roll about the length axis.
    imx_free, imy_free, imz_free : bool
        The rotational fixities at the i-node about the local x, y, and z axes.
    jmx_free, jmy_free, jmz_free : bool
        The rotational fixities at the j-node about the local x, y, and z axes.
    """
    SYMMETRIES = (None, 'x', 'y', 'xy')

    TRANSFORMS = {
        'x': {'p': 'x', 'x': 'p', 'y': 'xy', 'xy': 'y'},
        'y': {'p': 'y', 'x': 'xy', 'y': 'p', 'xy': 'x'},
    }

    # Custom properties
    name = propy.str_property('name')
    inode = propy.str_property('inode_name')
    jnode = propy.str_property('jnode_name')
    symmetry = propy.enum_property('symmetry', set(SYMMETRIES))

    imx_free = propy.bool_property('imx_free')
    imy_free = propy.bool_property('imy_free')
    imz_free = propy.bool_property('imz_free')

    jmx_free = propy.bool_property('jmx_free')
    jmy_free = propy.bool_property('jmy_free')
    jmz_free = propy.bool_property('jmz_free')

    inode_ref = propy.weakref_property('inode_ref')
    jnode_ref = propy.weakref_property('jnode_ref')

    def __init__(self, name, inode, jnode, group,
                 symmetry=None, roll=0, unstr_length=None,
                 imx_free=False, imy_free=False, imz_free=False,
                 jmx_free=False, jmy_free=False, jmz_free=False):
        self.name = name
        self.inode = inode
        self.jnode = jnode
        self.group = group
        self.symmetry = symmetry
        self.roll = roll
        self.unstr_length = unstr_length

        self.imx_free = imx_free
        self.imy_free = imy_free
        self.imz_free = imz_free

        self.jmx_free = jmx_free
        self.jmy_free = jmy_free
        self.jmz_free = jmz_free

        self.inode_ref = None
        self.jnode_ref = None

    __repr__ = propy.repr_method(
        'name', 'inode', 'jnode', 'group', 'symmetry', 'roll',
        'imx_free', 'imy_free', 'imz_free', 'jmx_free', 'jmy_free', 'jmz_free'
    )

    def __str__(self):
        return self.name

    def copy(self):
        """Returns a copy of the element."""
        return copy.copy(self)

    def i_free(self):
        """Sets the i end rotational fixities to free. Returns the element."""
        self.imx_free = self.imy_free = self.imz_free = True
        return self

    def j_free(self):
        """Sets the j end rotational fixities to free. Returns the element."""
        self.jmx_free = self.jmy_free = self.jmz_free = True
        return self

    def free(self):
        """Sets the end rotational fixities to free. Returns the element."""
        return self.i_free().j_free()

    def mx_free(self):
        """Sets the x rotational fixities to free. Returns the element."""
        self.imx_free = self.jmx_free = True
        return self

    def my_free(self):
        """Sets the y rotational fixities to free. Returns the element."""
        self.imy_free = self.jmy_free = True
        return self

    def mz_free(self):
        """Sets the z rotational fixities to free. Returns the element."""
        self.imz_free = self.jmz_free = True
        return self

    def set_nodes(self, ndict):
        """
        Sets the node references for the element.

        Parameters
        ----------
        ndict : dict
            A dictionary that maps node names to :class:`.Node` objects.
        """
        self.inode_ref = ndict[self.inode]
        self.jnode_ref = ndict[self.jnode]

    def get_nodes(self):
        """Returns the i and j node objects."""
        if self.inode_ref is None or self.jnode_ref is None:
            raise ValueError('Node references have not been set.')
        return self.inode_ref, self.jnode_ref

    def get_unstr_length(self):
        """
        If the unstressed length of the element is None, returns the initial
        distance between the nodes. If the unstressed length is a string,
        converts the string to a float and adds it to the initial distance
        between the nodes. Otherwise, returns the assigned unstressed length.
        """
        if self.unstr_length is None:
            return self.length()

        elif isinstance(self.unstr_length, str):
            return self.length() + float(self.unstr_length)

        return self.unstr_length

    def length(self, di=(0, 0, 0), dj=(0, 0, 0)):
        """
        Returns the length of the element between nodes.

        Parameters
        ----------
        di, dj : array
            The deflections at the i and j ends of the element.
        """
        xi, xj = self.get_nodes()
        di, dj = np.asarray(di), np.asarray(dj)
        delta = (xj - xi) + (dj - di)
        return np.linalg.norm(delta)

    def sym_elements(self):
        """Returns the symmetric elements for the element."""
        def trans(name, *sym):
            t = Element.TRANSFORMS
            n = name.split('_')

            for x in sym:
                n[-1] = t[x][n[-1]]

            return '_'.join(n)

        def primary():
            e = self.copy()
            e.name = '{}_p'.format(self.name)
            return e

        def x_sym():
            e = self.copy()
            e.name = '{}_x'.format(self.name)
            e.inode = trans(self.inode, 'x')
            e.jnode = trans(self.jnode, 'x')
            return e

        def y_sym():
            e = self.copy()
            e.name = '{}_y'.format(self.name)
            e.inode = trans(self.inode, 'y')
            e.jnode = trans(self.jnode, 'y')
            return e

        def xy_sym():
            e = self.copy()
            e.name = '{}_xy'.format(self.name)
            e.inode = trans(self.inode, 'x', 'y')
            e.jnode = trans(self.jnode, 'x', 'y')
            return e

        if self.symmetry is None:
            return primary(),

        elif self.symmetry == 'x':
            return primary(), x_sym()

        elif self.symmetry == 'y':
            return primary(), y_sym()

        elif self.symmetry == 'xy':
            return primary(), x_sym(), y_sym(), xy_sym()

    def rotation_matrix(self, di=(0, 0, 0), dj=(0, 0, 0)):
        """
        Returns the rotation matrix for the element.

        Parameters
        ----------
        di, dj : array
            The deflections at the i and j ends of the element.
        """
        xi, xj = self.get_nodes()
        di, dj = np.asarray(di), np.asarray(dj)
        dx, dy, dz = (xj - xi) + (dj - di)
        return rotation_matrix(dx, dy, dz, self.roll)

    def transformation_matrix(self, di=(0, 0, 0), dj=(0, 0, 0)):
        """
        Returns the transformation matrix for the element.

        Parameters
        ----------
        di, dj : array
            The deflections at the i and j ends of the element.
        """
        xi, xj = self.get_nodes()
        di, dj = np.asarray(di), np.asarray(dj)
        dx, dy, dz = (xj - xi) + (dj - di)
        return transformation_matrix(dx, dy, dz, self.roll)

    def local_stiffness(self, di=(0, 0, 0), dj=(0, 0, 0)):
        """
        Returns the local stiffness for the element.

        Parameters
        ----------
        di, dj : array
            The deflections at the i and j ends of the element.
        """
        group = self.group
        sect = group.section
        mat = group.material

        return local_stiffness(
            l=self.length(di, dj),
            lu=self.get_unstr_length(),
            a=sect.area,
            ix=sect.inertia_x,
            iy=sect.inertia_y,
            j=sect.inertia_j,
            e=mat.elasticity,
            g=mat.rigidity,
            imx_free=self.imx_free,
            imy_free=self.imy_free,
            imz_free=self.imz_free,
            jmx_free=self.jmx_free,
            jmy_free=self.jmy_free,
            jmz_free=self.jmz_free
        )

    def global_stiffness(self, di=(0, 0, 0), dj=(0, 0, 0)):
        """
        Returns the global stiffness matrix for the element.

        Parameters
        ----------
        di, dj : array
            The deflections at the i and j ends of the element.
        """
        di, dj = np.asarray(di), np.asarray(dj)
        t = self.transformation_matrix(di, dj)
        k = self.local_stiffness(di, dj)
        return t.T.dot(k).dot(t)
Exemple #7
0
class NodeLoad(np.ndarray):
    """
    A class representing a load applied to a node.

    Parameters
    ----------
    node : str
        The name of the node to which the load will be applied.
    fx, fy, fz : float
        The applied global node forces.
    mx, my, mz : float
        The applied global moments.
    dx, dy, dz : float
        The applied node deflections.
    rx, ry, rz : float
        The applied node rotations.
    """
    # Custom properties
    node = propy.str_property('node')
    _node_ref = propy.weakref_property('_node_ref')
    fx = propy.index_property(0)
    fy = propy.index_property(1)
    fz = propy.index_property(2)
    mx = propy.index_property(3)
    my = propy.index_property(4)
    mz = propy.index_property(5)
    dx = propy.index_property(6)
    dy = propy.index_property(7)
    dz = propy.index_property(8)
    rx = propy.index_property(9)
    ry = propy.index_property(10)
    rz = propy.index_property(11)

    def __new__(cls,
                node,
                fx=0,
                fy=0,
                fz=0,
                mx=0,
                my=0,
                mz=0,
                dx=0,
                dy=0,
                dz=0,
                rx=0,
                ry=0,
                rz=0):
        obj = np.array([fx, fy, fz, mx, my, mz, dx, dy, dz, rx, ry, rz],
                       dtype='float').view(cls)
        obj.node = node
        return obj

    def __array_finalize__(self, obj):
        if obj is None: return
        self.node = getattr(obj, 'node', '')
        self._node_ref = None

    def __repr__(self):
        s = [
            'node={!r}'.format(self.node), 'forces={!r}'.format(
                (self.fx, self.fy, self.fz)), 'moments={!r}'.format(
                    (self.mx, self.my, self.mz)), 'defl={!r}'.format(
                        (self.dx, self.dy, self.dz)), 'rot={!r}'.format(
                            (self.rx, self.ry, self.rz))
        ]

        return '{}({})'.format(type(self).__name__, ', '.join(s))

    def forces(self):
        """Returns the applied force and moment matrix."""
        return self[:6]

    def deflections(self):
        """Returns the applied deflection and rotation matrix."""
        return self[6:]

    def get_node(self):
        """Gets the referenced node."""
        if self._node_ref is None:
            raise ValueError('Node has not been set.')
        return self._node_ref

    def set_node(self, ndict):
        """
        Sets the node reference.

        Parameters
        ----------
        ndict : dict
            A dictionary mapping node names to node objects.
        """
        self._node_ref = ndict[self.node]
Exemple #8
0
class Structure(object):
    """
    A class representing a structure.

    Parameters
    ----------
    name : str
        The name of the structure.
    nodes : list
        A list of :class:`.Node`.
    elements : list
        A list of :class:`.Element`.
    symmetry : bool
        If True, symmetry will be applied to the structure.

    Examples
    --------
    The following example creates an a structure and performs linear analysis
    for a load case.

    .. plot:: ../examples/structures/structure_ex1.py
        :include-source:
    """
    # Custom properties
    name = propy.str_property('name')
    symmetry = propy.bool_property('symmetry')

    def __init__(self, name, nodes, elements, symmetry=False):
        self.name = name
        self.nodes = nodes
        self.elements = elements
        self.symmetry = symmetry
        self.build = {}

    def _create_build(self, load_cases=[]):
        """
        Builds the structure and places all components in the object
        model dictionary.

        Parameters
        ----------
        load_cases : list
            A list of :class:`.LoadCase`.
        """
        if not self.symmetry:
            nodes = self.nodes
            elements = self.elements
        else:
            # Make symmetric components
            nodes = []
            for n in self.nodes:
                nodes += n.sym_nodes()

            elements = []
            for e in self.elements:
                elements += e.sym_elements()

        ndict = {n.name: n for n in nodes}
        edict = {e.name: e for e in elements}

        # Set nodes to elements
        for e in elements:
            e.set_nodes(ndict)

        # Set nodes and elements to loads
        for lc in load_cases:
            lc.set_nodes(ndict)
            lc.set_elements(edict)

        ndict = {n.name: 6 * i for i, n in enumerate(nodes)}
        edict = {e.name: i for i, e in enumerate(elements)}

        self.build = {
            'nodes': nodes,
            'elements': elements,
            'ndict': ndict,
            'edict': edict,
            'load_cases': load_cases
        }

    @build
    def plot_3d(self, ax=None, symbols={}):
        """
        Plots the structure in 3D.

        Parameters
        ----------
        ax
            The axes to which the plot will be added. If None, a new figure
            and axes will be created.
        symbols : dict
            The plot symbols with any of the following keys:

                * 'nodes': The node point symbols, default is 'r.'
                * 'elements': The element lines, default is 'b--'.
        """
        # Build the structure
        x = np.array(self.build['nodes'])

        # Create figure is one not provided
        if ax is None:
            mx = x.max(axis=0)
            c = 0.5 * (mx + x.min(axis=0))
            rng = 1.1 * np.max(mx - c)
            xlim, ylim, zlim = np.column_stack([c - rng, c + rng])

            fig = plt.figure()
            ax = fig.add_subplot(111,
                                 projection='3d',
                                 xlim=xlim,
                                 ylim=ylim,
                                 zlim=zlim,
                                 xlabel='X',
                                 ylabel='Y',
                                 zlabel='Z',
                                 aspect='equal')

        # Symbols
        sym = dict(elements='b--', nodes='r.', ntext='k', etext='r')
        sym.update(symbols)

        # Plot elements
        if sym['elements'] is not None:
            for e in self.build['elements']:
                e = np.array(e.get_nodes())
                ax.plot(e[:, 0], e[:, 1], e[:, 2], sym['elements'])

        # Plot element text
        if sym['etext'] is not None:
            for e in self.build['elements']:
                p, q = e.get_nodes()
                p = (q - p) / 3 + p
                ax.text(p[0],
                        p[1],
                        p[2],
                        e.name,
                        ha='center',
                        va='center',
                        color=sym['etext'])

        # Plot nodes
        if sym['nodes'] is not None:
            ax.plot(x[:, 0], x[:, 1], x[:, 2], sym['nodes'])

        # Plot node text
        if sym['ntext'] is not None:
            for n in self.build['nodes']:
                ax.text(n[0], n[1], n[2], n.name, color=sym['ntext'])

        return ax

    @build
    def plot_2d(self, ax=None, angle_x=0, angle_y=0, angle_z=0, symbols={}):
        """
        Plots the 2D projection of the structure.

        Parameters
        ----------
        ax
            The axes to which the plot will be added. If None, a new figure
            and axes will be created.
        angle_x, angle_y, angle_z : float
            The rotation angles about the x, y, and z axes.
        symbols : dict
            The plot symbols with any of the following keys:

                * 'nodes': The node point symbols, default is 'r.'
                * 'elements': The element lines, default is 'b--'.
        """
        # Build the structure
        r = rotation_matrix3(angle_x, angle_y, angle_z).T
        x = np.array(self.build['nodes']).dot(r)

        # Create figure is one not provided
        if ax is None:
            mx = x.max(axis=0)
            c = 0.5 * (mx + x.min(axis=0))
            rng = 1.1 * np.max(mx - c)
            xlim, ylim, _ = np.column_stack([c - rng, c + rng])

            fig = plt.figure()
            ax = fig.add_subplot(111,
                                 xlim=xlim,
                                 ylim=ylim,
                                 xlabel="X'",
                                 ylabel="Y'",
                                 aspect='equal')

        # Symbols
        sym = dict(elements='b--', nodes='r.', ntext='k', etext='r')
        sym.update(symbols)

        # Plot elements
        if sym['elements'] is not None:
            for e in self.build['elements']:
                e = np.array(e.get_nodes()).dot(r)
                ax.plot(e[:, 0], e[:, 1], sym['elements'])

        # Plot element text
        if sym['etext'] is not None:
            for e in self.build['elements']:
                p = np.array(e.get_nodes()).dot(r)
                p = (p[1] - p[0]) / 3 + p[0]
                ax.text(p[0],
                        p[1],
                        e.name,
                        ha='center',
                        va='center',
                        color=sym['etext'])

        # Plot nodes
        if sym['nodes'] is not None:
            ax.plot(x[:, 0], x[:, 1], sym['nodes'])

        # Plot node text
        if sym['ntext'] is not None:
            for n in self.build['nodes']:
                p = n.dot(r)
                ax.text(p[0], p[1], n.name, color=sym['ntext'])

        return ax

    @build
    def global_stiffness(self, defl=None):
        """
        Returns the global stiffness matrix for the structure.

        Parameters
        ----------
        defl : array
            The deflection matrix. If None, all deflections will be
            assumed to be zero.
        """
        n = len(self.build['nodes'])
        k = np.zeros((6 * n, 6 * n), dtype='float')
        ndict = self.build['ndict']

        if defl is None:
            defl = np.zeros(6 * n)

        for e in self.elements:
            i, j = ndict[e.inode], ndict[e.jnode]
            di, dj = defl[i:i + 3], defl[j:j + 3]

            ke = e.global_stiffness(di, dj)
            k[i:i + 6, i:i + 6] += ke[:6, :6]
            k[i:i + 6, j:j + 6] += ke[:6, 6:12]
            k[j:j + 6, i:i + 6] += ke[6:12, :6]
            k[j:j + 6, j:j + 6] += ke[6:12, 6:12]

        return k

    @build
    def global_node_loads(self, lc):
        """
        Returns the global node load matrix for the input load case.

        Parameters
        ----------
        lc : :class:`.LoadCase`
            The applied load case.
        """
        n = len(self.build['nodes'])
        q = np.zeros(6 * n, dtype='float')
        ndict = self.build['ndict']

        for n in lc.node_loads:
            i = ndict[n.node]
            q[i:i + 6] += n.forces()

        return q

    @build
    def local_elem_loads(self, lc, defl=None):
        """
        Returns the local element loads for the input load case.

        Parameters
        ----------
        lc : :class:`.LoadCase`
            The applied load case.
        defl : array
            The global node deflections.
        """
        n = len(self.build['nodes'])
        m = len(self.build['elements'])
        q = np.zeros((m, 12), dtype='float')
        ndict = self.build['ndict']
        edict = self.build['edict']

        if defl is None:
            defl = np.zeros(6 * n)

        for e in lc.elem_loads:
            ref = e.get_element()
            i, j, k = ndict[ref.inode], ndict[ref.jnode], edict[ref.name]
            di, dj = defl[i:i + 3], defl[j:j + 3]
            q[k] += e.local_reactions(di, dj)

        return q

    @build
    def global_elem_loads(self, lc, defl=None):
        """
        Returns the global node load matrix for the input load case.

        Parameters
        ----------
        lc : :class:`.LoadCase`
            The applied load case.
        defl : array
            The global node deflections.
        """
        n = len(self.build['nodes'])
        q = np.zeros(6 * n, dtype='float')
        ndict = self.build['ndict']

        if defl is None:
            defl = np.zeros(6 * n)

        for e in lc.elem_loads:
            ref = e.get_element()
            i, j = ndict[ref.inode], ndict[ref.jnode]
            di, dj = defl[i:i + 3], defl[j:j + 3]

            f = e.global_reactions(di, dj)
            q[i:i + 6] += f[:6]
            q[j:j + 6] += f[6:12]

        return q

    @build
    def global_defl(self, lc):
        """
        Returns the global applied deflection matrix for the input load case.

        Parameters
        ----------
        lc : :class:`.LoadCase`
            The applied load case.
        """
        n = len(self.build['nodes'])
        d = np.zeros(6 * n, dtype='float')
        ndict = self.build['ndict']

        for n in lc.node_loads:
            i = ndict[n.node]
            d[i:i + 6] += n.deflections()

        return d

    def _create_summary(self, r):
        """
        Creates dataframe summaries for the structural analysis results.

        Parameters
        ----------
        r : dict
            A dictionary of result arrays.
        """
        n = len(self.build['nodes'])
        m = len(self.build['elements'])
        lc = self.build['load_cases']
        u = [x.fixities() for x in self.nodes] * len(lc)
        u = np.array(u, dtype='bool')

        # Global load data frame
        df1 = pd.DataFrame()
        df1['load_case'] = np.array([[l.name] * n for l in lc]).ravel()
        df1['node'] = [x.name for x in self.build['nodes']] * len(lc)

        # Process global forces
        x = np.array(r.pop('glob_force')).reshape(-1, 6)
        x[np.abs(x) < 1e-8] = 0
        df1['force_x'] = x[:, 0]
        df1['force_y'] = x[:, 1]
        df1['force_z'] = x[:, 2]
        df1['moment_x'] = x[:, 3]
        df1['moment_y'] = x[:, 4]
        df1['moment_z'] = x[:, 5]

        # Process global deflections
        x = np.array(r.pop('glob_defl')).reshape(-1, 6)
        x[np.abs(x) < 1e-8] = 0
        df1['defl_x'] = x[:, 0]
        df1['defl_y'] = x[:, 1]
        df1['defl_z'] = x[:, 2]
        df1['rot_x'] = x[:, 3]
        df1['rot_y'] = x[:, 4]
        df1['rot_z'] = x[:, 5]

        # Global reaction data frame
        df2 = df1.copy()
        del df2['defl_x'], df2['defl_y'], df2['defl_z']
        del df2['rot_x'], df2['rot_y'], df2['rot_z']

        df2.loc[u[:, 0], 'force_x'] = np.nan
        df2.loc[u[:, 1], 'force_y'] = np.nan
        df2.loc[u[:, 2], 'force_z'] = np.nan
        df2.loc[u[:, 3], 'moment_x'] = np.nan
        df2.loc[u[:, 4], 'moment_y'] = np.nan
        df2.loc[u[:, 5], 'moment_z'] = np.nan

        df2 = df2[~u.all(axis=1)].copy()
        df2 = df2.reset_index(drop=True)

        # Local reaction data frame
        df3 = pd.DataFrame()
        df3['load_case'] = np.array([[l.name] * m for l in lc]).ravel()
        df3['element'] = [x.name for x in self.build['elements']] * len(lc)

        # Process local forces
        x = np.array(r.pop('loc_force')).reshape(-1, 12)
        x[np.abs(x) < 1e-8] = 0

        df3['i_axial'] = x[:, 0]
        df3['i_shear_x'] = x[:, 1]
        df3['i_shear_y'] = x[:, 2]
        df3['i_torsion'] = x[:, 3]
        df3['i_moment_x'] = x[:, 4]
        df3['i_moment_y'] = x[:, 5]

        df3['j_axial'] = x[:, 6]
        df3['j_shear_x'] = x[:, 7]
        df3['j_shear_y'] = x[:, 8]
        df3['j_torsion'] = x[:, 9]
        df3['j_moment_x'] = x[:, 10]
        df3['j_moment_y'] = x[:, 11]

        # Process local deflections
        x = np.array(r.pop('loc_defl')).reshape(-1, 12)
        x[np.abs(x) < 1e-8] = 0

        df3['i_defl_ax'] = x[:, 0]
        df3['i_defl_x'] = x[:, 1]
        df3['i_defl_y'] = x[:, 2]
        df3['i_twist'] = x[:, 3]
        df3['i_rot_x'] = x[:, 4]
        df3['i_rot_y'] = x[:, 5]

        df3['j_defl_ax'] = x[:, 6]
        df3['j_defl_x'] = x[:, 7]
        df3['j_defl_y'] = x[:, 8]
        df3['j_twist'] = x[:, 9]
        df3['j_rot_x'] = x[:, 10]
        df3['j_rot_y'] = x[:, 11]

        return dict(glob=df1, react=df2, loc=df3)

    @build
    def linear_analysis(self, lc):
        """
        Performs linear analysis on the structure.

        Parameters
        ----------
        lc : :class:`.LoadCase` or list
            A load case or list of load cases to perform analysis for.
        """
        n = len(self.build['nodes'])
        k = self.global_stiffness()
        ndict = self.build['ndict']

        # Result dictionary
        r = dict(glob_force=[], glob_defl=[], loc_force=[], loc_defl=[])

        # Determine free and nonzero matrix rows and columns
        u = np.array([x.fixities() for x in self.nodes], dtype='bool').ravel()

        if not u.any():
            raise ValueError('No node fixities found.')

        u &= k.any(axis=1)
        v = ~u

        # Calculate inverse and create unknown-known stiffness partition
        ki = np.linalg.inv(k[u][:, u])
        kuv = k[u][:, v]

        for l in self.build['load_cases']:
            # Find unknown deflections and global forces
            d = self.global_defl(l)
            q = self.global_node_loads(l)
            qe = self.global_elem_loads(l)
            q -= qe
            d[u] = ki.dot(q[u] - kuv.dot(d[v]))
            q = k.dot(d) + qe

            # Add to results dictionary
            r['glob_force'].append(q)
            r['glob_defl'].append(d)

            # Find local forces
            q = self.local_elem_loads(l)

            for m, e in enumerate(self.build['elements']):
                i, j = ndict[e.inode], ndict[e.jnode]
                dl = np.array([d[i:i + 6], d[j:j + 6]]).ravel()
                dl = e.transformation_matrix().dot(dl)
                q[m] += e.local_stiffness().dot(dl)
                r['loc_defl'].append(dl)

            r['loc_force'].append(q)

        # Destory obsolete objects
        del k, ki, kuv, u, v, d, q

        return self._create_summary(r)
Exemple #9
0
class Node(np.ndarray):
    """
    A class representing a structural node.

    Parameters
    ----------
    name : str
        A unique name for the node.
    x, y, z : float
        The x, y, and z coordinates of the node.
    symmetry : {None, 'x', 'y', 'xy'}
        The symmetry of the node.
    fx_free, fy_free, fz_free : bool
        The force fixities of the node in the x, y, and z directions.
    mx_free, my_free, mz_free : bool
        The moment fixities of the node about the x, y, and z axes.
    """
    SYMMETRIES = (None, 'x', 'y', 'xy')

    # Custom properties
    name = propy.str_property('name')
    x = propy.index_property(0)
    y = propy.index_property(1)
    z = propy.index_property(2)
    symmetry = propy.enum_property('symmetry', set(SYMMETRIES))

    fx_free = propy.bool_property('fx_free')
    fy_free = propy.bool_property('fy_free')
    fz_free = propy.bool_property('fz_free')

    mx_free = propy.bool_property('mx_free')
    my_free = propy.bool_property('my_free')
    mz_free = propy.bool_property('mz_free')

    def __new__(cls,
                name,
                x=0,
                y=0,
                z=0,
                symmetry=None,
                fx_free=True,
                fy_free=True,
                fz_free=True,
                mx_free=True,
                my_free=True,
                mz_free=True):
        obj = np.array([x, y, z], dtype='float').view(cls)

        obj.name = name
        obj.symmetry = symmetry

        obj.fx_free = fx_free
        obj.fy_free = fy_free
        obj.fz_free = fz_free

        obj.mx_free = mx_free
        obj.my_free = my_free
        obj.mz_free = mz_free

        return obj

    def __array_finalize__(self, obj):
        if obj is None: return

        self.name = getattr(obj, 'name', '')
        self.symmetry = getattr(obj, 'symmetry', None)

        self.fx_free = getattr(obj, 'fx_free', True)
        self.fy_free = getattr(obj, 'fy_free', True)
        self.fz_free = getattr(obj, 'fz_free', True)

        self.mx_free = getattr(obj, 'mx_free', True)
        self.my_free = getattr(obj, 'my_free', True)
        self.mz_free = getattr(obj, 'mz_free', True)

    __repr__ = propy.repr_method('name', 'x', 'y', 'z', 'symmetry', 'fx_free',
                                 'fy_free', 'fz_free', 'mx_free', 'my_free',
                                 'mz_free')

    def __str__(self):
        return self.name

    def copy(self):
        """Returns a copy of the node."""
        return copy.copy(self)

    def f_fixed(self):
        """Sets the node force reactions to fixed."""
        self.fx_free = self.fy_free = self.fz_free = False
        return self

    def m_fixed(self):
        """Sets the node moment reactions to fixed."""
        self.mx_free = self.my_free = self.mz_free = False
        return self

    def fixed(self):
        """Sets the node force and moment reactions to fixed."""
        return self.f_fixed().m_fixed()

    def fixities(self):
        """Returns the force and moment fixities for the node."""
        return [
            self.fx_free, self.fy_free, self.fz_free, self.mx_free,
            self.my_free, self.mz_free
        ]

    def sym_nodes(self):
        """Returns the symmetric nodes for the node."""
        def primary():
            n = self.copy()
            n.name = '{}_p'.format(self.name)
            return n

        def x_sym():
            n = self.copy()
            n.name = '{}_x'.format(self.name)
            n[1] *= -1
            return n

        def y_sym():
            n = self.copy()
            n.name = '{}_y'.format(self.name)
            n[0] *= -1
            return n

        def xy_sym():
            n = self.copy()
            n.name = '{}_xy'.format(self.name)
            n[:2] *= -1
            return n

        if self.symmetry is None:
            return primary(),

        elif self.symmetry == 'x':
            return primary(), x_sym()

        elif self.symmetry == 'y':
            return primary(), y_sym()

        elif self.symmetry == 'xy':
            return primary(), x_sym(), y_sym(), xy_sym()