E. Network datastructure

1. COMPAS Network Constructor

COMPAS Network is a connectivity graph that encodes the relationships between nodes and edges, which is a useful data structure to represent elements in a cable-net.

1.a: construct Network by addnode, addedge

The simplest way to build a network is by using Network.addnode and Network.addedge.

# ==============================================================================
# Import
# ==============================================================================
from compas.datastructures import Network

# ==============================================================================
# Parameters
# ==============================================================================
row = 5
col = 5
unit_len = 10

# ==============================================================================
# Create Network
# ==============================================================================
cablenet = Network()

for i in range(row + 1):
    for j in range(col + 1):
    
        # index of the node
        index = i * (row + 1) + j
        
        # add the xyz coordinates to nodes
        cablenet.add_node(key=index, x=i * unit_len, y=j * unit_len, z=0)

        # add horizontal edge connectivity
        if i < row:
            cablenet.add_edge(index, index + row + 1)
        # add vertical edge connectivity
        if j < col:
            cablenet.add_edge(index, index + 1)

print(cablenet.summary())
print(cablenet)

Network summary nodes: 32 edges: 43

1.b: construct Network by from_node_and_edges

With the nodes and edges list from the last session, a Network could also be constructed by using Network.from_nodes_and_edges().

from compas.datastructures import Network

nodes = [[0, 0, 0], [0, 10, 0], [0, 20, 0], [0, 30, 0], [0, 40, 0], [0, 50, 0], [10, 0, 0], [10, 10, 0], [10, 20, 0], [10, 30, 0], [10, 40, 0], [10, 50, 0], [20, 0, 0], [20, 10, 0], [20, 20, 0], [20, 30, 0], [20, 40, 0], [20, 50, 0], [30, 0, 0], [30, 10, 0], [30, 20, 0], [30, 30, 0], [30, 40, 0], [30, 50, 0], [40, 0, 0], [40, 10, 0], [40, 20, 0], [40, 30, 0], [40, 40, 0], [40, 50, 0], [50, 0, 0], [50, 10, 0],
         [50, 20, 0], [50, 30, 0], [50, 40, 0], [50, 50, 0]]
edges = [[0, 6], [0, 1], [1, 7], [1, 2], [2, 8], [2, 3], [3, 9], [3, 4], [4, 10], [4, 5], [5, 11], [6, 12], [6, 7], [7, 13], [7, 8], [8, 14], [8, 9], [9, 15], [9, 10], [10, 16], [10, 11], [11, 17], [12, 18],
         [12, 13], [13, 19], [13, 14], [14, 20], [14, 15], [15, 21], [15, 16], [16, 22], [16, 17], [17, 23], [18, 24], [18, 19], [19, 25], [19, 20], [20, 26], [20, 21], [21, 27], [21, 22], [22, 28], [22, 23], [23, 29], [24, 30], [24, 25], [25, 31], [25, 26], [26, 32], [26, 27], [27, 33], [27, 28], [28, 34], [28, 29], [29, 35], [30, 31], [31, 32], [32, 33], [33, 34], [34, 35]]

cablenet = Network.from_nodes_and_edges(nodes, edges)

2. NetworkPlotter

A Network could be visualized in Plotter using NetworkPlotter.

from compas_plotters import NetworkPlotter
text = {node: str(node) for node in cablenet.nodes()}
plotter = NetworkPlotter(cablenet, figsize=(12, 7.5))
plotter.draw_nodes(text=text, radius=1)
plotter.draw_edges()
plotter.show()

3. COMPAS Network Data-structure

So, what information is contained in the cable-net Network?

print(cablenet)

