Пример #1
0
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]
Пример #2
0
    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)
Пример #3
0
def _beam_shear(S, X, inds, indi, indf, EIx, EIy):
    """ Generate the beam nodal shear forces Sx, Sy and Sz.

    Parameters:
        S (array): Nodal shear force array.
        X (array): Co-ordinates of nodes.
        inds (list): Indices of beam element start nodes.
        indi (list): Indices of beam element intermediate nodes.
        indf (list): Indices of beam element finish nodes beams.
        EIx (array): Nodal EIx flexural stiffnesses.
        EIy (array): Nodal EIy flexural stiffnesses.

    Returns:
        array: Updated beam nodal shears.
    """
    S *= 0
    Xs = X[inds, :]
    Xi = X[indi, :]
    Xf = X[indf, :]
    Qa = Xi - Xs
    Qb = Xf - Xi
    Qc = Xf - Xs
    Qn = cross(Qa, Qb)
    mu = 0.5 * (Xf - Xs)
    La = normrow(Qa)
    Lb = normrow(Qb)
    Lc = normrow(Qc)
    LQn = normrow(Qn)
    Lmu = normrow(mu)
    a = arccos((La**2 + Lb**2 - Lc**2) / (2 * La * Lb))
    k = 2 * sin(a) / Lc
    ex = Qn / tile(LQn, (1, 3))  # temporary simplification
    ez = mu / tile(Lmu, (1, 3))
    ey = cross(ez, ex)
    K = tile(k / LQn, (1, 3)) * Qn
    Kx = tile(sum(K * ex, 1)[:, newaxis], (1, 3)) * ex
    Ky = tile(sum(K * ey, 1)[:, newaxis], (1, 3)) * ey
    Mc = EIx * Kx + EIy * Ky
    cma = cross(Mc, Qa)
    cmb = cross(Mc, Qb)
    ua = cma / tile(normrow(cma), (1, 3))
    ub = cmb / tile(normrow(cmb), (1, 3))
    c1 = cross(Qa, ua)
    c2 = cross(Qb, ub)
    Lc1 = normrow(c1)
    Lc2 = normrow(c2)
    Ms = sum(Mc**2, 1)[:, newaxis]
    Sa = ua * tile(Ms * Lc1 / (La * sum(Mc * c1, 1)[:, newaxis]), (1, 3))
    Sb = ub * tile(Ms * Lc2 / (Lb * sum(Mc * c2, 1)[:, newaxis]), (1, 3))
    Sa[isnan(Sa)] = 0
    Sb[isnan(Sb)] = 0
    S[inds, :] += Sa
    S[indi, :] -= Sa + Sb
    S[indf, :] += Sb
    # Add node junction duplication for when elements cross each other
    # mu[0, :] = -1.25*x[0, :] + 1.5*x[1, :] - 0.25*x[2, :]
    # mu[-1, :] = 0.25*x[-3, :] - 1.5*x[-2, :] + 1.25*x[-1, :]
    return S
Пример #4
0
    def objfunc(_x):
        _xy[_free, 0] = _x

        update_form_from_force(xy, _xy, free, leaves, i_j, ij_e, _C)

        l = normrow(C.dot(xy))
        _l = normrow(_C.dot(_xy))
        li = l[internal]
        _li = _l[internal]
        lp = li.T.dot(_li)[0, 0]

        print(lp)

        return (lp)
