예제 #1
0
 def update_angle_deviations(self):
     """Compute the angle deviation with the corresponding edge in the FormDiagram.
     """
     for edge in self.edges():
         edge_ = self.primal_edge(edge)
         uv = self.edge_vector(*edge)
         uv_ = self.primal.edge_vector(*edge_)
         a = angle_vectors_xy(uv, cross_vectors(uv_, (0, 0, 1)), deg=True)
         self.edge_attribute(edge, '_a', a)
         self.primal.edge_attribute(edge_, '_a', a)
예제 #2
0
def check_deviations(form, force, tol=10e-3):
    """Checks whether the form and force diagrams are indeed reciprocal, i.e. have their corresponding edges parallel.

    Parameters
    ----------
    form: compas_ags.diagrams.FormDiagram
        The form diagram to check deviations.
    force: compas_ags.diagrams.ForceDiagram
        The force diagram to check deviations.
    tol: float (10e-3)
        The tolerance allowd for the deviations.

    Returns
    -------
    checked : bool
        Return whether of not the diagram passes the check with no deviations greater than the tolerance.

    """

    edges_form = list(form.edges())
    edges_force = force.ordered_edges(form)
    checked = True

    for i in range(len(edges_form)):
        pt0, pt1 = form.edge_coordinates(edges_form[i][0], edges_form[i][1])
        _pt0, _pt1 = force.edge_coordinates(edges_force[i][0],
                                            edges_force[i][1])
        a = angle_vectors_xy(subtract_vectors(pt1, pt0),
                             subtract_vectors(_pt1, _pt0),
                             deg=True)
        if a < tol or a > 180 - tol:
            a = 0.0
        else:
            checked = False
            a = min(a, 180 - a)
        form.edge_attribute(edges_form[i], 'a', a)

    return checked
예제 #3
0
파일: planarity.py 프로젝트: tclim/compas
def network_embed_in_plane(network, fix=None, straightline=True):
    """Embed the network in the plane.

    Parameters
    ----------
    network : Network
        A network object.
    fix : list (None)
        Two fixed points.
    straightline : bool (True)
        Embed using straight lines.

    Returns
    -------
    bool
        True if the embedding was successful.
        False otherwise.

    Raises
    ------
    ImportError
        If NetworkX is not installed.

    Warning
    -------
    This function uses the Python package NetworkX. NetworkX can be *pip installed*.

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

        import compas

        from compas.datastructures import Network
        from compas.topology import network_embed_in_plane
        from compas.plotters import NetworkPlotter

        network = Network.from_obj(compas.get('fink.obj'))

        embedding = network.copy()

        fix = (1, 12)

        if network_embed_in_plane(embedding, fix=fix):

            plotter = NetworkPlotter(embedding)

            plotter.draw_lines([{'start': network.vertex_coordinates(u, 'xy'),
                                  'end': network.vertex_coordinates(v, 'xy'),
                                  'color': '#cccccc'} for u, v in network.edges()])

            plotter.draw_vertices(radius=0.3,
                                  text={key: key for key in embedding.vertices()},
                                  facecolor={key: '#ff0000' for key in fix})

            plotter.draw_edges()
            plotter.show()

    """
    try:
        import networkx as nx
    except ImportError:
        print(
            "NetworkX is not installed. Get NetworkX at https://networkx.github.io/."
        )
        raise

    x = network.get_vertices_attribute('x')
    y = network.get_vertices_attribute('y')
    xmin, xmax = min(x), max(x)
    ymin, ymax = min(y), max(y)
    xspan = xmax - xmin
    yspan = ymax - ymin

    edges = [(u, v) for u, v in network.edges()
             if not network.is_vertex_leaf(u) and not network.is_vertex_leaf(v)
             ]

    is_embedded = False

    count = 100
    while count:
        graph = nx.Graph(edges)
        pos = nx.spring_layout(graph,
                               2,
                               iterations=100,
                               scale=max(xspan, yspan))
        if not _network_are_edges_crossed(edges, pos):
            is_embedded = True
            break
        count -= 1

    if not is_embedded:
        return False

    if fix:
        a, b = fix
        p0 = network.vertex_coordinates(a, 'xy')
        p1 = network.vertex_coordinates(b, 'xy')
        p2 = pos[b]
        vec0 = [
            network.vertex[b][axis] - network.vertex[a][axis] for axis in 'xy'
        ]
        vec1 = [pos[b][axis] - pos[a][axis] for axis in (0, 1)]
        # rotate
        a = angle_vectors_xy(vec0, vec1)
        if is_ccw_xy(p0, p1, p2):
            a = 2 * pi - a
        cosa = cos(a)
        sina = sin(a)
        for key in pos:
            x, y = pos[key]
            pos[key][0] = cosa * x - sina * y
            pos[key][1] = sina * x + cosa * y
        # scale
        l0 = (vec0[0]**2 + vec0[1]**2)**0.5
        l1 = (vec1[0]**2 + vec1[1]**2)**0.5
        scale = l0 / l1
        for key in pos:
            pos[key][0] *= scale
            pos[key][1] *= scale
        # translate
        t = network.vertex[fix[0]]['x'] - pos[fix[0]][0], network.vertex[
            fix[0]]['y'] - pos[fix[0]][1]
        for key in pos:
            pos[key][0] += t[0]
            pos[key][1] += t[1]

    # update network vertex coordinates
    for key in network.vertices():
        if key in pos:
            network.vertex[key]['x'] = pos[key][0]
            network.vertex[key]['y'] = pos[key][1]

    return True
