diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ee95cf723f2..38552efbc2f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +* Added `compas.dtastructures.Network` as alias of `compas.datastructures.Graph`. + ### Changed +* Merged `compas.datastructures.Network` into `compas.datastructures.Graph`. + ### Removed +* Removed `compas.datastructures.Network`. + ## [2.0.0-beta.2] 2024-01-12 ### Added diff --git a/docs/api/compas.datastructures.CellNetwork.rst b/docs/api/compas.datastructures.CellNetwork.rst index 6680a9e68830..b5d3449f781b 100644 --- a/docs/api/compas.datastructures.CellNetwork.rst +++ b/docs/api/compas.datastructures.CellNetwork.rst @@ -149,14 +149,13 @@ Topology ~CellNetwork.halfface_opposite_cell ~CellNetwork.halfface_opposite_halfface ~CellNetwork.halfface_vertex_ancestor - ~CellNetwork.halfface_vertex_descendant + ~CellNetwork.halfface_vertex_descendent ~CellNetwork.halfface_vertices ~CellNetwork.has_edge ~CellNetwork.has_halfface ~CellNetwork.has_vertex ~CellNetwork.is_cell_on_boundary ~CellNetwork.is_edge_on_boundary - ~CellNetwork.is_halfedge_on_boundary ~CellNetwork.is_halfface_on_boundary ~CellNetwork.is_valid ~CellNetwork.is_vertex_on_boundary diff --git a/docs/api/compas.datastructures.Graph.rst b/docs/api/compas.datastructures.Graph.rst new file mode 100644 index 000000000000..0c41402455c4 --- /dev/null +++ b/docs/api/compas.datastructures.Graph.rst @@ -0,0 +1,199 @@ +****************************************************************************** +Graph +****************************************************************************** + +.. currentmodule:: compas.datastructures + +.. autoclass:: Graph + +Methods +======= + +Constructors +------------ + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + ~Graph.from_edges + ~Graph.from_json + ~Graph.from_lines + ~Graph.from_networkx + ~Graph.from_nodes_and_edges + ~Graph.from_obj + ~Graph.from_pointcloud + +Conversions +----------- + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + ~Graph.to_json + ~Graph.to_lines + ~Graph.to_networkx + ~Graph.to_obj + ~Graph.to_points + +Builders and Modifiers +---------------------- + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + ~Graph.add_edge + ~Graph.add_node + ~Graph.delete_edge + ~Graph.delete_node + ~Graph.join_edges + ~Graph.split_edge + +Accessors +--------- + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + ~Graph.edge_sample + ~Graph.edges + ~Graph.edges_where + ~Graph.edges_where_predicate + ~Graph.node_sample + ~Graph.nodes + ~Graph.nodes_where + ~Graph.nodes_where_predicate + +Attributes +---------- + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + ~Graph.edge_attribute + ~Graph.edge_attributes + ~Graph.edges_attribute + ~Graph.edges_attributes + ~Graph.node_attribute + ~Graph.node_attributes + ~Graph.nodes_attribute + ~Graph.nodes_attributes + ~Graph.update_default_edge_attributes + ~Graph.update_default_node_attributes + ~Graph.unset_edge_attribute + ~Graph.unset_node_attribute + +Topology +-------- + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + ~Graph.complement + ~Graph.connected_nodes + ~Graph.connected_edges + ~Graph.degree + ~Graph.degree_out + ~Graph.degree_in + ~Graph.exploded + ~Graph.has_edge + ~Graph.has_node + ~Graph.is_leaf + ~Graph.is_node_connected + ~Graph.neighborhood + ~Graph.neighbors + ~Graph.neighbors_in + ~Graph.neighbors_out + ~Graph.node_edges + ~Graph.number_of_edges + ~Graph.number_of_nodes + +Geometry +-------- + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + ~Graph.edge_coordinates + ~Graph.edge_direction + ~Graph.edge_end + ~Graph.edge_length + ~Graph.edge_line + ~Graph.edge_midpoint + ~Graph.edge_point + ~Graph.edge_start + ~Graph.edge_vector + ~Graph.node_coordinates + ~Graph.node_point + ~Graph.node_laplacian + ~Graph.node_neighborhood_centroid + ~Graph.transform + ~Graph.transformed + +Paths +----- + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + ~Graph.shortest_path + +Planarity +--------- + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + ~Graph.count_crossings + ~Graph.embed_in_plane + ~Graph.find_crossings + ~Graph.find_cycles + ~Graph.is_crossed + ~Graph.is_planar + ~Graph.is_planar_embedding + ~Graph.is_xy + +Matrices +-------- + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + ~Graph.adjacency_matrix + ~Graph.connectivity_matrix + ~Graph.degree_matrix + ~Graph.laplacian_matrix + +Mappings +-------- + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + ~Graph.gkey_node + ~Graph.node_gkey + ~Graph.node_index + ~Graph.edge_index + ~Graph.index_node + ~Graph.index_edge + +Utilities +--------- + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + ~Graph.summary + ~Graph.copy + ~Graph.clear diff --git a/docs/api/compas.datastructures.Network.rst b/docs/api/compas.datastructures.Network.rst deleted file mode 100644 index 6ed4b72647fc..000000000000 --- a/docs/api/compas.datastructures.Network.rst +++ /dev/null @@ -1,199 +0,0 @@ -****************************************************************************** -Network -****************************************************************************** - -.. currentmodule:: compas.datastructures - -.. autoclass:: Network - -Methods -======= - -Constructors ------------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - ~Network.from_edges - ~Network.from_json - ~Network.from_lines - ~Network.from_networkx - ~Network.from_nodes_and_edges - ~Network.from_obj - ~Network.from_pointcloud - -Conversions ------------ - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - ~Network.to_json - ~Network.to_lines - ~Network.to_networkx - ~Network.to_obj - ~Network.to_points - -Builders and Modifiers ----------------------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - ~Network.add_edge - ~Network.add_node - ~Network.delete_edge - ~Network.delete_node - ~Network.join_edges - ~Network.split_edge - -Accessors ---------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - ~Network.edge_sample - ~Network.edges - ~Network.edges_where - ~Network.edges_where_predicate - ~Network.node_sample - ~Network.nodes - ~Network.nodes_where - ~Network.nodes_where_predicate - -Attributes ----------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - ~Network.edge_attribute - ~Network.edge_attributes - ~Network.edges_attribute - ~Network.edges_attributes - ~Network.node_attribute - ~Network.node_attributes - ~Network.nodes_attribute - ~Network.nodes_attributes - ~Network.update_default_edge_attributes - ~Network.update_default_node_attributes - ~Network.unset_edge_attribute - ~Network.unset_node_attribute - -Topology --------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - ~Network.complement - ~Network.connected_nodes - ~Network.connected_edges - ~Network.degree - ~Network.degree_out - ~Network.degree_in - ~Network.exploded - ~Network.has_edge - ~Network.has_node - ~Network.is_leaf - ~Network.is_node_connected - ~Network.neighborhood - ~Network.neighbors - ~Network.neighbors_in - ~Network.neighbors_out - ~Network.node_edges - ~Network.number_of_edges - ~Network.number_of_nodes - -Geometry --------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - ~Network.edge_coordinates - ~Network.edge_direction - ~Network.edge_end - ~Network.edge_length - ~Network.edge_line - ~Network.edge_midpoint - ~Network.edge_point - ~Network.edge_start - ~Network.edge_vector - ~Network.node_coordinates - ~Network.node_point - ~Network.node_laplacian - ~Network.node_neighborhood_centroid - ~Network.transform - ~Network.transformed - -Paths ------ - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - ~Network.shortest_path - -Planarity ---------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - ~Network.count_crossings - ~Network.embed_in_plane - ~Network.find_crossings - ~Network.find_cycles - ~Network.is_crossed - ~Network.is_planar - ~Network.is_planar_embedding - ~Network.is_xy - -Matrices --------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - ~Network.adjacency_matrix - ~Network.connectivity_matrix - ~Network.degree_matrix - ~Network.laplacian_matrix - -Mappings --------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - ~Network.gkey_node - ~Network.node_gkey - ~Network.node_index - ~Network.edge_index - ~Network.index_node - ~Network.index_edge - -Utilities ---------- - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - ~Network.summary - ~Network.copy - ~Network.clear diff --git a/docs/api/compas.datastructures.rst b/docs/api/compas.datastructures.rst index 13d45f814587..f51f1be65912 100644 --- a/docs/api/compas.datastructures.rst +++ b/docs/api/compas.datastructures.rst @@ -18,7 +18,7 @@ Classes :maxdepth: 1 :titlesonly: - compas.datastructures.Network + compas.datastructures.Graph compas.datastructures.Mesh compas.datastructures.CellNetwork compas.datastructures.Tree diff --git a/docs/api/compas.scene.rst b/docs/api/compas.scene.rst index 942b0eeef978..86aec900c8ec 100644 --- a/docs/api/compas.scene.rst +++ b/docs/api/compas.scene.rst @@ -22,7 +22,7 @@ Classes GeometryObject MeshObject - NetworkObject + GraphObject NoSceneObjectContextError Scene SceneObject diff --git a/docs/api/compas_blender.scene.rst b/docs/api/compas_blender.scene.rst index 0b8a598c40d5..6f10b8f9283a 100644 --- a/docs/api/compas_blender.scene.rst +++ b/docs/api/compas_blender.scene.rst @@ -29,7 +29,7 @@ Classes FrameObject LineObject MeshObject - NetworkObject + GraphObject PointObject PointcloudObject PolygonObject diff --git a/docs/api/compas_ghpython.scene.rst b/docs/api/compas_ghpython.scene.rst index bcfa0053be8f..87f7258b9257 100644 --- a/docs/api/compas_ghpython.scene.rst +++ b/docs/api/compas_ghpython.scene.rst @@ -30,7 +30,7 @@ Classes GHSceneObject LineObject MeshObject - NetworkObject + GraphObject PointObject PolygonObject PolyhedronObject diff --git a/docs/api/compas_rhino.scene.rst b/docs/api/compas_rhino.scene.rst index 0a147af50172..23c322d98c0b 100644 --- a/docs/api/compas_rhino.scene.rst +++ b/docs/api/compas_rhino.scene.rst @@ -30,7 +30,7 @@ Classes FrameObject LineObject MeshObject - NetworkObject + GraphObject PlaneObject PointObject PolygonObject diff --git a/docs/userguide/basics.datastructures.networks.rst b/docs/userguide/basics.datastructures.graphs.rst similarity index 94% rename from docs/userguide/basics.datastructures.networks.rst rename to docs/userguide/basics.datastructures.graphs.rst index 74df68e79b59..2db0da7b9191 100644 --- a/docs/userguide/basics.datastructures.networks.rst +++ b/docs/userguide/basics.datastructures.graphs.rst @@ -1,3 +1,3 @@ ******************************************************************************** -Networks +Graphs ******************************************************************************** diff --git a/docs/userguide/basics.datastructures.rst b/docs/userguide/basics.datastructures.rst index 419c7c4cd0f0..dddfd52f3eb0 100644 --- a/docs/userguide/basics.datastructures.rst +++ b/docs/userguide/basics.datastructures.rst @@ -7,7 +7,7 @@ Datastructures :titlesonly: :caption: Data Structures - basics.datastructures.networks + basics.datastructures.graphs basics.datastructures.meshes basics.datastructures.cells basics.datastructures.trees diff --git a/docs/userguide/scene.py b/docs/userguide/scene.py index 6a8e384cdde6..39ead4d4722e 100644 --- a/docs/userguide/scene.py +++ b/docs/userguide/scene.py @@ -14,10 +14,11 @@ from compas.geometry import Sphere from compas.geometry import Torus from compas.geometry import Vector + # from compas.geometry import Plane from compas.datastructures import Mesh -from compas.datastructures import Network +from compas.datastructures import Graph from compas.datastructures import VolMesh from compas.geometry import Translation @@ -44,7 +45,7 @@ mesh = Mesh.from_polyhedron(8) -network = Network.from_nodes_and_edges([(0, 0, 0), (0, -1.5, 0), (-1, 1, 0), (1, 1, 0)], [(0, 1), (0, 2), (0, 3)]) +graph = Graph.from_nodes_and_edges([(0, 0, 0), (0, -1.5, 0), (-1, 1, 0), (1, 1, 0)], [(0, 1), (0, 2), (0, 3)]) volmesh = VolMesh.from_meshgrid(1, 1, 1, 2, 2, 2) @@ -68,7 +69,7 @@ # scene.add(plane) scene.add(mesh) -scene.add(network) +scene.add(graph) scene.add(volmesh) diff --git a/src/compas/datastructures/__init__.py b/src/compas/datastructures/__init__.py index 6afb1650bb7a..de8338863ab8 100644 --- a/src/compas/datastructures/__init__.py +++ b/src/compas/datastructures/__init__.py @@ -11,11 +11,7 @@ # Graphs # ============================================================================= -# ============================================================================= -# Networks -# ============================================================================= - -from .network.planarity import network_embed_in_plane_proxy # noqa: F401 +from .graph.planarity import graph_embed_in_plane_proxy # noqa: F401 # ============================================================================= # Halfedges @@ -56,7 +52,6 @@ # ============================================================================= from .graph.graph import Graph -from .network.network import Network from .halfedge.halfedge import HalfEdge from .mesh.mesh import Mesh from .halfface.halfface import HalfFace @@ -67,11 +62,12 @@ from .cell_network.cell_network import CellNetwork from .tree.tree import Tree, TreeNode +Network = Graph + __all__ = [ "Datastructure", - "Graph", "CellNetwork", - "Network", + "Graph", "HalfEdge", "Mesh", "HalfFace", diff --git a/src/compas/datastructures/assembly/assembly.py b/src/compas/datastructures/assembly/assembly.py index dd2d8328ded0..e639052fc41e 100644 --- a/src/compas/datastructures/assembly/assembly.py +++ b/src/compas/datastructures/assembly/assembly.py @@ -24,7 +24,7 @@ class Assembly(Datastructure): See Also -------- - :class:`compas.datastructures.Network` + :class:`compas.datastructures.Graph` :class:`compas.datastructures.Mesh` :class:`compas.datastructures.VolMesh` diff --git a/src/compas/datastructures/cell_network/cell_network.py b/src/compas/datastructures/cell_network/cell_network.py index 36e8ed6f0a44..ceda4bb10171 100644 --- a/src/compas/datastructures/cell_network/cell_network.py +++ b/src/compas/datastructures/cell_network/cell_network.py @@ -4,7 +4,7 @@ from compas.datastructures import HalfFace from compas.datastructures import Mesh -from compas.datastructures import Network +from compas.datastructures import Graph from compas.geometry import Line from compas.geometry import Point from compas.geometry import Polygon @@ -1227,17 +1227,18 @@ def data(self, data): # -------------------------------------------------------------------------- def to_network(self): - """Convert the cell network to a network. + """Convert the cell network to a graph. Returns ------- - :class:`compas.datastructures.Network` - A network object. + :class:`compas.datastructures.Graph` + A graph object. + """ - network = Network() + graph = Graph() for vertex, attr in self.vertices(data=True): x, y, z = self.vertex_coordinates(vertex) - network.add_node(key=vertex, x=x, y=y, z=z, attr_dict=attr) + graph.add_node(key=vertex, x=x, y=y, z=z, attr_dict=attr) for (u, v), attr in self.edges(data=True): - network.add_edge(u, v, attr_dict=attr) - return network + graph.add_edge(u, v, attr_dict=attr) + return graph diff --git a/src/compas/datastructures/network/duality.py b/src/compas/datastructures/graph/duality.py similarity index 71% rename from src/compas/datastructures/network/duality.py rename to src/compas/datastructures/graph/duality.py index 994254694b72..2786296650f5 100644 --- a/src/compas/datastructures/network/duality.py +++ b/src/compas/datastructures/graph/duality.py @@ -12,13 +12,13 @@ PI2 = 2.0 * pi -def network_find_cycles(network, breakpoints=None): - """Find the faces of a network. +def graph_find_cycles(graph, breakpoints=None): + """Find the faces of a graph. Parameters ---------- - network : :class:`compas.datastructures.Network` - The network object. + graph : :class:`compas.datastructures.Graph` + The graph object. breakpoints : list, optional The vertices at which to break the found faces. @@ -32,75 +32,75 @@ def network_find_cycles(network, breakpoints=None): Warnings -------- This algorithms is essentially a wall follower (a type of maze-solving algorithm). - It relies on the geometry of the network to be repesented as a planar, + It relies on the geometry of the graph to be repesented as a planar, straight-line embedding. It determines an ordering of the neighboring vertices - around each vertex, and then follows the *walls* of the network, always + around each vertex, and then follows the *walls* of the graph, always taking turns in the same direction. """ if not breakpoints: breakpoints = [] - for u, v in network.edges(): - network.adjacency[u][v] = None - network.adjacency[v][u] = None + for u, v in graph.edges(): + graph.adjacency[u][v] = None + graph.adjacency[v][u] = None - network_sort_neighbors(network) + graph_sort_neighbors(graph) - leaves = list(network.leaves()) + leaves = list(graph.leaves()) if leaves: - key_xy = list(zip(leaves, network.nodes_attributes("xy", keys=leaves))) + key_xy = list(zip(leaves, graph.nodes_attributes("xy", keys=leaves))) else: - key_xy = list(zip(network.nodes(), network.nodes_attributes("xy"))) + key_xy = list(zip(graph.nodes(), graph.nodes_attributes("xy"))) u = sorted(key_xy, key=lambda x: (x[1][1], x[1][0]))[0][0] cycles = {} found = {} ckey = 0 - v = network_node_find_first_neighbor(network, u) - cycle = network_find_edge_cycle(network, (u, v)) + v = graph_node_find_first_neighbor(graph, u) + cycle = graph_find_edge_cycle(graph, (u, v)) frozen = frozenset(cycle) found[frozen] = ckey cycles[ckey] = cycle for a, b in pairwise(cycle + cycle[:1]): - network.adjacency[a][b] = ckey + graph.adjacency[a][b] = ckey ckey += 1 - for u, v in network.edges(): - if network.adjacency[u][v] is None: - cycle = network_find_edge_cycle(network, (u, v)) + for u, v in graph.edges(): + if graph.adjacency[u][v] is None: + cycle = graph_find_edge_cycle(graph, (u, v)) frozen = frozenset(cycle) if frozen not in found: found[frozen] = ckey cycles[ckey] = cycle ckey += 1 for a, b in pairwise(cycle + cycle[:1]): - network.adjacency[a][b] = found[frozen] - if network.adjacency[v][u] is None: - cycle = network_find_edge_cycle(network, (v, u)) + graph.adjacency[a][b] = found[frozen] + if graph.adjacency[v][u] is None: + cycle = graph_find_edge_cycle(graph, (v, u)) frozen = frozenset(cycle) if frozen not in found: found[frozen] = ckey cycles[ckey] = cycle ckey += 1 for a, b in pairwise(cycle + cycle[:1]): - network.adjacency[a][b] = found[frozen] + graph.adjacency[a][b] = found[frozen] cycles = _break_cycles(cycles, breakpoints) return cycles -def network_node_find_first_neighbor(network, key): - nbrs = network.neighbors(key) +def graph_node_find_first_neighbor(graph, key): + nbrs = graph.neighbors(key) if len(nbrs) == 1: return nbrs[0] ab = [-1.0, -1.0, 0.0] - a = network.node_coordinates(key, "xyz") + a = graph.node_coordinates(key, "xyz") b = [a[0] + ab[0], a[1] + ab[1], 0] angles = [] for nbr in nbrs: - c = network.node_coordinates(nbr, "xyz") + c = graph.node_coordinates(nbr, "xyz") ac = [c[0] - a[0], c[1] - a[1], 0] alpha = angle_vectors(ab, ac) if is_ccw_xy(a, b, c, True): @@ -109,14 +109,14 @@ def network_node_find_first_neighbor(network, key): return nbrs[angles.index(min(angles))] -def network_sort_neighbors(network, ccw=True): +def graph_sort_neighbors(graph, ccw=True): sorted_neighbors = {} - xyz = {key: network.node_coordinates(key) for key in network.nodes()} - for key in network.nodes(): - nbrs = network.neighbors(key) + xyz = {key: graph.node_coordinates(key) for key in graph.nodes()} + for key in graph.nodes(): + nbrs = graph.neighbors(key) sorted_neighbors[key] = node_sort_neighbors(key, nbrs, xyz, ccw=ccw) for key, nbrs in sorted_neighbors.items(): - network.node_attribute(key, "neighbors", nbrs[::-1]) + graph.node_attribute(key, "neighbors", nbrs[::-1]) return sorted_neighbors @@ -149,12 +149,12 @@ def node_sort_neighbors(key, nbrs, xyz, ccw=True): return ordered -def network_find_edge_cycle(network, edge): +def graph_find_edge_cycle(graph, edge): u, v = edge cycle = [u] while True: cycle.append(v) - nbrs = network.node_attribute(v, "neighbors") + nbrs = graph.node_attribute(v, "neighbors") nbr = nbrs[nbrs.index(u) - 1] u, v = v, nbr if v == cycle[0]: diff --git a/src/compas/datastructures/graph/graph.py b/src/compas/datastructures/graph/graph.py index 1d32f51771e1..74a802b1a059 100644 --- a/src/compas/datastructures/graph/graph.py +++ b/src/compas/datastructures/graph/graph.py @@ -6,6 +6,27 @@ from ast import literal_eval from itertools import combinations +import compas + +if compas.PY2: + from collections import Mapping +else: + from collections.abc import Mapping + +from compas.tolerance import TOL +from compas.files import OBJ +from compas.geometry import Point +from compas.geometry import Vector +from compas.geometry import Line +from compas.geometry import centroid_points +from compas.geometry import subtract_vectors +from compas.geometry import distance_point_point +from compas.geometry import midpoint_line +from compas.geometry import normalize_vector +from compas.geometry import add_vectors +from compas.geometry import scale_vector +from compas.geometry import transform_points +from compas.topology import astar_shortest_path from compas.topology import breadth_first_traverse from compas.topology import connected_components @@ -13,18 +34,31 @@ from compas.datastructures.attributes import NodeAttributeView from compas.datastructures.attributes import EdgeAttributeView +from .operations.split import graph_split_edge +from .operations.join import graph_join_edges + +from .planarity import graph_is_crossed +from .planarity import graph_is_planar +from .planarity import graph_is_planar_embedding +from .planarity import graph_is_xy +from .planarity import graph_count_crossings +from .planarity import graph_find_crossings +from .planarity import graph_embed_in_plane +from .smoothing import graph_smooth_centroid +from .duality import graph_find_cycles + class Graph(Datastructure): - """Base graph data structure for describing the topological relationships between nodes connected by edges. + """Data structure for describing the relationships between nodes connected by edges. Parameters ---------- - default_node_attributes : dict[str, Any], optional + default_node_attributes: dict, optional Default values for node attributes. - default_edge_attributes : dict[str, Any], optional + default_edge_attributes: dict, optional Default values for edge attributes. **kwargs : dict, optional - Additional keyword arguments are passed to the base class, and will be stored in the :attr:`attributes` attribute. + Additional attributes to add to the graph. Attributes ---------- @@ -37,10 +71,6 @@ class Graph(Datastructure): It is recommended to add a default to this dictionary using :meth:`update_default_edge_attributes` for every edge attribute used in the data structure. - See Also - -------- - :class:`compas.datastructures.Network` - """ DATASCHEMA = { @@ -70,13 +100,27 @@ class Graph(Datastructure): ], } + split_edge = graph_split_edge + join_edges = graph_join_edges + smooth = graph_smooth_centroid + + is_crossed = graph_is_crossed + is_planar = graph_is_planar + is_planar_embedding = graph_is_planar_embedding + is_xy = graph_is_xy + count_crossings = graph_count_crossings + find_crossings = graph_find_crossings + embed_in_plane = graph_embed_in_plane + + find_cycles = graph_find_cycles + def __init__(self, default_node_attributes=None, default_edge_attributes=None, **kwargs): super(Graph, self).__init__(**kwargs) self._max_node = -1 self.node = {} self.edge = {} self.adjacency = {} - self.default_node_attributes = {} + self.default_node_attributes = {"x": 0.0, "y": 0.0, "z": 0.0} self.default_edge_attributes = {} if default_node_attributes: self.default_node_attributes.update(default_node_attributes) @@ -133,10 +177,6 @@ def from_data(cls, data): return graph - # -------------------------------------------------------------------------- - # Properties - # -------------------------------------------------------------------------- - # -------------------------------------------------------------------------- # Constructors # -------------------------------------------------------------------------- @@ -197,6 +237,226 @@ def from_networkx(cls, graph): return g + @classmethod + def from_obj(cls, filepath, precision=None): + """Construct a graph from the data contained in an OBJ file. + + Parameters + ---------- + filepath : path string | file-like object | URL string + A path, a file-like object or a URL pointing to a file. + precision: str, optional + The precision of the geometric map that is used to connect the lines. + + Returns + ------- + :class:`compas.datastructures.Graph` + A graph object. + + See Also + -------- + :meth:`to_obj` + :meth:`from_lines`, :meth:`from_nodes_and_edges`, :meth:`from_pointcloud` + :class:`compas.files.OBJ` + + """ + graph = cls() + obj = OBJ(filepath, precision) + obj.read() + nodes = obj.vertices + edges = obj.lines + for i, (x, y, z) in enumerate(nodes): # type: ignore + graph.add_node(i, x=x, y=y, z=z) + for edge in edges: # type: ignore + graph.add_edge(*edge) + return graph + + @classmethod + def from_lines(cls, lines, precision=None): + """Construct a graph from a set of lines represented by their start and end point coordinates. + + Parameters + ---------- + lines : list[tuple[list[float, list[float]]]] + A list of pairs of point coordinates. + precision : int, optional + Precision for converting numbers to strings. + Default is :attr:`TOL.precision`. + + Returns + ------- + :class:`compas.datastructures.Graph` + A graph object. + + See Also + -------- + :meth:`to_lines` + :meth:`from_obj`, :meth:`from_nodes_and_edges`, :meth:`from_pointcloud` + + """ + graph = cls() + edges = [] + node = {} + for line in lines: + sp = line[0] + ep = line[1] + a = TOL.geometric_key(sp, precision) + b = TOL.geometric_key(ep, precision) + node[a] = sp + node[b] = ep + edges.append((a, b)) + key_index = dict((k, i) for i, k in enumerate(iter(node))) + for key, xyz in iter(node.items()): + i = key_index[key] + graph.add_node(i, x=xyz[0], y=xyz[1], z=xyz[2]) + for u, v in edges: + i = key_index[u] + j = key_index[v] + graph.add_edge(i, j) + return graph + + @classmethod + def from_nodes_and_edges(cls, nodes, edges): + """Construct a graph from nodes and edges. + + Parameters + ---------- + nodes : list[list[float]] | dict[hashable, list[float]] + A list of node coordinates or a dictionary of keys pointing to node coordinates to specify keys. + edges : list[tuple[hashable, hshable]] + + Returns + ------- + :class:`compas.datastructures.Graph` + A graph object. + + See Also + -------- + :meth:`to_nodes_and_edges` + :meth:`from_obj`, :meth:`from_lines`, :meth:`from_pointcloud` + + """ + graph = cls() + + if isinstance(nodes, Mapping): + for key, (x, y, z) in nodes.items(): + graph.add_node(key, x=x, y=y, z=z) + else: + for i, (x, y, z) in enumerate(nodes): + graph.add_node(i, x=x, y=y, z=z) + + for u, v in edges: + graph.add_edge(u, v) + + return graph + + @classmethod + def from_pointcloud(cls, cloud, degree=3): + """Construct a graph from random connections between the points of a pointcloud. + + Parameters + ---------- + cloud : :class:`compas.geometry.Pointcloud` + A pointcloud object. + degree : int, optional + The number of connections per node. + + Returns + ------- + :class:`compas.datastructures.Graph` + A graph object. + + See Also + -------- + :meth:`to_points` + :meth:`from_obj`, :meth:`from_lines`, :meth:`from_nodes_and_edges` + + """ + graph = cls() + for x, y, z in cloud: + graph.add_node(x=x, y=y, z=z) + for u in graph.nodes(): + for v in graph.node_sample(size=degree): + graph.add_edge(u, v) + return graph + + # -------------------------------------------------------------------------- + # Converters + # -------------------------------------------------------------------------- + + def to_obj(self): + """Write the graph to an OBJ file. + + Parameters + ---------- + filepath : path string | file-like object + A path or a file-like object pointing to a file. + + Returns + ------- + None + + See Also + -------- + :meth:`from_obj` + :meth:`to_lines`, :meth:`to_nodes_and_edges`, :meth:`to_points` + + """ + raise NotImplementedError + + def to_points(self): + """Return the coordinates of the graph. + + Returns + ------- + list[list[float]] + A list with the coordinates of the vertices of the graph. + + See Also + -------- + :meth:`from_pointcloud` + :meth:`to_lines`, :meth:`to_nodes_and_edges`, :meth:`to_obj` + + """ + return [self.node_coordinates(key) for key in self.nodes()] + + def to_lines(self): + """Return the lines of the graph as pairs of start and end point coordinates. + + Returns + ------- + list[tuple[list[float], list[float]]] + A list of lines each defined by a pair of point coordinates. + + See Also + -------- + :meth:`from_lines` + :meth:`to_nodes_and_edges`, :meth:`to_obj`, :meth:`to_points` + + """ + return [self.edge_coordinates(edge) for edge in self.edges()] + + def to_nodes_and_edges(self): + """Return the nodes and edges of a graph. + + Returns + ------- + list[list[float]] + A list of nodes, represented by their XYZ coordinates. + list[tuple[hashable, hashable]] + A list of edges, with each edge represented by a pair of indices in the node list. + + See Also + -------- + :meth:`from_nodes_and_edges` + :meth:`to_lines`, :meth:`to_obj`, :meth:`to_points` + + """ + key_index = dict((key, index) for index, key in enumerate(self.nodes())) + nodes = [self.node_coordinates(key) for key in self.nodes()] + edges = [(key_index[u], key_index[v]) for u, v in self.edges()] + return nodes, edges + def to_networkx(self): """Create a new NetworkX graph instance from a graph. @@ -228,7 +488,7 @@ def to_networkx(self): # -------------------------------------------------------------------------- def clear(self): - """Clear all the network data. + """Clear all the graph data. Returns ------- @@ -348,6 +608,56 @@ def index_edge(self): """ return dict(enumerate(self.edges())) + def node_gkey(self, precision=None): + """Returns a dictionary that maps node identifiers to the corresponding + *geometric key* up to a certain precision. + + Parameters + ---------- + precision : int, optional + Precision for converting numbers to strings. + Default is :attr:`TOL.precision`. + + Returns + ------- + dict[hashable, str] + A dictionary of (node, geometric key) pairs. + + See Also + -------- + :meth:`gkey_node` + :meth:`compas.Tolerance.geometric_key` + + """ + gkey = TOL.geometric_key + xyz = self.node_coordinates + return {key: gkey(xyz(key), precision) for key in self.nodes()} + + def gkey_node(self, precision=None): + """Returns a dictionary that maps *geometric keys* of a certain precision + to the identifiers of the corresponding nodes. + + Parameters + ---------- + precision : int, optional + Precision for converting numbers to strings. + Default is :attr:`TOL.precision`. + + Returns + ------- + dict[str, hashable] + A dictionary of (geometric key, node) pairs. + + See Also + -------- + :meth:`node_gkey` + :meth:`compas.Tolerance.geometric_key` + + """ + gkey = TOL.geometric_key + xyz = self.node_coordinates + return {gkey(xyz(key), precision): key for key in self.nodes()} + # -------------------------------------------------------------------------- # Builders # -------------------------------------------------------------------------- @@ -492,7 +802,7 @@ def delete_node(self, key): del self.adjacency[u][v] def delete_edge(self, edge): - """Delete an edge from the network. + """Delete an edge from the graph. Parameters ---------- @@ -579,25 +889,25 @@ def number_of_edges(self): return len(list(self.edges())) def is_connected(self): - """Verify that the network is connected. + """Verify that the graph is connected. Returns ------- bool - True, if the network is connected. + True, if the graph is connected. False, otherwise. Notes ----- - A network is connected if for every two vertices a path exists connecting them. + A graph is connected if for every two vertices a path exists connecting them. Examples -------- >>> import compas - >>> from compas.datastructures import Network - >>> network = Network.from_obj(compas.get('lines.obj')) - >>> network.is_connected() + >>> from compas.datastructures import Graph + >>> graph = Graph.from_obj(compas.get('lines.obj')) + >>> graph.is_connected() True """ @@ -611,7 +921,7 @@ def is_connected(self): # -------------------------------------------------------------------------- def nodes(self, data=False): - """Iterate over the nodes of the network. + """Iterate over the nodes of the graph. Parameters ---------- @@ -747,7 +1057,7 @@ def nodes_where_predicate(self, predicate, data=False): yield key def edges(self, data=False): - """Iterate over the edges of the network. + """Iterate over the edges of the graph. Parameters ---------- @@ -874,8 +1184,30 @@ def edges_where_predicate(self, predicate, data=False): else: yield key + def shortest_path(self, u, v): + """Find the shortest path between two nodes using the A* algorithm. + + Parameters + ---------- + u : hashable + The identifier of the start node. + v : hashable + The identifier of the end node. + + Returns + ------- + list[hashable] | None + The path from root to goal, or None, if no path exists between the vertices. + + See Also + -------- + :meth:`compas.topology.astar_shortest_path` + + """ + return astar_shortest_path(self.adjacency, u, v) + # -------------------------------------------------------------------------- - # default attributes + # Default attributes # -------------------------------------------------------------------------- def update_default_node_attributes(self, attr_dict=None, **kwattr): @@ -1344,7 +1676,7 @@ def edges_attributes(self, names=None, values=None, keys=None): # -------------------------------------------------------------------------- def has_node(self, key): - """Verify if a specific node is present in the network. + """Verify if a specific node is present in the graph. Parameters ---------- @@ -1389,7 +1721,7 @@ def is_leaf(self, key): return self.degree(key) == 1 def leaves(self): - """Return all leaves of the network. + """Return all leaves of the graph. Returns ------- @@ -1601,7 +1933,7 @@ def node_edges(self, key): # -------------------------------------------------------------------------- def has_edge(self, edge, directed=True): - """Verify if the network contains a specific edge. + """Verify if the graph contains a specific edge. Parameters ---------- @@ -1625,6 +1957,320 @@ def has_edge(self, edge, directed=True): return u in self.edge and v in self.edge[u] return (u in self.edge and v in self.edge[u]) or (v in self.edge and u in self.edge[v]) + # -------------------------------------------------------------------------- + # Node geometry + # -------------------------------------------------------------------------- + + def node_coordinates(self, key, axes="xyz"): + """Return the coordinates of a node. + + Parameters + ---------- + key : hashable + The identifier of the node. + axes : str, optional + The components of the node coordinates to return. + + Returns + ------- + list[float] + The coordinates of the node. + + See Also + -------- + :meth:`node_point`, :meth:`node_laplacian`, :meth:`node_neighborhood_centroid` + + """ + return [self.node[key][axis] for axis in axes] + + def node_point(self, node): + """Return the point of a node. + + Parameters + ---------- + node : hashable + The identifier of the node. + + Returns + ------- + :class:`compas.geometry.Point` + The point of the node. + + See Also + -------- + :meth:`node_coordinates`, :meth:`node_laplacian`, :meth:`node_neighborhood_centroid` + + """ + return Point(*self.node_coordinates(node)) + + def node_laplacian(self, key): + """Return the vector from the node to the centroid of its 1-ring neighborhood. + + Parameters + ---------- + key : hashable + The identifier of the node. + + Returns + ------- + :class:`compas.geometry.Vector` + The laplacian vector. + + See Also + -------- + :meth:`node_coordinates`, :meth:`node_point`, :meth:`node_neighborhood_centroid` + + """ + c = centroid_points([self.node_coordinates(nbr) for nbr in self.neighbors(key)]) + p = self.node_coordinates(key) + return Vector(*subtract_vectors(c, p)) + + def node_neighborhood_centroid(self, key): + """Return the computed centroid of the neighboring nodes. + + Parameters + ---------- + key : hashable + The identifier of the node. + + Returns + ------- + :class:`compas.geometry.Point` + The point at the centroid. + + See Also + -------- + :meth:`node_coordinates`, :meth:`node_point`, :meth:`node_laplacian` + + """ + return Point(*centroid_points([self.node_coordinates(nbr) for nbr in self.neighbors(key)])) + + # -------------------------------------------------------------------------- + # Edge geometry + # -------------------------------------------------------------------------- + + def edge_coordinates(self, edge, axes="xyz"): + """Return the coordinates of the start and end point of an edge. + + Parameters + ---------- + edge : tuple[hashable, hashable] + The identifier of the edge. + axes : str, optional + The axes along which the coordinates should be included. + + Returns + ------- + tuple[list[float], list[float]] + The coordinates of the start point. + The coordinates of the end point. + + See Also + -------- + :meth:`edge_point`, :meth:`edge_start`, :meth:`edge_end`, :meth:`edge_midpoint` + + """ + u, v = edge + return self.node_coordinates(u, axes=axes), self.node_coordinates(v, axes=axes) + + def edge_start(self, edge): + """Return the start point of an edge. + + Parameters + ---------- + edge : tuple[hashable, hashable] + The identifier of the edge. + + Returns + ------- + :class:`compas.geometry.Point` + The start point of the edge. + + See Also + -------- + :meth:`edge_point`, :meth:`edge_end`, :meth:`edge_midpoint` + + """ + return self.node_point(edge[0]) + + def edge_end(self, edge): + """Return the end point of an edge. + + Parameters + ---------- + edge : tuple[hashable, hashable] + The identifier of the edge. + + Returns + ------- + :class:`compas.geometry.Point` + The end point of the edge. + + See Also + -------- + :meth:`edge_point`, :meth:`edge_start`, :meth:`edge_midpoint` + + """ + return self.node_point(edge[1]) + + def edge_point(self, edge, t=0.5): + """Return the point at a parametric location along an edge. + + Parameters + ---------- + edge : tuple[hashable, hashable] + The identifier of the edge. + t : float, optional + The location of the point on the edge. + If the value of `t` is outside the range 0-1, the point will + lie in the direction of the edge, but not on the edge vector. + + Returns + ------- + :class:`compas.geometry.Point` + The point at the specified location. + + See Also + -------- + :meth:`edge_start`, :meth:`edge_end`, :meth:`edge_midpoint` + + """ + if t == 0.0: + return self.edge_start(edge) + if t == 1.0: + return self.edge_end(edge) + if t == 0.5: + return self.edge_midpoint(edge) + + a, b = self.edge_coordinates(edge) + ab = subtract_vectors(b, a) + return Point(*add_vectors(a, scale_vector(ab, t))) + + def edge_midpoint(self, edge): + """Return the location of the midpoint of an edge. + + Parameters + ---------- + edge : tuple[hashable, hashable] + The identifier of the edge. + + Returns + ------- + :class:`compas.geometry.Point` + The midpoint of the edge. + + See Also + -------- + :meth:`edge_start`, :meth:`edge_end`, :meth:`edge_point` + + """ + a, b = self.edge_coordinates(edge) + return Point(*midpoint_line((a, b))) + + def edge_vector(self, edge): + """Return the vector of an edge. + + Parameters + ---------- + edge : tuple[hashable, hashable] + The identifier of the edge. + + Returns + ------- + :class:`compas.geometry.Vector` + The vector from start to end. + + See Also + -------- + :meth:`edge_direction`, :meth:`edge_line`, :meth:`edge_length` + + """ + a, b = self.edge_coordinates(edge) + return Vector.from_start_end(a, b) + + def edge_direction(self, edge): + """Return the direction vector of an edge. + + Parameters + ---------- + edge : tuple[hashable, hashable] + The identifier of the edge. + + Returns + ------- + :class:`compas.geometry.Vector` + The direction vector of the edge. + + See Also + -------- + :meth:`edge_vector`, :meth:`edge_line`, :meth:`edge_length` + + """ + return Vector(*normalize_vector(self.edge_vector(edge))) + + def edge_line(self, edge): + """Return the line of an edge. + + Parameters + ---------- + edge : tuple[hashable, hashable] + The identifier of the edge. + + Returns + ------- + :class:`compas.geometry.Line` + The line of the edge. + + See Also + -------- + :meth:`edge_vector`, :meth:`edge_direction`, :meth:`edge_length` + + """ + return Line(*self.edge_coordinates(edge)) + + def edge_length(self, edge): + """Return the length of an edge. + + Parameters + ---------- + edge : tuple[hashable, hashable] + The identifier of the edge. + + Returns + ------- + float + The length of the edge. + + See Also + -------- + :meth:`edge_vector`, :meth:`edge_direction`, :meth:`edge_line` + + """ + a, b = self.edge_coordinates(edge) + return distance_point_point(a, b) + + # -------------------------------------------------------------------------- + # Transformations + # -------------------------------------------------------------------------- + + def transform(self, transformation): + """Transform all nodes of the graph. + + Parameters + ---------- + transformation : :class:`Transformation` + The transformation used to transform the nodes. + + Returns + ------- + None + + """ + nodes = self.nodes_attributes("xyz") + points = transform_points(nodes, transformation) + for point, node in zip(points, self.nodes()): + self.node_attributes(node, "xyz", point) + # -------------------------------------------------------------------------- # Other Methods # -------------------------------------------------------------------------- @@ -1699,10 +2345,10 @@ def complement(self): Examples -------- >>> import compas - >>> from compas.datastructures import Network - >>> network = Network.from_obj(compas.get('lines.obj')) - >>> complement = network.complement() - >>> any(complement.has_edge(u, v, directed=False) for u, v in network.edges()) + >>> from compas.datastructures import Graph + >>> graph = Graph.from_obj(compas.get('lines.obj')) + >>> complement = graph.complement() + >>> any(complement.has_edge(u, v, directed=False) for u, v in graph.edges()) False """ @@ -1726,7 +2372,7 @@ def complement(self): # -------------------------------------------------------------------------- def adjacency_matrix(self, rtype="array"): - """Creates a node adjacency matrix from a Network datastructure. + """Creates a node adjacency matrix from a Graph datastructure. Parameters ---------- @@ -1746,7 +2392,7 @@ def adjacency_matrix(self, rtype="array"): return adjacency_matrix(adjacency, rtype=rtype) def connectivity_matrix(self, rtype="array"): - """Creates a connectivity matrix from a Network datastructure. + """Creates a connectivity matrix from a Graph datastructure. Parameters ---------- @@ -1766,7 +2412,7 @@ def connectivity_matrix(self, rtype="array"): return connectivity_matrix(edges, rtype=rtype) def degree_matrix(self, rtype="array"): - """Creates a degree matrix from a Network datastructure. + """Creates a degree matrix from a Graph datastructure. Parameters ---------- @@ -1786,7 +2432,7 @@ def degree_matrix(self, rtype="array"): return degree_matrix(adjacency, rtype=rtype) def laplacian_matrix(self, normalize=False, rtype="array"): - """Creates a Laplacian matrix from a Network datastructure. + """Creates a Laplacian matrix from a Graph datastructure. Parameters ---------- diff --git a/src/compas/datastructures/network/__init__.py b/src/compas/datastructures/graph/operations/__init__.py similarity index 100% rename from src/compas/datastructures/network/__init__.py rename to src/compas/datastructures/graph/operations/__init__.py diff --git a/src/compas/datastructures/network/operations/join.py b/src/compas/datastructures/graph/operations/join.py similarity index 63% rename from src/compas/datastructures/network/operations/join.py rename to src/compas/datastructures/graph/operations/join.py index ee26bcebacbc..9a06ecbdf36d 100644 --- a/src/compas/datastructures/network/operations/join.py +++ b/src/compas/datastructures/graph/operations/join.py @@ -6,20 +6,20 @@ from compas.utilities import pairwise -def network_join_edges(network, key): +def graph_join_edges(graph, key): """Join the edges incidental on the given node, if there are exactly two incident edges. Parameters ---------- - network : :class:`compas.geometry.Network` - A network data structure. + graph : :class:`compas.geometry.Graph` + A graph data structure. key : hashable The node identifier. Returns ------- None - The network is modified in place. + The graph is modified in place. Notes ----- @@ -28,39 +28,39 @@ def network_join_edges(network, key): Therefore, the new edge has only default edge attributes. """ - nbrs = network.vertex_neighbors(key) + nbrs = graph.vertex_neighbors(key) if len(nbrs) != 2: return a, b = nbrs - if a in network.edge[key]: - del network.edge[key][a] + if a in graph.edge[key]: + del graph.edge[key][a] else: - del network.edge[a][key] - del network.halfedge[key][a] - del network.halfedge[a][key] - if b in network.edge[key]: - del network.edge[key][b] + del graph.edge[a][key] + del graph.halfedge[key][a] + del graph.halfedge[a][key] + if b in graph.edge[key]: + del graph.edge[key][b] else: - del network.edge[b][key] - del network.halfedge[key][b] - del network.halfedge[b][key] - del network.vertex[key] - del network.halfedge[key] - del network.edge[key] + del graph.edge[b][key] + del graph.halfedge[key][b] + del graph.halfedge[b][key] + del graph.vertex[key] + del graph.halfedge[key] + del graph.edge[key] # set attributes based on average of two joining edges? - network.add_edge((a, b)) + graph.add_edge((a, b)) -def network_polylines(network, splits=None): - """Join network edges into polylines. +def graph_polylines(graph, splits=None): + """Join graph edges into polylines. - The polylines stop at points with a valency different from 2 in the network of line. + The polylines stop at points with a valency different from 2 in the graph of line. Optional splits can be included. Parameters ---------- - network : Network - A network. + graph : Graph + A graph. splits : sequence[[float, float, float] | :class:`compas.geometry.Point`], optional List of point coordinates for polyline splits. @@ -76,7 +76,7 @@ def network_polylines(network, splits=None): where a ... f are different point coordinates. This will result in the following polylines (a, b, c), (c, d) and (c, e, f). - >>> from compas.datastructures import Network + >>> from compas.datastructures import Graph >>> a = [0., 0., 0.] >>> b = [1., 0., 0.] >>> c = [2., 0., 0.] @@ -84,8 +84,8 @@ def network_polylines(network, splits=None): >>> e = [3., 0., 0.] >>> f = [4., 0., 0.] >>> lines = [(a, b), (b, c), (c, d), (c, e), (e, f)] - >>> network = Network.from_lines(lines) - >>> len(network_polylines(network)) == 3 + >>> graph = Graph.from_lines(lines) + >>> len(graph_polylines(graph)) == 3 True """ @@ -95,7 +95,7 @@ def network_polylines(network, splits=None): stop_geom_keys = set([TOL.geometric_key(xyz) for xyz in splits]) polylines = [] - edges_to_visit = set(network.edges()) + edges_to_visit = set(graph.edges()) # initiate a polyline from an unvisited edge while len(edges_to_visit) > 0: @@ -105,18 +105,18 @@ def network_polylines(network, splits=None): while polyline[0] != polyline[-1]: # ... or until both end are non-two-valent vertices if ( - len(network.neighbors(polyline[-1])) != 2 - or TOL.geometric_key(network.node_coordinates(polyline[-1])) in stop_geom_keys + len(graph.neighbors(polyline[-1])) != 2 + or TOL.geometric_key(graph.node_coordinates(polyline[-1])) in stop_geom_keys ): polyline = list(reversed(polyline)) if ( - len(network.neighbors(polyline[-1])) != 2 - or TOL.geometric_key(network.node_coordinates(polyline[-1])) in stop_geom_keys + len(graph.neighbors(polyline[-1])) != 2 + or TOL.geometric_key(graph.node_coordinates(polyline[-1])) in stop_geom_keys ): break # add next edge - polyline.append([nbr for nbr in network.neighbors(polyline[-1]) if nbr != polyline[-2]][0]) + polyline.append([nbr for nbr in graph.neighbors(polyline[-1]) if nbr != polyline[-2]][0]) # delete polyline edges from the list of univisted edges for u, v in pairwise(polyline): @@ -127,4 +127,4 @@ def network_polylines(network, splits=None): polylines.append(polyline) - return [[network.node_coordinates(vkey) for vkey in polyline] for polyline in polylines] + return [[graph.node_coordinates(vkey) for vkey in polyline] for polyline in polylines] diff --git a/src/compas/datastructures/network/operations/split.py b/src/compas/datastructures/graph/operations/split.py similarity index 59% rename from src/compas/datastructures/network/operations/split.py rename to src/compas/datastructures/graph/operations/split.py index 3eaebd3a523f..6e1c64e858db 100644 --- a/src/compas/datastructures/network/operations/split.py +++ b/src/compas/datastructures/graph/operations/split.py @@ -3,7 +3,7 @@ from __future__ import division -def network_split_edge(network, edge, t=0.5): +def graph_split_edge(graph, edge, t=0.5): """Split and edge by inserting a node along its length. Parameters @@ -23,11 +23,11 @@ def network_split_edge(network, edge, t=0.5): ValueError If `t` is not in the range 0-1. Exception - If the edge is not part of the network. + If the edge is not part of the graph. """ u, v = edge - if not network.has_edge(u, v): + if not graph.has_edge(u, v): return if t <= 0.0: @@ -36,28 +36,28 @@ def network_split_edge(network, edge, t=0.5): raise ValueError("t should be smaller than 1.0.") # the split node - x, y, z = network.edge_point(edge, t) - w = network.add_node(x=x, y=y, z=z) + x, y, z = graph.edge_point(edge, t) + w = graph.add_node(x=x, y=y, z=z) - network.add_edge((u, w)) - network.add_edge((w, v)) + graph.add_edge((u, w)) + graph.add_edge((w, v)) - if v in network.edge[u]: - del network.edge[u][v] - elif u in network.edge[v]: - del network.edge[v][u] + if v in graph.edge[u]: + del graph.edge[u][v] + elif u in graph.edge[v]: + del graph.edge[v][u] else: raise Exception # split half-edge UV - network.adjacency[u][w] = None - network.adjacency[w][v] = None - del network.adjacency[u][v] + graph.adjacency[u][w] = None + graph.adjacency[w][v] = None + del graph.adjacency[u][v] # split half-edge VU - network.adjacency[v][w] = None - network.adjacency[w][u] = None - del network.adjacency[v][u] + graph.adjacency[v][w] = None + graph.adjacency[w][u] = None + del graph.adjacency[v][u] # return the key of the split node return w diff --git a/src/compas/datastructures/network/planarity.py b/src/compas/datastructures/graph/planarity.py similarity index 59% rename from src/compas/datastructures/network/planarity.py rename to src/compas/datastructures/graph/planarity.py index aec0c9c1614a..7e6dfa113937 100644 --- a/src/compas/datastructures/network/planarity.py +++ b/src/compas/datastructures/graph/planarity.py @@ -15,40 +15,40 @@ from compas.geometry._core.predicates_2 import is_intersection_segment_segment_xy -def network_embed_in_plane_proxy(data, fixed=None, straightline=True): - from compas.datastructures import Network +def graph_embed_in_plane_proxy(data, fixed=None, straightline=True): + from compas.datastructures import Graph - network = Network.from_data(data) - network_embed_in_plane(network, fixed=fixed, straightline=straightline) - return network.to_data() + graph = Graph.from_data(data) + graph_embed_in_plane(graph, fixed=fixed, straightline=straightline) + return graph.to_data() -def network_is_crossed(network): - """Verify if a network has crossing edges. +def graph_is_crossed(graph): + """Verify if a graph has crossing edges. Parameters ---------- - network : :class:`compas.datastructures.Network` - A network object. + graph : :class:`compas.datastructures.Graph` + A graph object. Returns ------- bool - True if the network has at least one pair of crossing edges. + True if the graph has at least one pair of crossing edges. False otherwise. Notes ----- - This algorithm assumes that the network lies in the XY plane. + This algorithm assumes that the graph lies in the XY plane. """ - for (u1, v1), (u2, v2) in product(network.edges(), network.edges()): + for (u1, v1), (u2, v2) in product(graph.edges(), graph.edges()): if u1 == u2 or v1 == v2 or u1 == v2 or u2 == v1: continue - a = network.node_attributes(u1, "xy") - b = network.node_attributes(v1, "xy") - c = network.node_attributes(u2, "xy") - d = network.node_attributes(v2, "xy") + a = graph.node_attributes(u1, "xy") + b = graph.node_attributes(v1, "xy") + c = graph.node_attributes(u2, "xy") + d = graph.node_attributes(v2, "xy") if is_intersection_segment_segment_xy((a, b), (c, d)): return True return False @@ -67,13 +67,13 @@ def _are_edges_crossed(edges, vertices): return False -def network_count_crossings(network): - """Count the number of crossings (pairs of crossing edges) in the network. +def graph_count_crossings(graph): + """Count the number of crossings (pairs of crossing edges) in the graph. Parameters ---------- - network : :class:`compas.datastructures.Network` - A network object. + graph : :class:`compas.datastructures.Graph` + A graph object. Returns ------- @@ -82,19 +82,19 @@ def network_count_crossings(network): Notes ----- - This algorithm assumes that the network lies in the XY plane. + This algorithm assumes that the graph lies in the XY plane. """ - return len(network_find_crossings(network)) + return len(graph_find_crossings(graph)) -def network_find_crossings(network): - """Identify all pairs of crossing edges in a network. +def graph_find_crossings(graph): + """Identify all pairs of crossing edges in a graph. Parameters ---------- - network : :class:`compas.datastructures.Network` - A network object. + graph : :class:`compas.datastructures.Graph` + A graph object. Returns ------- @@ -103,33 +103,33 @@ def network_find_crossings(network): Notes ----- - This algorithm assumes that the network lies in the XY plane. + This algorithm assumes that the graph lies in the XY plane. """ crossings = set() - for (u1, v1), (u2, v2) in product(network.edges(), network.edges()): + for (u1, v1), (u2, v2) in product(graph.edges(), graph.edges()): if u1 == u2 or v1 == v2 or u1 == v2 or u2 == v1: continue if ((u1, v1), (u2, v2)) in crossings: continue if ((u2, v2), (u1, v1)) in crossings: continue - a = network.node_attributes(u1, "xy") - b = network.node_attributes(v1, "xy") - c = network.node_attributes(u2, "xy") - d = network.node_attributes(v2, "xy") + a = graph.node_attributes(u1, "xy") + b = graph.node_attributes(v1, "xy") + c = graph.node_attributes(u2, "xy") + d = graph.node_attributes(v2, "xy") if is_intersection_segment_segment_xy((a, b), (c, d)): crossings.add(((u1, v1), (u2, v2))) return list(crossings) -def network_is_xy(network): - """Verify that a network lies in the XY plane. +def graph_is_xy(graph): + """Verify that a graph lies in the XY plane. Parameters ---------- - network : :class:`compas.datastructures.Network` - A network object. + graph : :class:`compas.datastructures.Graph` + A graph object. Returns ------- @@ -139,27 +139,27 @@ def network_is_xy(network): """ z = None - for key in network.nodes(): + for key in graph.nodes(): if z is None: - z = network.node_attribute(key, "z") or 0.0 + z = graph.node_attribute(key, "z") or 0.0 else: - if z != network.node_attribute(key, "z") or 0.0: + if z != graph.node_attribute(key, "z") or 0.0: return False return True -def network_is_planar(network): - """Check if the network is planar. +def graph_is_planar(graph): + """Check if the graph is planar. Parameters ---------- - network : :class:`compas.datastructures.Network` - A network object. + graph : :class:`compas.datastructures.Graph` + A graph object. Returns ------- bool - True if the network is planar. + True if the graph is planar. False otherwise. Raises @@ -169,8 +169,8 @@ def network_is_planar(network): Notes ----- - A network is planar if it can be drawn in the plane without crossing edges. - If a network is planar, it can be shown that an embedding of the network in + A graph is planar if it can be drawn in the plane without crossing edges. + If a graph is planar, it can be shown that an embedding of the graph in the plane exists, and, furthermore, that straight-line embedding in the plane exists. @@ -181,35 +181,35 @@ def network_is_planar(network): print("NetworkX is not installed.") raise - nxgraph = network.to_networkx() + nxgraph = graph.to_networkx() return nx.is_planar(nxgraph) -def network_is_planar_embedding(network): - """Verify that a network is embedded in the plane without crossing edges. +def graph_is_planar_embedding(graph): + """Verify that a graph is embedded in the plane without crossing edges. Parameters ---------- - network : :class:`compas.datastructures.Network` - A network object. + graph : :class:`compas.datastructures.Graph` + A graph object. Returns ------- bool - True if the network is embedded in the plane without crossing edges. + True if the graph is embedded in the plane without crossing edges. Fase otherwise. """ - return network_is_planar(network) and network_is_xy(network) and not network_is_crossed(network) + return graph_is_planar(graph) and graph_is_xy(graph) and not graph_is_crossed(graph) -def network_embed_in_plane(network, fixed=None, straightline=True): - """Embed the network in the plane. +def graph_embed_in_plane(graph, fixed=None, straightline=True): + """Embed the graph in the plane. Parameters ---------- - network : :class:`compas.datastructures.Network` - A network object. + graph : :class:`compas.datastructures.Graph` + A graph object. fixed : [hashable, hashable], optional Two fixed points. straightline : bool, optional @@ -233,14 +233,14 @@ def network_embed_in_plane(network, fixed=None, straightline=True): print("NetworkX is not installed. Get NetworkX at https://networkx.github.io/.") raise - x = network.nodes_attribute("x") - y = network.nodes_attribute("y") + x = graph.nodes_attribute("x") + y = graph.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)] + edges = [(u, v) for u, v in graph.edges() if not graph.is_leaf(u) and not graph.is_leaf(v)] is_embedded = False pos = {} @@ -262,8 +262,8 @@ def network_embed_in_plane(network, fixed=None, straightline=True): if fixed: a, b = fixed - p0 = network.node_attributes(a, "xy") - p1 = network.node_attributes(b, "xy") + p0 = graph.node_attributes(a, "xy") + p1 = graph.node_attributes(b, "xy") p2 = pos[b] vec0 = subtract_vectors_xy(p1, p0) vec1 = subtract_vectors_xy(pos[b], pos[a]) @@ -290,9 +290,9 @@ def network_embed_in_plane(network, fixed=None, straightline=True): pos[key][0] += t[0] pos[key][1] += t[1] - # update network node coordinates - for key in network.nodes(): + # update graph node coordinates + for key in graph.nodes(): if key in pos: - network.node_attributes(key, "xy", pos[key]) + graph.node_attributes(key, "xy", pos[key]) return True diff --git a/src/compas/datastructures/network/smoothing.py b/src/compas/datastructures/graph/smoothing.py similarity index 73% rename from src/compas/datastructures/network/smoothing.py rename to src/compas/datastructures/graph/smoothing.py index 271fd53d2827..c91349bfb850 100644 --- a/src/compas/datastructures/network/smoothing.py +++ b/src/compas/datastructures/graph/smoothing.py @@ -5,15 +5,15 @@ from compas.geometry import centroid_points -def network_smooth_centroid(network, fixed=None, kmax=100, damping=0.5, callback=None, callback_args=None): - """Smooth a network by moving every free node to the centroid of its neighbors. +def graph_smooth_centroid(graph, fixed=None, kmax=100, damping=0.5, callback=None, callback_args=None): + """Smooth a graph by moving every free node to the centroid of its neighbors. Parameters ---------- - network : Mesh - A network object. + graph : Mesh + A graph object. fixed : list, optional - The fixed nodes of the network. + The fixed nodes of the graph. kmax : int, optional The maximum number of iterations. damping : float, optional @@ -41,15 +41,15 @@ def network_smooth_centroid(network, fixed=None, kmax=100, damping=0.5, callback fixed = set(fixed) for k in range(kmax): - key_xyz = {key: network.node_coordinates(key) for key in network.nodes()} + key_xyz = {key: graph.node_coordinates(key) for key in graph.nodes()} - for key, attr in network.nodes(True): + for key, attr in graph.nodes(True): if key in fixed: continue x, y, z = key_xyz[key] - cx, cy, cz = centroid_points([key_xyz[nbr] for nbr in network.neighbors(key)]) + cx, cy, cz = centroid_points([key_xyz[nbr] for nbr in graph.neighbors(key)]) attr["x"] += damping * (cx - x) attr["y"] += damping * (cy - y) diff --git a/src/compas/datastructures/mesh/mesh.py b/src/compas/datastructures/mesh/mesh.py index 9d41369231da..d30e065ed803 100644 --- a/src/compas/datastructures/mesh/mesh.py +++ b/src/compas/datastructures/mesh/mesh.py @@ -358,11 +358,11 @@ def from_lines(cls, lines, delete_boundary_face=False, precision=None): # type: A mesh object. """ - from compas.datastructures import Network + from compas.datastructures import Graph - network = Network.from_lines(lines, precision=precision) - vertices = network.to_points() - faces = network.find_cycles() + graph = Graph.from_lines(lines, precision=precision) + vertices = graph.to_points() + faces = graph.find_cycles() mesh = cls.from_vertices_and_faces(vertices, faces) if delete_boundary_face: mesh.delete_face(0) diff --git a/src/compas/datastructures/network/network.py b/src/compas/datastructures/network/network.py deleted file mode 100644 index ab989d8f87b8..000000000000 --- a/src/compas/datastructures/network/network.py +++ /dev/null @@ -1,738 +0,0 @@ -from __future__ import print_function -from __future__ import absolute_import -from __future__ import division - -import compas - -if compas.PY2: - from collections import Mapping -else: - from collections.abc import Mapping - -from compas.tolerance import TOL -from compas.files import OBJ -from compas.geometry import Point -from compas.geometry import Vector -from compas.geometry import Line -from compas.geometry import centroid_points -from compas.geometry import subtract_vectors -from compas.geometry import distance_point_point -from compas.geometry import midpoint_line -from compas.geometry import normalize_vector -from compas.geometry import add_vectors -from compas.geometry import scale_vector -from compas.geometry import transform_points -from compas.topology import astar_shortest_path - -from compas.datastructures import Graph - -from .operations.split import network_split_edge -from .operations.join import network_join_edges - -from .planarity import network_is_crossed -from .planarity import network_is_planar -from .planarity import network_is_planar_embedding -from .planarity import network_is_xy -from .planarity import network_count_crossings -from .planarity import network_find_crossings -from .planarity import network_embed_in_plane -from .smoothing import network_smooth_centroid -from .duality import network_find_cycles - - -class Network(Graph): - """Geometric implementation of an edge graph. - - Parameters - ---------- - default_node_attributes: dict, optional - Default values for node attributes. - default_edge_attributes: dict, optional - Default values for edge attributes. - **kwargs : dict, optional - Additional attributes to add to the network. - - """ - - split_edge = network_split_edge - join_edges = network_join_edges - smooth = network_smooth_centroid - - is_crossed = network_is_crossed - is_planar = network_is_planar - is_planar_embedding = network_is_planar_embedding - is_xy = network_is_xy - count_crossings = network_count_crossings - find_crossings = network_find_crossings - embed_in_plane = network_embed_in_plane - - find_cycles = network_find_cycles - - def __init__(self, default_node_attributes=None, default_edge_attributes=None, **kwargs): - _default_node_attributes = {"x": 0.0, "y": 0.0, "z": 0.0} - _default_edge_attributes = {} - if default_node_attributes: - _default_node_attributes.update(default_node_attributes) - if default_edge_attributes: - _default_edge_attributes.update(default_edge_attributes) - super(Network, self).__init__( - default_node_attributes=_default_node_attributes, default_edge_attributes=_default_edge_attributes, **kwargs - ) - - def __str__(self): - tpl = "" - return tpl.format(self.number_of_nodes(), self.number_of_edges()) - - # -------------------------------------------------------------------------- - # customisation - # -------------------------------------------------------------------------- - - # -------------------------------------------------------------------------- - # special properties - # -------------------------------------------------------------------------- - - # -------------------------------------------------------------------------- - # constructors - # -------------------------------------------------------------------------- - - @classmethod - def from_obj(cls, filepath, precision=None): - """Construct a network from the data contained in an OBJ file. - - Parameters - ---------- - filepath : path string | file-like object | URL string - A path, a file-like object or a URL pointing to a file. - precision: str, optional - The precision of the geometric map that is used to connect the lines. - - Returns - ------- - :class:`compas.datastructures.Network` - A network object. - - See Also - -------- - :meth:`to_obj` - :meth:`from_lines`, :meth:`from_nodes_and_edges`, :meth:`from_pointcloud` - :class:`compas.files.OBJ` - - """ - network = cls() - obj = OBJ(filepath, precision) - obj.read() - nodes = obj.vertices - edges = obj.lines - for i, (x, y, z) in enumerate(nodes): # type: ignore - network.add_node(i, x=x, y=y, z=z) - for edge in edges: # type: ignore - network.add_edge(*edge) - return network - - @classmethod - def from_lines(cls, lines, precision=None): - """Construct a network from a set of lines represented by their start and end point coordinates. - - Parameters - ---------- - lines : list[tuple[list[float, list[float]]]] - A list of pairs of point coordinates. - precision : int, optional - Precision for converting numbers to strings. - Default is :attr:`TOL.precision`. - - Returns - ------- - :class:`compas.datastructures.Network` - A network object. - - See Also - -------- - :meth:`to_lines` - :meth:`from_obj`, :meth:`from_nodes_and_edges`, :meth:`from_pointcloud` - - """ - network = cls() - edges = [] - node = {} - for line in lines: - sp = line[0] - ep = line[1] - a = TOL.geometric_key(sp, precision) - b = TOL.geometric_key(ep, precision) - node[a] = sp - node[b] = ep - edges.append((a, b)) - key_index = dict((k, i) for i, k in enumerate(iter(node))) - for key, xyz in iter(node.items()): - i = key_index[key] - network.add_node(i, x=xyz[0], y=xyz[1], z=xyz[2]) - for u, v in edges: - i = key_index[u] - j = key_index[v] - network.add_edge(i, j) - return network - - @classmethod - def from_nodes_and_edges(cls, nodes, edges): - """Construct a network from nodes and edges. - - Parameters - ---------- - nodes : list[list[float]] | dict[hashable, list[float]] - A list of node coordinates or a dictionary of keys pointing to node coordinates to specify keys. - edges : list[tuple[hashable, hshable]] - - Returns - ------- - :class:`compas.datastructures.Network` - A network object. - - See Also - -------- - :meth:`to_nodes_and_edges` - :meth:`from_obj`, :meth:`from_lines`, :meth:`from_pointcloud` - - """ - network = cls() - - if isinstance(nodes, Mapping): - for key, (x, y, z) in nodes.items(): - network.add_node(key, x=x, y=y, z=z) - else: - for i, (x, y, z) in enumerate(nodes): - network.add_node(i, x=x, y=y, z=z) - - for u, v in edges: - network.add_edge(u, v) - - return network - - @classmethod - def from_pointcloud(cls, cloud, degree=3): - """Construct a network from random connections between the points of a pointcloud. - - Parameters - ---------- - cloud : :class:`compas.geometry.Pointcloud` - A pointcloud object. - degree : int, optional - The number of connections per node. - - Returns - ------- - :class:`compas.datastructures.Network` - A network object. - - See Also - -------- - :meth:`to_points` - :meth:`from_obj`, :meth:`from_lines`, :meth:`from_nodes_and_edges` - - """ - network = cls() - for x, y, z in cloud: - network.add_node(x=x, y=y, z=z) - for u in network.nodes(): - for v in network.node_sample(size=degree): - network.add_edge(u, v) - return network - - # -------------------------------------------------------------------------- - # converters - # -------------------------------------------------------------------------- - - def to_obj(self): - """Write the network to an OBJ file. - - Parameters - ---------- - filepath : path string | file-like object - A path or a file-like object pointing to a file. - - Returns - ------- - None - - See Also - -------- - :meth:`from_obj` - :meth:`to_lines`, :meth:`to_nodes_and_edges`, :meth:`to_points` - - """ - raise NotImplementedError - - def to_points(self): - """Return the coordinates of the network. - - Returns - ------- - list[list[float]] - A list with the coordinates of the vertices of the network. - - See Also - -------- - :meth:`from_pointcloud` - :meth:`to_lines`, :meth:`to_nodes_and_edges`, :meth:`to_obj` - - """ - return [self.node_coordinates(key) for key in self.nodes()] - - def to_lines(self): - """Return the lines of the network as pairs of start and end point coordinates. - - Returns - ------- - list[tuple[list[float], list[float]]] - A list of lines each defined by a pair of point coordinates. - - See Also - -------- - :meth:`from_lines` - :meth:`to_nodes_and_edges`, :meth:`to_obj`, :meth:`to_points` - - """ - return [self.edge_coordinates(edge) for edge in self.edges()] - - def to_nodes_and_edges(self): - """Return the nodes and edges of a network. - - Returns - ------- - list[list[float]] - A list of nodes, represented by their XYZ coordinates. - list[tuple[hashable, hashable]] - A list of edges, with each edge represented by a pair of indices in the node list. - - See Also - -------- - :meth:`from_nodes_and_edges` - :meth:`to_lines`, :meth:`to_obj`, :meth:`to_points` - - """ - key_index = dict((key, index) for index, key in enumerate(self.nodes())) - nodes = [self.node_coordinates(key) for key in self.nodes()] - edges = [(key_index[u], key_index[v]) for u, v in self.edges()] - return nodes, edges - - # -------------------------------------------------------------------------- - # helpers - # -------------------------------------------------------------------------- - - def node_gkey(self, precision=None): - """Returns a dictionary that maps node identifiers to the corresponding - *geometric key* up to a certain precision. - - Parameters - ---------- - precision : int, optional - Precision for converting numbers to strings. - Default is :attr:`TOL.precision`. - - Returns - ------- - dict[hashable, str] - A dictionary of (node, geometric key) pairs. - - See Also - -------- - :meth:`gkey_node` - :meth:`compas.Tolerance.geometric_key` - - """ - gkey = TOL.geometric_key - xyz = self.node_coordinates - return {key: gkey(xyz(key), precision) for key in self.nodes()} - - def gkey_node(self, precision=None): - """Returns a dictionary that maps *geometric keys* of a certain precision - to the identifiers of the corresponding nodes. - - Parameters - ---------- - precision : int, optional - Precision for converting numbers to strings. - Default is :attr:`TOL.precision`. - - Returns - ------- - dict[str, hashable] - A dictionary of (geometric key, node) pairs. - - See Also - -------- - :meth:`node_gkey` - :meth:`compas.Tolerance.geometric_key` - - """ - gkey = TOL.geometric_key - xyz = self.node_coordinates - return {gkey(xyz(key), precision): key for key in self.nodes()} - - # -------------------------------------------------------------------------- - # builders - # -------------------------------------------------------------------------- - - # -------------------------------------------------------------------------- - # modifiers - # -------------------------------------------------------------------------- - - # -------------------------------------------------------------------------- - # info - # -------------------------------------------------------------------------- - - # -------------------------------------------------------------------------- - # accessors - # -------------------------------------------------------------------------- - - def shortest_path(self, u, v): - """Find the shortest path between two nodes using the A* algorithm. - - Parameters - ---------- - u : hashable - The identifier of the start node. - v : hashable - The identifier of the end node. - - Returns - ------- - list[hashable] | None - The path from root to goal, or None, if no path exists between the vertices. - - See Also - -------- - :meth:`compas.topology.astar_shortest_path` - - """ - return astar_shortest_path(self.adjacency, u, v) - - # -------------------------------------------------------------------------- - # node attributes - # -------------------------------------------------------------------------- - - # -------------------------------------------------------------------------- - # edge attributes - # -------------------------------------------------------------------------- - - # -------------------------------------------------------------------------- - # node topology - # -------------------------------------------------------------------------- - - # -------------------------------------------------------------------------- - # edge topology - # -------------------------------------------------------------------------- - - # -------------------------------------------------------------------------- - # node geometry - # -------------------------------------------------------------------------- - - def node_coordinates(self, key, axes="xyz"): - """Return the coordinates of a node. - - Parameters - ---------- - key : hashable - The identifier of the node. - axes : str, optional - The components of the node coordinates to return. - - Returns - ------- - list[float] - The coordinates of the node. - - See Also - -------- - :meth:`node_point`, :meth:`node_laplacian`, :meth:`node_neighborhood_centroid` - - """ - return [self.node[key][axis] for axis in axes] - - def node_point(self, node): - """Return the point of a node. - - Parameters - ---------- - node : hashable - The identifier of the node. - - Returns - ------- - :class:`compas.geometry.Point` - The point of the node. - - See Also - -------- - :meth:`node_coordinates`, :meth:`node_laplacian`, :meth:`node_neighborhood_centroid` - - """ - return Point(*self.node_coordinates(node)) - - def node_laplacian(self, key): - """Return the vector from the node to the centroid of its 1-ring neighborhood. - - Parameters - ---------- - key : hashable - The identifier of the node. - - Returns - ------- - :class:`compas.geometry.Vector` - The laplacian vector. - - See Also - -------- - :meth:`node_coordinates`, :meth:`node_point`, :meth:`node_neighborhood_centroid` - - """ - c = centroid_points([self.node_coordinates(nbr) for nbr in self.neighbors(key)]) - p = self.node_coordinates(key) - return Vector(*subtract_vectors(c, p)) - - def node_neighborhood_centroid(self, key): - """Return the computed centroid of the neighboring nodes. - - Parameters - ---------- - key : hashable - The identifier of the node. - - Returns - ------- - :class:`compas.geometry.Point` - The point at the centroid. - - See Also - -------- - :meth:`node_coordinates`, :meth:`node_point`, :meth:`node_laplacian` - - """ - return Point(*centroid_points([self.node_coordinates(nbr) for nbr in self.neighbors(key)])) - - # -------------------------------------------------------------------------- - # edge geometry - # -------------------------------------------------------------------------- - - def edge_coordinates(self, edge, axes="xyz"): - """Return the coordinates of the start and end point of an edge. - - Parameters - ---------- - edge : tuple[hashable, hashable] - The identifier of the edge. - axes : str, optional - The axes along which the coordinates should be included. - - Returns - ------- - tuple[list[float], list[float]] - The coordinates of the start point. - The coordinates of the end point. - - See Also - -------- - :meth:`edge_point`, :meth:`edge_start`, :meth:`edge_end`, :meth:`edge_midpoint` - - """ - u, v = edge - return self.node_coordinates(u, axes=axes), self.node_coordinates(v, axes=axes) - - def edge_start(self, edge): - """Return the start point of an edge. - - Parameters - ---------- - edge : tuple[hashable, hashable] - The identifier of the edge. - - Returns - ------- - :class:`compas.geometry.Point` - The start point of the edge. - - See Also - -------- - :meth:`edge_point`, :meth:`edge_end`, :meth:`edge_midpoint` - - """ - return self.node_point(edge[0]) - - def edge_end(self, edge): - """Return the end point of an edge. - - Parameters - ---------- - edge : tuple[hashable, hashable] - The identifier of the edge. - - Returns - ------- - :class:`compas.geometry.Point` - The end point of the edge. - - See Also - -------- - :meth:`edge_point`, :meth:`edge_start`, :meth:`edge_midpoint` - - """ - return self.node_point(edge[1]) - - def edge_point(self, edge, t=0.5): - """Return the point at a parametric location along an edge. - - Parameters - ---------- - edge : tuple[hashable, hashable] - The identifier of the edge. - t : float, optional - The location of the point on the edge. - If the value of `t` is outside the range 0-1, the point will - lie in the direction of the edge, but not on the edge vector. - - Returns - ------- - :class:`compas.geometry.Point` - The point at the specified location. - - See Also - -------- - :meth:`edge_start`, :meth:`edge_end`, :meth:`edge_midpoint` - - """ - if t == 0.0: - return self.edge_start(edge) - if t == 1.0: - return self.edge_end(edge) - if t == 0.5: - return self.edge_midpoint(edge) - - a, b = self.edge_coordinates(edge) - ab = subtract_vectors(b, a) - return Point(*add_vectors(a, scale_vector(ab, t))) - - def edge_midpoint(self, edge): - """Return the location of the midpoint of an edge. - - Parameters - ---------- - edge : tuple[hashable, hashable] - The identifier of the edge. - - Returns - ------- - :class:`compas.geometry.Point` - The midpoint of the edge. - - See Also - -------- - :meth:`edge_start`, :meth:`edge_end`, :meth:`edge_point` - - """ - a, b = self.edge_coordinates(edge) - return Point(*midpoint_line((a, b))) - - def edge_vector(self, edge): - """Return the vector of an edge. - - Parameters - ---------- - edge : tuple[hashable, hashable] - The identifier of the edge. - - Returns - ------- - :class:`compas.geometry.Vector` - The vector from start to end. - - See Also - -------- - :meth:`edge_direction`, :meth:`edge_line`, :meth:`edge_length` - - """ - a, b = self.edge_coordinates(edge) - return Vector.from_start_end(a, b) - - def edge_direction(self, edge): - """Return the direction vector of an edge. - - Parameters - ---------- - edge : tuple[hashable, hashable] - The identifier of the edge. - - Returns - ------- - :class:`compas.geometry.Vector` - The direction vector of the edge. - - See Also - -------- - :meth:`edge_vector`, :meth:`edge_line`, :meth:`edge_length` - - """ - return Vector(*normalize_vector(self.edge_vector(edge))) - - def edge_line(self, edge): - """Return the line of an edge. - - Parameters - ---------- - edge : tuple[hashable, hashable] - The identifier of the edge. - - Returns - ------- - :class:`compas.geometry.Line` - The line of the edge. - - See Also - -------- - :meth:`edge_vector`, :meth:`edge_direction`, :meth:`edge_length` - - """ - return Line(*self.edge_coordinates(edge)) - - def edge_length(self, edge): - """Return the length of an edge. - - Parameters - ---------- - edge : tuple[hashable, hashable] - The identifier of the edge. - - Returns - ------- - float - The length of the edge. - - See Also - -------- - :meth:`edge_vector`, :meth:`edge_direction`, :meth:`edge_line` - - """ - a, b = self.edge_coordinates(edge) - return distance_point_point(a, b) - - # -------------------------------------------------------------------------- - # transformations - # -------------------------------------------------------------------------- - - def transform(self, transformation): - """Transform all nodes of the network. - - Parameters - ---------- - transformation : :class:`Transformation` - The transformation used to transform the nodes. - - Returns - ------- - None - - """ - nodes = self.nodes_attributes("xyz") - points = transform_points(nodes, transformation) - for point, node in zip(points, self.nodes()): - self.node_attributes(node, "xyz", point) diff --git a/src/compas/datastructures/network/operations/__init__.py b/src/compas/datastructures/network/operations/__init__.py deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/compas/scene/__init__.py b/src/compas/scene/__init__.py index 773901cac8fd..14431c21faf8 100644 --- a/src/compas/scene/__init__.py +++ b/src/compas/scene/__init__.py @@ -12,7 +12,7 @@ from .exceptions import NoSceneObjectContextError from .sceneobject import SceneObject from .meshobject import MeshObject -from .networkobject import NetworkObject +from .graphobject import GraphObject from .geometryobject import GeometryObject from .volmeshobject import VolMeshObject @@ -31,7 +31,7 @@ "NoSceneObjectContextError", "SceneObject", "MeshObject", - "NetworkObject", + "GraphObject", "GeometryObject", "VolMeshObject", "Scene", diff --git a/src/compas/scene/networkobject.py b/src/compas/scene/graphobject.py similarity index 81% rename from src/compas/scene/networkobject.py rename to src/compas/scene/graphobject.py index 85f2818fd50f..f25987e1a6cc 100644 --- a/src/compas/scene/networkobject.py +++ b/src/compas/scene/graphobject.py @@ -9,21 +9,21 @@ from .descriptors.colordict import ColorDictAttribute -class NetworkObject(SceneObject): - """Scene object for drawing network data structures. +class GraphObject(SceneObject): + """Scene object for drawing graph data structures. Parameters ---------- - network : :class:`compas.datastructures.Network` - A COMPAS network. + graph : :class:`compas.datastructures.Graph` + A COMPAS graph. Attributes ---------- - network : :class:`compas.datastructures.Network` - The COMPAS network associated with the scene object. + graph : :class:`compas.datastructures.Graph` + The COMPAS graph associated with the scene object. node_xyz : dict[hashable, list[float]] Mapping between nodes and their view coordinates. - The default view coordinates are the actual coordinates of the nodes of the network. + The default view coordinates are the actual coordinates of the nodes of the graph. nodecolor : :class:`compas.colors.ColorDict` Mapping between nodes and RGB color values. edgecolor : :class:`compas.colors.ColorDict` @@ -47,11 +47,11 @@ class NetworkObject(SceneObject): nodecolor = ColorDictAttribute() edgecolor = ColorDictAttribute() - def __init__(self, network, **kwargs): - super(NetworkObject, self).__init__(item=network, **kwargs) - self._network = None + def __init__(self, graph, **kwargs): + super(GraphObject, self).__init__(item=graph, **kwargs) + self._graph = None self._node_xyz = None - self.network = network + self.graph = graph self.nodecolor = kwargs.get("nodecolor", self.color) self.edgecolor = kwargs.get("edgecolor", self.color) self.nodesize = kwargs.get("nodesize", 1.0) @@ -60,12 +60,12 @@ def __init__(self, network, **kwargs): self.show_edges = kwargs.get("show_edges", True) @property - def network(self): - return self._network + def graph(self): + return self._graph - @network.setter - def network(self, network): - self._network = network + @graph.setter + def graph(self, graph): + self._graph = graph self._transformation = None self._node_xyz = None @@ -81,9 +81,9 @@ def transformation(self, transformation): @property def node_xyz(self): if self._node_xyz is None: - points = self.network.nodes_attributes("xyz") # type: ignore + points = self.graph.nodes_attributes("xyz") # type: ignore points = transform_points(points, self.worldtransformation) - self._node_xyz = dict(zip(self.network.nodes(), points)) # type: ignore + self._node_xyz = dict(zip(self.graph.nodes(), points)) # type: ignore return self._node_xyz @node_xyz.setter @@ -92,7 +92,7 @@ def node_xyz(self, node_xyz): @abstractmethod def draw_nodes(self, nodes=None, color=None, text=None): - """Draw the nodes of the network. + """Draw the nodes of the graph. Parameters ---------- @@ -117,7 +117,7 @@ def draw_nodes(self, nodes=None, color=None, text=None): @abstractmethod def draw_edges(self, edges=None, color=None, text=None): - """Draw the edges of the network. + """Draw the edges of the graph. Parameters ---------- @@ -141,7 +141,7 @@ def draw_edges(self, edges=None, color=None, text=None): raise NotImplementedError def clear_nodes(self): - """Clear the nodes of the network. + """Clear the nodes of the graph. Returns ------- @@ -151,7 +151,7 @@ def clear_nodes(self): raise NotImplementedError def clear_edges(self): - """Clear the edges of the network. + """Clear the edges of the graph. Returns ------- @@ -161,7 +161,7 @@ def clear_edges(self): raise NotImplementedError def clear(self): - """Clear the nodes and the edges of the network. + """Clear the nodes and the edges of the graph. Returns ------- diff --git a/src/compas/scene/meshobject.py b/src/compas/scene/meshobject.py index 00bdd9045674..80224f24083f 100644 --- a/src/compas/scene/meshobject.py +++ b/src/compas/scene/meshobject.py @@ -45,7 +45,7 @@ class MeshObject(SceneObject): See Also -------- - :class:`compas.scene.NetworkObject` + :class:`compas.scene.GraphObject` :class:`compas.scene.VolMeshObject` """ diff --git a/src/compas/scene/volmeshobject.py b/src/compas/scene/volmeshobject.py index 9c074715446c..3ed16fd2618b 100644 --- a/src/compas/scene/volmeshobject.py +++ b/src/compas/scene/volmeshobject.py @@ -51,7 +51,7 @@ class VolMeshObject(SceneObject): See Also -------- - :class:`compas.scene.NetworkObject` + :class:`compas.scene.GraphObject` :class:`compas.scene.MeshObject` """ diff --git a/src/compas/topology/combinatorics.py b/src/compas/topology/combinatorics.py index eb7e6808669f..a5d5e1d802b9 100644 --- a/src/compas/topology/combinatorics.py +++ b/src/compas/topology/combinatorics.py @@ -27,7 +27,7 @@ def vertex_coloring(adjacency): Notes ----- This algorithms works on any data structure that can be interpreted as a graph, e.g. - networks, meshes, volmeshes, etc.. + graphs, meshes, volmeshes, etc.. For more info, see [1]_. @@ -38,17 +38,17 @@ def vertex_coloring(adjacency): Warnings -------- - This is a greedy algorithm, so it might be slow for large networks. + This is a greedy algorithm, so it might be slow for large graphs. Examples -------- >>> import compas - >>> from compas.datastructures import Network - >>> network = Network.from_obj(compas.get('lines.obj')) - >>> key_color = vertex_coloring(network.adjacency) - >>> key = network.get_any_node() + >>> from compas.datastructures import Graph + >>> graph = Graph.from_obj(compas.get('lines.obj')) + >>> key_color = vertex_coloring(graph.adjacency) + >>> key = graph.get_any_node() >>> color = key_color[key] - >>> any(key_color[nbr] == color for nbr in network.neighbors(key)) + >>> any(key_color[nbr] == color for nbr in graph.neighbors(key)) False """ diff --git a/src/compas/topology/matrices.py b/src/compas/topology/matrices.py index 86e04b56a184..863c9c9c794b 100644 --- a/src/compas/topology/matrices.py +++ b/src/compas/topology/matrices.py @@ -125,7 +125,7 @@ def connectivity_matrix(edges, rtype="array"): Notes ----- - The connectivity matrix encodes how edges in a network are connected + The connectivity matrix encodes how edges in a graph are connected together. Each row represents an edge and has 1 and -1 inserted into the columns for the start and end nodes. @@ -273,7 +273,7 @@ def laplacian_matrix(edges, normalize=False, rtype="array"): # def mass_matrix(Ct, ks, q=0, c=1, tiled=True): -# r"""Creates a network's nodal mass matrix. +# r"""Creates a graph's nodal mass matrix. # Parameters # ---------- diff --git a/src/compas/topology/traversal.py b/src/compas/topology/traversal.py index f5af0b20f983..b577cd014d64 100644 --- a/src/compas/topology/traversal.py +++ b/src/compas/topology/traversal.py @@ -40,7 +40,7 @@ def depth_first_ordering(adjacency, root): Notes ----- - Return all nodes of a connected component containing `root` of a network + Return all nodes of a connected component containing `root` of a graph represented by an adjacency dictionary. This implementation uses a "to visit" stack. The principle of a stack @@ -59,7 +59,7 @@ def depth_first_ordering(adjacency, root): stack, the entire structure has been traversed. Note that this returns a depth-first spanning tree of a connected component - of the network. + of the graph. """ adjacency = {key: set(nbrs) for key, nbrs in iter(adjacency.items())} @@ -167,7 +167,7 @@ def breadth_first_ordering(adjacency, root): the list of nodes to visit. By appending the neighbors to the end of the list of nodes to visit, - and by visiting the nodes at the start of the list first, the network is + and by visiting the nodes at the start of the list first, the graph is traversed in *breadth-first* order. """ @@ -313,7 +313,7 @@ def breadth_first_tree(adjacency, root): def shortest_path(adjacency, root, goal): - """Find the shortest path between two vertices of a network. + """Find the shortest path between two vertices of a graph. Parameters ---------- @@ -448,8 +448,8 @@ def astar_shortest_path(graph, root, goal): Parameters ---------- - graph : :class:`compas.datastructures.Network` | :class:`compas.datastructures.Mesh` - A network or mesh data structure. + graph : :class:`compas.datastructures.Graph` | :class:`compas.datastructures.Mesh` + A graph or mesh data structure. root : hashable The identifier of the starting node. goal : hashable diff --git a/src/compas_blender/scene/__init__.py b/src/compas_blender/scene/__init__.py index 8a75753b206f..8d0ba1694e2f 100644 --- a/src/compas_blender/scene/__init__.py +++ b/src/compas_blender/scene/__init__.py @@ -27,7 +27,7 @@ from compas.geometry import Torus from compas.geometry import Vector from compas.datastructures import Mesh -from compas.datastructures import Network +from compas.datastructures import Graph from compas.datastructures import VolMesh from .sceneobject import BlenderSceneObject @@ -40,7 +40,7 @@ from .frameobject import FrameObject from .lineobject import LineObject from .meshobject import MeshObject -from .networkobject import NetworkObject +from .graphobject import GraphObject from .planeobject import PlaneObject from .pointobject import PointObject from .pointcloudobject import PointcloudObject @@ -75,7 +75,7 @@ def register_scene_objects(): register(Frame, FrameObject, context="Blender") register(Line, LineObject, context="Blender") register(Mesh, MeshObject, context="Blender") - register(Network, NetworkObject, context="Blender") + register(Graph, GraphObject, context="Blender") register(Plane, PlaneObject, context="Blender") register(Point, PointObject, context="Blender") register(Pointcloud, PointcloudObject, context="Blender") @@ -101,7 +101,7 @@ def register_scene_objects(): "FrameObject", "LineObject", "MeshObject", - "NetworkObject", + "GraphObject", "PlaneObject", "PointObject", "PointcloudObject", diff --git a/src/compas_blender/scene/networkobject.py b/src/compas_blender/scene/graphobject.py similarity index 86% rename from src/compas_blender/scene/networkobject.py rename to src/compas_blender/scene/graphobject.py index 48da9aa5d818..4e8e8e1e0312 100644 --- a/src/compas_blender/scene/networkobject.py +++ b/src/compas_blender/scene/graphobject.py @@ -8,28 +8,28 @@ import bpy # type: ignore import compas_blender -from compas.datastructures import Network +from compas.datastructures import Graph from compas.colors import Color from compas.geometry import Line -from compas.scene import NetworkObject as BaseSceneObject +from compas.scene import GraphObject as BaseSceneObject from .sceneobject import BlenderSceneObject from compas_blender import conversions -class NetworkObject(BlenderSceneObject, BaseSceneObject): - """Scene object for drawing network data structures in Blender. +class GraphObject(BlenderSceneObject, BaseSceneObject): + """Scene object for drawing graph data structures in Blender. Parameters ---------- - network : :class:`compas.datastructures.Network` - A COMPAS network. + graph : :class:`compas.datastructures.Graph` + A COMPAS graph. """ - def __init__(self, network: Network, **kwargs: Any): - super().__init__(network=network, **kwargs) + def __init__(self, graph: Graph, **kwargs: Any): + super().__init__(graph=graph, **kwargs) self.nodeobjects = [] self.edgeobjects = [] @@ -88,7 +88,7 @@ def draw( nodecolor: Optional[Union[Color, Dict[int, Color]]] = None, edgecolor: Optional[Union[Color, Dict[Tuple[int, int], Color]]] = None, ) -> list[bpy.types.Object]: - """Draw the network. + """Draw the graph. Parameters ---------- @@ -142,10 +142,10 @@ def draw_nodes( self.nodecolor = color - for node in nodes or self.network.nodes(): # type: ignore - name = f"{self.network.name}.node.{node}" # type: ignore + for node in nodes or self.graph.nodes(): # type: ignore + name = f"{self.graph.name}.node.{node}" # type: ignore color = self.nodecolor[node] # type: ignore - point = self.network.nodes_attributes("xyz")[node] # type: ignore + point = self.graph.nodes_attributes("xyz")[node] # type: ignore # there is no such thing as a sphere data block bpy.ops.mesh.primitive_uv_sphere_add(location=point, radius=radius, segments=u, ring_count=v) @@ -183,11 +183,11 @@ def draw_edges( self.edgecolor = color - for u, v in edges or self.network.edges(): # type: ignore - name = f"{self.network.name}.edge.{u}-{v}" # type: ignore + for u, v in edges or self.graph.edges(): # type: ignore + name = f"{self.graph.name}.edge.{u}-{v}" # type: ignore color = self.edgecolor[u, v] # type: ignore curve = conversions.line_to_blender_curve( - Line(self.network.nodes_attributes("xyz")[u], self.network.nodes_attributes("xyz")[v]) + Line(self.graph.nodes_attributes("xyz")[u], self.graph.nodes_attributes("xyz")[v]) ) obj = self.create_object(curve, name=name) @@ -219,8 +219,8 @@ def draw_edges( # for node in self.node_text: # labels.append( # { - # "pos": self.network.nodes_attributes("xyz")[node], - # "name": f"{self.network.name}.nodelabel.{node}", + # "pos": self.graph.nodes_attributes("xyz")[node], + # "name": f"{self.graph.name}.nodelabel.{node}", # "text": self.node_text[node], # "color": self.nodecolor[node], # } @@ -247,8 +247,8 @@ def draw_edges( # u, v = edge # labels.append( # { - # "pos": centroid_points([self.network.nodes_attributes("xyz")[u], self.network.nodes_attributes("xyz")[v]]), - # "name": f"{self.network.name}.edgelabel.{u}-{v}", + # "pos": centroid_points([self.graph.nodes_attributes("xyz")[u], self.graph.nodes_attributes("xyz")[v]]), + # "name": f"{self.graph.name}.edgelabel.{u}-{v}", # "text": self.edge_text[edge], # "color": self.edgecolor[edge], # } diff --git a/src/compas_ghpython/scene/__init__.py b/src/compas_ghpython/scene/__init__.py index eeb6d8435d74..39386224e21e 100644 --- a/src/compas_ghpython/scene/__init__.py +++ b/src/compas_ghpython/scene/__init__.py @@ -28,7 +28,7 @@ from compas.geometry import Brep from compas.datastructures import Mesh -from compas.datastructures import Network +from compas.datastructures import Graph from compas.datastructures import VolMesh from .sceneobject import GHSceneObject @@ -42,7 +42,7 @@ from .frameobject import FrameObject from .lineobject import LineObject from .meshobject import MeshObject -from .networkobject import NetworkObject +from .graphobject import GraphObject from .planeobject import PlaneObject from .pointobject import PointObject from .polygonobject import PolygonObject @@ -78,7 +78,7 @@ def register_scene_objects(): register(Frame, FrameObject, context="Grasshopper") register(Line, LineObject, context="Grasshopper") register(Mesh, MeshObject, context="Grasshopper") - register(Network, NetworkObject, context="Grasshopper") + register(Graph, GraphObject, context="Grasshopper") register(Plane, PlaneObject, context="Grasshopper") register(Point, PointObject, context="Grasshopper") register(Polygon, PolygonObject, context="Grasshopper") @@ -105,7 +105,7 @@ def register_scene_objects(): "FrameObject", "LineObject", "MeshObject", - "NetworkObject", + "GraphObject", "PlaneObject", "PointObject", "PolygonObject", diff --git a/src/compas_ghpython/scene/networkobject.py b/src/compas_ghpython/scene/graphobject.py similarity index 73% rename from src/compas_ghpython/scene/networkobject.py rename to src/compas_ghpython/scene/graphobject.py index bcb138b8fbd0..9329b69204ab 100644 --- a/src/compas_ghpython/scene/networkobject.py +++ b/src/compas_ghpython/scene/graphobject.py @@ -4,27 +4,27 @@ from compas_rhino import conversions -from compas.scene import NetworkObject as BaseNetworkObject +from compas.scene import GraphObject as BaseGraphObject from .sceneobject import GHSceneObject -class NetworkObject(GHSceneObject, BaseNetworkObject): - """Scene object for drawing network data structures. +class GraphObject(GHSceneObject, BaseGraphObject): + """Scene object for drawing graph data structures. Parameters ---------- - network : :class:`compas.datastructures.Network` - A COMPAS network. + graph : :class:`compas.datastructures.Graph` + A COMPAS graph. **kwargs : dict, optional Additional keyword arguments. """ - def __init__(self, network, **kwargs): - super(NetworkObject, self).__init__(network=network, **kwargs) + def __init__(self, graph, **kwargs): + super(GraphObject, self).__init__(graph=graph, **kwargs) def draw(self): - """Draw the entire network with default color settings. + """Draw the entire graph with default color settings. Returns ------- @@ -50,7 +50,7 @@ def draw_nodes(self, nodes=None): """ points = [] - for node in nodes or self.network.nodes(): # type: ignore + for node in nodes or self.graph.nodes(): # type: ignore points.append(conversions.point_to_rhino(self.node_xyz[node])) return points @@ -71,7 +71,7 @@ def draw_edges(self, edges=None): """ lines = [] - for edge in edges or self.network.edges(): # type: ignore + for edge in edges or self.graph.edges(): # type: ignore lines.append(conversions.line_to_rhino((self.node_xyz[edge[0]], self.node_xyz[edge[1]]))) return lines diff --git a/src/compas_ghpython/utilities/__init__.py b/src/compas_ghpython/utilities/__init__.py index eb74b3fcf605..6946dffbc0dc 100644 --- a/src/compas_ghpython/utilities/__init__.py +++ b/src/compas_ghpython/utilities/__init__.py @@ -12,7 +12,7 @@ draw_pipes, draw_spheres, draw_mesh, - draw_network, + draw_graph, draw_circles, draw_brep, ) @@ -31,7 +31,7 @@ "draw_pipes", "draw_spheres", "draw_mesh", - "draw_network", + "draw_graph", "draw_circles", "draw_brep", "list_to_ghtree", diff --git a/src/compas_ghpython/utilities/drawing.py b/src/compas_ghpython/utilities/drawing.py index 18523940b465..b02f391988dd 100644 --- a/src/compas_ghpython/utilities/drawing.py +++ b/src/compas_ghpython/utilities/drawing.py @@ -403,12 +403,12 @@ def draw_mesh(vertices, faces, color=None, vertex_normals=None, texture_coordina return mesh -def draw_network(network): - """Draw a network in Grasshopper. +def draw_graph(graph): + """Draw a graph in Grasshopper. Parameters ---------- - network : :class:`compas.datastructures.Network` + graph : :class:`compas.datastructures.Graph` Returns ------- @@ -418,11 +418,11 @@ def draw_network(network): """ points = [] - for key in network.nodes(): - points.append({"pos": network.node_coordinates(key)}) + for key in graph.nodes(): + points.append({"pos": graph.node_coordinates(key)}) lines = [] - for u, v in network.edges(): - lines.append({"start": network.node_coordinates(u), "end": network.node_coordinates(v)}) + for u, v in graph.edges(): + lines.append({"start": graph.node_coordinates(u), "end": graph.node_coordinates(v)}) points_rg = draw_points(points) lines_rg = draw_lines(lines) diff --git a/src/compas_rhino/scene/__init__.py b/src/compas_rhino/scene/__init__.py index f2fb63de6e66..07a5c78c2c3c 100644 --- a/src/compas_rhino/scene/__init__.py +++ b/src/compas_rhino/scene/__init__.py @@ -30,7 +30,7 @@ from compas.geometry import Brep from compas.datastructures import Mesh -from compas.datastructures import Network +from compas.datastructures import Graph from compas.datastructures import VolMesh import compas_rhino @@ -55,7 +55,7 @@ from .torusobject import TorusObject from .meshobject import MeshObject -from .networkobject import NetworkObject +from .graphobject import GraphObject from .volmeshobject import VolMeshObject from .curveobject import CurveObject @@ -92,7 +92,7 @@ def register_scene_objects(): register(Sphere, SphereObject, context="Rhino") register(Torus, TorusObject, context="Rhino") register(Mesh, MeshObject, context="Rhino") - register(Network, NetworkObject, context="Rhino") + register(Graph, GraphObject, context="Rhino") register(VolMesh, VolMeshObject, context="Rhino") register(Curve, CurveObject, context="Rhino") register(Surface, SurfaceObject, context="Rhino") @@ -119,7 +119,7 @@ def register_scene_objects(): "SphereObject", "TorusObject", "MeshObject", - "NetworkObject", + "GraphObject", "VolMeshObject", "CurveObject", "SurfaceObject", diff --git a/src/compas_rhino/scene/networkobject.py b/src/compas_rhino/scene/graphobject.py similarity index 88% rename from src/compas_rhino/scene/networkobject.py rename to src/compas_rhino/scene/graphobject.py index 5fff2c491220..31e7f1cd2c5f 100644 --- a/src/compas_rhino/scene/networkobject.py +++ b/src/compas_rhino/scene/graphobject.py @@ -9,7 +9,7 @@ from compas.geometry import Line from compas.geometry import Cylinder from compas.geometry import Sphere -from compas.scene import NetworkObject as BaseNetworkObject +from compas.scene import GraphObject as BaseGraphObject from compas_rhino.conversions import point_to_rhino from compas_rhino.conversions import line_to_rhino from compas_rhino.conversions import sphere_to_rhino @@ -18,20 +18,20 @@ from ._helpers import attributes -class NetworkObject(RhinoSceneObject, BaseNetworkObject): - """Scene object for drawing network data structures. +class GraphObject(RhinoSceneObject, BaseGraphObject): + """Scene object for drawing graph data structures. Parameters ---------- - network : :class:`compas.datastructures.Network` - A COMPAS network. + graph : :class:`compas.datastructures.Graph` + A COMPAS graph. **kwargs : dict, optional Additional keyword arguments. """ - def __init__(self, network, **kwargs): - super(NetworkObject, self).__init__(network=network, **kwargs) + def __init__(self, graph, **kwargs): + super(GraphObject, self).__init__(graph=graph, **kwargs) # ========================================================================== # clear @@ -45,7 +45,7 @@ def clear(self): None """ - guids = compas_rhino.get_objects(name="{}.*".format(self.network.name)) # type: ignore + guids = compas_rhino.get_objects(name="{}.*".format(self.graph.name)) # type: ignore compas_rhino.delete_objects(guids, purge=True) def clear_nodes(self): @@ -56,7 +56,7 @@ def clear_nodes(self): None """ - guids = compas_rhino.get_objects(name="{}.node.*".format(self.network.name)) # type: ignore + guids = compas_rhino.get_objects(name="{}.node.*".format(self.graph.name)) # type: ignore compas_rhino.delete_objects(guids, purge=True) def clear_edges(self): @@ -67,7 +67,7 @@ def clear_edges(self): None """ - guids = compas_rhino.get_objects(name="{}.edge.*".format(self.network.name)) # type: ignore + guids = compas_rhino.get_objects(name="{}.edge.*".format(self.graph.name)) # type: ignore compas_rhino.delete_objects(guids, purge=True) # ========================================================================== @@ -81,7 +81,7 @@ def draw( nodecolor=None, edgecolor=None, ): - """Draw the network using the chosen visualisation settings. + """Draw the graph using the chosen visualisation settings. Parameters ---------- @@ -129,8 +129,8 @@ def draw_nodes(self, nodes=None, color=None, group=None): self.nodecolor = color - for node in nodes or self.network.nodes(): # type: ignore - name = "{}.node.{}".format(self.network.name, node) # type: ignore + for node in nodes or self.graph.nodes(): # type: ignore + name = "{}.node.{}".format(self.graph.name, node) # type: ignore attr = attributes(name=name, color=self.nodecolor[node], layer=self.layer) # type: ignore point = point_to_rhino(self.node_xyz[node]) @@ -169,11 +169,11 @@ def draw_edges(self, edges=None, color=None, group=None, show_direction=False): arrow = "end" if show_direction else None self.edgecolor = color - for edge in edges or self.network.edges(): # type: ignore + for edge in edges or self.graph.edges(): # type: ignore u, v = edge color = self.edgecolor[edge] # type: ignore - name = "{}.edge.{}-{}".format(self.network.name, u, v) # type: ignore + name = "{}.edge.{}-{}".format(self.graph.name, u, v) # type: ignore attr = attributes(name=name, color=color, layer=self.layer, arrow=arrow) # type: ignore line = Line(self.node_xyz[u], self.node_xyz[v]) @@ -217,7 +217,7 @@ def draw_nodelabels(self, text, color=None, group=None, fontheight=10, fontface= self.nodecolor = color for node in text: - name = "{}.node.{}.label".format(self.network.name, node) # type: ignore + name = "{}.node.{}.label".format(self.graph.name, node) # type: ignore attr = attributes(name=name, color=self.nodecolor[node], layer=self.layer) # type: ignore point = point_to_rhino(self.node_xyz[node]) @@ -264,7 +264,7 @@ def draw_edgelabels(self, text, color=None, group=None, fontheight=10, fontface= u, v = edge color = self.edgecolor[edge] # type: ignore - name = "{}.edge.{}-{}.label".format(self.network.name, u, v) # type: ignore + name = "{}.edge.{}-{}.label".format(self.graph.name, u, v) # type: ignore attr = attributes(name=name, color=color, layer=self.layer) line = Line(self.node_xyz[u], self.node_xyz[v]) @@ -287,7 +287,7 @@ def draw_edgelabels(self, text, color=None, group=None, fontheight=10, fontface= # ============================================================================= def draw_spheres(self, radius, color=None, group=None): - """Draw spheres at the vertices of the network. + """Draw spheres at the vertices of the graph. Parameters ---------- @@ -309,7 +309,7 @@ def draw_spheres(self, radius, color=None, group=None): self.nodecolor = color for node in radius: - name = "{}.node.{}.sphere".format(self.network.name, node) # type: ignore + name = "{}.node.{}.sphere".format(self.graph.name, node) # type: ignore color = self.nodecolor[node] # type: ignore attr = attributes(name=name, color=color, layer=self.layer) @@ -325,7 +325,7 @@ def draw_spheres(self, radius, color=None, group=None): return guids def draw_pipes(self, radius, color=None, group=None): - """Draw pipes around the edges of the network. + """Draw pipes around the edges of the graph. Parameters ---------- @@ -347,7 +347,7 @@ def draw_pipes(self, radius, color=None, group=None): self.edgecolor = color for edge in radius: - name = "{}.edge.{}-{}.pipe".format(self.network.name, *edge) # type: ignore + name = "{}.edge.{}-{}.pipe".format(self.graph.name, *edge) # type: ignore color = self.edgecolor[edge] # type: ignore attr = attributes(name=name, color=color, layer=self.layer) diff --git a/src/compas_rhino/utilities/layers.py b/src/compas_rhino/utilities/layers.py index 16e63659fa4e..b833ba6a96f8 100644 --- a/src/compas_rhino/utilities/layers.py +++ b/src/compas_rhino/utilities/layers.py @@ -118,7 +118,7 @@ def create_layers_from_paths(names, separator="::"): * COMPAS * Datastructures * Mesh - * Network + * Graph * Geometry * Point * Vector @@ -127,7 +127,7 @@ def create_layers_from_paths(names, separator="::"): create_layers_from_paths([ "COMPAS::Datastructures::Mesh", - "COMPAS::Datastructures::Network", + "COMPAS::Datastructures::Graph", "COMPAS::Geometry::Point", "COMPAS::Geometry::Vector", ]) @@ -158,7 +158,7 @@ def create_layers_from_dict(layers): layers = {'COMPAS', {'layers': { 'Datastructures': {'color': (255, 0, 0), 'layers': { 'Mesh': {}, - 'Network': {} + 'Graph': {} }}, 'Geometry': {'color': (0, 0, 255),'layers': { 'Point': {}, @@ -307,11 +307,11 @@ def delete_layers(layers): -------- .. code-block:: python - layers = {'COMPAS': {'layers': {'Datastructures': {'layers': {'Mesh': {}, 'Network': {}}}}}} + layers = {'COMPAS': {'layers': {'Datastructures': {'layers': {'Mesh': {}, 'Graph': {}}}}}} create_layers(layers) - delete_layers(['COMPAS::Datastructures::Network']) + delete_layers(['COMPAS::Datastructures::Graph']) delete_layers({'COMPAS': {'layers': {'Datastructures': {'layers': {'Mesh': {}}}}}}) """ diff --git a/tests/compas/compas_api.json b/tests/compas/compas_api.json index 74592bd0ef08..50bdb070d8a0 100644 --- a/tests/compas/compas_api.json +++ b/tests/compas/compas_api.json @@ -50,7 +50,7 @@ "Assembly", "AssemblyError", "BaseMesh", - "BaseNetwork", + "BaseGraph", "BaseVolMesh", "Datastructure", "Feature", @@ -60,7 +60,7 @@ "HalfEdge", "HalfFace", "Mesh", - "Network", + "Graph", "ParametricFeature", "Part", "VolMesh", @@ -132,31 +132,31 @@ "mesh_weld", "meshes_join", "meshes_join_and_weld", - "network_adjacency_matrix", - "network_complement", - "network_connectivity_matrix", - "network_count_crossings", - "network_degree_matrix", - "network_disconnected_edges", - "network_disconnected_nodes", - "network_embed_in_plane", - "network_embed_in_plane_proxy", - "network_explode", - "network_find_crossings", - "network_find_cycles", - "network_is_connected", - "network_is_crossed", - "network_is_planar", - "network_is_planar_embedding", - "network_is_xy", - "network_join_edges", - "network_laplacian_matrix", - "network_polylines", - "network_shortest_path", - "network_smooth_centroid", - "network_split_edge", - "network_transform", - "network_transformed", + "graph_adjacency_matrix", + "graph_complement", + "graph_connectivity_matrix", + "graph_count_crossings", + "graph_degree_matrix", + "graph_disconnected_edges", + "graph_disconnected_nodes", + "graph_embed_in_plane", + "graph_embed_in_plane_proxy", + "graph_explode", + "graph_find_crossings", + "graph_find_cycles", + "graph_is_connected", + "graph_is_crossed", + "graph_is_planar", + "graph_is_planar_embedding", + "graph_is_xy", + "graph_join_edges", + "graph_laplacian_matrix", + "graph_polylines", + "graph_shortest_path", + "graph_smooth_centroid", + "graph_split_edge", + "graph_transform", + "graph_transformed", "trimesh_collapse_edge", "trimesh_cotangent_laplacian_matrix", "trimesh_descent", @@ -648,4 +648,4 @@ "yellow" ] } -} +} \ No newline at end of file diff --git a/tests/compas/compas_api_ipy.json b/tests/compas/compas_api_ipy.json index 28abcc97ebe7..efc776f6e77f 100644 --- a/tests/compas/compas_api_ipy.json +++ b/tests/compas/compas_api_ipy.json @@ -50,7 +50,7 @@ "Assembly", "AssemblyError", "BaseMesh", - "BaseNetwork", + "BaseGraph", "BaseVolMesh", "Datastructure", "Feature", @@ -60,7 +60,7 @@ "HalfEdge", "HalfFace", "Mesh", - "Network", + "Graph", "ParametricFeature", "Part", "VolMesh", @@ -120,27 +120,27 @@ "mesh_weld", "meshes_join", "meshes_join_and_weld", - "network_complement", - "network_count_crossings", - "network_disconnected_edges", - "network_disconnected_nodes", - "network_embed_in_plane", - "network_embed_in_plane_proxy", - "network_explode", - "network_find_crossings", - "network_find_cycles", - "network_is_connected", - "network_is_crossed", - "network_is_planar", - "network_is_planar_embedding", - "network_is_xy", - "network_join_edges", - "network_polylines", - "network_shortest_path", - "network_smooth_centroid", - "network_split_edge", - "network_transform", - "network_transformed", + "graph_complement", + "graph_count_crossings", + "graph_disconnected_edges", + "graph_disconnected_nodes", + "graph_embed_in_plane", + "graph_embed_in_plane_proxy", + "graph_explode", + "graph_find_crossings", + "graph_find_cycles", + "graph_is_connected", + "graph_is_crossed", + "graph_is_planar", + "graph_is_planar_embedding", + "graph_is_xy", + "graph_join_edges", + "graph_polylines", + "graph_shortest_path", + "graph_smooth_centroid", + "graph_split_edge", + "graph_transform", + "graph_transformed", "trimesh_collapse_edge", "trimesh_face_circle", "trimesh_gaussian_curvature", @@ -578,4 +578,4 @@ "yellow" ] } -} +} \ No newline at end of file diff --git a/tests/compas/data/test_json.py b/tests/compas/data/test_json.py index 26d9b0994a5c..f6cd2f8f2b9f 100644 --- a/tests/compas/data/test_json.py +++ b/tests/compas/data/test_json.py @@ -3,7 +3,7 @@ import compas from compas.datastructures import Mesh -from compas.datastructures import Network +from compas.datastructures import Graph from compas.datastructures import VolMesh from compas.geometry import Box from compas.geometry import Frame @@ -42,8 +42,8 @@ def test_json_xform(): assert before.guid == after.guid -def test_json_network(): - before = Network() +def test_json_graph(): + before = Graph() a = before.add_node() b = before.add_node() before.add_edge(a, b) diff --git a/tests/compas/datastructures/test_graph.py b/tests/compas/datastructures/test_graph.py index fe8fcbba79a7..7f54d49cb59d 100644 --- a/tests/compas/datastructures/test_graph.py +++ b/tests/compas/datastructures/test_graph.py @@ -1,14 +1,19 @@ -import pytest import json -import compas +import os +import random -from compas.datastructures import Graph +import pytest +import compas +from compas.datastructures import Graph +from compas.geometry import Pointcloud # ============================================================================== # Fixtures # ============================================================================== +BASE_FOLDER = os.path.dirname(__file__) + @pytest.fixture def graph(): @@ -19,16 +24,74 @@ def graph(): return graph +@pytest.fixture +def planar_graph(): + return Graph.from_obj(os.path.join(BASE_FOLDER, "fixtures", "planar.obj")) + + +@pytest.fixture +def non_planar_graph(): + return Graph.from_obj(os.path.join(BASE_FOLDER, "fixtures", "non-planar.obj")) + + +@pytest.fixture +def k5_graph(): + graph = Graph() + graph.add_edge("a", "b") + graph.add_edge("a", "c") + graph.add_edge("a", "d") + graph.add_edge("a", "e") + + graph.add_edge("b", "c") + graph.add_edge("b", "d") + graph.add_edge("b", "e") + + graph.add_edge("c", "d") + graph.add_edge("c", "e") + + graph.add_edge("d", "e") + + return graph + + # ============================================================================== # Basics # ============================================================================== +# ============================================================================== +# Constructors +# ============================================================================== + + +@pytest.mark.parametrize( + "filepath", + [ + compas.get("lines.obj"), + compas.get("grid_irregular.obj"), + ], +) +def test_graph_from_obj(filepath): + graph = Graph.from_obj(filepath) + assert graph.number_of_nodes() > 0 + assert graph.number_of_edges() > 0 + assert len(list(graph.nodes())) == graph._max_node + 1 + assert graph.is_connected() + + +def test_graph_from_pointcloud(): + cloud = Pointcloud.from_bounds(random.random(), random.random(), random.random(), random.randint(10, 100)) + graph = Graph.from_pointcloud(cloud=cloud, degree=3) + assert graph.number_of_nodes() == len(cloud) + for node in graph.nodes(): + assert graph.degree(node) >= 3 + + # ============================================================================== # Data # ============================================================================== -def test_graph_data(graph): +def test_graph_data1(graph): other = Graph.from_data(json.loads(json.dumps(graph.data))) assert graph.data == other.data @@ -42,9 +105,17 @@ def test_graph_data(graph): assert Graph.validate_data(other.data) -# ============================================================================== -# Constructors -# ============================================================================== +def test_graph_data2(): + cloud = Pointcloud.from_bounds(random.random(), random.random(), random.random(), random.randint(10, 100)) + graph = Graph.from_pointcloud(cloud=cloud, degree=3) + other = Graph.from_data(json.loads(json.dumps(graph.data))) + + assert graph.data == other.data + + if not compas.IPY: + assert Graph.validate_data(graph.data) + assert Graph.validate_data(other.data) + # ============================================================================== # Properties @@ -58,6 +129,15 @@ def test_graph_data(graph): # Builders # ============================================================================== + +def test_add_node(): + graph = Graph() + assert graph.add_node(1) == 1 + assert graph.add_node("1", x=0, y=0, z=0) == "1" + assert graph.add_node(2) == 2 + assert graph.add_node(0, x=1) == 0 + + # ============================================================================== # Modifiers # ============================================================================== @@ -162,3 +242,21 @@ def test_graph_to_networkx(): assert g2.edge_attribute((0, 1), "attr_value") == 10 assert g2.attributes["name"] == "DiGraph", "Graph attributes must be preserved" assert g2.attributes["val"] == (0, 0, 0), "Graph attributes must be preserved" + + +# ============================================================================== +# Methods +# ============================================================================== + + +def test_non_planar(k5_graph, non_planar_graph): + if not compas.IPY: + assert k5_graph.is_planar() is not True + assert non_planar_graph.is_planar() is not True + + +def test_planar(k5_graph, planar_graph): + if not compas.IPY: + k5_graph.delete_edge(("a", "b")) # Delete (a, b) edge to make K5 planar + assert k5_graph.is_planar() is True + assert planar_graph.is_planar() is True diff --git a/tests/compas/datastructures/test_network.py b/tests/compas/datastructures/test_network.py deleted file mode 100644 index ca0957cfb120..000000000000 --- a/tests/compas/datastructures/test_network.py +++ /dev/null @@ -1,149 +0,0 @@ -import json -import os -import random - -import pytest - -import compas -from compas.datastructures import Network -from compas.geometry import Pointcloud - -# ============================================================================== -# Fixtures -# ============================================================================== - -BASE_FOLDER = os.path.dirname(__file__) - - -@pytest.fixture -def planar_network(): - return Network.from_obj(os.path.join(BASE_FOLDER, "fixtures", "planar.obj")) - - -@pytest.fixture -def non_planar_network(): - return Network.from_obj(os.path.join(BASE_FOLDER, "fixtures", "non-planar.obj")) - - -@pytest.fixture -def k5_network(): - network = Network() - network.add_edge("a", "b") - network.add_edge("a", "c") - network.add_edge("a", "d") - network.add_edge("a", "e") - - network.add_edge("b", "c") - network.add_edge("b", "d") - network.add_edge("b", "e") - - network.add_edge("c", "d") - network.add_edge("c", "e") - - network.add_edge("d", "e") - - return network - - -# ============================================================================== -# Basics -# ============================================================================== - -# ============================================================================== -# Constructors -# ============================================================================== - - -@pytest.mark.parametrize( - "filepath", - [ - compas.get("lines.obj"), - compas.get("grid_irregular.obj"), - ], -) -def test_network_from_obj(filepath): - network = Network.from_obj(filepath) - assert network.number_of_nodes() > 0 - assert network.number_of_edges() > 0 - assert len(list(network.nodes())) == network._max_node + 1 - assert network.is_connected() - - -def test_network_from_pointcloud(): - cloud = Pointcloud.from_bounds(random.random(), random.random(), random.random(), random.randint(10, 100)) - network = Network.from_pointcloud(cloud=cloud, degree=3) - assert network.number_of_nodes() == len(cloud) - for node in network.nodes(): - assert network.degree(node) >= 3 - - -# ============================================================================== -# Data -# ============================================================================== - - -def test_network_data(): - cloud = Pointcloud.from_bounds(random.random(), random.random(), random.random(), random.randint(10, 100)) - network = Network.from_pointcloud(cloud=cloud, degree=3) - other = Network.from_data(json.loads(json.dumps(network.data))) - - assert network.data == other.data - - if not compas.IPY: - assert Network.validate_data(network.data) - assert Network.validate_data(other.data) - - -# ============================================================================== -# Properties -# ============================================================================== - -# ============================================================================== -# Accessors -# ============================================================================== - -# ============================================================================== -# Builders -# ============================================================================== - - -def test_add_node(): - network = Network() - assert network.add_node(1) == 1 - assert network.add_node("1", x=0, y=0, z=0) == "1" - assert network.add_node(2) == 2 - assert network.add_node(0, x=1) == 0 - - -# ============================================================================== -# Modifiers -# ============================================================================== - -# ============================================================================== -# Samples -# ============================================================================== - -# ============================================================================== -# Attributes -# ============================================================================== - -# ============================================================================== -# Conversion -# ============================================================================== - -# ============================================================================== -# Methods -# ============================================================================== - - -def test_non_planar(k5_network, non_planar_network): - if not compas.IPY: - assert k5_network.is_planar() is not True - assert non_planar_network.is_planar() is not True - - -def test_planar(k5_network, planar_network): - if not compas.IPY: - k5_network.delete_edge(("a", "b")) # Delete (a, b) edge to make K5 planar - assert k5_network.is_planar() is True - assert planar_network.is_planar() is True diff --git a/tests/compas/donttest_api_completeness.py b/tests/compas/donttest_api_completeness.py index 7db2f3429d1c..e782572a9e5d 100644 --- a/tests/compas/donttest_api_completeness.py +++ b/tests/compas/donttest_api_completeness.py @@ -101,7 +101,7 @@ # for name in compas_api[packmod]: # if name in [ # "BaseMesh", -# "BaseNetwork", +# "BaseGraph", # "BaseVolMesh", # "Datastructure", # "Graph", diff --git a/tests/compas/topology/test_traversal.py b/tests/compas/topology/test_traversal.py index 5bb8f1352d6f..7c4c51e5c23e 100644 --- a/tests/compas/topology/test_traversal.py +++ b/tests/compas/topology/test_traversal.py @@ -1,13 +1,12 @@ -from compas.datastructures import Graph from compas.datastructures import Mesh -from compas.datastructures import Network +from compas.datastructures import Graph from compas.geometry import Box, Frame from compas.topology import astar_shortest_path from compas.topology.traversal import astar_lightest_path def test_astar_shortest_path(): - n = Network() + n = Graph() a = n.add_node(x=1, y=2, z=0) b = n.add_node(x=3, y=1, z=0) n.add_edge(a, b) @@ -16,7 +15,7 @@ def test_astar_shortest_path(): def test_astar_shortest_path_cycle(): - n = Network() + n = Graph() a = n.add_node(x=1, y=0, z=0) b = n.add_node(x=2, y=0, z=0) c = n.add_node(x=3, y=0, z=0) @@ -32,7 +31,7 @@ def test_astar_shortest_path_cycle(): def test_astar_shortest_path_disconnected(): - n = Network() + n = Graph() a = n.add_node(x=1, y=0, z=0) b = n.add_node(x=2, y=0, z=0) c = n.add_node(x=3, y=0, z=0)