Пример #5
0
def _beam_shear(S, X, inds, indi, indf, EIx, EIy):

    S *= 0

    Xs = X[inds, :]
    Xi = X[indi, :]
    Xf = X[indf, :]
    Qa = Xi - Xs
    Qb = Xf - Xi
    Qc = Xf - Xs
    Qn = cross(Qa, Qb)
    mu = 0.5 * (Xf - Xs)

    La = normrow(Qa)
    Lb = normrow(Qb)
    Lc = normrow(Qc)
    LQn = normrow(Qn)
    Lmu = normrow(mu)
    a = arccos((La**2 + Lb**2 - Lc**2) / (2 * La * Lb))
    k = 2 * sin(a) / Lc

    ex = Qn / tile(LQn, (1, 3))  # temporary simplification
    ez = mu / tile(Lmu, (1, 3))
    ey = cross(ez, ex)

    K = tile(k / LQn, (1, 3)) * Qn
    Kx = tile(sum(K * ex, 1)[:, newaxis], (1, 3)) * ex
    Ky = tile(sum(K * ey, 1)[:, newaxis], (1, 3)) * ey
    Mc = EIx * Kx + EIy * Ky

    cma = cross(Mc, Qa)
    cmb = cross(Mc, Qb)
    ua = cma / tile(normrow(cma), (1, 3))
    ub = cmb / tile(normrow(cmb), (1, 3))
    c1 = cross(Qa, ua)
    c2 = cross(Qb, ub)
    Lc1 = normrow(c1)
    Lc2 = normrow(c2)
    Ms = sum(Mc**2, 1)[:, newaxis]

    Sa = ua * tile(Ms * Lc1 / (La * sum(Mc * c1, 1)[:, newaxis]), (1, 3))
    Sb = ub * tile(Ms * Lc2 / (Lb * sum(Mc * c2, 1)[:, newaxis]), (1, 3))
    Sa[isnan(Sa)] = 0
    Sb[isnan(Sb)] = 0
    S[inds, :] += Sa
    S[indi, :] -= Sa + Sb
    S[indf, :] += Sb

    # Add node junction duplication for when elements cross each other
    # mu[0, :] = -1.25*x[0, :] + 1.5*x[1, :] - 0.25*x[2, :]
    # mu[-1, :] = 0.25*x[-3, :] - 1.5*x[-2, :] + 1.25*x[-1, :]

    return S
Пример #6
0
def icp_numpy(source, target, tol=1e-3):
    """Align two point clouds using the Iterative Closest Point (ICP) method.

    Parameters
    ----------
    source : list of point
        The source data.
    target : list of point
        The target data.
    tol : float, optional
        Tolerance for finding matches.
        Default is ``1e-3``.

    Returns
    -------

        The transformed points

    Notes
    -----
    First we align the source with the target cloud using the frames resulting
    from a PCA of each of the clouds, simply by calculating a frame-to-frame transformation.

    This initial alignment is used to establish an initial correspondence between
    the points of the two clouds.

    Then we iteratively improve the alignment by computing successive "best-fit"
    transformations using SVD of the cross-covariance matrix of the two data sets.
    During this iterative process, we continuously update the correspondence
    between the point clouds by finding the closest point in the target to each
    of the source points.

    The algorithm terminates when the alignment error is below a specified tolerance.

    Examples
    --------
    >>>

    """
    A = asarray(source)
    B = asarray(target)

    origin, axes, _ = pca_numpy(A)
    A_frame = Frame(origin, axes[0], axes[1])

    origin, axes, _ = pca_numpy(B)
    B_frame = Frame(origin, axes[0], axes[1])

    X = Transformation.from_frame_to_frame(A_frame, B_frame)
    A = transform_points_numpy(A, X)

    for i in range(20):
        D = cdist(A, B, 'euclidean')
        closest = argmin(D, axis=1)
        if norm(normrow(A - B[closest])) < tol:
            break
        X = bestfit_transform(A, B[closest])
        A = transform_points_numpy(A, X)

    return A, X
Пример #7
0
def trimesh_vertexarea_matrix(mesh):
    """Compute the n x n diagonal matrix of per-vertex voronoi areas.

    Parameters
    ----------
    mesh : :class:`compas.datastructures.Mesh`
        The triangle mesh data structure.

    Returns
    -------
    sparse matrix
        The diagonal voronoi area matrix.

    Examples
    --------
    >>> from compas.datastructures import Mesh
    >>> mesh = Mesh.from_polygons([[[0, 0, 0], [1, 0, 0], [0, 1, 0]]])
    >>> A = trimesh_vertexarea_matrix(mesh)
    >>> A.diagonal().tolist()
    [0.1666, 0.1666, 0.1666]

    """
    key_index = mesh.key_index()
    xyz = asarray(mesh.vertices_attributes('xyz'), dtype=float)
    tris = asarray([[key_index[key] for key in mesh.face_vertices(fkey)] for fkey in mesh.faces()], dtype=int)
    e1 = xyz[tris[:, 1]] - xyz[tris[:, 0]]
    e2 = xyz[tris[:, 2]] - xyz[tris[:, 0]]
    n = cross(e1, e2)
    a = 0.5 * normrow(n).ravel()
    a3 = a / 3.0
    area = zeros(xyz.shape[0])
    for i in (0, 1, 2):
        b = bincount(tris[:, i], a3)
        area[:len(b)] += b
    return spdiags(area, 0, xyz.shape[0], xyz.shape[0])
