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 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 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 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 form_update_from_force_direct(form, force): r"""Update the form diagram after a modification of the force diagram. Compute the geometry of the form diagram from the geometry of the force diagram and some constraints (location of fixed points). The form diagram is computed by formulating the reciprocal relationships to the approach in described in AGS. In order to include the constraints, the reciprocal force densities and form diagram coordinates are solved for at the same time, by formulating the equation system: .. math:: \mathbf{M}\mathbf{X} = \mathbf{r} with :math:`\mathbf{M}` containing the coefficients of the system of equations including constraints, :math:`\mathbf{X}` the coordinates of the vertices of the form diagram and the reciprocal force densities, in *Fortran* order (first all :math:`\mathbf{x}`-coordinates, then all :math:`\mathbf{y}`-coordinates, then all reciprocal force densities, :math:`\mathbf{q}^{-1}`), and :math:`\mathbf{r}` contains the residual (all zeroes except for the constraint rows). The addition of constraints reduces the number of independent edges, which must be identified during the solving procedure. Additionally, the algorithm fails if any force density is zero (corresponding to a zero-length edge in the force diagram) or if it is over-constrained. Parameters ---------- form : compas_ags.diagrams.formdiagram.FormDiagram The form diagram to update. force : compas_bi_ags.diagrams.forcediagram.ForceDiagram The force diagram on which the update is based. """ # -------------------------------------------------------------------------- # form diagram # -------------------------------------------------------------------------- k_i = form.key_index() # i_j = {i: [k_i[n] for n in form.vertex_neighbours(k)] for i, k in enumerate(form.vertices())} uv_e = form.uv_index() ij_e = {(k_i[u], k_i[v]): uv_e[(u, v)] for u, v in uv_e} edges = [(k_i[u], k_i[v]) for u, v in form.edges()] C = connectivity_matrix(edges, 'array') # add opposite edges for convenience... ij_e.update({(k_i[v], k_i[u]): uv_e[(u, v)] for u, v in uv_e}) 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)) # -------------------------------------------------------------------------- # force diagram # -------------------------------------------------------------------------- _i_k = {index: key for index, key in enumerate(force.vertices())} _xy = array(force.xy(), dtype=float64) _edges = force.ordered_edges(form) _uv_e = {(_i_k[i], _i_k[j]): e for e, (i, j) in enumerate(_edges)} _vcount = force.number_of_vertices() _C = connectivity_matrix(_edges, 'array') _e_v = force.external_vertices(form) _free = list(set(range(_vcount)) - set(_e_v)) # -------------------------------------------------------------------------- # compute the coordinates of the form based on the force diagram # with linear constraints # -------------------------------------------------------------------------- # Compute dual equilibrium matrix and Laplacian matrix import numpy as np _E = equilibrium_matrix(_C, _xy, _free, 'array') L = laplacian_matrix(edges, normalize=False, rtype='array') # Get dual coordinate difference vectors _uv = _C.dot(_xy) _U = np.diag(_uv[:, 0]) _V = np.diag(_uv[:, 1]) # Get reciprocal force densities from compas_bi_ags.utilities.errorhandler import SolutionError if any(abs(q) < 1e-14): raise SolutionError( 'Found zero force density, direct solution not possible.') q = np.divide(1, q) # Formulate the equation system z = np.zeros(L.shape) z2 = np.zeros((_E.shape[0], L.shape[1])) M = np.bmat([[L, z, -C.T.dot(_U)], [z, L, -C.T.dot(_V)], [z2, z2, _E]]) rhs = np.zeros((M.shape[0], 1)) X = np.vstack((matrix(xy)[:, 0], matrix(xy)[:, 1], matrix(q)[:, 0])) # Add constraints constraint_rows, res = force.compute_constraints(form, M) M = np.vstack((M, constraint_rows)) rhs = np.vstack((rhs, res)) # Get independent variables from compas_bi_ags.utilities.helpers import get_independent_stress, check_solutions nr_free_vars, free_vars, dependent_vars = get_independent_stress(M) #k, m = dof(M) #ind = nonpivots(rref(M)) # Partition system Mid = M[:, free_vars] Md = M[:, dependent_vars] Xid = X[free_vars] # Check that solution exists check_solutions(M, rhs) # Solve Xd = np.asarray(np.linalg.lstsq(Md, rhs - Mid * Xid)[0]) X[dependent_vars] = Xd # Store solution nx = xy.shape[0] ny = xy.shape[0] xy[:, 0] = X[:nx].T xy[:, 1] = X[nx:(nx + ny)].T # -------------------------------------------------------------------------- # update # -------------------------------------------------------------------------- uv = C.dot(xy) _uv = _C.dot(_xy) a = [angle_vectors_xy(uv[i], _uv[i]) for i in range(len(edges))] l = normrow(uv) _l = normrow(_uv) q = _l / l # -------------------------------------------------------------------------- # update form diagram # -------------------------------------------------------------------------- for key, attr in form.vertices(True): index = k_i[key] attr['x'] = xy[index, 0] attr['y'] = xy[index, 1] for u, v, attr in form.edges(True): e = uv_e[(u, v)] attr['l'] = l[e, 0] attr['a'] = a[e] if a[e] < 90: attr['f'] = _l[e, 0] attr['q'] = q[e, 0] else: attr['f'] = -_l[e, 0] attr['q'] = -q[e, 0] # -------------------------------------------------------------------------- # update force diagram # -------------------------------------------------------------------------- for u, v, attr in force.edges(True): e = _uv_e[(u, v)] attr['a'] = a[e] attr['l'] = _l[e, 0]
def optimise_single(form, solver='devo', polish='slsqp', qmin=1e-6, qmax=5, population=300, generations=500, printout=10, tol=0.001, plot=False, frange=[], indset=None, tension=False, planar=False): """ Finds the optimised load-path for a FormDiagram. Parameters ---------- form : obj The FormDiagram. solver : str Differential Evolution 'devo' or Genetic Algorithm 'ga' evolutionary solver to use. polish : str 'slsqp' polish or None. qmin : float Minimum qid value. qmax : float Maximum qid value. population : int Number of agents for the evolution solver. generations : int Number of generations for the evolution solver. printout : int Frequency of print output to the terminal. tol : float Tolerance on horizontal force balance. plot : bool Plot progress of the evolution. frange : list Minimum and maximum function value to plot. indset : list Independent set to use. tension : bool Allow tension edge force densities (experimental). planar : bool Only consider the x-y plane. Returns ------- float Optimum load-path value. list Optimum qids """ if printout: print('\n' + '-' * 50) print('Load-path optimisation started') print('-' * 50) # Mapping k_i = form.key_index() i_k = form.index_key() i_uv = form.index_uv() uv_i = form.uv_index() # Vertices and edges n = form.number_of_vertices() m = form.number_of_edges() fixed = [k_i[key] for key in form.fixed()] rol = [k_i[key] for key in form.vertices_where({'is_roller': True})] edges = [(k_i[u], k_i[v]) for u, v in form.edges()] sym = [uv_i[uv] for uv in form.edges_where({'is_symmetry': True})] free = list(set(range(n)) - set(fixed) - set(rol)) # Co-ordinates and loads xyz = zeros((n, 3)) x = zeros((n, 1)) y = zeros((n, 1)) z = zeros((n, 1)) px = zeros((n, 1)) py = zeros((n, 1)) pz = zeros((n, 1)) for key, vertex in form.vertex.items(): i = k_i[key] xyz[i, :] = form.vertex_coordinates(key) x[i] = vertex.get('x') y[i] = vertex.get('y') px[i] = vertex.get('px', 0) py[i] = vertex.get('py', 0) pz[i] = vertex.get('pz', 0) xy = xyz[:, :2] px = px[free] py = py[free] pz = pz[free] # C and E matrices C = connectivity_matrix(edges, 'csr') Ci = C[:, free] Cf = C[:, fixed] Cit = Ci.transpose() E = equilibrium_matrix(C, xy, free, 'csr').toarray() uvw = C.dot(xyz) U = uvw[:, 0] V = uvw[:, 1] # Independent and dependent branches if indset: ind = [] for u, v in form.edges(): if geometric_key(form.edge_midpoint(u, v)[:2] + [0]) in indset: ind.append(uv_i[(u, v)]) else: ind = nonpivots(sympy.Matrix(E).rref()[0].tolist()) k = len(ind) dep = list(set(range(m)) - set(ind)) for u, v in form.edges(): form.set_edge_attribute((u, v), 'is_ind', True if uv_i[(u, v)] in ind else False) if printout: _, s, _ = svd(E) print('Form diagram has {0} (RREF): {1} (SVD) independent branches '. format(len(ind), m - len(s))) # Set-up lh = normrow(C.dot(xy))**2 Edinv = -csr_matrix(pinv(E[:, dep])) Ei = E[:, ind] p = vstack([px, py]) q = array([attr['q'] for u, v, attr in form.edges(True)])[:, newaxis] bounds = [[qmin, qmax]] * k args = (q, ind, dep, Edinv, Ei, C, Ci, Cit, U, V, p, px, py, pz, tol, z, free, planar, lh, sym, tension) # Horizontal checks checked = True if tol == 0: for i in range(10**3): q[ind, 0] = rand(k) * qmax q[dep] = -Edinv.dot(p - Ei.dot(q[ind])) Rx = Cit.dot(U * q.ravel()) - px.ravel() Ry = Cit.dot(V * q.ravel()) - py.ravel() R = max(sqrt(Rx**2 + Ry**2)) if R > tol: checked = False break if checked: # Solve if solver == 'devo': fopt, qopt = _diff_evo(_fint, bounds, population, generations, printout, plot, frange, args) if polish == 'slsqp': fopt_, qopt_ = _slsqp(_fint_, qopt, bounds, printout, _fieq, args) q1 = _zlq_from_qid(qopt_, args)[2] if fopt_ < fopt: if (min(q1) > -0.001 and not tension) or tension: fopt, qopt = fopt_, qopt_ z, _, q, q_ = _zlq_from_qid(qopt, args) # Unique key gkeys = [] for i in ind: u, v = i_uv[i] gkeys.append(geometric_key(form.edge_midpoint(u, v)[:2] + [0])) form.attributes['indset'] = gkeys # Update FormDiagram for i in range(n): key = i_k[i] form.set_vertex_attribute(key=key, name='z', value=float(z[i])) for c, qi in enumerate(list(q_.ravel())): u, v = i_uv[c] form.set_edge_attribute((u, v), 'q', float(qi)) # Relax q = array([attr['q'] for u, v, attr in form.edges(True)]) Q = diags(q) CitQ = Cit.dot(Q) Di = CitQ.dot(Ci) Df = CitQ.dot(Cf) bx = px - Df.dot(x[fixed]) by = py - Df.dot(y[fixed]) # bz = pz - Df.dot(z[fixed]) x[free, 0] = spsolve(Di, bx) y[free, 0] = spsolve(Di, by) # z[free, 0] = spsolve(Di, bz) for i in range(n): form.set_vertex_attributes( key=i_k[i], names='xyz', values=[float(j) for j in [x[i], y[i], z[i]]]) fopt = 0 for u, v in form.edges(): if form.get_edge_attribute((u, v), 'is_symmetry') is False: qi = form.get_edge_attribute((u, v), 'q') li = form.edge_length(u, v) fopt += abs(qi) * li**2 form.attributes['loadpath'] = fopt if printout: print('\n' + '-' * 50) print('qid range : {0:.3f} : {1:.3f}'.format(min(qopt), max(qopt))) print('q range : {0:.3f} : {1:.3f}'.format(min(q), max(q))) print('fopt : {0:.3f}'.format(fopt)) print('-' * 50 + '\n') return fopt, qopt else: if printout: print('Horizontal equillibrium checks failed') return None, None
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 : compas_ags.diagrams.formdiagram.FormDiagram The form diagram. force : compas_bi_ags.diagrams.forcediagram.ForceDiagram The force diagram. Returns ------- jacobian Jacobian matrix (2 * _vcount, 2 * vcount) """ # Update force diagram based on form form_update_q_from_qind(form) force_update_from_form(force, form) # -------------------------------------------------------------------------- # 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 = np.asmatrix(uv[:, 0]).transpose() v = np.asmatrix(uv[:, 1]).transpose() Ct = C.transpose() Cti = Ct[free, :] q = array(form.q(), dtype=float64).reshape((-1, 1)) Q = np.diag(np.asmatrix(q).getA1()) Q = np.asmatrix(Q) independent_edges = 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] # -------------------------------------------------------------------------- # force diagram # -------------------------------------------------------------------------- _vcount = force.number_of_vertices() _edges = force.ordered_edges(form) _L = laplacian_matrix(_edges, normalize=False, rtype='array') _C = connectivity_matrix(_edges, 'array') _Ct = _C.transpose() _Ct = np.asmatrix(_Ct) _k_i = force.key_index() _known = [_k_i[force.anchor()]] # -------------------------------------------------------------------------- # Jacobian # -------------------------------------------------------------------------- jacobian = np.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 = np.diag(Ct[i, :]) dxdxi = np.transpose(np.asmatrix(Ct[i, :])) dEdXi = np.zeros((vicount * 2, ecount)) dEdXi[idx, :] = np.asmatrix(Cti) * np.asmatrix( dXdxi) # Always half the matrix 0 depending on j (x/y) dEdXi_d = dEdXi[:, dependent_edges_idx] dEdXi_id = dEdXi[:, independent_edges_idx] EdInv = np.linalg.inv(np.asmatrix(Ed)) dEdXiInv = -EdInv * (np.asmatrix(dEdXi_d) * EdInv) dqdXi_d = -dEdXiInv * (Eid * np.asmatrix(qid)) - EdInv * ( dEdXi_id * np.asmatrix(qid)) dqdXi = np.zeros((ecount, 1)) dqdXi[dependent_edges_idx] = dqdXi_d dqdXi[independent_edges_idx] = 0 dQdXi = np.asmatrix(np.diag(dqdXi[:, 0])) d_XdXiTop = np.zeros((_L.shape[0])) d_XdXiBot = np.zeros((_L.shape[0])) if j == 0: d_XdXiTop = solve_with_known( _L, np.array(_Ct * (dQdXi * u + Q * dxdxi)).flatten(), d_XdXiTop, _known) d_XdXiBot = solve_with_known( _L, np.array(_Ct * (dQdXi * v)).flatten(), d_XdXiBot, _known) elif j == 1: d_XdXiTop = solve_with_known( _L, np.array(_Ct * (dQdXi * u)).flatten(), d_XdXiTop, _known) d_XdXiBot = solve_with_known( _L, np.array(_Ct * (dQdXi * v + Q * dxdxi)).flatten(), d_XdXiBot, _known) d_XdXi = np.hstack((d_XdXiTop, d_XdXiBot)) jacobian[:, i + j * vcount] = d_XdXi return jacobian