{ "compas": "0.18.0", "data": { "adjacency": { "0": { "1": null, "6": null }, "1": { "0": null, "2": null, "7": null }, "10": { "11": null, "16": null, "4": null, "9": null }, "11": { "10": null, "17": null, "5": null }, "12": { "13": null, "18": null, "6": null }, "13": { "12": null, "14": null, "19": null, "7": null }, "14": { "13": null, "15": null, "20": null, "8": null }, "15": { "14": null, "16": null, "21": null, "9": null }, "16": { "10": null, "15": null, "17": null, "22": null }, "17": { "11": null, "16": null, "23": null }, "18": { "12": null, "19": null, "24": null }, "19": { "13": null, "18": null, "20": null, "25": null }, "2": { "1": null, "3": null, "8": null }, "20": { "14": null, "19": null, "21": null, "26": null }, "21": { "15": null, "20": null, "22": null, "27": null }, "22": { "16": null, "21": null, "23": null, "28": null }, "23": { "17": null, "22": null, "29": null }, "24": { "18": null, "25": null, "30": null }, "25": { "19": null, "24": null, "26": null, "31": null }, "26": { "20": null, "25": null, "27": null, "32": null }, "27": { "21": null, "26": null, "28": null, "33": null }, "28": { "22": null, "27": null, "29": null, "34": null }, "29": { "23": null, "28": null, "35": null }, "3": { "2": null, "4": null, "9": null }, "30": { "24": null, "31": null }, "31": { "25": null, "30": null, "32": null }, "32": { "26": null, "31": null, "33": null }, "33": { "27": null, "32": null, "34": null }, "34": { "28": null, "33": null, "35": null }, "35": { "29": null, "34": null }, "4": { "10": null, "3": null, "5": null }, "5": { "11": null, "4": null }, "6": { "0": null, "12": null, "7": null }, "7": { "1": null, "13": null, "6": null, "8": null }, "8": { "14": null, "2": null, "7": null, "9": null }, "9": { "10": null, "15": null, "3": null, "8": null } }, "attributes": { "name": "Network" }, "edge": { "0": { "1": {}, "6": {} }, "1": { "2": {}, "7": {} }, "10": { "11": {}, "16": {} }, "11": { "17": {} }, "12": { "13": {}, "18": {} }, "13": { "14": {}, "19": {} }, "14": { "15": {}, "20": {} }, "15": { "16": {}, "21": {} }, "16": { "17": {}, "22": {} }, "17": { "23": {} }, "18": { "19": {}, "24": {} }, "19": { "20": {}, "25": {} }, "2": { "3": {}, "8": {} }, "20": { "21": {}, "26": {} }, "21": { "22": {}, "27": {} }, "22": { "23": {}, "28": {} }, "23": { "29": {} }, "24": { "25": {}, "30": {} }, "25": { "26": {}, "31": {} }, "26": { "27": {}, "32": {} }, "27": { "28": {}, "33": {} }, "28": { "29": {}, "34": {} }, "29": { "35": {} }, "3": { "4": {}, "9": {} }, "30": { "31": {} }, "31": { "32": {} }, "32": { "33": {} }, "33": { "34": {} }, "34": { "35": {} }, "35": {}, "4": { "10": {}, "5": {} }, "5": { "11": {} }, "6": { "12": {}, "7": {} }, "7": { "13": {}, "8": {} }, "8": { "14": {}, "9": {} }, "9": { "10": {}, "15": {} } }, "edge_attributes": {}, "max_int_key": 35, "node": { "0": { "x": 0, "y": 0, "z": 0 }, "1": { "x": 0, "y": 10, "z": 0 }, "10": { "x": 10, "y": 40, "z": 0 }, "11": { "x": 10, "y": 50, "z": 0 }, "12": { "x": 20, "y": 0, "z": 0 }, "13": { "x": 20, "y": 10, "z": 0 }, "14": { "x": 20, "y": 20, "z": 0 }, "15": { "x": 20, "y": 30, "z": 0 }, "16": { "x": 20, "y": 40, "z": 0 }, "17": { "x": 20, "y": 50, "z": 0 }, "18": { "x": 30, "y": 0, "z": 0 }, "19": { "x": 30, "y": 10, "z": 0 }, "2": { "x": 0, "y": 20, "z": 0 }, "20": { "x": 30, "y": 20, "z": 0 }, "21": { "x": 30, "y": 30, "z": 0 }, "22": { "x": 30, "y": 40, "z": 0 }, "23": { "x": 30, "y": 50, "z": 0 }, "24": { "x": 40, "y": 0, "z": 0 }, "25": { "x": 40, "y": 10, "z": 0 }, "26": { "x": 40, "y": 20, "z": 0 }, "27": { "x": 40, "y": 30, "z": 0 }, "28": { "x": 40, "y": 40, "z": 0 }, "29": { "x": 40, "y": 50, "z": 0 }, "3": { "x": 0, "y": 30, "z": 0 }, "30": { "x": 50, "y": 0, "z": 0 }, "31": { "x": 50, "y": 10, "z": 0 }, "32": { "x": 50, "y": 20, "z": 0 }, "33": { "x": 50, "y": 30, "z": 0 }, "34": { "x": 50, "y": 40, "z": 0 }, "35": { "x": 50, "y": 50, "z": 0 }, "4": { "x": 0, "y": 40, "z": 0 }, "5": { "x": 0, "y": 50, "z": 0 }, "6": { "x": 10, "y": 0, "z": 0 }, "7": { "x": 10, "y": 10, "z": 0 }, "8": { "x": 10, "y": 20, "z": 0 }, "9": { "x": 10, "y": 30, "z": 0 } }, "node_attributes": { "x": 0.0, "y": 0.0, "z": 0.0 } }, "datatype": "compas.datastructures/Network" }