Пример #8
0
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]
Пример #9
0
def trimesh_vertexarea_matrix(mesh):
    """Compute the n x n diagonal matrix of per-vertex voronoi areas.

    Parameters
    ----------
    mesh : compas.datastructures.Mesh
        The triangle mesh data structure.

    Returns
    -------
    sparse matrix
        The diagonal voronoi area matrix.

    Examples
    --------
    .. plot::
        :include-source:

        import compas

        from compas.datastructures import Mesh
        from compas.datastructures import mesh_quads_to_triangles
        from compas.datastructures import trimesh_vertexarea_matrix
        from compas_plotters import MeshPlotter

        mesh = Mesh.from_obj(compas.get('faces.obj'))

        mesh_quads_to_triangles(mesh)
        A = trimesh_vertexarea_matrix(mesh)
        area = A.diagonal().tolist()

        plotter = MeshPlotter(mesh, tight=True)

        plotter.draw_vertices(
            text={key: "{:.1f}".format(area[i]) for i, key in enumerate(mesh.vertices())},
            radius=0.2
        )
        plotter.draw_edges()
        plotter.draw_faces()
        plotter.show()

    """
    key_index = mesh.key_index()
    xyz = asarray(mesh.get_vertices_attributes('xyz'), dtype=float)
    tris = asarray([[key_index[key] for key in mesh.face_vertices(fkey)]
                    for fkey in mesh.faces()],
                   dtype=int)
    e1 = xyz[tris[:, 1]] - xyz[tris[:, 0]]
    e2 = xyz[tris[:, 2]] - xyz[tris[:, 0]]
    n = cross(e1, e2)
    a = 0.5 * normrow(n).ravel()
    a3 = a / 3.0
    area = zeros(xyz.shape[0])
    for i in (0, 1, 2):
        b = bincount(tris[:, i], a3)
        area[:len(b)] += b
    return spdiags(area, 0, xyz.shape[0], xyz.shape[0])
Пример #10
0
def _beam_shear(S, X, inds, indi, indf, EIx, EIy):

    S *= 0

    Xs = X[inds, :]
    Xi = X[indi, :]
    Xf = X[indf, :]
    Qa = Xi - Xs
    Qb = Xf - Xi
    Qc = Xf - Xs
    Qn = cross(Qa, Qb)
    mu = 0.5 * (Xf - Xs)

    La = normrow(Qa)
    Lb = normrow(Qb)
    Lc = normrow(Qc)
    LQn = normrow(Qn)
    Lmu = normrow(mu)
    a = arccos((La**2 + Lb**2 - Lc**2) / (2 * La * Lb))
    k = 2 * sin(a) / Lc

    ex = Qn / tile(LQn, (1, 3))
    ez = mu / tile(Lmu, (1, 3))
    ey = cross(ez, ex)

    K = tile(k / LQn, (1, 3)) * Qn
    Kx = tile(sum(K * ex, 1)[:, newaxis], (1, 3)) * ex
    Ky = tile(sum(K * ey, 1)[:, newaxis], (1, 3)) * ey
    Mc = EIx * Kx + EIy * Ky

    cma = cross(Mc, Qa)
    cmb = cross(Mc, Qb)
    ua = cma / tile(normrow(cma), (1, 3))
    ub = cmb / tile(normrow(cmb), (1, 3))
    c1 = cross(Qa, ua)
    c2 = cross(Qb, ub)
    Lc1 = normrow(c1)
    Lc2 = normrow(c2)
    Ms = sum(Mc**2, 1)[:, newaxis]

    Sa = ua * tile(Ms * Lc1 / (La * sum(Mc * c1, 1)[:, newaxis]), (1, 3))
    Sb = ub * tile(Ms * Lc2 / (Lb * sum(Mc * c2, 1)[:, newaxis]), (1, 3))
    Sa[isnan(Sa)] = 0
    Sb[isnan(Sb)] = 0
    S[inds, :] += Sa
    S[indi, :] -= Sa + Sb
    S[indf, :] += Sb

    return S
