topolib 0.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of topolib might be problematic. Click here for more details.

@@ -0,0 +1,8 @@
1
+
2
+ from .node import Node
3
+ from .link import Link
4
+
5
+ # Hide re-exported Link from Sphinx index to avoid duplicate warnings
6
+ Link.__doc__ = """.. :noindex:"""
7
+
8
+ __all__ = ["Node", "Link"]
@@ -0,0 +1,145 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from topolib.elements.node import Node
7
+
8
+
9
+ class Link:
10
+
11
+ """
12
+ .. :noindex:
13
+
14
+ Represents a link between two nodes.
15
+
16
+ Parameters
17
+ ----------
18
+ id : int
19
+ Unique identifier for the link.
20
+ source : :class:`topolib.elements.node.Node`
21
+ Source node-like object (must have id, name, latitude, longitude).
22
+ target : :class:`topolib.elements.node.Node`
23
+ Target node-like object (must have id, name, latitude, longitude).
24
+ length : float
25
+ Length of the link (must be non-negative).
26
+
27
+ Attributes
28
+ ----------
29
+ id : int
30
+ Unique identifier for the link.
31
+ source : :class:`topolib.elements.node.Node`
32
+ Source node of the link.
33
+ target : :class:`topolib.elements.node.Node`
34
+ Target node of the link.
35
+ length : float
36
+ Length of the link.
37
+
38
+ Examples
39
+ --------
40
+ >>> link = Link(1, nodeA, nodeB, 10.5)
41
+ >>> link.length
42
+ """
43
+
44
+ def __init__(self, id: int, source: "Node", target: "Node", length: float):
45
+ self._id = id
46
+ self.source = source
47
+ self.target = target
48
+ self.length = length
49
+
50
+ @property
51
+ def id(self) -> int:
52
+ """
53
+ .. :noindex:
54
+
55
+ int: Unique identifier for the link.
56
+ """
57
+ return self._id
58
+
59
+ @id.setter
60
+ def id(self, value: int) -> None:
61
+ """
62
+ Set the link's unique identifier.
63
+ """
64
+ self._id = value
65
+
66
+ @property
67
+ def source(self) -> "Node":
68
+ """
69
+ .. :noindex:
70
+
71
+ :class:`topolib.elements.node.Node`: Source node of the link.
72
+ """
73
+ return self._source
74
+
75
+ @source.setter
76
+ def source(self, value: "Node") -> None:
77
+ """
78
+ Set the source node. Must have id, name, latitude, longitude.
79
+ """
80
+ required_attrs = ("id", "name", "latitude", "longitude")
81
+ for attr in required_attrs:
82
+ if not hasattr(value, attr):
83
+ raise TypeError(
84
+ f"source must behave like a Node (missing {attr})")
85
+ self._source = value
86
+
87
+ @property
88
+ def target(self) -> "Node":
89
+ """
90
+ .. :noindex:
91
+
92
+ :class:`topolib.elements.node.Node`: Target node of the link.
93
+ """
94
+ return self._target
95
+
96
+ @target.setter
97
+ def target(self, value: "Node") -> None:
98
+ """
99
+ Set the target node. Must have id, name, latitude, longitude.
100
+ """
101
+ required_attrs = ("id", "name", "latitude", "longitude")
102
+ for attr in required_attrs:
103
+ if not hasattr(value, attr):
104
+ raise TypeError(
105
+ f"target must behave like a Node (missing {attr})")
106
+ self._target = value
107
+
108
+ @property
109
+ def length(self) -> float:
110
+ """
111
+ .. :noindex:
112
+
113
+ float: Length of the link (non-negative).
114
+ """
115
+ return self._length
116
+
117
+ @length.setter
118
+ def length(self, value: float) -> None:
119
+ """
120
+ Set the length of the link. Must be a non-negative float.
121
+ """
122
+ try:
123
+ numeric = float(value)
124
+ except Exception:
125
+ raise TypeError("length must be a numeric value")
126
+ if numeric < 0:
127
+ raise ValueError("length must be non-negative")
128
+ self._length = numeric
129
+
130
+ def endpoints(self):
131
+ """
132
+ Return the (source, target) nodes as a tuple.
133
+
134
+ Returns
135
+ -------
136
+ tuple
137
+ (source_node, target_node)
138
+ """
139
+ return self._source, self._target
140
+
141
+ def __repr__(self) -> str:
142
+ """
143
+ Return a string representation of the Link.
144
+ """
145
+ return f"Link(id={self._id}, source={self._source.id}, target={self._target.id}, length={self._length})"
@@ -0,0 +1,221 @@
1
+ """
2
+ Node class for optical network topologies.
3
+
4
+ This module defines the Node class, representing a network node with geographic coordinates.
5
+ """
6
+
7
+ from typing import Tuple
8
+
9
+
10
+ class Node:
11
+ """
12
+ .. :noindex:
13
+
14
+ Represents a node in an optical network topology.
15
+
16
+ :param id: Unique identifier for the node.
17
+ :type id: int
18
+ :param name: Name of the node.
19
+ :type name: str
20
+ :param latitude: Latitude coordinate of the node.
21
+ :type latitude: float
22
+ :param longitude: Longitude coordinate of the node.
23
+ :type longitude: float
24
+ :param weight: Node weight (optional, default 0).
25
+ :type weight: float or int
26
+ :param pop: Node population (optional, default 0).
27
+ :type pop: int
28
+ :param dc: Datacenter (DC) value for the node (optional, default 0).
29
+ :type dc: int
30
+ :param ixp: IXP (Internet Exchange Point) value for the node (optional, default 0).
31
+ :type ixp: int
32
+ """
33
+
34
+ def __init__(
35
+ self,
36
+ id: int,
37
+ name: str,
38
+ latitude: float,
39
+ longitude: float,
40
+ weight: float = 0,
41
+ pop: int = 0,
42
+ dc: int = 0,
43
+ ixp: int = 0,
44
+ ):
45
+ self._id = id
46
+ self._name = name
47
+ self._latitude = latitude
48
+ self._longitude = longitude
49
+ self._weight = weight
50
+ self._pop = pop
51
+ self._dc = dc
52
+ self._ixp = ixp
53
+
54
+ @property
55
+ def dc(self) -> int:
56
+ """
57
+ Get the datacenter (DC) count or value for the node.
58
+
59
+ :return: Node DC value.
60
+ :rtype: int
61
+ """
62
+ return self._dc
63
+
64
+ @dc.setter
65
+ def dc(self, value: int) -> None:
66
+ """
67
+ Set the datacenter (DC) value for the node.
68
+
69
+ :param value: Node DC value.
70
+ :type value: int
71
+ """
72
+ self._dc = value
73
+
74
+ @property
75
+ def ixp(self) -> int:
76
+ """
77
+ Get the IXP (Internet Exchange Point) count or value for the node.
78
+
79
+ :return: Node IXP value.
80
+ :rtype: int
81
+ """
82
+ return self._ixp
83
+
84
+ @ixp.setter
85
+ def ixp(self, value: int) -> None:
86
+ """
87
+ Set the IXP (Internet Exchange Point) value for the node.
88
+
89
+ :param value: Node IXP value.
90
+ :type value: int
91
+ """
92
+ self._ixp = value
93
+
94
+ @property
95
+ def id(self) -> int:
96
+ """
97
+ Get the unique identifier of the node.
98
+
99
+ :return: Node ID.
100
+ :rtype: int
101
+ """
102
+ return self._id
103
+
104
+ @id.setter
105
+ def id(self, value: int) -> None:
106
+ """
107
+ Set the unique identifier of the node.
108
+
109
+ :param value: Node ID.
110
+ :type value: int
111
+ """
112
+ self._id = value
113
+
114
+ @property
115
+ def name(self) -> str:
116
+ """
117
+ Get the name of the node.
118
+
119
+ :return: Node name.
120
+ :rtype: str
121
+ """
122
+ return self._name
123
+
124
+ @name.setter
125
+ def name(self, value: str) -> None:
126
+ """
127
+ Set the name of the node.
128
+
129
+ :param value: Node name.
130
+ :type value: str
131
+ """
132
+ self._name = value
133
+
134
+ @property
135
+ def latitude(self) -> float:
136
+ """
137
+ Get the latitude coordinate of the node.
138
+
139
+ :return: Latitude value.
140
+ :rtype: float
141
+ """
142
+ return self._latitude
143
+
144
+ @latitude.setter
145
+ def latitude(self, value: float) -> None:
146
+ """
147
+ Set the latitude coordinate of the node.
148
+
149
+ :param value: Latitude value.
150
+ :type value: float
151
+ """
152
+ self._latitude = value
153
+
154
+ @property
155
+ def longitude(self) -> float:
156
+ """
157
+ Get the longitude coordinate of the node.
158
+
159
+ :return: Longitude value.
160
+ :rtype: float
161
+ """
162
+ return self._longitude
163
+
164
+ @longitude.setter
165
+ def longitude(self, value: float) -> None:
166
+ """
167
+ Set the longitude coordinate of the node.
168
+
169
+ :param value: Longitude value.
170
+ :type value: float
171
+ """
172
+ self._longitude = value
173
+
174
+ @property
175
+ def weight(self) -> float:
176
+ """
177
+ Get the weight of the node.
178
+
179
+ :return: Node weight.
180
+ :rtype: float
181
+ """
182
+ return self._weight
183
+
184
+ @weight.setter
185
+ def weight(self, value: float) -> None:
186
+ """
187
+ Set the weight of the node.
188
+
189
+ :param value: Node weight.
190
+ :type value: float
191
+ """
192
+ self._weight = value
193
+
194
+ @property
195
+ def pop(self) -> int:
196
+ """
197
+ Get the population of the node.
198
+
199
+ :return: Node population.
200
+ :rtype: int
201
+ """
202
+ return self._pop
203
+
204
+ @pop.setter
205
+ def pop(self, value: int) -> None:
206
+ """
207
+ Set the population of the node.
208
+
209
+ :param value: Node population.
210
+ :type value: int
211
+ """
212
+ self._pop = value
213
+
214
+ def coordinates(self) -> Tuple[float, float]:
215
+ """
216
+ Returns the (latitude, longitude) coordinates of the node.
217
+
218
+ :return: Tuple containing latitude and longitude.
219
+ :rtype: Tuple[float, float]
220
+ """
221
+ return self._latitude, self._longitude
@@ -0,0 +1,4 @@
1
+ from .path import Path
2
+ from .topology import Topology
3
+
4
+ __all__ = ["Path", "Topology"]
@@ -0,0 +1,84 @@
1
+ """
2
+ Path class for representing a sequence of nodes and links in a topology.
3
+ """
4
+
5
+ from typing import List, Any
6
+
7
+
8
+ class Path:
9
+ """
10
+ Represents a path through the network topology as an ordered sequence of nodes and links.
11
+
12
+ Parameters
13
+ ----------
14
+ nodes : list
15
+ Ordered list of node objects in the path.
16
+ links : list
17
+ Ordered list of link objects in the path (len(links) == len(nodes) - 1).
18
+
19
+ Raises
20
+ ------
21
+ ValueError
22
+ If the number of nodes and links is inconsistent or empty.
23
+
24
+ Attributes
25
+ ----------
26
+ nodes : list
27
+ Ordered list of nodes in the path.
28
+ links : list
29
+ Ordered list of links in the path.
30
+
31
+ Examples
32
+ --------
33
+ >>> nodes = [Node(1), Node(2), Node(3)]
34
+ >>> links = [Link('a'), Link('b')]
35
+ >>> path = Path(nodes, links)
36
+ >>> path.length()
37
+ 2
38
+ >>> path.endpoints()
39
+ (Node(1), Node(3))
40
+ """
41
+
42
+ def __init__(self, nodes: List[Any], links: List[Any]):
43
+ if not nodes or not links:
44
+ raise ValueError("A path must have at least one node and one link.")
45
+ if len(nodes) != len(links) + 1:
46
+ raise ValueError("Number of nodes must be one more than number of links.")
47
+ self.nodes = nodes
48
+ self.links = links
49
+
50
+ def length(self) -> int:
51
+ """
52
+ Return the number of links in the path.
53
+
54
+ Returns
55
+ -------
56
+ int
57
+ Number of links in the path.
58
+ """
59
+ return len(self.links)
60
+
61
+ def hop_count(self) -> int:
62
+ """
63
+ Return the number of hops (links) in the path.
64
+
65
+ Returns
66
+ -------
67
+ int
68
+ Number of hops (links) in the path.
69
+ """
70
+ return self.length()
71
+
72
+ def endpoints(self):
73
+ """
74
+ Return the source and target nodes of the path as a tuple.
75
+
76
+ Returns
77
+ -------
78
+ tuple
79
+ (source_node, target_node)
80
+ """
81
+ return (self.nodes[0], self.nodes[-1])
82
+
83
+ def __repr__(self):
84
+ return f"Path(nodes={self.nodes}, links={self.links})"
@@ -0,0 +1,172 @@
1
+ """
2
+ Topology class for optical network topologies.
3
+
4
+ This module defines the Topology class, representing a network topology with nodes and links,
5
+ and providing an adjacency matrix using numpy.
6
+
7
+ This file uses NetworkX (BSD 3-Clause License):
8
+ https://github.com/networkx/networkx/blob/main/LICENSE.txt
9
+ """
10
+
11
+ from typing import List, Dict, Any, Optional
12
+ import numpy as np
13
+ import networkx as nx
14
+ from topolib.elements.node import Node
15
+ from topolib.elements.link import Link
16
+
17
+
18
+ class Topology:
19
+ """
20
+ Represents a network topology with nodes and links.
21
+
22
+ :param nodes: Initial list of nodes (optional).
23
+ :type nodes: list[topolib.elements.node.Node] or None
24
+ :param links: Initial list of links (optional).
25
+ :type links: list[topolib.elements.link.Link] or None
26
+
27
+ :ivar nodes: List of nodes in the topology.
28
+ :vartype nodes: list[Node]
29
+ :ivar links: List of links in the topology.
30
+ :vartype links: list[Link]
31
+
32
+ **Examples**
33
+ >>> from topolib.elements.node import Node
34
+ >>> from topolib.elements.link import Link
35
+ >>> from topolib.topology import Topology
36
+ >>> n1 = Node(1, "A", 0.0, 0.0)
37
+ >>> n2 = Node(2, "B", 1.0, 1.0)
38
+ >>> l1 = Link(1, n1, n2, 10.0)
39
+ >>> topo = Topology(nodes=[n1, n2], links=[l1])
40
+ >>> topo.adjacency_matrix()
41
+ array([[0, 1],
42
+ [1, 0]])
43
+ """
44
+
45
+ def __init__(
46
+ self,
47
+ nodes: Optional[List[Node]] = None,
48
+ links: Optional[List[Link]] = None,
49
+ name: Optional[str] = None,
50
+ ):
51
+ """
52
+ Initialize a Topology object.
53
+
54
+ :param nodes: Initial list of nodes (optional).
55
+ :type nodes: list[topolib.elements.node.Node] or None
56
+ :param links: Initial list of links (optional).
57
+ :type links: list[topolib.elements.link.Link] or None
58
+ :param name: Name of the topology (optional).
59
+ :type name: str or None
60
+ """
61
+ self.nodes = nodes if nodes is not None else []
62
+ self.links = links if links is not None else []
63
+ self.name = name
64
+ # Internal NetworkX graph for algorithms and visualization
65
+ self._graph = nx.Graph()
66
+ for node in self.nodes:
67
+ self._graph.add_node(node.id, node=node)
68
+ for link in self.links:
69
+ self._graph.add_edge(link.source.id, link.target.id, link=link)
70
+
71
+ @classmethod
72
+ def from_json(cls, json_path: str) -> "Topology":
73
+ """
74
+ Create a Topology object from a JSON file.
75
+
76
+ :param json_path: Path to the JSON file containing the topology.
77
+ :type json_path: str
78
+ :return: Topology instance loaded from the file.
79
+ :rtype: Topology
80
+ """
81
+ import json
82
+ from topolib.elements.node import Node
83
+ from topolib.elements.link import Link
84
+
85
+ with open(json_path, "r", encoding="utf-8") as f:
86
+ data = json.load(f)
87
+ nodes = [
88
+ Node(
89
+ n["id"],
90
+ n["name"],
91
+ n["latitude"],
92
+ n["longitude"],
93
+ n.get("weight", 0),
94
+ n.get("pop", 0),
95
+ n.get("dc", n.get("DC", 0)),
96
+ n.get("ixp", n.get("IXP", 0)),
97
+ )
98
+ for n in data["nodes"]
99
+ ]
100
+ # Crear un dict para mapear id a Node
101
+ node_dict = {n.id: n for n in nodes}
102
+ links = [
103
+ Link(l["id"], node_dict[l["src"]], node_dict[l["dst"]], l["length"])
104
+ for l in data["links"]
105
+ ]
106
+ name = data.get("name", None)
107
+ return cls(nodes=nodes, links=links, name=name)
108
+
109
+ def add_node(self, node: Node) -> None:
110
+ """
111
+ Add a node to the topology.
112
+
113
+ :param node: Node to add.
114
+ :type node: Node
115
+ """
116
+ self.nodes.append(node)
117
+ self._graph.add_node(node.id, node=node)
118
+
119
+ def add_link(self, link: Link) -> None:
120
+ """
121
+ Add a link to the topology.
122
+
123
+ :param link: Link to add.
124
+ :type link: Link
125
+ """
126
+ self.links.append(link)
127
+ self._graph.add_edge(link.source.id, link.target.id, link=link)
128
+
129
+ def remove_node(self, node_id: int) -> None:
130
+ """
131
+ Remove a node and all its links by node id.
132
+
133
+ :param node_id: ID of the node to remove.
134
+ :type node_id: int
135
+ """
136
+ self.nodes = [n for n in self.nodes if n.id != node_id]
137
+ self.links = [
138
+ l for l in self.links if l.source.id != node_id and l.target.id != node_id
139
+ ]
140
+ self._graph.remove_node(node_id)
141
+
142
+ def remove_link(self, link_id: int) -> None:
143
+ """
144
+ Remove a link by its id.
145
+
146
+ :param link_id: ID of the link to remove.
147
+ :type link_id: int
148
+ """
149
+ # Find the link and remove from graph
150
+ link = next((l for l in self.links if l.id == link_id), None)
151
+ if link:
152
+ self._graph.remove_edge(link.source.id, link.target.id)
153
+ self.links = [l for l in self.links if l.id != link_id]
154
+
155
+ def adjacency_matrix(self) -> np.ndarray:
156
+ """
157
+ Return the adjacency matrix of the topology as a numpy array.
158
+
159
+ :return: Adjacency matrix (1 if connected, 0 otherwise).
160
+ :rtype: numpy.ndarray
161
+
162
+ **Example**
163
+ >>> topo.adjacency_matrix()
164
+ array([[0, 1],
165
+ [1, 0]])
166
+ """
167
+ # Usa NetworkX para obtener la matriz de adyacencia
168
+ if not self.nodes:
169
+ return np.zeros((0, 0), dtype=int)
170
+ node_ids = [n.id for n in self.nodes]
171
+ mat = nx.to_numpy_array(self._graph, nodelist=node_ids, dtype=int)
172
+ return mat
@@ -0,0 +1 @@
1
+ from .mapview import MapView