Print the network would output the data of the Network, which is useful for serialization. It contains the following data:

  • compas

  • data

    • adjacency

    • attributes

    • edge

    • edge_attributes

    • max_int_key

    • node

    • node_attributes

  • datatype

It includes the compas version, under which is the Network created. datatype shows the data type of the data-structure, now it's a Network. adjacency and edges are both dictionaries of dictionaries, which is similar to the node_neighbors dictionary we have learned in the last session. The keys are node index. In edges, the dictionary values are dictionaries of edge attribute, for example: cablenet.edge[1][2] -> {...}. Whereasadjacency shows the undirected connectivity information. edge_attributes is a dictionary mapping edge attribute names to their default values. attributes is a dictionary of miscellaneous information. max_int_key is the max number of the key index. node is a dictionary, where each key represents a node of the Network and maps to a dictionary of node attributes. node_attribute is a dictionary and by default is the xyz coordinates of the node.

4. Network Nodes, Edges

Network.nodes() and Network.edges() would return generator objects containing key, the identifier of the node, and key pairs. A generator is very similar to list, which can both be iterated by a for loop, but a generator is more time- and space-efficient compared to a list. Network.node_coordinates(key) would return a list containing xyz coordinates of the node.

for key in cablenet.nodes():
    print(key)
    node_xyz = cablenet.node_coordinates(key)
    print(node_xyz)

for (u, v) in cablenet.edges():
    print(u, v)

5. Network methods

Network contains useful geometrical and topological methods.

5.a: Ex: Highlight nodes and edges

Question:

Highlight node 20 as well as its neighbors. Visualize them with a plotter.

Answer:

neighbors = cablenet.neighbors(20)
print(neighbors)

[14 19 26 21]

node = 20
nbrs = cablenet.neighbors(node)  # find the heighbors of the node

text = {node: str(node) for node in cablenet.nodes()}

facecolor = {node: (255, 0, 0)}
for nbr in nbrs:
    facecolor[nbr] = (0, 0, 255)

edgecolor = {}
for nbr in nbrs:
    edgecolor[node, nbr] = (0, 255, 0)
    edgecolor[nbr, node] = (0, 255, 0)

plotter = NetworkPlotter(cablenet, figsize=(12, 7.5))
plotter.draw_nodes(text=text, facecolor=facecolor, radius=1)
plotter.draw_edges(color=edgecolor, width={edge: 2.0 for edge in edgecolor})
plotter.show()

5.b: Ex: Update node

Question:

Move node 20 along positive x-axis 1 unit and along positive y-axis 2 unit. Update the cable-net.

Answer:

ori_x = cablenet.node_attribute(node, "x")  # get x attribute of the node
ori_y = cablenet.node_attribute(node, "y")  # get y attribute of the node
cablenet.node_attribute(node, "x", ori_x + 1)  # set x attribute of the node
cablenet.node_attribute(node, "y", ori_y + 2)  # set y attribute of the node

5.c: Ex: Calculate cable length

Question:

Calculate the length of the cables, which are connected with node 20.

Answer:

# find the cables connect node 12 and the neighbor
for neighbor in neighbors:
    length = cablenet.edge_length(20, neighbor)
    print("Cable between node", neighbor, "and node 12 is",length, "in length.")

Cable between node 14 and node 12 is 11.18 in length. Cable between node 19 and node 12 is 12.04 in length. Cable between node 26 and node 12 is 9.22 in length. Cable between node 21 and node 12 is 8.06 in length.