Пример #11
0
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]
Пример #12
0
def parallelise_nodal(xy,
                      C,
                      targets,
                      i_nbrs,
                      ij_e,
                      fixed=None,
                      kmax=100,
                      lmin=None,
                      lmax=None):
    fixed = fixed or []
    fixed = set(fixed)

    n = xy.shape[0]

    for k in range(kmax):
        xy0 = xy.copy()
        uv = C.dot(xy)
        l = normrow(uv)  # noqa: E741

        if lmin is not None and lmax is not None:
            apply_bounds(l, lmin, lmax)

        for j in range(n):
            if j in fixed:
                continue

            nbrs = i_nbrs[j]
            xy[j, :] = 0.0

            for i in nbrs:
                if (i, j) in ij_e:
                    e = ij_e[(i, j)]
                    t = targets[e]
                elif (j, i) in ij_e:
                    e = ij_e[(j, i)]
                    t = -targets[e]
                else:
                    continue

                xy[j] += xy0[i] + l[e, 0] * t

            # add damping factor?
            xy[j] /= len(nbrs)

        for (i, j) in ij_e:
            e = ij_e[(i, j)]

            if l[e, 0] == 0.0:
                a = xy[i]
                b = xy[j]
                c = 0.5 * (a + b)
                xy[i] = c[:]
                xy[j] = c[:]