예제 #4
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]
예제 #5
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]
예제 #6
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
예제 #7
0
def form_update_from_force(form, force, kmax=100):
    r"""Update the form diagram after a modification of the force diagram.

    Parameters
    ----------
    form : FormDiagram
        The form diagram to update.
    force : ForceDiagram
        The force diagram on which the update is based.

    Returns
    -------
    None
        The form and force diagram are updated in-place.

    Notes
    -----
    Compute the geometry of the form diagram from the geometry of the form diagram
    and some constraints (location of fixed points).
    Since both diagrams are reciprocal, the coordinates of each vertex of the form
    diagram can be expressed as the intersection of three or more lines parallel
    to the corresponding edges of the force diagram.
    Essentially, this boils down to solving the following problem:

    .. math::

        \mathbf{A}\mathbf{x} = \mathbf{b}

    with :math:`\mathbf{A}` the coefficients of the x and y-coordinate of the  ,
    :math:`\mathbf{x}` the coordinates of the vertices of the form diagram,
    in *Fortran* order (first all x-coordinates, then all y-coordinates),
    and  :math:`\mathbf{b}` ....

    Examples
    --------
    >>>
    """
    # --------------------------------------------------------------------------
    # form diagram
    # --------------------------------------------------------------------------
    vertex_index = form.vertex_index()
    edge_index = form.edge_index()
    i_j = {
        index: [vertex_index[nbr] for nbr in form.vertex_neighbors(vertex)]
        for index, vertex in enumerate(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')
    # --------------------------------------------------------------------------
    # constraints
    # --------------------------------------------------------------------------
    leaves = [vertex_index[vertex] for vertex in form.leaves()]
    fixed = [vertex_index[vertex] for vertex in form.fixed()]
    free = list(
        set(range(form.number_of_vertices())) - set(fixed) - set(leaves))
    # --------------------------------------------------------------------------
    # force diagram
    # --------------------------------------------------------------------------
    _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')
    # --------------------------------------------------------------------------
    # compute the coordinates of thet *free* vertices
    # as a function of the fixed vertices and the previous coordinates of the *free* vertices
    # re-add the leaves and leaf-edges
    # --------------------------------------------------------------------------
    update_form_from_force(xy, _xy, free, leaves, i_j, ij_e, _C, kmax=kmax)
    # --------------------------------------------------------------------------
    # update
    # --------------------------------------------------------------------------
    uv = C.dot(xy)
    _uv = _C.dot(_xy)
    angles = [angle_vectors_xy(a, b, deg=True) for a, b in zip(uv, _uv)]
    lengths = normrow(uv)
    forces = normrow(_uv)
    q = forces / lengths
    # --------------------------------------------------------------------------
    # update form diagram
    # --------------------------------------------------------------------------
    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] < 90:
            attr['f'] = forces[index, 0]
            attr['q'] = q[index, 0]
        else:
            attr['f'] = -forces[index, 0]
            attr['q'] = -q[index, 0]
    # --------------------------------------------------------------------------
    # update force diagram
    # --------------------------------------------------------------------------
    for edge, attr in force.edges(True):
        index = _edge_index[edge]
        attr['a'] = angles[index]
        attr['l'] = forces[index, 0]
예제 #8
0
파일: planarity.py 프로젝트: tetov/compas
def network_embed_in_plane(network, fixed=None, straightline=True):
    """Embed the network in the plane.

    Parameters
    ----------
    network : Network
        A network object.
    fixed : list (None)
        Two fixed points.
    straightline : bool (True)
        Embed using straight lines.

    Returns
    -------
    bool
        True if the embedding was successful.
        False otherwise.

    Raises
    ------
    ImportError
        If NetworkX is not installed.

    Examples
    --------
    >>>
    """
    try:
        import networkx as nx
    except ImportError:
        print(
            "NetworkX is not installed. Get NetworkX at https://networkx.github.io/."
        )
        raise

    x = network.nodes_attribute('x')
    y = network.nodes_attribute('y')
    xmin, xmax = min(x), max(x)
    ymin, ymax = min(y), max(y)
    xspan = xmax - xmin
    yspan = ymax - ymin

    edges = [(u, v) for u, v in network.edges()
             if not network.is_leaf(u) and not network.is_leaf(v)]

    is_embedded = False

    count = 100
    while count:
        graph = nx.Graph(edges)
        pos = nx.spring_layout(graph, iterations=100, scale=max(xspan, yspan))
        if not _are_edges_crossed(edges, pos):
            is_embedded = True
            break
        count -= 1

    if not is_embedded:
        return False

    if fixed:
        a, b = fixed
        p0 = network.node_attributes(a, 'xy')
        p1 = network.node_attributes(b, 'xy')
        p2 = pos[b]
        vec0 = subtract_vectors_xy(p1, p0)
        vec1 = subtract_vectors_xy(pos[b], pos[a])
        # rotate
        angle = angle_vectors_xy(vec0, vec1)
        if is_ccw_xy(p0, p1, p2):
            angle = 2 * pi - angle
        cosa = cos(angle)
        sina = sin(angle)
        for key in pos:
            x, y = pos[key]
            pos[key][0] = cosa * x - sina * y
            pos[key][1] = sina * x + cosa * y
        # scale
        l0 = (vec0[0]**2 + vec0[1]**2)**0.5
        l1 = (vec1[0]**2 + vec1[1]**2)**0.5
        scale = l0 / l1
        for key in pos:
            pos[key][0] *= scale
            pos[key][1] *= scale
        # translate
        t = subtract_vectors_xy(p0, pos[a])
        for key in pos:
            pos[key][0] += t[0]
            pos[key][1] += t[1]

    # update network node coordinates
    for key in network.nodes():
        if key in pos:
            network.node_attributes(key, 'xy', pos[key])

    return True
예제 #9
0
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]
예제 #10
0
def horizontal_nodal(form, force, alpha=100, kmax=100, callback=None):
    r"""Compute horizontal equilibrium using a node-per-node approach.

    Parameters
    ----------
    form : compas_tna.diagrams.FormDiagram
        The form diagram.
    force : compas_tna.diagrams.ForceDiagram
        The force diagram.
    alpha : float, optional
        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, optional
       Maximum number of iterations.
       Default is ``100``.
    callback : callable, optional
        A callback function to be called at every iteration of the parallelisation algorithm.
        The callback should take the current iterand, the coordinates of the form diagram,
        and the coordinates of the force diagram as input parameters.
        Default is ``None``.

    Returns
    -------
    None

    Notes
    -----
    This function will update the form and force diagram instead of returning a result.
    The relationship between force densities (``q``), horizontal forces (``h``), and lengths (``l``)
    is the following:

    .. math::

        Q_{i} &= \frac{F_{i, thrust}}{L_{i, thrust}} \\
              &= \frac{H_{i, form}}{L_{i, form}} \\
              &= scale * \frac{L_{i, force}}{L_{i, form}}

    """
    alpha = float(alpha) / 100.0
    alpha = max(0, min(1, alpha))
    # --------------------------------------------------------------------------
    # form diagram
    # --------------------------------------------------------------------------
    k_i = form.key_index()
    i_nbrs = {k_i[key]: [k_i[nbr] for nbr in form.vertex_neighbors(key)] for key in form.vertices()}
    fixed = set(list(form.anchors()) + list(form.fixed()))
    fixed = [k_i[key] for key in fixed]
    xy = form.vertices_attributes('xy')

    edges = list(form.edges_where({'_is_edge': True}))
    lmin = form.edges_attribute('lmin', keys=edges)
    lmax = form.edges_attribute('lmax', keys=edges)
    hmin = form.edges_attribute('hmin', keys=edges)
    hmax = form.edges_attribute('hmax', keys=edges)

    flipmask = [-1.0 if form.edge_attribute(edge, '_is_tension') else 1.0 for edge in edges]

    uv_i = form.uv_index()
    ij_e = {(k_i[u], k_i[v]): index for (u, v), index in iter(uv_i.items())}

    edges = [[k_i[u], k_i[v]] for u, v in edges]
    # --------------------------------------------------------------------------
    # force diagram
    # --------------------------------------------------------------------------
    _k_i = force.key_index()
    _i_nbrs = {_k_i[key]: [_k_i[nbr] for nbr in force.vertex_neighbors(key)] for key in force.vertices()}
    _fixed = list(force.fixed())
    _fixed = [_k_i[key] for key in _fixed]
    _xy = force.vertices_attributes('xy')

    _edges = force.ordered_edges(form)
    _uv_i = {uv: index for index, uv in enumerate(_edges)}
    _ij_e = {(_k_i[u], _k_i[v]): index for (u, v), index in iter(_uv_i.items())}
    _lmin = force.edges_attribute('lmin', keys=_edges)
    _lmax = force.edges_attribute('lmax', keys=_edges)
    _edges = [[_k_i[u], _k_i[v]] for u, v in _edges]
    scale = force.attributes.get('scale', 1.0)
    # --------------------------------------------------------------------------
    # rotate force diagram to make it parallel to the form diagram
    # use CCW direction (opposite of cycle direction)
    # --------------------------------------------------------------------------
    _x, _y = zip(*_xy)
    _xy[:] = [list(item) for item in zip([-_ for _ in _y], _x)]
    # --------------------------------------------------------------------------
    # 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 = [[factor * (xy[j][0] - xy[i][0]), factor * (xy[j][1] - xy[i][1])] for (i, j), factor in zip(edges, flipmask)]
    _uv = [[_xy[j][0] - _xy[i][0], _xy[j][1] - _xy[i][1]] for i, j in _edges]
    lengths = [(dx**2 + dy**2)**0.5 for dx, dy in uv]
    forces = [(dx**2 + dy**2)**0.5 for dx, dy in _uv]
    # --------------------------------------------------------------------------
    # the target vectors
    # --------------------------------------------------------------------------
    form_targets = [[alpha * v[0] / l, alpha * v[1] / l] if l else [0, 0] for v, l in zip(uv, lengths)]
    force_targets = [[(1 - alpha) * v[0] / l, (1 - alpha) * v[1] / l] if l else [0, 0] for v, l in zip(_uv, forces)]
    targets = [[a[0] + b[0], a[1] + b[1]] for a, b in zip(form_targets, force_targets)]
    # --------------------------------------------------------------------------
    # proper force bounds
    # --------------------------------------------------------------------------
    hmin[:] = [_ / scale for _ in hmin]
    hmax[:] = [_ / scale for _ in hmax]
    _lmin[:] = [max(a, b) for a, b in zip(hmin, _lmin)]
    _lmax[:] = [min(a, b) for a, b in zip(hmax, _lmax)]
    # --------------------------------------------------------------------------
    # parallelise
    # --------------------------------------------------------------------------
    if alpha < 1:
        parallelise_edges(xy, edges, targets, i_nbrs, ij_e, fixed=fixed, kmax=kmax, lmin=lmin, lmax=lmax)
    if alpha > 0:
        parallelise_edges(_xy, _edges, targets, _i_nbrs, _ij_e, fixed=_fixed, kmax=kmax, lmin=_lmin, lmax=_lmax, callback=callback)
    # --------------------------------------------------------------------------
    # update the coordinate difference vectors
    # --------------------------------------------------------------------------
    uv = [[xy[j][0] - xy[i][0], xy[j][1] - xy[i][1]] for i, j in edges]
    _uv = [[_xy[j][0] - _xy[i][0], _xy[j][1] - _xy[i][1]] for i, j in _edges]
    lengths = [(dx**2 + dy**2)**0.5 for dx, dy in uv]
    forces = [(dx**2 + dy**2)**0.5 for dx, dy in _uv]
    # --------------------------------------------------------------------------
    # compute the force densities
    # --------------------------------------------------------------------------
    forces[:] = [f * factor for f, factor in zip(forces, flipmask)]
    q = [f / l for f, l in zip(forces, lengths)]
    # --------------------------------------------------------------------------
    # rotate the force diagram 90 degrees in CW direction
    # this way the relation between the two diagrams is easier to read
    # --------------------------------------------------------------------------
    _x, _y = zip(*_xy)
    _xy[:] = [list(item) for item in zip(_y, [-_ for _ in _x])]
    # --------------------------------------------------------------------------
    # angle deviations
    # note that this does not account for flipped edges!
    # --------------------------------------------------------------------------
    angles = [angle_vectors_xy(uv[i], _uv[i], deg=True) for i in range(len(edges))]
    # --------------------------------------------------------------------------
    # update form
    # --------------------------------------------------------------------------
    for key in form.vertices():
        i = k_i[key]
        form.vertex_attributes(key, 'xy', xy[i])
    for uv in form.edges_where({'_is_edge': True}):
        i = uv_i[uv]
        form.edge_attributes(uv, ['q', '_f', '_l', '_a'], [q[i], forces[i], lengths[i], angles[i]])
    # --------------------------------------------------------------------------
    # update force
    # --------------------------------------------------------------------------
    for key in force.vertices():
        i = _k_i[key]
        force.vertex_attributes(key, 'xy', _xy[i])
    for (u, v) in force.edges():
        if (u, v) in _uv_i:
            i = _uv_i[(u, v)]
        else:
            i = _uv_i[(v, u)]
        force.edge_attributes((u, v), ['_l', '_a'], [forces[i], angles[i]])
