diff --git a/civpy/math/linalg.py b/civpy/math/linalg.py index baba4a1..1ccd4ea 100644 --- a/civpy/math/linalg.py +++ b/civpy/math/linalg.py @@ -1,3 +1,7 @@ +""" +Copyright (c) 2019, Matt Pewsey +""" + import numpy as np from math import cos, sin diff --git a/civpy/math/optimize.py b/civpy/math/optimize.py index a53833b..d7934d4 100644 --- a/civpy/math/optimize.py +++ b/civpy/math/optimize.py @@ -1,3 +1,7 @@ +""" +Copyright (c) 2019, Matt Pewsey +""" + import scipy.optimize __all__ = ['fsolve'] diff --git a/civpy/structures/element.py b/civpy/structures/element.py index 04ca36e..176c68a 100644 --- a/civpy/structures/element.py +++ b/civpy/structures/element.py @@ -1,5 +1,10 @@ +""" +Copyright (c) 2019, Matt Pewsey +""" + import copy -import propy +import attr +import weakref import numpy as np from math import cos, sin from functools import lru_cache @@ -153,6 +158,7 @@ def clear_element_cache(): transformation_matrix.cache_clear() +@attr.s(hash=False) class Element(object): """ A class representing a structural element. @@ -174,57 +180,62 @@ class Element(object): 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') + P = 'p' + X = 'x' + Y = 'y' + XY = 'xy' + SYMMETRIES = (None, X, Y, XY) TRANSFORMS = { - 'x': {'p': 'x', 'x': 'p', 'y': 'xy', 'xy': 'y'}, - 'y': {'p': 'y', 'x': 'xy', 'y': 'p', 'xy': 'x'}, + 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' - ) + name = attr.ib(converter=str) + inode = attr.ib() + jnode = attr.ib() + group = attr.ib() + symmetry = attr.ib(default=None, validator=attr.validators.in_(SYMMETRIES)) + roll = attr.ib(default=0) + unstr_length = attr.ib(default=None) + imx_free = attr.ib(default=False) + imy_free = attr.ib(default=False) + imz_free = attr.ib(default=False) + jmx_free = attr.ib(default=False) + jmy_free = attr.ib(default=False) + jmz_free = attr.ib(default=False) + _inode_ref = attr.ib(default=None, init=False, repr=False) + _jnode_ref = attr.ib(default=None, init=False, repr=False) + + def inode_ref(): + def fget(self): + value = self._inode_ref + if value is None: + return value + return value() + def fset(self, value): + if value is not None: + value = weakref.ref(value) + self._inode_ref = value + def fdel(self): + del self._inode_ref + return locals() + inode_ref = property(**inode_ref()) + + def jnode_ref(): + def fget(self): + value = self._jnode_ref + if value is None: + return value + return value() + def fset(self, value): + if value is not None: + value = weakref.ref(value) + self._jnode_ref = value + def fdel(self): + del self._jnode_ref + return locals() + jnode_ref = property(**jnode_ref()) def __str__(self): return self.name diff --git a/civpy/structures/element_group.py b/civpy/structures/element_group.py index f1f6a0f..c81f465 100644 --- a/civpy/structures/element_group.py +++ b/civpy/structures/element_group.py @@ -1,8 +1,13 @@ -import propy +""" +Copyright (c) 2019, Matt Pewsey +""" + +import attr __all__ = ['ElementGroup'] +@attr.s(hash=False) class ElementGroup(object): """ A class representing a group of element properties. @@ -16,12 +21,6 @@ class ElementGroup(object): 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') + name = attr.ib() + section = attr.ib() + material = attr.ib() diff --git a/civpy/structures/element_load.py b/civpy/structures/element_load.py index 3fae3cb..ed0336a 100644 --- a/civpy/structures/element_load.py +++ b/civpy/structures/element_load.py @@ -1,4 +1,8 @@ -import propy +""" +Copyright (c) 2019, Matt Pewsey +""" + +import weakref import numpy as np from functools import lru_cache from .element import transformation_matrix @@ -248,16 +252,6 @@ class ElementLoad(np.ndarray): 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 @@ -270,7 +264,82 @@ def __array_finalize__(self, obj): self.element = getattr(obj, 'element', '') self.ix = getattr(obj, 'ix', 0) self.dx = getattr(obj, 'dx', 0) - self._element_ref = None + self.element_ref = None + + def element(): + def fget(self): + return self._element + def fset(self, value): + if not isinstance(value, str): + value = str(value) + self._element = value + def fdel(self): + del self._element + return locals() + element = property(**element()) + + def element_ref(): + def fget(self): + value = self._element_ref + if value is None: + return value + return value() + def fset(self, value): + if value is not None: + value = weakref.ref(value) + self._element_ref = value + def fdel(self): + del self._element_ref + return locals() + element_ref = property(**element_ref()) + + def fx(): + def fget(self): + return self[0] + def fset(self, value): + self[0] = value + return locals() + fx = property(**fx()) + + def fy(): + def fget(self): + return self[1] + def fset(self, value): + self[1] = value + return locals() + fy = property(**fy()) + + def fz(): + def fget(self): + return self[2] + def fset(self, value): + self[2] = value + return locals() + fz = property(**fz()) + + def mx(): + def fget(self): + return self[3] + def fset(self, value): + self[3] = value + return locals() + mx = property(**mx()) + + def my(): + def fget(self): + return self[4] + def fset(self, value): + self[4] = value + return locals() + my = property(**my()) + + def mz(): + def fget(self): + return self[5] + def fset(self, value): + self[5] = value + return locals() + mz = property(**mz()) def __repr__(self): s = [ @@ -278,7 +347,7 @@ def __repr__(self): '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) + 'dx={!r}'.format(self.dx), ] return '{}({})'.format(type(self).__name__, ', '.join(s)) diff --git a/civpy/structures/load_case.py b/civpy/structures/load_case.py index 3566c3b..3e4f97c 100644 --- a/civpy/structures/load_case.py +++ b/civpy/structures/load_case.py @@ -1,8 +1,13 @@ -import propy +""" +Copyright (c) 2019, Matt Pewsey +""" + +import attr __all__ = ['LoadCase'] +@attr.s(hash=False) class LoadCase(object): """ A class representing a structural load case. @@ -17,14 +22,9 @@ class LoadCase(object): 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') + name = attr.ib() + node_loads = attr.ib(default=[]) + elem_loads = attr.ib(default=[]) def set_nodes(self, ndict): """ diff --git a/civpy/structures/material.py b/civpy/structures/material.py index 3a8dc3a..906e5e8 100644 --- a/civpy/structures/material.py +++ b/civpy/structures/material.py @@ -1,8 +1,13 @@ -import propy +""" +Copyright (c) 2019, Matt Pewsey +""" + +import attr __all__ = ['Material'] +@attr.s(hash=False) class Material(object): """ A class representing an engineered material. @@ -16,12 +21,6 @@ class Material(object): 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') + name = attr.ib() + elasticity = attr.ib() + rigidity = attr.ib(default=0) diff --git a/civpy/structures/node.py b/civpy/structures/node.py index dee5ff9..5e436ab 100644 --- a/civpy/structures/node.py +++ b/civpy/structures/node.py @@ -1,5 +1,8 @@ +""" +Copyright (c) 2019, Matt Pewsey +""" + import copy -import propy import numpy as np __all__ = ['Node'] @@ -22,22 +25,10 @@ class Node(np.ndarray): 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') + X = 'x' + Y = 'y' + XY = 'xy' + SYMMETRIES = (None, X, Y, XY) def __new__(cls, name, x=0, y=0, z=0, symmetry=None, fx_free=True, fy_free=True, fz_free=True, @@ -71,8 +62,59 @@ def __array_finalize__(self, obj): 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 x(): + def fget(self): + return self[0] + def fset(self, value): + self[0] = value + return locals() + x = property(**x()) + + def y(): + def fget(self): + return self[1] + def fset(self, value): + self[1] = value + return locals() + y = property(**y()) + + def z(): + def fget(self): + return self[2] + def fset(self, value): + self[2] = value + return locals() + z = property(**z()) + + def symmetry(): + def fget(self): + return self._symmetry + def fset(self, value): + if value not in self.SYMMETRIES: + raise ValueError('Type {!r} must be in {!r}.'.format(value, self.SYMMETRIES)) + self._symmetry = value + def fdel(self): + del self._symmetry + return locals() + symmetry = property(**symmetry()) + + def __repr__(self): + s = [ + ('name', self.name), + ('x', self.x), + ('y', self.y), + ('z', self.z), + ('symmetry', self.symmetry), + ('fx_free', self.fx_free), + ('fy_free', self.fy_free), + ('fz_free', self.fz_free), + ('mx_free', self.mx_free), + ('my_free', self.my_free), + ('mz_free', self.mz_free), + ] + + s = ', '.join('{}={!r}'.format(x, y) for x, y in s) + return '{}({})'.format(type(self).__name__, s) def __str__(self): return self.name diff --git a/civpy/structures/node_load.py b/civpy/structures/node_load.py index 685c99d..f1c958a 100644 --- a/civpy/structures/node_load.py +++ b/civpy/structures/node_load.py @@ -1,4 +1,8 @@ -import propy +""" +Copyright (c) 2019, Matt Pewsey +""" + +import weakref import numpy as np __all__ = ['NodeLoad'] @@ -21,22 +25,6 @@ class NodeLoad(np.ndarray): 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, @@ -47,7 +35,142 @@ def __new__(cls, node, fx=0, fy=0, fz=0, mx=0, my=0, mz=0, def __array_finalize__(self, obj): if obj is None: return self.node = getattr(obj, 'node', '') - self._node_ref = None + self.node_ref = None + + def node(): + def fget(self): + return self._node + def fset(self, value): + if not isinstance(value, str): + value = str(value) + self._node = value + def fdel(self): + del self._node + return locals() + node = property(**node()) + + def node_ref(): + def fget(self): + value = self._node_ref + if value is None: + return value + return value() + def fset(self, value): + if value is not None: + value = weakref.ref(value) + self._node_ref = value + def fdel(self): + del self._node_ref + return locals() + node_ref = property(**node_ref()) + + def fx(): + def fget(self): + return self[0] + def fset(self, value): + self[0] = value + return locals() + fx = property(**fx()) + + def fy(): + def fget(self): + return self[1] + def fset(self, value): + self[1] = value + return locals() + fy = property(**fy()) + + def fz(): + def fget(self): + return self[2] + def fset(self, value): + self[2] = value + return locals() + fz = property(**fz()) + + def mx(): + def fget(self): + return self[3] + def fset(self, value): + self[3] = value + return locals() + mx = property(**mx()) + + def my(): + def fget(self): + return self[4] + def fset(self, value): + self[4] = value + return locals() + my = property(**my()) + + def mz(): + def fget(self): + return self[5] + def fset(self, value): + self[5] = value + return locals() + mz = property(**mz()) + + def dx(): + def fget(self): + return self[6] + def fset(self, value): + self[6] = value + def fdel(self): + del self._dx + return locals() + dx = property(**dx()) + + def dy(): + def fget(self): + return self[7] + def fset(self, value): + self[7] = value + def fdel(self): + del self._dy + return locals() + dy = property(**dy()) + + def dz(): + def fget(self): + return self[8] + def fset(self, value): + self[8] = value + def fdel(self): + del self._dz + return locals() + dz = property(**dz()) + + def rx(): + def fget(self): + return self[9] + def fset(self, value): + self[9] = value + def fdel(self): + del self._rx + return locals() + rx = property(**rx()) + + def ry(): + def fget(self): + return self[10] + def fset(self, value): + self[10] = value + def fdel(self): + del self._ry + return locals() + ry = property(**ry()) + + def rz(): + def fget(self): + return self[11] + def fset(self, value): + self[11] = value + def fdel(self): + del self._rz + return locals() + rz = property(**rz()) def __repr__(self): s = [ @@ -70,9 +193,9 @@ def deflections(self): def get_node(self): """Gets the referenced node.""" - if self._node_ref is None: + if self.node_ref is None: raise ValueError('Node has not been set.') - return self._node_ref + return self.node_ref def set_node(self, ndict): """ @@ -83,4 +206,4 @@ def set_node(self, ndict): ndict : dict A dictionary mapping node names to node objects. """ - self._node_ref = ndict[self.node] + self.node_ref = ndict[self.node] diff --git a/civpy/structures/structure.py b/civpy/structures/structure.py index db9441d..cad2d22 100644 --- a/civpy/structures/structure.py +++ b/civpy/structures/structure.py @@ -1,4 +1,8 @@ -import propy +""" +Copyright (c) 2019, Matt Pewsey +""" + +import attr import inspect import numpy as np import pandas as pd @@ -25,7 +29,7 @@ def index(lst, s): @wraps(func) def wrapper(obj, *args, **kwargs): # If already built, perform the operation - if obj.build: + if obj._build: return func(obj, *args, **kwargs) # If not built, build it, perform the operation, and destroy the build @@ -41,7 +45,7 @@ def wrapper(obj, *args, **kwargs): obj._create_build(lc) result = func(obj, *args, **kwargs) - obj.build.clear() + obj._build.clear() clear_element_cache() clear_element_load_cache() @@ -53,6 +57,7 @@ def wrapper(obj, *args, **kwargs): return wrapper +@attr.s(hash=False) class Structure(object): """ A class representing a structure. @@ -76,16 +81,11 @@ class Structure(object): .. 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 = {} + name = attr.ib() + nodes = attr.ib() + elements = attr.ib() + symmetry = attr.ib(default=False) + _build = attr.ib(default={}, init=False, repr=False) def _create_build(self, load_cases=[]): """ @@ -125,7 +125,7 @@ def _create_build(self, load_cases=[]): ndict = {n.name: 6*i for i, n in enumerate(nodes)} edict = {e.name: i for i, e in enumerate(elements)} - self.build = { + self._build = { 'nodes': nodes, 'elements': elements, 'ndict': ndict, @@ -150,7 +150,7 @@ def plot_3d(self, ax=None, symbols={}): * 'elements': The element lines, default is 'b--'. """ # Build the structure - x = np.array(self.build['nodes']) + x = np.array(self._build['nodes']) # Create figure is one not provided if ax is None: @@ -182,13 +182,13 @@ def plot_3d(self, ax=None, symbols={}): # Plot elements if sym['elements'] is not None: - for e in self.build['elements']: + 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']: + 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', @@ -200,7 +200,7 @@ def plot_3d(self, ax=None, symbols={}): # Plot node text if sym['ntext'] is not None: - for n in self.build['nodes']: + for n in self._build['nodes']: ax.text(n[0], n[1], n[2], n.name, color=sym['ntext']) return ax @@ -226,7 +226,7 @@ def plot_2d(self, ax=None, angle_x=0, angle_y=0, """ # Build the structure r = rotation_matrix3(angle_x, angle_y, angle_z).T - x = np.array(self.build['nodes']).dot(r) + x = np.array(self._build['nodes']).dot(r) # Create figure is one not provided if ax is None: @@ -255,13 +255,13 @@ def plot_2d(self, ax=None, angle_x=0, angle_y=0, # Plot elements if sym['elements'] is not None: - for e in self.build['elements']: + 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']: + 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', @@ -273,7 +273,7 @@ def plot_2d(self, ax=None, angle_x=0, angle_y=0, # Plot node text if sym['ntext'] is not None: - for n in self.build['nodes']: + for n in self._build['nodes']: p = n.dot(r) ax.text(p[0], p[1], n.name, color=sym['ntext']) @@ -290,9 +290,9 @@ def global_stiffness(self, defl=None): The deflection matrix. If None, all deflections will be assumed to be zero. """ - n = len(self.build['nodes']) + n = len(self._build['nodes']) k = np.zeros((6*n, 6*n), dtype='float') - ndict = self.build['ndict'] + ndict = self._build['ndict'] if defl is None: defl = np.zeros(6*n) @@ -319,9 +319,9 @@ def global_node_loads(self, lc): lc : :class:`.LoadCase` The applied load case. """ - n = len(self.build['nodes']) + n = len(self._build['nodes']) q = np.zeros(6*n, dtype='float') - ndict = self.build['ndict'] + ndict = self._build['ndict'] for n in lc.node_loads: i = ndict[n.node] @@ -341,11 +341,11 @@ def local_elem_loads(self, lc, defl=None): defl : array The global node deflections. """ - n = len(self.build['nodes']) - m = len(self.build['elements']) + 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'] + ndict = self._build['ndict'] + edict = self._build['edict'] if defl is None: defl = np.zeros(6*n) @@ -370,9 +370,9 @@ def global_elem_loads(self, lc, defl=None): defl : array The global node deflections. """ - n = len(self.build['nodes']) + n = len(self._build['nodes']) q = np.zeros(6*n, dtype='float') - ndict = self.build['ndict'] + ndict = self._build['ndict'] if defl is None: defl = np.zeros(6*n) @@ -398,9 +398,9 @@ def global_defl(self, lc): lc : :class:`.LoadCase` The applied load case. """ - n = len(self.build['nodes']) + n = len(self._build['nodes']) d = np.zeros(6*n, dtype='float') - ndict = self.build['ndict'] + ndict = self._build['ndict'] for n in lc.node_loads: i = ndict[n.node] @@ -417,16 +417,16 @@ def _create_summary(self, r): r : dict A dictionary of result arrays. """ - n = len(self.build['nodes']) - m = len(self.build['elements']) - lc = self.build['load_cases'] + 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) + 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) @@ -466,7 +466,7 @@ def _create_summary(self, r): # 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) + 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) @@ -516,9 +516,9 @@ def linear_analysis(self, lc): lc : :class:`.LoadCase` or list A load case or list of load cases to perform analysis for. """ - n = len(self.build['nodes']) + n = len(self._build['nodes']) k = self.global_stiffness() - ndict = self.build['ndict'] + ndict = self._build['ndict'] # Result dictionary r = dict(glob_force=[], glob_defl=[], loc_force=[], loc_defl=[]) @@ -536,7 +536,7 @@ def linear_analysis(self, lc): ki = np.linalg.inv(k[u][:,u]) kuv = k[u][:,v] - for l in self.build['load_cases']: + for l in self._build['load_cases']: # Find unknown deflections and global forces d = self.global_defl(l) q = self.global_node_loads(l) @@ -552,7 +552,7 @@ def linear_analysis(self, lc): # Find local forces q = self.local_elem_loads(l) - for m, e in enumerate(self.build['elements']): + 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) diff --git a/civpy/structures/structure_test.py b/civpy/structures/structure_test.py index 585a6f6..ec735d3 100644 --- a/civpy/structures/structure_test.py +++ b/civpy/structures/structure_test.py @@ -37,7 +37,7 @@ def Structure1(): def test_rotation_matrix(): struct = Structure1() struct._create_build() - e1, e2, e3 = struct.build['elements'] + e1, e2, e3 = struct._build['elements'] a = e1.rotation_matrix().ravel() b = np.identity(3).ravel() @@ -58,7 +58,7 @@ def test_rotation_matrix(): def test_transformation_matrix(): struct = Structure1() struct._create_build() - e1, e2, e3 = struct.build['elements'] + e1, e2, e3 = struct._build['elements'] a = e1.transformation_matrix().ravel() b = np.identity(12).ravel() @@ -85,7 +85,7 @@ def test_transformation_matrix(): def test_length(): struct = Structure1() struct._create_build() - e1, e2, e3 = struct.build['elements'] + e1, e2, e3 = struct._build['elements'] a = e1.length() assert pytest.approx(a) == 240 @@ -100,7 +100,7 @@ def test_length(): def test_local_stiffness(): struct = Structure1() struct._create_build() - e1, _, _ = struct.build['elements'] + e1, _, _ = struct._build['elements'] a = e1.local_stiffness().ravel() b = np.array([ @@ -124,7 +124,7 @@ def test_local_stiffness(): def test_global_stiffness(): struct = Structure1() struct._create_build() - e1, e2, e3 = struct.build['elements'] + e1, e2, e3 = struct._build['elements'] a = e1.global_stiffness().ravel() b = np.array([ diff --git a/civpy/survey/alignment.py b/civpy/survey/alignment.py index 9b324c0..f756dfd 100644 --- a/civpy/survey/alignment.py +++ b/civpy/survey/alignment.py @@ -1,5 +1,8 @@ -from __future__ import division -import propy +""" +Copyright (c) 2019, Matt Pewsey +""" + +import attr import numpy as np import matplotlib.pyplot as plt from .spatial_hash import SpatialHash @@ -7,6 +10,7 @@ __all__ = ['Alignment'] +@attr.s(hash=False) class Alignment(object): """ A class representing a survey alignment. @@ -34,22 +38,16 @@ class Alignment(object): .. plot:: ../examples/survey/alignment_ex1.py :include-source: """ + # Global class variables 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') + # Properties + name = attr.ib() + pis = attr.ib(default=[]) + stakes = attr.ib(default=[]) + grid = attr.ib(default=10) + view_offset = attr.ib(default=15) + view_margin = attr.ib(default=15) def set_stake_xy(self): """ diff --git a/civpy/survey/pi.py b/civpy/survey/pi.py index 624933a..9d2f06a 100644 --- a/civpy/survey/pi.py +++ b/civpy/survey/pi.py @@ -1,4 +1,7 @@ -import propy +""" +Copyright (c) 2019, Matt Pewsey +""" + import numpy as np __all__ = ['PI'] @@ -16,11 +19,6 @@ class PI(np.ndarray): The radius of the horizontal curve. Use zero if a curve does not exist. """ - # Custom properties - x = propy.index_property(0) - y = propy.index_property(1) - z = propy.index_property(2) - def __new__(cls, x, y, z=0, radius=0): obj = np.array([x, y, z], dtype='float').view(cls) obj.radius = radius @@ -30,4 +28,37 @@ def __array_finalize__(self, obj): if obj is None: return self.radius = getattr(obj, 'radius', 0) - __repr__ = propy.repr_method('x', 'y', 'z', 'radius') + def x(): + def fget(self): + return self[0] + def fset(self, value): + self[0] = value + return locals() + x = property(**x()) + + def y(): + def fget(self): + return self[1] + def fset(self, value): + self[1] = value + return locals() + y = property(**y()) + + def z(): + def fget(self): + return self[2] + def fset(self, value): + self[2] = value + return locals() + z = property(**z()) + + def __repr__(self): + s = [ + ('x', self.x), + ('y', self.y), + ('z', self.z), + ('radius', self.radius), + ] + + s = ', '.join('{}={!r}'.format(x, y) for x, y in s) + return '{}({})'.format(type(self).__name__, s) diff --git a/civpy/survey/spatial_hash.py b/civpy/survey/spatial_hash.py index d37bc4d..1eb9eb4 100644 --- a/civpy/survey/spatial_hash.py +++ b/civpy/survey/spatial_hash.py @@ -1,4 +1,7 @@ -from __future__ import division +""" +Copyright (c) 2019, Matt Pewsey +""" + import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d.art3d import Poly3DCollection @@ -109,6 +112,8 @@ def _hash(self, point, norm): def multi_get(self, points, norm=True): """ + Returns the point indices corresponding to the input array of points. + Parameters ---------- points : list diff --git a/civpy/survey/survey_point.py b/civpy/survey/survey_point.py index ce80c21..11eefbc 100644 --- a/civpy/survey/survey_point.py +++ b/civpy/survey/survey_point.py @@ -1,4 +1,7 @@ -import propy +""" +Copyright (c) 2019, Matt Pewsey +""" + import numpy as np __all__ = ['SurveyPoint'] @@ -13,11 +16,6 @@ class SurveyPoint(np.ndarray): x, y, z : float The x, y, and z coordinates. """ - # Custom properties - x = propy.index_property(0) - y = propy.index_property(1) - z = propy.index_property(2) - def __new__(cls, x, y, z, **kwargs): obj = np.array([x, y, z], dtype='float').view(cls) obj.meta = dict(**kwargs) @@ -27,4 +25,37 @@ def __array_finalize__(self, obj): if obj is None: return self.meta = getattr(obj, 'meta', {}) - __repr__ = propy.repr_method('x', 'y', 'z', 'meta') + def x(): + def fget(self): + return self[0] + def fset(self, value): + self[0] = value + return locals() + x = property(**x()) + + def y(): + def fget(self): + return self[1] + def fset(self, value): + self[1] = value + return locals() + y = property(**y()) + + def z(): + def fget(self): + return self[2] + def fset(self, value): + self[2] = value + return locals() + z = property(**z()) + + def __repr__(self): + s = [ + ('x', self.x), + ('y', self.y), + ('z', self.z), + ('meta', self.meta), + ] + + s = ', '.join('{}={!r}'.format(x, y) for x, y in s) + return '{}({})'.format(type(self).__name__, s) diff --git a/civpy/survey/survey_stake.py b/civpy/survey/survey_stake.py index e9a2e3b..88db428 100644 --- a/civpy/survey/survey_stake.py +++ b/civpy/survey/survey_stake.py @@ -1,4 +1,7 @@ -import propy +""" +Copyright (c) 2019, Matt Pewsey +""" + import numpy as np __all__ = ['SurveyStake'] @@ -10,20 +13,15 @@ class SurveyStake(np.ndarray): using the :meth:`SurveyStake.init_xy` or :meth:`SurveyStake.init_station` class methods. """ - TYPES = ('xy', 'station') - - # Custom properties - x = propy.index_property(0) - y = propy.index_property(1) - z = propy.index_property(2) - lock_z = propy.bool_property('lock_z') - _type = propy.enum_property('_type', TYPES) + XY = 'xy' + STATION = 'station' + TYPES = (XY, STATION) def __new__(cls, x, y, z, station, offset, height, rotation, lock_z, _type, _init=False, **kwargs): if not _init: raise ValueError('SurveyStake should be initialized using the ' - '`SurveyStake.init_xy` or `SurveyStake.init_station` methods ' + 'SurveyStake.init_xy or SurveyStake.init_station methods ' 'in lieu of the standard initializer.') obj = np.array([x, y, z], dtype='float').view(cls) @@ -46,8 +44,54 @@ def __array_finalize__(self, obj): self._type = getattr(obj, '_type', 'xy') self.meta = getattr(obj, 'meta', {}) - __repr__ = propy.repr_method('_type', 'x', 'y', 'z', 'station', 'offset', - 'lock_z', 'meta') + def x(): + def fget(self): + return self[0] + def fset(self, value): + self[0] = value + return locals() + x = property(**x()) + + def y(): + def fget(self): + return self[1] + def fset(self, value): + self[1] = value + return locals() + y = property(**y()) + + def z(): + def fget(self): + return self[2] + def fset(self, value): + self[2] = value + return locals() + z = property(**z()) + + def _type(): + def fget(self): + return self.__type + def fset(self, value): + if value not in self.TYPES: + raise ValueError('Type {!r} must be in {!r}.'.format(value, self.TYPES)) + self.__type = value + return locals() + _type = property(**_type()) + + def __repr__(self): + s = [ + ('_type', self._type), + ('x', self.x), + ('y', self.y), + ('z', self.z), + ('station', self.station), + ('offset', self.offset), + ('lock_z', self.lock_z), + ('meta', self.meta), + ] + + s = ', '.join('{}={!r}'.format(x, y) for x, y in s) + return '{}({})'.format(type(self).__name__, s) @classmethod def init_xy(cls, x, y, z=0, height=0, rotation=0, lock_z=False, **kwargs): diff --git a/civpy/survey/tin.py b/civpy/survey/tin.py index b80f37b..bfc10b4 100644 --- a/civpy/survey/tin.py +++ b/civpy/survey/tin.py @@ -1,6 +1,10 @@ -from __future__ import division -import propy +""" +Copyright (c) 2019, Matt Pewsey +""" + +import attr import numpy as np +from math import ceil import matplotlib.pyplot as plt from scipy.spatial import Delaunay from .spatial_hash import SpatialHash @@ -8,6 +12,7 @@ __all__ = ['TIN'] +@attr.s(hash=False) class TIN(object): """ A class for creating triangulated irregular networks (TIN) models for @@ -37,17 +42,17 @@ class TIN(object): .. plot:: ../examples/survey/tin_ex1.py :include-source: """ - def __init__(self, name, points=[], breaklines=[], max_edge=None, - step=0.1, grid=10): - self.name = name - self.breaklines = breaklines - self.max_edge = max_edge - self.step = step - self._create_triangulation(points, grid) + name = attr.ib() + points = attr.ib(default=[], converter=np.asarray) + breaklines = attr.ib(default=[]) + max_edge = attr.ib(default=None) + step = attr.ib(default=0.1) + grid = attr.ib(default=10) - __repr__ = propy.repr_method('name') + def __attrs_post_init__(self): + self._create_triangulation() - def _create_triangulation(self, points, grid): + def _create_triangulation(self): """ Creates the Delaunay trianguation and spatial hash. @@ -58,7 +63,7 @@ def _create_triangulation(self, points, grid): grid : float The spatial hash grid spacing. """ - points = np.asarray(points) + points = self.points b = self.breakpoints() if b.shape[0] > 0: @@ -70,7 +75,7 @@ def _create_triangulation(self, points, grid): self.points = points self.tri = Delaunay(points[:,:2]) - self.hash = SpatialHash(points[:,:2], grid) + self.hash = SpatialHash(points[:,:2], self.grid) self._remove_simplices() def _remove_simplices(self): @@ -101,7 +106,7 @@ def breakpoints(self): for i, (a, b) in enumerate(zip(line[:-1], line[1:])): m = a - b - n = int(np.ceil(np.linalg.norm(m) / self.step)) + n = int(ceil(np.linalg.norm(m) / self.step)) if n < 2: n = 2 if i == 0 else 1 x = np.expand_dims(np.linspace(0, 1, n), 1) y = m * x + b diff --git a/pytest.ini b/pytest.ini index 7c6da45..2ebd36a 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,2 +1,3 @@ [pytest] python_files = tests.py test_*.py *_test.py +addopts = --cov=. --cov-report=html diff --git a/requirements.txt b/requirements.txt index 1b4c904..361b4b2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ numpy>=1.14.5 scipy>=1.1.0 matplotlib>=2.2.2 -propy>=1.0.0 +attrs>=19.1.0 pandas>=0.23.0 xsect>=1.1.0 diff --git a/setup.cfg b/setup.cfg index 1e1dc91..5b8d2fe 100644 --- a/setup.cfg +++ b/setup.cfg @@ -29,7 +29,7 @@ install_requires = numpy>=1.14.5 scipy>=1.1.0 matplotlib>=2.2.2 - propy>=1.0.0 + attrs>=19.1.0 pandas>=0.23.0 xsect>=1.1.0 python_requires = >=3.5