Пример #13
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]
Пример #14
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
    --------
    >>>
    """
    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]])
Пример #15
0
def update_form_from_force(xy, _xy, free, leaves, i_nbrs, ij_e, _C, kmax=100):
    r"""Update the coordinates of a form diagram using the coordinates of the corresponding force diagram.

    Parameters
    ----------
    xy : array-like
        XY coordinates of the vertices of the form diagram.
    _xy : array-like
        XY coordinates of the vertices of the force diagram.
    free : list
        The free vertices of the form diagram.
    leaves : list
        The leaves of the form diagram.
    i_nbrs : list of list of int
        Vertex neighbours per vertex.
    ij_e : dict
        Edge index for every vertex pair.
    _C : sparse matrix in csr format
        The connectivity matrix of the force diagram.
    kmax : int, optional
        Maximum number of iterations.
        Default is ``100``.

    Returns
    -------
    None
        The vertex coordinates are modified in-place.

    Notes
    -----
    This function should be used to update the form diagram after modifying the
    geometry of the force diagram. The objective is to compute new locations
    for the vertices of the form diagram such that the corrsponding lines of the
    form and force diagram are parallel while any geometric constraints imposed on
    the form diagram are satisfied.

    The location of each vertex of the form diagram is computed as the intersection
    of the lines connected to it. Each of the connected lines is based at the connected
    neighbouring vertex and taken parallel to the corresponding line in the force
    diagram.

    For a point :math:`\mathbf{p}`, which is the least-squares intersection of *K*
    lines, with every line *j* defined by a point :math:`\mathbf{a}_{j}` on the line
    and a direction vector :math:`\mathbf{n}_{j}`, we can write

    .. math::

        \mathbf{R} \mathbf{p} = \mathbf{q}

    with

    .. math::

        \mathbf{R} = \displaystyle\sum_{j=1}^{K}(\mathbf{I} - \mathbf{n}_{j}\mathbf{n}_{j}^{T})
        \quad,\quad
        \mathbf{q} = \displaystyle\sum_{j=1}^{K}(\mathbf{I} - \mathbf{n}_{j}\mathbf{n}_{j}^{T})\mathbf{a}_{j}

    This system of linear equations can be solved using the normal equations

    .. math::

        \mathbf{p} = (\mathbf{R}^{T}\mathbf{R})^{-1}\mathbf{R}^{T}\mathbf{q}

    Examples
    --------
    >>>
    """
    _uv = _C.dot(_xy)
    _t = normalizerow(_uv)
    I = eye(2, dtype=float64)  # noqa: E741
    xy0 = array(xy, copy=True)
    A = zeros((2 * len(free), 2 * len(free)), dtype=float64)
    b = zeros((2 * len(free), 1), dtype=float64)

    # update the free vertices
    for k in range(kmax):
        row = 0

        # in order for the two diagrams to have parallel corresponding edges,
        # each free vertex location of the form diagram is computed as the intersection
        # of the connected lines. each of these lines is based at the corresponding
        # connected neighbouring vertex and taken parallel to the corresponding
        # edge in the force diagram.
        # the intersection is the point that minimises the distance to all connected
        # lines.
        for i in free:
            R = zeros((2, 2), dtype=float64)
            q = zeros((2, 1), dtype=float64)

            # add line constraints based on connected edges
            for j in i_nbrs[i]:
                if j in leaves:
                    continue

                n = _t[ij_e[(i, j)], None]  # the direction of the line (the line parallel to the corresponding line in the force diagram)
                _l = _uv[ij_e[(i, j)], None]

                if normrow(_l)[0, 0] < 0.001:
                    continue

                r = I - n.T.dot(n)          # projection into the orthogonal space of the direction vector
                a = xy[j, None]             # a point on the line (the neighbour of the vertex)
                R += r
                q += r.dot(a.T)

            A[row: row + 2, row: row + 2] = R
            b[row: row + 2] = q
            row += 2

            # p = solve(R.T.dot(R), R.T.dot(q))
            # xy[i] = p.reshape((-1, 2), order='C')

        # res = solve(A.T.dot(A), A.T.dot(b))
        # xy[free] = res.reshape((-1, 2), order='C')
        res = lstsq(A, b)
        xy[free] = res[0].reshape((-1, 2), order='C')

    # reconnect leaves
    for i in leaves:
        j = i_nbrs[i][0]
        xy[i] = xy[j] + xy0[i] - xy0[j]
Пример #16
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
Пример #17
0
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]
Пример #18
0
def fn(dofs, *args):
    network, Xt, tol, ds = args
    X = update(dofs=dofs, network=network, tol=tol, plot=False, Xt=Xt, ds=ds)
    ind = argmin(cdist(X, Xt), axis=1)
    return 1000 * mean(normrow(X - Xt[ind, :]))
Пример #19
0
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
Пример #20
0
def drx_solver(tol, steps, factor, C, Ct, X, ks, l0, f0, ind_c, ind_t, P, S, B,
               M, V, refresh, beams, inds, indi, indf, EIx, EIy, callback,
               **kwargs):
    """ NumPy and SciPy dynamic relaxation solver.

    Parameters:
        tol (float): Tolerance limit.
        steps (int): Maximum number of steps.
        factor (float): Convergence factor.
        C (array): Connectivity matrix.
        Ct (array): Transposed connectivity matrix.
        X (array): Nodal co-ordinates.
        ks (array): Initial edge axial stiffnesses.
        l0 (array): Initial edge lengths.
        f0 (array): Initial edge forces.
        ind_c (list): Indices of compression only edges.
        ind_t (list): Indices of tension only edges.
        P (array): Nodal loads Px, Py, Pz.
        S (array): Shear forces Sx, Sy, Sz.
        B (array): Constraint conditions.
        M (array): Mass matrix.
        V (array): Nodal velocities Vx, Vy, Vz.
        refresh (int): Update progress every n steps.
        beams (bool): Dictionary of beam information.
        inds (list): Indices of beam element start nodes.
        indi (list): Indices of beam element intermediate nodes.
        indf (list): Indices of beam element finish nodes beams.
        EIx (array): Nodal EIx flexural stiffnesses.
        EIy (array): Nodal EIy flexural stiffnesses.
        callback (obj): Callback function.

    Returns:
        array: Updated nodal co-ordinates.
        array: Updated forces.
        array: Updated lengths.
    """
    res = 1000 * tol
    ts, Uo = 0, 0
    M = factor * tile(M, (1, 3))
    while (ts <= steps) and (res > tol):
        uvw, l = uvw_lengths(C, X)
        f = f0 + ks * (l - l0)
        if ind_t:
            f[ind_t] *= f[ind_t] > 0
        if ind_c:
            f[ind_c] *= f[ind_c] < 0
        if beams:
            S = _beam_shear(S, X, inds, indi, indf, EIx, EIy)
        q = f / l
        qt = tile(q, (1, 3))
        R = (P - S - Ct.dot(uvw * qt)) * B
        res = mean(normrow(R))
        V += R / M
        Un = sum(M * V**2)
        if Un < Uo:
            V *= 0
        Uo = Un
        X += V
        if refresh:
            if (ts % refresh == 0) or (res < tol):
                print('Step:{0} Residual:{1:.3g}'.format(ts, res))
                if callback:
                    callback(X, **kwargs)
        ts += 1
    return X, f, l
Пример #21
0
def mesh_geodesic_distances_numpy(mesh, sources, m=1.0):
    """Compute geodesic from the vertices of a mesh to given source vertices.

    Parameters
    ----------
    mesh : compas.datastructures.Mesh
        A mesh instance.
    sources : list
        A list of vertex identifiers from which the distances should be calculated.
    m : float (1.0)
        ?

    Returns
    -------
    array
        Distance values.

    """
    Lc = trimesh_cotangent_laplacian_matrix(mesh)

    key_index = mesh.key_index()
    vertices = mesh.vertices_attributes('xyz')
    faces = [[key_index[key] for key in mesh.face_vertices(fkey)]
             for fkey in mesh.faces()]

    V = array(vertices)
    F = array(faces, dtype=int)

    e01 = V[F[:, 1]] - V[F[:, 0]]
    e12 = V[F[:, 2]] - V[F[:, 1]]
    e20 = V[F[:, 0]] - V[F[:, 2]]

    normal = cross(e01, e12)
    A2 = normrow(normal)
    A3 = A2.ravel() / 6

    VA = zeros(V.shape[0])
    for i in (0, 1, 2):
        b = bincount(F[:, i], A3)
        VA[:len(b)] += b
    VA = spdiags(VA, 0, V.shape[0], V.shape[0])

    h = mean([normrow(e01), normrow(e12), normrow(e20)])
    t = m * h**2

    u0 = zeros(V.shape[0])
    u0[sources] = 1.0

    # A = VA - t * Lc
    # print(A.sum(axis=1))

    u = splu((VA - t * Lc).tocsc()).solve(u0)

    unit = normal / A2

    unit_e01 = cross(unit, e01)
    unit_e12 = cross(unit, e12)
    unit_e20 = cross(unit, e20)

    grad_u = (unit_e01 * u[F[:, 2], None] + unit_e12 * u[F[:, 0], None] +
              unit_e20 * u[F[:, 1], None]) / A2

    X = -grad_u / normrow(grad_u)

    div_X = zeros(V.shape[0])

    for i1, i2, i3 in [(0, 1, 2), (1, 2, 0), (2, 0, 1)]:
        v1 = F[:, i1]
        v2 = F[:, i2]
        v3 = F[:, i3]

        e1 = V[v2] - V[v1]
        e2 = V[v3] - V[v1]
        e0 = V[v3] - V[v2]

        a = 1 / tan(arccos(sum(normalizerow(-e2) * normalizerow(-e0), axis=1)))
        b = 1 / tan(arccos(sum(normalizerow(-e1) * normalizerow(+e0), axis=1)))

        div_X += bincount(v1,
                          0.5 *
                          (a * sum(e1 * X, axis=1) + b * sum(e2 * X, axis=1)),
                          minlength=V.shape[0])

    # print(Lc.sum(axis=1))

    phi = splu(Lc.tocsc()).solve(div_X)
    phi -= phi.min()

    return phi
Пример #22
0
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
Пример #23
0
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]
Пример #24
0
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
Пример #25
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.

    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
Пример #26
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
Пример #27
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
Пример #28
0
def mesh_geodesic_distances(mesh, sources, m=1.0):
    Lc = trimesh_cotangent_laplacian_matrix(mesh)

    key_index = mesh.key_index()
    vertices = mesh.get_vertices_attributes('xyz')
    faces = [[key_index[key] for key in mesh.face_vertices(fkey)] for fkey in mesh.faces()]

    V = array(vertices)
    F = array(faces, dtype=int)

    # Laplacian matrix with symmetric cotangent weights

    # W = empty(0)
    # I = empty(0, dtype=int)
    # J = empty(0, dtype=int)

    # for i1, i2, i3 in [(0, 1, 2), (1, 2, 0), (2, 0, 1)]:
    #     v1 = F[:, i1]
    #     v2 = F[:, i2]
    #     v3 = F[:, i3]

    #     e1 = V[v2] - V[v1]
    #     e2 = V[v3] - V[v1]

    #     cotan = 0.5 * sum(e1 * e2, axis=1) / normrow(cross(e1, e2)).ravel()

    #     W = append(W, cotan)
    #     I = append(I, v2)
    #     J = append(J, v3)

    #     W = append(W, cotan)
    #     I = append(I, v3)
    #     J = append(J, v2)

    # Lc = csr_matrix((W, (I, J)), shape=(V.shape[0], V.shape[0]))
    # Lc = Lc - spdiags(Lc * ones(V.shape[0]), 0, V.shape[0], V.shape[0])

    # Step I
    # Heat *u* is allowed to diffuse for a brief period of time (t)

    e01 = V[F[:, 1]] - V[F[:, 0]]
    e12 = V[F[:, 2]] - V[F[:, 1]]
    e20 = V[F[:, 0]] - V[F[:, 2]]

    normal = cross(e01, e12)
    A2 = normrow(normal)

    A3 = A2.ravel() / 6

    VA = zeros(V.shape[0])
    for i in (0, 1, 2):
        b = bincount(F[:, i], A3)
        VA[:len(b)] += b
    VA = spdiags(VA, 0, V.shape[0], V.shape[0])

    h = mean([normrow(e01), normrow(e12), normrow(e20)])
    t = m * h ** 2

    u0 = zeros(V.shape[0])
    u0[sources] = 1.0

    A = VA - t * Lc

    print(A.sum(axis=1))

    u = splu((VA - t * Lc).tocsc()).solve(u0)
    # u = spsolve(VA - t * Lc, u0)

    # A = VA - t * Lc
    # b = u0
    # u = spsolve(A.transpose().dot(A), A.transpose().dot(b))

    unit = normal / A2

    unit_e01 = cross(unit, e01)
    unit_e12 = cross(unit, e12)
    unit_e20 = cross(unit, e20)

    grad_u = (
        unit_e01 * u[F[:, 2], None] +
        unit_e12 * u[F[:, 0], None] +
        unit_e20 * u[F[:, 1], None]
    ) / A2

    X = - grad_u / normrow(grad_u)

    div_X = zeros(V.shape[0])

    for i1, i2, i3 in [(0, 1, 2), (1, 2, 0), (2, 0, 1)]:
        v1 = F[:, i1]
        v2 = F[:, i2]
        v3 = F[:, i3]

        e1 = V[v2] - V[v1]
        e2 = V[v3] - V[v1]
        e0 = V[v3] - V[v2]

        a = 1 / tan(arccos(sum(normalizerow(-e2) * normalizerow(-e0), axis=1)))
        b = 1 / tan(arccos(sum(normalizerow(-e1) * normalizerow(+e0), axis=1)))

        div_X += bincount(
            v1,
            0.5 * (a * sum(e1 * X, axis=1) + b * sum(e2 * X, axis=1)),
            minlength=V.shape[0]
        )

    print(Lc.sum(axis=1))

    phi = splu(Lc.tocsc()).solve(div_X)
    # phi = spsolve(Lc, div_X)

    # A = Lc
    # b = div_X
    # phi = spsolve(A.transpose().dot(A), A.transpose().dot(b))

    phi -= phi.min()

    return phi
Пример #29
0
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]
Пример #30
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]