예제 #11
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
    -------
    None

    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.

    """
    k_i = form.key_index()
    i_j = dict((i, [k_i[n] for n in form.vertex_neighbors(k)])
               for i, k in enumerate(form.vertices()))
    uv_e = form.uv_index()
    ij_e = dict(((k_i[u], k_i[v]), uv_e[(u, v)]) for u, v in uv_e)
    xy = array(form.xy(), dtype=float64)
    edges = [(k_i[u], k_i[v]) for u, v in form.edges()]
    C = connectivity_matrix(edges, 'csr')
    # add opposite edges for convenience...
    ij_e.update(dict(((k_i[v], k_i[u]), uv_e[(u, v)]) for u, v in uv_e))

    leaves = [k_i[key] for key in form.leaves()]
    fixed = [k_i[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 k_i[u] not in leaves and k_i[v] not in leaves
    ]

    _k_i = dict((key, index) for index, key in enumerate(force.vertices()))
    _i_k = dict((index, key) for index, key in enumerate(force.vertices()))
    _xy = array(force.xy(), dtype=float64)
    _edges = force.ordered_edges(form)
    _C = connectivity_matrix(_edges, 'csr')
    _uv_e = dict(((_i_k[i], _i_k[j]), e) for e, (i, j) in enumerate(_edges))
    _uv_e.update({(v, u): _uv_e[u, v] for u, v in _uv_e})

    _free = [key for key, attr in force.vertices(True) if attr['is_param']]
    _free = [_k_i[key] for key in _free]

    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)

    x0 = _xy[_free, 0]
    # bounds = [(None, 0) for i in range(len(_free))]
    res = minimize(
        objfunc,
        x0,
        method=algo,
        tol=1e-12,
        # bounds=bounds,
        options={'maxiter': 1000})

    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

    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] - 3.14159)**2 < 0.25 * 3.14159:
            attr['f'] = -_l[e, 0]
            attr['q'] = -q[e, 0]
        else:
            attr['f'] = _l[e, 0]
            attr['q'] = q[e, 0]

    for key, attr in force.vertices(True):
        index = _k_i[key]
        attr['x'] = _xy[index, 0]
        attr['y'] = _xy[index, 1]

    for (u, v), attr in force.edges(True):
        e = _uv_e[(u, v)]
        attr['a'] = a[e]
        attr['l'] = _l[e, 0]
예제 #12
0
def form_update_from_force(form, force, kmax=100):
    r"""Update the form diagram after a modification of the force diagram.

    Parameters
    ----------
    form : FormDiagram
        The form diagram to update.
    force : ForceDiagram
        The force diagram on which the update is based.

    Returns
    -------
    None
        The form and force diagram are updated in-place.

    Notes
    -----
    Compute the geometry of the form diagram from the geometry of the form diagram
    and some constraints (location of fixed points).
    Since both diagrams are reciprocal, the coordinates of each vertex of the form
    diagram can be expressed as the intersection of three or more lines parallel
    to the corresponding edges of the force diagram.

    Essentially, this boils down to solving the following problem:

    .. math::

        \mathbf{A}\mathbf{x} = \mathbf{b}

    with :math:`\mathbf{A}` the coefficients of the x and y-coordinate of the  ,
    :math:`\mathbf{x}` the coordinates of the vertices of the form diagram,
    in *Fortran* order (first all x-coordinates, then all y-coordinates),
    and  :math:`\mathbf{b}` ....

    Examples
    --------
    .. code-block:: python


    """
    # --------------------------------------------------------------------------
    # form diagram
    # --------------------------------------------------------------------------
    k_i = form.key_index()
    i_j = {
        i: [k_i[n] for n in form.vertex_neighbors(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}
    xy = array(form.xy(), dtype=float64)
    edges = [(k_i[u], k_i[v]) for u, v in form.edges()]
    C = connectivity_matrix(edges, 'csr')
    # add opposite edges for convenience...
    ij_e.update({(k_i[v], k_i[u]): uv_e[(u, v)] for u, v in uv_e})
    # --------------------------------------------------------------------------
    # constraints
    # --------------------------------------------------------------------------
    leaves = [k_i[key] for key in form.leaves()]
    fixed = [k_i[key] for key in form.fixed()]
    free = list(
        set(range(form.number_of_vertices())) - set(fixed) - set(leaves))
    # --------------------------------------------------------------------------
    # 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)}
    _C = connectivity_matrix(_edges, 'csr')
    # --------------------------------------------------------------------------
    # compute the coordinates of thet *free* vertices
    # as a function of the fixed vertices and the previous coordinates of the *free* vertices
    # re-add the leaves and leaf-edges
    # --------------------------------------------------------------------------
    update_form_from_force(xy, _xy, free, leaves, i_j, ij_e, _C, kmax=kmax)
    # --------------------------------------------------------------------------
    # update
    # --------------------------------------------------------------------------
    uv = C.dot(xy)
    _uv = _C.dot(_xy)
    a = [angle_vectors_xy(uv[i], _uv[i], deg=True) 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]