def compute_internal_work_compression(form, force): """Compute the work done by the internal compressive forces of a structure. Parameters ---------- form : FormDiagram The form diagram. force : ForceDiagram The force diagram. Returns ------- float The internal work done by the compressive forces in a structure. Notes ----- ... References ---------- ... See Also -------- :func:`compute_internal_work_tension` Examples -------- .. code-block:: python # """ k_i = form.key_index() xy = array(form.xy(), dtype=float64) edges = [(k_i[u], k_i[v]) for u, v in form.edges()] C = connectivity_matrix(edges, 'csr') q = array(form.q(), dtype=float64).reshape((-1, 1)) _xy = force.xy() _edges = force.ordered_edges(form) _C = connectivity_matrix(_edges, 'csr') leaves = set(form.leaves()) internal = [ i for i, (u, v) in enumerate(form.edges()) if u not in leaves and v not in leaves ] compression = [i for i in internal if q[i, 0] < 0] l = normrow(C.dot(xy)) _l = normrow(_C.dot(_xy)) li = l[compression] _li = _l[compression] return li.T.dot(_li)[0, 0]
def force_update_from_form(force, form): """Update the force diagram after modifying the (force densities of) the form diagram. Parameters ---------- force : :class:`ForceDiagram` The force diagram on which the update is based. form : :class:`FormDiagram` The form diagram to update. Returns ------- force: :class:`ForceDiagram` The updated force diagram. """ # -------------------------------------------------------------------------- # form diagram # -------------------------------------------------------------------------- vertex_index = form.vertex_index() xy = array(form.xy(), dtype=float64) edges = [[vertex_index[u], vertex_index[v]] for u, v in form.edges_where({'_is_edge': True})] C = connectivity_matrix(edges, 'csr') Q = diags([form.q()], [0]) uv = C.dot(xy) # -------------------------------------------------------------------------- # force diagram # -------------------------------------------------------------------------- _vertex_index = force.vertex_index() _known = [_vertex_index[force.anchor()]] _xy = array(force.xy(), dtype=float64) _edges = force.ordered_edges(form) _edges[:] = [(_vertex_index[u], _vertex_index[v]) for u, v in _edges] _C = connectivity_matrix(_edges, 'csr') _Ct = _C.transpose() # -------------------------------------------------------------------------- # compute reciprocal for given q # -------------------------------------------------------------------------- _xy = spsolve_with_known(_Ct.dot(_C), _Ct.dot(Q).dot(uv), _xy, _known) # -------------------------------------------------------------------------- # update force diagram # -------------------------------------------------------------------------- for vertex, attr in force.vertices(True): index = _vertex_index[vertex] attr['x'] = _xy[index, 0] attr['y'] = _xy[index, 1] return force
def mesh_connectivity_matrix(mesh, rtype='array'): """Creates a connectivity matrix from a Mesh datastructure. Parameters ---------- mesh : compas.datastructures.Mesh Instance of mesh. rtype : {'array', 'csc', 'csr', 'coo', 'list'} Format of the result. Returns ------- array-like Constructed connectivity matrix. Examples -------- >>> C = mesh_connectivity_matrix(mesh) >>> type(C) <class 'numpy.ndarray'> >>> C = mesh_connectivity_matrix(mesh, rtype='csr') >>> type(C) <class 'scipy.sparse.csr.csr_matrix'> >>> xyz = asarray(mesh.vertices_attributes('xyz')) >>> C = mesh_connectivity_matrix(mesh, rtype='csr') >>> uv = C.dot(xyz) """ key_index = mesh.key_index() edges = [(key_index[u], key_index[v]) for u, v in mesh.edges()] return connectivity_matrix(edges, rtype=rtype)
def mesh_connectivity_matrix(mesh, rtype='array'): """Creates a connectivity matrix from a Mesh datastructure. Parameters ---------- mesh : :class:`compas.datastructures.Mesh` Instance of mesh. rtype : Literal['array', 'csc', 'csr', 'coo', 'list'], optional Format of the result. Returns ------- array_like Constructed connectivity matrix. Examples -------- >>> from compas.datastructures import Mesh >>> mesh = Mesh.from_polyhedron(6) >>> C = mesh_connectivity_matrix(mesh) >>> type(C) <class 'numpy.ndarray'> >>> C = mesh_connectivity_matrix(mesh, rtype='csr') >>> xyz = asarray(mesh.vertices_attributes('xyz')) >>> C = mesh_connectivity_matrix(mesh, rtype='csr') >>> uv = C.dot(xyz) """ key_index = mesh.key_index() edges = [(key_index[u], key_index[v]) for u, v in mesh.edges()] return connectivity_matrix(edges, rtype=rtype)
def form_identify_dof(form): r"""Identify the DOF of a form diagram. Parameters ---------- form : FormDiagram The form diagram. Returns ------- k : int Dimension of the null space (nullity) of the equilibrium matrix. Number of independent states of self-stress. m : int Size of the left null space of the equilibrium matrix. Number of (infenitesimal) mechanisms. ind : list Indices of the independent edges. Notes ----- The equilibrium matrix of the form diagram is .. math:: \mathbf{E} = \begin{bmatrix} \mathbf{C}_{i}^{t}\mathbf{U} \\ \mathbf{C}_{i}^{t}\mathbf{V} \end{bmatrix} If ``k == 0`` and ``m == 0``, the system described by the equilibrium matrix is statically determined. If ``k > 0`` and ``m == 0``, the system is statically indetermined with `k` idependent states of stress. If ``k == 0`` asnd ``m > 0``, the system is unstable, with `m` independent mechanisms. The dimension of a vector space (such as the null space) is the number of vectors of a basis of that vector space. A set of vectors forms a basis of a vector space if they are linearly independent vectors and every vector of the space is a linear combination of this set. Examples -------- >>> """ k_i = form.key_index() xy = form.vertices_attributes('xy') fixed = [k_i[key] for key in form.fixed()] free = list(set(range(len(form.vertex))) - set(fixed)) edges = [(k_i[u], k_i[v]) for u, v in form.edges()] C = connectivity_matrix(edges) E = equilibrium_matrix(C, xy, free) k, m = dof(E) ind = nonpivots(rref(E)) return k, m, [edges[i] for i in ind]
def compute_external_work(form, force): """Compute the external work of a structure. The external work done by a structure is equal to the work done by the external forces. This is equal to the sum of the dot products of the force vectors and the vectors defined by the force application point and a fixed arbitrary point. Parameters ---------- form : FormDiagram The form diagram. force : ForceDiagram The force diagram. Returns ------- float The external work done by the structure. Examples -------- >>> """ vertex_index = form.vertex_index() xy = array(form.xy(), dtype=float64) edges = [(vertex_index[u], vertex_index[v]) for u, v in form.edges()] C = connectivity_matrix(edges, 'csr') _vertex_index = force.vertex_index() _xy = force.xy() _edges = force.ordered_edges(form) _edges[:] = [(_vertex_index[u], _vertex_index[v]) for u, v in _edges] _C = connectivity_matrix(_edges, 'csr') leaves = set(form.leaves()) external = [ i for i, (u, v) in enumerate(form.edges()) if u in leaves or v in leaves ] lengths = normrow(C.dot(xy)) forces = normrow(_C.dot(_xy)) return lengths[external].T.dot(forces[external])[0, 0]
def force_update_from_form(force, form): """Update the force diagram after modifying the (force densities of) the form diagram. Parameters ---------- force : ForceDiagram The force diagram on which the update is based. form : FormDiagram The form diagram to update. Returns ------- None The form and force diagram are updated in-place. """ # -------------------------------------------------------------------------- # form diagram # -------------------------------------------------------------------------- k_i = form.key_index() xy = array(form.xy(), dtype=float64) edges = [[k_i[u], k_i[v]] for u, v in form.edges()] C = connectivity_matrix(edges, 'csr') Q = diags([form.q()], [0]) uv = C.dot(xy) # -------------------------------------------------------------------------- # force diagram # -------------------------------------------------------------------------- _k_i = force.key_index() _known = [_k_i[force.anchor()]] _xy = array(force.xy(), dtype=float64) _edges = force.ordered_edges(form) _C = connectivity_matrix(_edges, 'csr') _Ct = _C.transpose() # -------------------------------------------------------------------------- # compute reciprocal for given q # -------------------------------------------------------------------------- _xy = spsolve_with_known(_Ct.dot(_C), _Ct.dot(Q).dot(uv), _xy, _known) # -------------------------------------------------------------------------- # update force diagram # -------------------------------------------------------------------------- for key, attr in force.vertices(True): i = _k_i[key] attr['x'] = _xy[i, 0] attr['y'] = _xy[i, 1]
def form_count_dof(form): k2i = form.key_index() xyz = form.vertices_attributes('xyz') fixed = [k2i[key] for key in form.anchors()] free = list(set(range(form.number_of_vertices())) - set(fixed)) edges = [(k2i[u], k2i[v]) for u, v in form.edges_where({'_is_edge': True})] C = connectivity_matrix(edges) E = equilibrium_matrix(C, xyz, free) return dof(E)
def count_dof(form): k2i = form.key_index() xyz = form.xyz() fixed = form.anchors(k2i=k2i) free = list(set(range(form.number_of_vertices())) - set(fixed)) edges = [(k2i[u], k2i[v]) for u, v in form.edges_where({'is_edge': True})] C = connectivity_matrix(edges) E = equilibrium_matrix(C, xyz, free) return dof(E)
def form_identify_dof(form, **kwargs): algo = kwargs.get('algo') or 'sympy' k2i = form.key_index() xyz = form.vertices_attributes('xyz') fixed = [k2i[key] for key in form.anchors()] free = list(set(range(form.number_of_vertices())) - set(fixed)) edges = [(k2i[u], k2i[v]) for u, v in form.edges_where({'_is_edge': True})] C = connectivity_matrix(edges) E = equilibrium_matrix(C, xyz, free) return nonpivots(rref(E, algo=algo))
def compute_internal_work_compression(form, force): """Compute the work done by the internal compressive forces of a structure. Parameters ---------- form : FormDiagram The form diagram. force : ForceDiagram The force diagram. Returns ------- float The internal work done by the compressive forces in a structure. Examples -------- >>> """ vertex_index = form.vertex_index() xy = array(form.xy(), dtype=float64) edges = [(vertex_index[u], vertex_index[v]) for u, v in form.edges()] C = connectivity_matrix(edges, 'csr') q = array(form.q(), dtype=float64).reshape((-1, 1)) _vertex_index = force.vertex_index() _xy = force.xy() _edges = force.ordered_edges(form) _edges[:] = [(_vertex_index[u], _vertex_index[v]) for u, v in _edges] _C = connectivity_matrix(_edges, 'csr') leaves = set(form.leaves()) internal = [ i for i, (u, v) in enumerate(form.edges()) if u not in leaves and v not in leaves ] compression = [i for i in internal if q[i, 0] < 0] lengths = normrow(C.dot(xy)) forces = normrow(_C.dot(_xy)) return lengths[compression].T.dot(forces[compression])[0, 0]
def form_update_q_from_qind(form): """Update the force densities of the dependent edges of a form diagram using the values of the independent ones. Parameters ---------- form : FormDiagram The form diagram. Returns ------- None The updated force densities are stored as attributes of the edges of the form diagram. Examples -------- .. code-block:: python # """ k_i = form.key_index() uv_index = form.uv_index() vcount = form.number_of_vertices() ecount = form.number_of_edges() fixed = form.leaves() fixed = [k_i[key] for key in fixed] free = list(set(range(vcount)) - set(fixed)) ind = [ index for index, (u, v, attr) in enumerate(form.edges(True)) if attr['is_ind'] ] dep = list(set(range(ecount)) - set(ind)) edges = [(k_i[u], k_i[v]) for u, v in form.edges()] xy = array(form.xy(), dtype=float64).reshape((-1, 2)) q = array(form.q(), dtype=float64).reshape((-1, 1)) C = connectivity_matrix(edges, 'csr') E = equilibrium_matrix(C, xy, free, 'csr') update_q_from_qind(E, q, dep, ind) uv = C.dot(xy) l = normrow(uv) f = q * l for u, v, attr in form.edges(True): index = uv_index[(u, v)] attr['q'] = q[index, 0] attr['f'] = f[index, 0] attr['l'] = l[index, 0]
def form_count_dof(form): r"""Count the number of degrees of freedom of a form diagram. Parameters ---------- form : FormDiagram The form diagram. Returns ------- k : int Dimension of the null space (*nullity*) of the equilibrium matrix of the form diagram. m : int Dimension of the left null space of the equilibrium matrix of the form diagram. Notes ----- The equilibrium matrix of the form diagram is .. math:: \mathbf{E} = \begin{bmatrix} \mathbf{C}_{i}^{t}\mathbf{U} \\ \mathbf{C}_{i}^{t}\mathbf{V} \end{bmatrix} References ---------- ... Examples -------- .. code-block:: python # """ k_i = form.key_index() xy = form.get_vertices_attributes('xy') fixed = [k_i[key] for key in form.fixed()] free = list(set(range(len(form.vertex))) - set(fixed)) edges = [(k_i[u], k_i[v]) for u, v in form.edges_where({'is_edge': True})] C = connectivity_matrix(edges) E = equilibrium_matrix(C, xy, free) k, m = dof(E) return k, m
def form_count_dof(form): r"""Count the number of degrees of freedom of a form diagram. Parameters ---------- form : FormDiagram The form diagram. Returns ------- k : int Dimension of the null space (*nullity*) of the equilibrium matrix of the form diagram. m : int Dimension of the left null space of the equilibrium matrix of the form diagram. Notes ----- The equilibrium matrix of the form diagram is .. math:: \mathbf{E} = \begin{bmatrix} \mathbf{C}_{i}^{t}\mathbf{U} \\ \mathbf{C}_{i}^{t}\mathbf{V} \end{bmatrix} Examples -------- >>> """ vertex_index = form.vertex_index() xy = form.vertices_attributes('xy') fixed = [vertex_index[vertex] for vertex in form.leaves()] free = list(set(range(form.number_of_vertices())) - set(fixed)) edges = [(vertex_index[u], vertex_index[v]) for u, v in form.edges()] C = connectivity_matrix(edges) E = equilibrium_matrix(C, xy, free) k, m = dof(E) return int(k), int(m)
def form_update_q_from_qind(form): """Update the force densities of the dependent edges of a form diagram using the values of the independent ones. Parameters ---------- form : FormDiagram The form diagram. Returns ------- None The updated force densities are stored as attributes of the edges of the form diagram. Examples -------- >>> """ vertex_index = form.vertex_index() edge_index = form.edge_index() vcount = form.number_of_vertices() ecount = form.number_of_edges() fixed = form.leaves() fixed = [vertex_index[vertex] for vertex in fixed] free = list(set(range(vcount)) - set(fixed)) ind = [edge_index[edge] for edge in form.ind()] dep = list(set(range(ecount)) - set(ind)) edges = [(vertex_index[u], vertex_index[v]) for u, v in form.edges()] xy = array(form.xy(), dtype=float64).reshape((-1, 2)) q = array(form.q(), dtype=float64).reshape((-1, 1)) C = connectivity_matrix(edges, 'csr') E = equilibrium_matrix(C, xy, free, 'csr') update_q_from_qind(E, q, dep, ind) uv = C.dot(xy) lengths = normrow(uv) forces = q * lengths for edge in form.edges(): index = edge_index[edge] form.edge_attributes( edge, ['q', 'f', 'l'], [q[index, 0], forces[index, 0], lengths[index, 0]])
def network_connectivity_matrix(network, rtype='array'): """Creates a connectivity matrix from a Network datastructure. Parameters ---------- network : :class:`compas.datastructures.Network` Network data structure. rtype : Literal['array', 'csc', 'csr', 'coo', 'list'], optional Format of the result. Returns ------- array_like Constructed connectivity matrix. """ key_index = network.key_index() edges = [(key_index[u], key_index[v]) for u, v in network.edges()] return connectivity_matrix(edges, rtype=rtype)
def network_connectivity_matrix(network, rtype='array'): """Creates a connectivity matrix from a Network datastructure. Parameters ---------- network : obj Network datastructure object to get data from. rtype : {'array', 'csc', 'csr', 'coo', 'list'} Format of the result. Returns ------- array-like Constructed connectivity matrix. """ key_index = network.key_index() edges = [(key_index[u], key_index[v]) for u, v in network.edges()] return connectivity_matrix(edges, rtype=rtype)
def mesh_connectivity_matrix(mesh, rtype='array'): """Creates a connectivity matrix from a Mesh datastructure. Parameters ---------- mesh : obj Mesh datastructure object to get data from. rtype : {'array', 'csc', 'csr', 'coo', 'list'} Format of the result. Returns ------- array-like Constructed connectivity matrix. """ key_index = mesh.key_index() edges = [(key_index[u], key_index[v]) for u, v in mesh.edges()] return connectivity_matrix(edges, rtype=rtype)
def vertical_from_q(form, scale=1.0, density=1.0, kmax=100, tol=1e-3, display=True): """Compute vertical equilibrium from the force densities of the independent edges. Parameters ---------- form : FormDiagram The form diagram scale : float The scale of the horizontal forces. Default is ``1.0``. density : float, optional The density for computation of the self-weight of the thrust network. Set this to 0.0 to ignore self-weight and only consider specified point loads. Default is ``1.0``. kmax : int, optional The maximum number of iterations for computing vertical equilibrium. Default is ``100``. tol : float The stopping criterion. Default is ``0.001``. display : bool Display information about the current iteration. Default is ``True``. """ k_i = form.key_index() uv_i = form.uv_index() vcount = form.number_of_vertices() anchors = list(form.anchors()) fixed = list(form.fixed()) fixed = set(anchors + fixed) fixed = [k_i[key] for key in fixed] edges = [(k_i[u], k_i[v]) for u, v in form.edges_where({'is_edge': True})] free = list(set(range(vcount)) - set(fixed)) xyz = array(form.get_vertices_attributes('xyz'), dtype=float64) thick = array(form.get_vertices_attribute('t'), dtype=float64).reshape( (-1, 1)) p = array(form.get_vertices_attributes(('px', 'py', 'pz')), dtype=float64) q = [ attr.get('q', 1.0) for u, v, attr in form.edges_where({'is_edge': True}, True) ] q = array(q, dtype=float64).reshape((-1, 1)) C = connectivity_matrix(edges, 'csr') # -------------------------------------------------------------------------- # original data # -------------------------------------------------------------------------- p0 = array(p, copy=True) q0 = array(q, copy=True) # -------------------------------------------------------------------------- # load updater # -------------------------------------------------------------------------- update_loads = LoadUpdater(form, p0, thickness=thick, density=density) # -------------------------------------------------------------------------- # update forcedensity based on given q[ind] # -------------------------------------------------------------------------- q = scale * q0 Q = diags([q.ravel()], [0]) # -------------------------------------------------------------------------- # compute vertical # -------------------------------------------------------------------------- update_z(xyz, Q, C, p, free, fixed, update_loads, tol=tol, kmax=kmax, display=display) # -------------------------------------------------------------------------- # update # -------------------------------------------------------------------------- l = normrow(C.dot(xyz)) f = q * l r = C.transpose().dot(Q).dot(C).dot(xyz) - p sw = p - p0 # -------------------------------------------------------------------------- # form # -------------------------------------------------------------------------- for key, attr in form.vertices(True): index = k_i[key] attr['z'] = xyz[index, 2] attr['rx'] = r[index, 0] attr['ry'] = r[index, 1] attr['rz'] = r[index, 2] attr['sw'] = sw[index, 2] for u, v, attr in form.edges_where({'is_edge': True}, True): index = uv_i[(u, v)] attr['f'] = f[index, 0] attr['l'] = l[index, 0]
def vertical_from_zmax(form, zmax, kmax=100, xtol=1e-2, rtol=1e-3, density=1.0, display=True): """For the given form and force diagram, compute the scale of the force diagram for which the highest point of the thrust network is equal to a specified value. Parameters ---------- form : compas_tna.diagrams.formdiagram.FormDiagram The form diagram force : compas_tna.diagrams.forcediagram.ForceDiagram The corresponding force diagram. zmax : float The maximum height of the thrust network (the default is None, which implies that the maximum height will be equal to a quarter of the diagonal of the bounding box of the form diagram). kmax : int The maximum number of iterations for computing vertical equilibrium (the default is 100). tol : float The stopping criterion. density : float The density for computation of the self-weight of the thrust network (the default is 1.0). Set this to 0.0 to ignore self-weight and only consider specified point loads. display : bool If True, information about the current iteration will be displayed. """ xtol2 = xtol**2 # -------------------------------------------------------------------------- # FormDiagram # -------------------------------------------------------------------------- k_i = form.key_index() uv_i = form.uv_index() vcount = len(form.vertex) anchors = list(form.anchors()) fixed = list(form.fixed()) fixed = set(anchors + fixed) fixed = [k_i[key] for key in fixed] free = list(set(range(vcount)) - set(fixed)) edges = [(k_i[u], k_i[v]) for u, v in form.edges_where({'is_edge': True})] xyz = array(form.get_vertices_attributes('xyz'), dtype=float64) thick = array(form.get_vertices_attribute('t'), dtype=float64).reshape( (-1, 1)) p = array(form.get_vertices_attributes(('px', 'py', 'pz')), dtype=float64) q = [ attr.get('q', 1.0) for u, v, attr in form.edges_where({'is_edge': True}, True) ] q = array(q, dtype=float64).reshape((-1, 1)) C = connectivity_matrix(edges, 'csr') Ci = C[:, free] Cf = C[:, fixed] Cit = Ci.transpose() Ct = C.transpose() # -------------------------------------------------------------------------- # original data # -------------------------------------------------------------------------- p0 = array(p, copy=True) q0 = array(q, copy=True) # -------------------------------------------------------------------------- # load updater # -------------------------------------------------------------------------- update_loads = LoadUpdater(form, p0, thickness=thick, density=density) # -------------------------------------------------------------------------- # scale to zmax # note that zmax should not exceed scale * diagonal # -------------------------------------------------------------------------- scale = 1.0 for k in range(kmax): if display: print(k) update_loads(p, xyz) q = scale * q0 Q = diags([q.ravel()], [0]) A = Cit.dot(Q).dot(Ci) b = p[free, 2] - Cit.dot(Q).dot(Cf).dot(xyz[fixed, 2]) xyz[free, 2] = spsolve(A, b) z = max(xyz[free, 2]) res2 = (z - zmax)**2 if res2 < xtol2: break scale = scale * (z / zmax) # -------------------------------------------------------------------------- # vertical # -------------------------------------------------------------------------- q = scale * q0 Q = diags([q.ravel()], [0]) res = update_z(xyz, Q, C, p, free, fixed, update_loads, tol=rtol, kmax=kmax, display=display) # -------------------------------------------------------------------------- # update # -------------------------------------------------------------------------- l = normrow(C.dot(xyz)) f = q * l r = Ct.dot(Q).dot(C).dot(xyz) - p sw = p - p0 # -------------------------------------------------------------------------- # form # -------------------------------------------------------------------------- for key, attr in form.vertices(True): index = k_i[key] attr['z'] = xyz[index, 2] attr['rx'] = r[index, 0] attr['ry'] = r[index, 1] attr['rz'] = r[index, 2] attr['sw'] = sw[index, 2] for u, v, attr in form.edges_where({'is_edge': True}, True): index = uv_i[(u, v)] attr['f'] = f[index, 0] attr['l'] = l[index, 0] return scale
def vertical_from_bbox(form, factor=5.0, kmax=100, tol=1e-3, density=1.0, display=True): # -------------------------------------------------------------------------- # FormDiagram # -------------------------------------------------------------------------- k_i = form.key_index() uv_i = form.uv_index() vcount = len(form.vertex) anchors = list(form.anchors()) fixed = list(form.fixed()) fixed = set(anchors + fixed) fixed = [k_i[key] for key in fixed] free = list(set(range(vcount)) - set(fixed)) edges = [(k_i[u], k_i[v]) for u, v in form.edges_where({'is_edge': True})] xyz = array(form.get_vertices_attributes('xyz'), dtype=float64) thick = array(form.get_vertices_attribute('t'), dtype=float64).reshape( (-1, 1)) p = array(form.get_vertices_attributes(('px', 'py', 'pz')), dtype=float64) q = [ attr.get('q', 1.0) for u, v, attr in form.edges_where({'is_edge': True}, True) ] q = array(q, dtype=float64).reshape((-1, 1)) C = connectivity_matrix(edges, 'csr') Ci = C[:, free] Cf = C[:, fixed] Cit = Ci.transpose() Ct = C.transpose() # -------------------------------------------------------------------------- # original data # -------------------------------------------------------------------------- p0 = array(p, copy=True) q0 = array(q, copy=True) # -------------------------------------------------------------------------- # load updater # -------------------------------------------------------------------------- update_loads = LoadUpdater(form, p0, thickness=thick, density=density) # -------------------------------------------------------------------------- # scale # -------------------------------------------------------------------------- (xmin, ymin, zmin), (xmax, ymax, zmax) = form.bbox() d = ((xmax - xmin)**2 + (ymax - ymin)**2)**0.5 scale = d / factor # -------------------------------------------------------------------------- # vertical # -------------------------------------------------------------------------- q = scale * q0 Q = diags([q.ravel()], [0]) update_z(xyz, Q, C, p, free, fixed, update_loads, tol=tol, kmax=kmax, display=display) # -------------------------------------------------------------------------- # update # -------------------------------------------------------------------------- l = normrow(C.dot(xyz)) f = q * l r = Ct.dot(Q).dot(C).dot(xyz) - p sw = p - p0 # -------------------------------------------------------------------------- # form # -------------------------------------------------------------------------- for key, attr in form.vertices(True): index = k_i[key] attr['z'] = xyz[index, 2] attr['rx'] = r[index, 0] attr['ry'] = r[index, 1] attr['rz'] = r[index, 2] attr['sw'] = sw[index, 2] for u, v, attr in form.edges_where({'is_edge': True}, True): index = uv_i[(u, v)] attr['f'] = f[index, 0] attr['l'] = l[index, 0] return scale
def compute_jacobian(form, force): r"""Compute the Jacobian matrix. The actual computation of the Jacobian matrix :math:`\partial \mathbf{X}^* / \partial \mathbf{X}` where :math:`\mathbf{X}` contains the form diagram coordinates in *Fortran* order (first all :math:`\mathbf{x}`-coordinates, then all :math:`\mathbf{y}`-coordinates) and :math:`\mathbf{X}^*` contains the force diagram coordinates in *Fortran* order (first all :math:`\mathbf{x}^*`-coordinates, then all :math:`\mathbf{y}^*`-coordinates). Parameters ---------- form: :class:`FormDiagram` The form diagram. force: :class:`ForceDiagram` The force diagram. Returns ------- jacobian Jacobian matrix (2 * _vcount, 2 * vcount) References ---------- .. [1] Alic, V. and Åkesson, D., 2017. Bi-directional algebraic graphic statics. Computer-Aided Design, 93, pp.26-37. Examples -------- >>> """ # -------------------------------------------------------------------------- # form diagram # -------------------------------------------------------------------------- vcount = form.number_of_vertices() k_i = form.key_index() leaves = [k_i[key] for key in form.leaves()] free = list(set(range(form.number_of_vertices())) - set(leaves)) vicount = len(free) edges = [(k_i[u], k_i[v]) for u, v in form.edges()] xy = array(form.xy(), dtype=float64).reshape((-1, 2)) ecount = len(edges) C = connectivity_matrix(edges, 'array') E = equilibrium_matrix(C, xy, free, 'array') uv = C.dot(xy) u = uv[:, 0].reshape(-1, 1) v = uv[:, 1].reshape(-1, 1) Ct = C.transpose() Cti = array(Ct[free, :]) q = array(form.q(), dtype=float64).reshape((-1, 1)) Q = diag(q.flatten()) # TODO: Explore sparse (diags) independent_edges = [(k_i[u], k_i[v]) for (u, v) in list(form.edges_where({'is_ind': True}))] independent_edges_idx = [edges.index(i) for i in independent_edges] dependent_edges_idx = list(set(range(ecount)) - set(independent_edges_idx)) Ed = E[:, dependent_edges_idx] Eid = E[:, independent_edges_idx] qid = q[independent_edges_idx] EdInv = inv(array(Ed)) # TODO: Explore sparse (spinv) # -------------------------------------------------------------------------- # force diagram # -------------------------------------------------------------------------- _vertex_index = force.vertex_index() _vcount = force.number_of_vertices() _edges = force.ordered_edges(form) _edges[:] = [(_vertex_index[u], _vertex_index[v]) for u, v in _edges] _L = laplacian_matrix(_edges, normalize=False, rtype='array') _C = connectivity_matrix(_edges, 'array') _Ct = _C.transpose() _Ct = array(_Ct) _known = [_vertex_index[force.anchor()]] # -------------------------------------------------------------------------- # Jacobian # -------------------------------------------------------------------------- jacobian = zeros((_vcount * 2, vcount * 2)) for j in range(2): # Loop for x and y idx = list(range(j * vicount, (j + 1) * vicount)) for i in range(vcount): dXdxi = diag(Ct[i, :]) dxdxi = Ct[i, :].reshape(-1, 1) dEdXi = zeros((vicount * 2, ecount)) dEdXi[idx, :] = Cti.dot(dXdxi) dEdXi_d = dEdXi[:, dependent_edges_idx] dEdXi_id = dEdXi[:, independent_edges_idx] dEdXiInv = - EdInv.dot(dEdXi_d.dot(EdInv)) dqdXi_d = - dEdXiInv.dot(Eid.dot(qid)) - EdInv.dot(dEdXi_id.dot(qid)) dqdXi = zeros((ecount, 1)) dqdXi[dependent_edges_idx] = dqdXi_d dqdXi[independent_edges_idx] = 0 dQdXi = diag(dqdXi[:, 0]) d_XdXiTop = zeros((_L.shape[0])) d_XdXiBot = zeros((_L.shape[0])) if j == 0: d_XdXiTop = solve_with_known(_L, (_Ct.dot(dQdXi.dot(u) + Q.dot(dxdxi))).flatten(), d_XdXiTop, _known) d_XdXiBot = solve_with_known(_L, (_Ct.dot(dQdXi.dot(v))).flatten(), d_XdXiBot, _known) elif j == 1: d_XdXiTop = solve_with_known(_L, (_Ct.dot(dQdXi.dot(u))).flatten(), d_XdXiTop, _known) d_XdXiBot = solve_with_known(_L, (_Ct.dot(dQdXi.dot(v) + Q.dot(dxdxi))).flatten(), d_XdXiBot, _known) d_XdXi = hstack((d_XdXiTop, d_XdXiBot)) jacobian[:, i + j * vcount] = d_XdXi return jacobian
def dr_numpy(vertices, edges, fixed, loads, qpre, fpre, lpre, linit, E, radius, callback=None, callback_args=None, **kwargs): """Implementation of the dynamic relaxation method for form findong and analysis of articulated networks of axial-force members. Parameters ---------- vertices : list XYZ coordinates of the vertices. edges : list Connectivity of the vertices. fixed : list Indices of the fixed vertices. loads : list XYZ components of the loads on the vertices. qpre : list Prescribed force densities in the edges. fpre : list Prescribed forces in the edges. lpre : list Prescribed lengths of the edges. linit : list Initial length of the edges. E : list Stiffness of the edges. radius : list Radius of the edges. callback : callable, optional User-defined function that is called at every iteration. callback_args : tuple, optional Additional arguments passed to the callback. Returns ------- xyz : array XYZ coordinates of the equilibrium geometry. q : array Force densities in the edges. f : array Forces in the edges. l : array Lengths of the edges r : array Residual forces. Notes ----- For more info, see [1]_. References ---------- .. [1] De Laet L., Veenendaal D., Van Mele T., Mollaert M. and Block P., *Bending incorporated: designing tension structures by integrating bending-active elements*, Proceedings of Tensinet Symposium 2013,Istanbul, Turkey, 2013. Examples -------- >>> """ # -------------------------------------------------------------------------- # callback # -------------------------------------------------------------------------- if callback: assert callable(callback), 'The provided callback is not callable.' # -------------------------------------------------------------------------- # configuration # -------------------------------------------------------------------------- kmax = kwargs.get('kmax', 10000) dt = kwargs.get('dt', 1.0) tol1 = kwargs.get('tol1', 1e-3) tol2 = kwargs.get('tol2', 1e-6) coeff = Coeff(kwargs.get('c', 0.1)) ca = coeff.a cb = coeff.b # -------------------------------------------------------------------------- # attribute lists # -------------------------------------------------------------------------- num_v = len(vertices) num_e = len(edges) free = list(set(range(num_v)) - set(fixed)) # -------------------------------------------------------------------------- # attribute arrays # -------------------------------------------------------------------------- x = array(vertices, dtype=float).reshape((-1, 3)) # m p = array(loads, dtype=float).reshape((-1, 3)) # kN qpre = array(qpre, dtype=float).reshape((-1, 1)) fpre = array(fpre, dtype=float).reshape((-1, 1)) # kN lpre = array(lpre, dtype=float).reshape((-1, 1)) # m linit = array(linit, dtype=float).reshape((-1, 1)) # m E = array(E, dtype=float).reshape((-1, 1)) # kN/mm2 => GPa radius = array(radius, dtype=float).reshape((-1, 1)) # mm # -------------------------------------------------------------------------- # sectional properties # -------------------------------------------------------------------------- A = 3.14159 * radius**2 # mm2 EA = E * A # kN # -------------------------------------------------------------------------- # create the connectivity matrices # after spline edges have been aligned # -------------------------------------------------------------------------- C = connectivity_matrix(edges, 'csr') Ct = C.transpose() Ci = C[:, free] Cit = Ci.transpose() Ct2 = Ct.copy() Ct2.data **= 2 # -------------------------------------------------------------------------- # if none of the initial lengths are set, # set the initial lengths to the current lengths # -------------------------------------------------------------------------- if all(linit == 0): linit = normrow(C.dot(x)) # -------------------------------------------------------------------------- # initial values # -------------------------------------------------------------------------- q = ones((num_e, 1), dtype=float) l = normrow(C.dot(x)) # noqa: E741 f = q * l v = zeros((num_v, 3), dtype=float) r = zeros((num_v, 3), dtype=float) # -------------------------------------------------------------------------- # helpers # -------------------------------------------------------------------------- def rk(x0, v0, steps=2): def a(t, v): dx = v * t x[free] = x0[free] + dx[free] # update residual forces r[free] = p[free] - D.dot(x) return cb * r / mass if steps == 1: return a(dt, v0) if steps == 2: B = [0.0, 1.0] K0 = dt * a(K[0][0] * dt, v0) K1 = dt * a(K[1][0] * dt, v0 + K[1][1] * K0) dv = B[0] * K0 + B[1] * K1 return dv if steps == 4: B = [1. / 6., 1. / 3., 1. / 3., 1. / 6.] K0 = dt * a(K[0][0] * dt, v0) K1 = dt * a(K[1][0] * dt, v0 + K[1][1] * K0) K2 = dt * a(K[2][0] * dt, v0 + K[2][1] * K0 + K[2][2] * K1) K3 = dt * a(K[3][0] * dt, v0 + K[3][1] * K0 + K[3][2] * K1 + K[3][3] * K2) dv = B[0] * K0 + B[1] * K1 + B[2] * K2 + B[3] * K3 return dv raise NotImplementedError # -------------------------------------------------------------------------- # start iterating # -------------------------------------------------------------------------- for k in range(kmax): # print(k) q_fpre = fpre / l q_lpre = f / lpre q_EA = EA * (l - linit) / (linit * l) q_lpre[isinf(q_lpre)] = 0 q_lpre[isnan(q_lpre)] = 0 q_EA[isinf(q_EA)] = 0 q_EA[isnan(q_EA)] = 0 q = qpre + q_fpre + q_lpre + q_EA Q = diags([q[:, 0]], [0]) D = Cit.dot(Q).dot(C) mass = 0.5 * dt**2 * Ct2.dot(qpre + q_fpre + q_lpre + EA / linit) # RK x0 = x.copy() v0 = ca * v.copy() dv = rk(x0, v0, steps=4) v[free] = v0[free] + dv[free] dx = v * dt x[free] = x0[free] + dx[free] # update u = C.dot(x) l = normrow(u) # noqa: E741 f = q * l r = p - Ct.dot(Q).dot(u) # crits crit1 = norm(r[free]) crit2 = norm(dx[free]) # callback if callback: callback(k, x, [crit1, crit2], callback_args) # convergence if crit1 < tol1: break if crit2 < tol2: break return x, q, f, l, r
def update_xyz_numpy(mesh): """Find the equilibrium shape of a mesh for the given force densities. Parameters ---------- mesh : compas_fofin.datastructures.Cablenet The mesh to equilibriate. Returns ------- None The function updates the input mesh and returns nothing. """ k_i = mesh.key_index() fixed = mesh.vertices_where({'is_anchor': True}) fixed = [k_i[key] for key in fixed] free = list(set(range(mesh.number_of_vertices())) - set(fixed)) xyz = array(mesh.vertices_attributes('xyz'), dtype=float64) p = array(mesh.vertices_attributes(('px', 'py', 'pz')), dtype=float64) edges = [(k_i[u], k_i[v]) for u, v in mesh.edges_where({'is_edge': True})] q = array( [attr['q'] for key, attr in mesh.edges_where({'is_edge': True}, True)], dtype=float64).reshape((-1, 1)) density = mesh.attributes['density'] calculate_sw = SelfweightCalculator(mesh, density=density) if density: sw = calculate_sw(xyz) p[:, 2] = -sw[:, 0] C = connectivity_matrix(edges, 'csr') Ci = C[:, free] Cf = C[:, fixed] Ct = C.transpose() Cit = Ci.transpose() Q = diags([q.flatten()], [0]) A = Cit.dot(Q).dot(Ci) b = p[free] - Cit.dot(Q).dot(Cf).dot(xyz[fixed]) xyz[free] = spsolve(A, b) if density: sw = calculate_sw(xyz) p[:, 2] = -sw[:, 0] l = normrow(C.dot(xyz)) f = q * l r = p - Ct.dot(Q).dot(C).dot(xyz) for key, attr in mesh.vertices(True): index = k_i[key] attr['x'] = xyz[index, 0] attr['y'] = xyz[index, 1] attr['z'] = xyz[index, 2] attr['rx'] = r[index, 0] attr['ry'] = r[index, 1] attr['rz'] = r[index, 2] for index, (key, attr) in enumerate(mesh.edges_where({'is_edge': True}, True)): attr['q'] = q[index, 0] attr['f'] = f[index, 0] attr['l'] = l[index, 0]
def dr_numpy(vertices, edges, fixed, loads, qpre, fpre, lpre, linit, E, radius, callback=None, callback_args=None, **kwargs): """Implementation of the dynamic relaxation method for form findong and analysis of articulated networks of axial-force members. Parameters ---------- vertices : list XYZ coordinates of the vertices. edges : list Connectivity of the vertices. fixed : list Indices of the fixed vertices. loads : list XYZ components of the loads on the vertices. qpre : list Prescribed force densities in the edges. fpre : list Prescribed forces in the edges. lpre : list Prescribed lengths of the edges. linit : list Initial length of the edges. E : list Stiffness of the edges. radius : list Radius of the edges. callback : callable, optional User-defined function that is called at every iteration. callback_args : tuple, optional Additional arguments passed to the callback. Notes ----- For more info, see [1]_. References ---------- .. [1] De Laet L., Veenendaal D., Van Mele T., Mollaert M. and Block P., *Bending incorporated: designing tension structures by integrating bending-active elements*, Proceedings of Tensinet Symposium 2013,Istanbul, Turkey, 2013. Examples -------- .. plot:: :include-source: import compas from compas.datastructures import Network from compas_plotters import NetworkPlotter from compas.numerical import dr_numpy dva = { 'is_fixed': False, 'x': 0.0, 'y': 0.0, 'z': 0.0, 'px': 0.0, 'py': 0.0, 'pz': 0.0, 'rx': 0.0, 'ry': 0.0, 'rz': 0.0, } dea = { 'qpre': 1.0, 'fpre': 0.0, 'lpre': 0.0, 'linit': 0.0, 'E': 0.0, 'radius': 0.0, } network = Network.from_obj(compas.get('lines.obj')) network.update_default_vertex_attributes(dva) network.update_default_edge_attributes(dea) for key, attr in network.vertices(True): attr['is_fixed'] = network.vertex_degree(key) == 1 for index, (u, v, attr) in enumerate(network.edges(True)): attr['qpre'] = index + 1 k_i = network.key_index() vertices = network.get_vertices_attributes(('x', 'y', 'z')) edges = [(k_i[u], k_i[v]) for u, v in network.edges()] fixed = [k_i[key] for key in network.vertices_where({'is_fixed': True})] loads = network.get_vertices_attributes(('px', 'py', 'pz')) qpre = network.get_edges_attribute('qpre') fpre = network.get_edges_attribute('fpre') lpre = network.get_edges_attribute('lpre') linit = network.get_edges_attribute('linit') E = network.get_edges_attribute('E') radius = network.get_edges_attribute('radius') lines = [] for u, v in network.edges(): lines.append({ 'start': network.vertex_coordinates(u, 'xy'), 'end' : network.vertex_coordinates(v, 'xy'), 'color': '#cccccc', 'width': 1.0 }) plotter = NetworkPlotter(network) plotter.draw_lines(lines) xyz, q, f, l, r = dr_numpy(vertices, edges, fixed, loads, qpre, fpre, lpre, linit, E, radius) for key, attr in network.vertices(True): index = k_i[key] attr['x'] = xyz[index, 0] attr['y'] = xyz[index, 1] attr['z'] = xyz[index, 2] plotter.draw_vertices( facecolor={key: '#ff0000' for key in network.vertices_where({'is_fixed': True})}) plotter.draw_edges() plotter.show() """ # -------------------------------------------------------------------------- # callback # -------------------------------------------------------------------------- if callback: assert callable(callback), 'The provided callback is not callable.' # -------------------------------------------------------------------------- # configuration # -------------------------------------------------------------------------- kmax = kwargs.get('kmax', 10000) dt = kwargs.get('dt', 1.0) tol1 = kwargs.get('tol1', 1e-3) tol2 = kwargs.get('tol2', 1e-6) coeff = Coeff(kwargs.get('c', 0.1)) ca = coeff.a cb = coeff.b # -------------------------------------------------------------------------- # attribute lists # -------------------------------------------------------------------------- num_v = len(vertices) num_e = len(edges) free = list(set(range(num_v)) - set(fixed)) # -------------------------------------------------------------------------- # attribute arrays # -------------------------------------------------------------------------- x = array(vertices, dtype=float).reshape((-1, 3)) # m p = array(loads, dtype=float).reshape((-1, 3)) # kN qpre = array(qpre, dtype=float).reshape((-1, 1)) fpre = array(fpre, dtype=float).reshape((-1, 1)) # kN lpre = array(lpre, dtype=float).reshape((-1, 1)) # m linit = array(linit, dtype=float).reshape((-1, 1)) # m E = array(E, dtype=float).reshape((-1, 1)) # kN/mm2 => GPa radius = array(radius, dtype=float).reshape((-1, 1)) # mm # -------------------------------------------------------------------------- # sectional properties # -------------------------------------------------------------------------- A = 3.14159 * radius ** 2 # mm2 EA = E * A # kN # -------------------------------------------------------------------------- # create the connectivity matrices # after spline edges have been aligned # -------------------------------------------------------------------------- C = connectivity_matrix(edges, 'csr') Ct = C.transpose() Ci = C[:, free] Cit = Ci.transpose() Ct2 = Ct.copy() Ct2.data **= 2 # -------------------------------------------------------------------------- # if none of the initial lengths are set, # set the initial lengths to the current lengths # -------------------------------------------------------------------------- if all(linit == 0): linit = normrow(C.dot(x)) # -------------------------------------------------------------------------- # initial values # -------------------------------------------------------------------------- q = ones((num_e, 1), dtype=float) l = normrow(C.dot(x)) # noqa: E741 f = q * l v = zeros((num_v, 3), dtype=float) r = zeros((num_v, 3), dtype=float) # -------------------------------------------------------------------------- # helpers # -------------------------------------------------------------------------- def rk(x0, v0, steps=2): def a(t, v): dx = v * t x[free] = x0[free] + dx[free] # update residual forces r[free] = p[free] - D.dot(x) return cb * r / mass if steps == 1: return a(dt, v0) if steps == 2: B = [0.0, 1.0] K0 = dt * a(K[0][0] * dt, v0) K1 = dt * a(K[1][0] * dt, v0 + K[1][1] * K0) dv = B[0] * K0 + B[1] * K1 return dv if steps == 4: B = [1. / 6., 1. / 3., 1. / 3., 1. / 6.] K0 = dt * a(K[0][0] * dt, v0) K1 = dt * a(K[1][0] * dt, v0 + K[1][1] * K0) K2 = dt * a(K[2][0] * dt, v0 + K[2][1] * K0 + K[2][2] * K1) K3 = dt * a(K[3][0] * dt, v0 + K[3][1] * K0 + K[3][2] * K1 + K[3][3] * K2) dv = B[0] * K0 + B[1] * K1 + B[2] * K2 + B[3] * K3 return dv raise NotImplementedError # -------------------------------------------------------------------------- # start iterating # -------------------------------------------------------------------------- for k in range(kmax): # print(k) q_fpre = fpre / l q_lpre = f / lpre q_EA = EA * (l - linit) / (linit * l) q_lpre[isinf(q_lpre)] = 0 q_lpre[isnan(q_lpre)] = 0 q_EA[isinf(q_EA)] = 0 q_EA[isnan(q_EA)] = 0 q = qpre + q_fpre + q_lpre + q_EA Q = diags([q[:, 0]], [0]) D = Cit.dot(Q).dot(C) mass = 0.5 * dt ** 2 * Ct2.dot(qpre + q_fpre + q_lpre + EA / linit) # RK x0 = x.copy() v0 = ca * v.copy() dv = rk(x0, v0, steps=4) v[free] = v0[free] + dv[free] dx = v * dt x[free] = x0[free] + dx[free] # update u = C.dot(x) l = normrow(u) # noqa: E741 f = q * l r = p - Ct.dot(Q).dot(u) # crits crit1 = norm(r[free]) crit2 = norm(dx[free]) # callback if callback: callback(k, x, [crit1, crit2], callback_args) # convergence if crit1 < tol1: break if crit2 < tol2: break return x, q, f, l, r
def _create_arrays(network): """ Create arrays for dynamic relaxation solver. Parameters: network (obj): Network to analyse. Returns: array: Nodal co-ordinates x, y, z. array: Constraint conditions Bx, By, Bz. array: Nodal loads Px, Py, Pz. array: Resultant nodal loads. array: Shear force components Sx, Sy, Sz. array: Nodal velocities Vx, Vy, Vz. array: Edge Young's moduli. array: Edge areas. array: Connectivity matrix. array: Transposed connectivity matrix. array: Edge initial forces. array: Edge initial lengths. list: Compression only edges indices. list: Tension only edges indices. array: Network edges' start points. array: Network edges' end points. array: Mass matrix. array: Edge axial stiffnesses. """ # Vertices n = network.number_of_vertices() B = zeros((n, 3)) P = zeros((n, 3)) X = zeros((n, 3)) S = zeros((n, 3)) V = zeros((n, 3)) k_i = network.key_index() for key in network.vertices(): i = k_i[key] vertex = network.vertex[key] B[i, :] = vertex.get('B', [1, 1, 1]) P[i, :] = vertex.get('P', [0, 0, 0]) X[i, :] = [vertex[j] for j in 'xyz'] Pn = normrow(P) # Edges uv_i = network.uv_index() edges = list(network.edges()) m = len(edges) u = zeros(m, dtype=int64) v = zeros(m, dtype=int64) E = zeros((m, 1)) A = zeros((m, 1)) s0 = zeros((m, 1)) l0 = zeros((m, 1)) ind_c = [] ind_t = [] for c, uv in enumerate(edges): ui, vi = uv i = uv_i[(ui, vi)] edge = network.edge[ui][vi] E[i] = edge.get('E', 0) A[i] = edge.get('A', 0) l0[i] = edge.get('l0', network.edge_length(ui, vi)) s0[i] = edge.get('s0', 0) u[c] = k_i[ui] v[c] = k_i[vi] ct = edge.get('ct', None) if ct == 'c': ind_c.append(i) elif ct == 't': ind_t.append(i) f0 = s0 * A ks = E * A / l0 q0 = f0 / l0 # Faces (testing) # if network.face: # for face in faces: # fdata = network.facedata[face] # Eh = fdata.get('E', 0) # th = fdata.get('t', 0) # Ah = network.face_area(face) # for ui, vi in network.face_edges(face): # i = uv_i[(ui, vi)] # ks[i] += 1.5 * Eh * Ah * th / l0[i]**2 # Arrays C = connectivity_matrix([[k_i[ui], k_i[vi]] for ui, vi in edges], 'csr') Ct = C.transpose() M = mass_matrix(Ct=Ct, ks=ks, q=q0, c=1, tiled=False) return X, B, P, Pn, S, V, E, A, C, Ct, f0, l0, ind_c, ind_t, u, v, M, ks
def fd_numpy(vertices, edges, fixed, q, loads, **kwargs): """Implementation of the force density method to compute equilibrium of axial force networks. Parameters ---------- vertices : list XYZ coordinates of the vertices of the network edges : list Edges between vertices represented by fixed : list Indices of fixed vertices. q : list Force density of edges. loads : list XYZ components of the loads on the vertices. Returns ------- xyz : array XYZ coordinates of the equilibrium geometry. q : array Force densities in the edges. f : array Forces in the edges. l : array Lengths of the edges r : array Residual forces. Notes ----- For more info, see [1]_ References ---------- .. [1] Schek H., *The Force Density Method for Form Finding and Computation of General Networks*, Computer Methods in Applied Mechanics and Engineering 3: 115-134, 1974. Examples -------- .. plot:: :include-source: import compas from compas.datastructures import Mesh from compas.plotters import MeshPlotter from compas.numerical import fd_numpy from compas.utilities import i_to_black # make a mesh # add default attributes for form finding mesh = Mesh.from_obj(compas.get('faces.obj')) mesh.update_default_vertex_attributes({'is_anchor': False, 'px': 0.0, 'py': 0.0, 'pz': 0.0}) mesh.update_default_edge_attributes({'q': 1.0}) # identify the anchors # move two anchors up to create anticlastic boundary conditions for key, attr in mesh.vertices(True): attr['is_anchor'] = mesh.vertex_degree(key) == 2 if key in (18, 35): attr['z'] = 5.0 # preprocess k_i = mesh.key_index() xyz = mesh.get_vertices_attributes(('x', 'y', 'z')) loads = mesh.get_vertices_attributes(('px', 'py', 'pz')) q = mesh.get_edges_attribute('q') fixed = mesh.vertices_where({'is_anchor': True}) fixed = [k_i[k] for k in fixed] edges = [(k_i[u], k_i[v]) for u, v in mesh.edges()] # compute equilibrium # update the mesh geometry xyz, q, f, l, r = fd_numpy(xyz, edges, fixed, q, loads) for key, attr in mesh.vertices(True): index = k_i[key] attr['x'] = xyz[index, 0] attr['y'] = xyz[index, 1] attr['z'] = xyz[index, 2] # visualisae the result # color the vertices according to their elevation plotter = MeshPlotter(mesh) zmax = max(mesh.get_vertices_attribute('z')) plotter.draw_vertices( facecolor={key: i_to_black(attr['z'] / zmax) for key, attr in mesh.vertices(True)} ) plotter.draw_faces() plotter.draw_edges() plotter.show() """ v = len(vertices) free = list(set(range(v)) - set(fixed)) xyz = asarray(vertices, dtype=float).reshape((-1, 3)) q = asarray(q, dtype=float).reshape((-1, 1)) p = asarray(loads, dtype=float).reshape((-1, 3)) C = connectivity_matrix(edges, 'csr') Ci = C[:, free] Cf = C[:, fixed] Ct = C.transpose() Cit = Ci.transpose() Q = diags([q.flatten()], [0]) A = Cit.dot(Q).dot(Ci) b = p[free] - Cit.dot(Q).dot(Cf).dot(xyz[fixed]) xyz[free] = spsolve(A, b) l = normrow(C.dot(xyz)) f = q * l r = p - Ct.dot(Q).dot(C).dot(xyz) return xyz, q, f, l, r
def horizontal(form, force, alpha=100.0, kmax=100, display=False): r"""Compute horizontal equilibrium. Parameters ---------- form : compas_tna.diagrams.formdiagram.FormDiagram force : compas_tna.diagrams.forcediagram.ForceDiagram alpha : float Weighting factor for computation of the target vectors (the default is 100.0, which implies that the target vectors are the edges of the form diagram). If 0.0, the target vectors are the edges of the force diagram. kmax : int Maximum number of iterations (the default is 100). display : bool Display information about the current iteration (the default is False). Notes ----- This implementation is based on the following formulation .. math:: \mathbf{C}^{T} \mathbf{C} \mathbf{xy} = \mathbf{C}^{T} \mathbf{t} with :math:`\mathbf{C}` the connectivity matrix and :math:`\mathbf{t}` the target vectors. """ # -------------------------------------------------------------------------- # alpha == 1 : form diagram fixed # alpha == 0 : force diagram fixed # -------------------------------------------------------------------------- alpha = max(0., min(1., float(alpha) / 100.0)) # -------------------------------------------------------------------------- # form diagram # -------------------------------------------------------------------------- k_i = form.key_index() uv_i = form.uv_index() fixed = set(list(form.anchors()) + list(form.fixed())) fixed = [k_i[key] for key in fixed] edges = [[k_i[u], k_i[v]] for u, v in form.edges_where({'_is_edge': True})] xy = array(form.vertices_attributes('xy'), dtype=float64) lmin = array([ attr.get('lmin', 1e-7) for key, attr in form.edges_where({'_is_edge': True}, True) ], dtype=float64).reshape((-1, 1)) lmax = array([ attr.get('lmax', 1e+7) for key, attr in form.edges_where({'_is_edge': True}, True) ], dtype=float64).reshape((-1, 1)) fmin = array([ attr.get('fmin', 1e-7) for key, attr in form.edges_where({'_is_edge': True}, True) ], dtype=float64).reshape((-1, 1)) fmax = array([ attr.get('fmax', 1e+7) for key, attr in form.edges_where({'_is_edge': True}, True) ], dtype=float64).reshape((-1, 1)) C = connectivity_matrix(edges, 'csr') Ct = C.transpose() CtC = Ct.dot(C) # -------------------------------------------------------------------------- # force diagram # -------------------------------------------------------------------------- _k_i = force.key_index() _uv_i = force.uv_index(form=form) _fixed = list(force.fixed()) _fixed = [_k_i[key] for key in _fixed] _fixed = _fixed or [0] _edges = force.ordered_edges(form) _xy = array(force.vertices_attributes('xy'), dtype=float64) _C = connectivity_matrix(_edges, 'csr') _Ct = _C.transpose() _Ct_C = _Ct.dot(_C) # -------------------------------------------------------------------------- # rotate force diagram to make it parallel to the form diagram # use CCW direction (opposite of cycle direction) # -------------------------------------------------------------------------- _xy[:] = rot90(_xy, +1.0) # -------------------------------------------------------------------------- # make the diagrams parallel to a target vector # that is the (alpha) weighted average of the directions of corresponding # edges of the two diagrams # -------------------------------------------------------------------------- uv = C.dot(xy) _uv = _C.dot(_xy) l = normrow(uv) _l = normrow(_uv) t = alpha * normalizerow(uv) + (1 - alpha) * normalizerow(_uv) # parallelise # add the outer loop to the parallelise function for k in range(kmax): # apply length bounds apply_bounds(l, lmin, lmax) apply_bounds(_l, fmin, fmax) # print, if allowed if display: print(k) if alpha != 1.0: # if emphasis is not entirely on the form # update the form diagram xy = parallelise_sparse(CtC, Ct.dot(l * t), xy, fixed, 'CtC') uv = C.dot(xy) l = normrow(uv) if alpha != 0.0: # if emphasis is not entirely on the force # update the force diagram _xy = parallelise_sparse(_Ct_C, _Ct.dot(_l * t), _xy, _fixed, '_Ct_C') _uv = _C.dot(_xy) _l = normrow(_uv) # -------------------------------------------------------------------------- # compute the force densities # -------------------------------------------------------------------------- f = _l q = (f / l).astype(float64) # -------------------------------------------------------------------------- # rotate the force diagram 90 degrees in CW direction # this way the relation between the two diagrams is easier to read # -------------------------------------------------------------------------- _xy[:] = rot90(_xy, -1.0) # -------------------------------------------------------------------------- # angle deviations # note that this does not account for flipped edges! # -------------------------------------------------------------------------- a = [angle_vectors_xy(uv[i], _uv[i], deg=True) for i in range(len(edges))] # -------------------------------------------------------------------------- # update form # -------------------------------------------------------------------------- for key, attr in form.vertices(True): i = k_i[key] attr['x'] = xy[i, 0] attr['y'] = xy[i, 1] for (u, v), attr in form.edges_where({'_is_edge': True}, True): i = uv_i[(u, v)] attr['q'] = q[i, 0] attr['_f'] = f[i, 0] attr['_l'] = l[i, 0] attr['_a'] = a[i] # -------------------------------------------------------------------------- # update force # -------------------------------------------------------------------------- for key, attr in force.vertices(True): i = _k_i[key] attr['x'] = _xy[i, 0] attr['y'] = _xy[i, 1] for (u, v), attr in force.edges_where({'_is_edge': True}, True): i = _uv_i[(u, v)] attr['_l'] = _l[i, 0]
def horizontal_nodal(form, force, alpha=100, kmax=100, display=False): """Compute horizontal equilibrium using a node-per-node approach. Parameters ---------- form : compas_tna.diagrams.FormDiagram force : compas_tna.diagrams.ForceDiagram alpha : float Weighting factor for computation of the target vectors (the default is 100.0, which implies that the target vectors are the edges of the form diagram). If 0.0, the target vectors are the edges of the force diagram. kmax : int Maximum number of iterations (the default is 100). display : bool Display information about the current iteration (the default is False). """ alpha = float(alpha) / 100.0 alpha = max(0., min(1., alpha)) # -------------------------------------------------------------------------- # form diagram # -------------------------------------------------------------------------- k_i = form.key_index() uv_i = form.uv_index() i_nbrs = { k_i[key]: [k_i[nbr] for nbr in form.vertex_neighbors(key)] for key in form.vertices() } ij_e = {(k_i[u], k_i[v]): index for (u, v), index in iter(uv_i.items())} fixed = set(list(form.anchors()) + list(form.fixed())) fixed = [k_i[key] for key in fixed] edges = [[k_i[u], k_i[v]] for u, v in form.edges_where({'_is_edge': True})] lmin = array([ attr.get('lmin', 1e-7) for key, attr in form.edges_where({'_is_edge': True}, True) ], dtype=float64).reshape((-1, 1)) lmax = array([ attr.get('lmax', 1e+7) for key, attr in form.edges_where({'_is_edge': True}, True) ], dtype=float64).reshape((-1, 1)) fmin = array([ attr.get('fmin', 1e-7) for key, attr in form.edges_where({'_is_edge': True}, True) ], dtype=float64).reshape((-1, 1)) fmax = array([ attr.get('fmax', 1e+7) for key, attr in form.edges_where({'_is_edge': True}, True) ], dtype=float64).reshape((-1, 1)) flipmask = array([ 1.0 if not attr['_is_tension'] else -1.0 for key, attr in form.edges_where({'_is_edge': True}, True) ], dtype=float).reshape((-1, 1)) xy = array(form.vertices_attributes('xy'), dtype=float64) C = connectivity_matrix(edges, 'csr') # -------------------------------------------------------------------------- # force diagram # -------------------------------------------------------------------------- _k_i = force.key_index() _uv_i = force.uv_index(form=form) _i_nbrs = { _k_i[key]: [_k_i[nbr] for nbr in force.vertex_neighbors(key)] for key in force.vertices() } _ij_e = {(_k_i[u], _k_i[v]): index for (u, v), index in iter(_uv_i.items())} _fixed = list(force.fixed()) _fixed = [_k_i[key] for key in _fixed] _fixed = _fixed or [0] _edges = force.ordered_edges(form) _xy = array(force.vertices_attributes('xy'), dtype=float64) _C = connectivity_matrix(_edges, 'csr') # -------------------------------------------------------------------------- # rotate force diagram to make it parallel to the form diagram # use CCW direction (opposite of cycle direction) # -------------------------------------------------------------------------- _xy[:] = rot90(_xy, +1.0) # -------------------------------------------------------------------------- # make the diagrams parallel to a target vector # that is the (alpha) weighted average of the directions of corresponding # edges of the two diagrams # -------------------------------------------------------------------------- uv = flipmask * C.dot(xy) _uv = _C.dot(_xy) l = normrow(uv) _l = normrow(_uv) # -------------------------------------------------------------------------- # the target vectors # -------------------------------------------------------------------------- targets = alpha * normalizerow(uv) + (1 - alpha) * normalizerow(_uv) # -------------------------------------------------------------------------- # parallelise # -------------------------------------------------------------------------- if alpha < 1: parallelise_nodal(xy, C, targets, i_nbrs, ij_e, fixed=fixed, kmax=kmax, lmin=lmin, lmax=lmax) if alpha > 0: parallelise_nodal(_xy, _C, targets, _i_nbrs, _ij_e, kmax=kmax, lmin=fmin, lmax=fmax) # -------------------------------------------------------------------------- # update the coordinate difference vectors # -------------------------------------------------------------------------- uv = C.dot(xy) _uv = _C.dot(_xy) l = normrow(uv) _l = normrow(_uv) # -------------------------------------------------------------------------- # compute the force densities # -------------------------------------------------------------------------- f = flipmask * _l q = (f / l).astype(float64) # -------------------------------------------------------------------------- # rotate the force diagram 90 degrees in CW direction # this way the relation between the two diagrams is easier to read # -------------------------------------------------------------------------- _xy[:] = rot90(_xy, -1.0) # -------------------------------------------------------------------------- # angle deviations # note that this does not account for flipped edges! # -------------------------------------------------------------------------- a = [angle_vectors_xy(uv[i], _uv[i], deg=True) for i in range(len(edges))] # -------------------------------------------------------------------------- # update form # -------------------------------------------------------------------------- for key, attr in form.vertices(True): i = k_i[key] attr['x'] = xy[i, 0] attr['y'] = xy[i, 1] for (u, v), attr in form.edges_where({'_is_edge': True}, True): i = uv_i[(u, v)] attr['q'] = q[i, 0] attr['_f'] = f[i, 0] attr['_l'] = l[i, 0] attr['_a'] = a[i] # -------------------------------------------------------------------------- # update force # -------------------------------------------------------------------------- for key, attr in force.vertices(True): i = _k_i[key] attr['x'] = _xy[i, 0] attr['y'] = _xy[i, 1] for (u, v), attr in force.edges(True): if (u, v) in _uv_i: i = _uv_i[(u, v)] attr['_l'] = _l[i, 0] elif (v, u) in _uv_i: i = _uv_i[(v, u)] attr['_l'] = _l[i, 0]
def optimise_loadpath(form, force, algo='COBYLA'): """Optimise the loadpath using the parameters of the force domain. The parameters of the force domain are the coordinates of the vertices of the force diagram. Parameters ---------- form : FormDiagram The form diagram. force : ForceDiagram The force diagram. algo : {'COBYLA', L-BFGS-B', 'SLSQ', 'MMA', 'GMMA'}, optional The optimisation algorithm. Returns ------- form: :class:`FormDiagram` The optimised form diagram. force: :class:`ForceDiagram` The optimised force diagram. Notes ----- In many cases, the number of paramters of the force domain involved in generating new solutions in the form domain is smaller than when using the elements of the form diagram directly. For example, the loadpath of a bridge with a compression arch can be optimised using only the x-coordinates of the vertices of the force diagram corresponding to internal spaces formed by the segments of the arch, the corresponding deck elements, and the hangers connecting them. Any solution generated by these parameters will be in equilibrium and automatically have a horizontal bridge deck. Although the *BRG* algorithm is the preferred choice, since it is (should be) tailored to the problem of optimising loadpaths using the domain of the force diagram, it does not have a stable and/or efficient implementation. The main problem is the generation of the form diagram, based on a given force diagram. For example, when edge forces flip from tension to compression, and vice versa, parallelisation is no longer effective. """ vertex_index = form.vertex_index() edge_index = form.edge_index() i_j = {vertex_index[vertex]: [vertex_index[nbr] for nbr in form.vertex_neighbors(vertex)] for vertex in form.vertices()} ij_e = {(vertex_index[u], vertex_index[v]): edge_index[u, v] for u, v in edge_index} ij_e.update({(vertex_index[v], vertex_index[u]): edge_index[u, v] for u, v in edge_index}) xy = array(form.xy(), dtype=float64) edges = [(vertex_index[u], vertex_index[v]) for u, v in form.edges()] C = connectivity_matrix(edges, 'csr') leaves = [vertex_index[key] for key in form.leaves()] fixed = [vertex_index[key] for key in form.fixed()] free = list(set(range(form.number_of_vertices())) - set(fixed) - set(leaves)) internal = [i for i, (u, v) in enumerate(form.edges()) if vertex_index[u] not in leaves and vertex_index[v] not in leaves] _vertex_index = force.vertex_index() _edge_index = force.edge_index(form) _edge_index.update({(v, u): _edge_index[u, v] for u, v in _edge_index}) _xy = array(force.xy(), dtype=float64) _edges = force.ordered_edges(form) _edges[:] = [(_vertex_index[u], _vertex_index[v]) for u, v in _edges] _C = connectivity_matrix(_edges, 'csr') _free = [key for key, attr in force.vertices(True) if attr['is_param']] _free = [_vertex_index[key] for key in _free] def objfunc(_x): _xy[_free, 0] = _x update_primal_from_dual(xy, _xy, free, leaves, i_j, ij_e, _C) length = normrow(C.dot(xy)) force = normrow(_C.dot(_xy)) lp = length[internal].T.dot(force[internal])[0, 0] print(lp) return(lp) x0 = _xy[_free, 0] result = minimize(objfunc, x0, method=algo, tol=1e-12, options={'maxiter': 1000}) # noqa: F841 uv = C.dot(xy) _uv = _C.dot(_xy) angles = [angle_vectors_xy(a, b) for a, b in zip(uv, _uv)] lengths = normrow(uv) forces = normrow(_uv) q = forces / lengths for vertex, attr in form.vertices(True): index = vertex_index[vertex] attr['x'] = xy[index, 0] attr['y'] = xy[index, 1] for edge, attr in form.edges(True): index = edge_index[edge] attr['l'] = lengths[index, 0] attr['a'] = angles[index] if (angles[index] - 3.14159) ** 2 < 0.25 * 3.14159: attr['f'] = - forces[index, 0] attr['q'] = - q[index, 0] else: attr['f'] = forces[index, 0] attr['q'] = q[index, 0] for vertex, attr in force.vertices(True): index = _vertex_index[vertex] attr['x'] = _xy[index, 0] attr['y'] = _xy[index, 1] for edge, attr in force.edges(True): index = _edge_index[edge] attr['a'] = angles[index] attr['l'] = forces[index, 0] return form, force