5.d: Ex: Find the node and neighborhood

Question:

You know a node whose xyz coordinates are [20, 10, 0]. Find its index key and highlights 2 rings of neighbor nodes away from the node.

Answer:

geometric_key_dict = cablenet.gkey_key()
print(geometric_key_dict)

{'0.000,0.000,0.000': 0, '0.000,10.000,0.000': 1, '0.000,20.000,0.000': 2, '0.000,30.000,0.000': 3, '0.000,40.000,0.000': 4, '0.000,50.000,0.000': 5, '10.000,0.000,0.000': 6, '10.000,10.000,0.000': 7, '10.000,20.000,0.000': 8, '10.000,30.000,0.000': 9, '10.000,40.000,0.000': 10, '10.000,50.000,0.000': 11, '20.000,0.000,0.000': 12, '20.000,10.000,0.000': 13, '20.000,20.000,0.000': 14, '20.000,30.000,0.000': 15, '20.000,40.000,0.000': 16, '20.000,50.000,0.000': 17, '30.000,0.000,0.000': 18, '30.000,10.000,0.000': 19, '31.000,22.000,0.000': 20, '30.000,30.000,0.000': 21, '30.000,40.000,0.000': 22, '30.000,50.000,0.000': 23, '40.000,0.000,0.000': 24, '40.000,10.000,0.000': 25, '40.000,20.000,0.000': 26, '40.000,30.000,0.000': 27, '40.000,40.000,0.000': 28, '40.000,50.000,0.000': 29, '50.000,0.000,0.000': 30, '50.000,10.000,0.000': 31, '50.000,20.000,0.000': 32, '50.000,30.000,0.000': 33, '50.000,40.000,0.000': 34, '50.000,50.000,0.000': 35}

Network.gkey_key([precision]) returns a dictionary that maps geometric keys of a certain precision to the keys of the corresponding nodes. The type of geometric keys is a string. Thus, the unknown node's xyz coordinates firstly need to be converted to a string so as to be used as a dictionary key.

from compas.utilities import geometric_key
node_xyz = [40, 50, 0]
node_index = geometric_key_dict[geometric_key(node_xyz)]
print(node_index)

29

nbrs = cablenet.neighborhood(node_index, 2)
print(nbrs)

text = {node: str(node) for node in cablenet.nodes()}

facecolor = {}
for nbr in nbrs:
    facecolor[nbr] = (0, 0, 255)
facecolor[node_index] = (255, 0, 0)

plotter = NetworkPlotter(cablenet, figsize=(12, 7.5))
plotter.draw_nodes(text=text, facecolor=facecolor, radius=1)
plotter.draw_edges()
plotter.show()

6. Serialization

All COMPAS data-structures and geometries can be serialized to JSON format, and unserialized from such a representation to reconstruct an equivalent object without loss of information.

6.a: Network Serialization

import os
HERE = os.path.dirname(__file__)
FILE = os.path.join(HERE, 'data', 'cablenet.json')

cablenet.to_json(FILE)

Network.to_json(path) would serialize the Network to a JSON file. Here, a new file called cablenet.json should be created in the data folder in the same directory of the python script.

6.b: Network unserialization

Network.from_json(path) would unserialize the Network.

import os
from compas.datastructures import Network

HERE = os.path.dirname(__file__)
FILE = os.path.join(HERE, 'cablenet.json')

cablenet = Network.from_json(FILE)
print(cablenet)

The sequence of items in the dictionary might change during the serialization and unserialization. This is because the dictionary stores key-value pairs and doesn't keep the keys' order, which makes look-up faster.

7. Load and Visualize the Cablenet in Rhino

Now load cablenet.json in Rhino and visualize it with compas_rhino.artists.NetworkArtist.

# ==============================================================================
# Import
# ==============================================================================
import os
from compas.datastructures import Network
from compas_rhino.artists import NetworkArtist

# ==============================================================================
# Unserialize
# ==============================================================================
# path to load the json file
HERE = os.path.dirname(__file__)
FILE = os.path.join(HERE, 'data', 'cablenet.json')

cablenet = Network.from_json(FILE)

# ==============================================================================
# Visualization
# ==============================================================================
artist = NetworkArtist(cablenet)
artist.draw()

Last updated