topologicpy 0.7.97__py3-none-any.whl → 0.7.98__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.
- topologicpy/Graph.py +507 -68
- topologicpy/Matrix.py +63 -0
- topologicpy/Vertex.py +83 -0
- topologicpy/version.py +1 -1
- {topologicpy-0.7.97.dist-info → topologicpy-0.7.98.dist-info}/METADATA +1 -1
- {topologicpy-0.7.97.dist-info → topologicpy-0.7.98.dist-info}/RECORD +9 -9
- {topologicpy-0.7.97.dist-info → topologicpy-0.7.98.dist-info}/LICENSE +0 -0
- {topologicpy-0.7.97.dist-info → topologicpy-0.7.98.dist-info}/WHEEL +0 -0
- {topologicpy-0.7.97.dist-info → topologicpy-0.7.98.dist-info}/top_level.txt +0 -0
topologicpy/Graph.py
CHANGED
@@ -1240,9 +1240,9 @@ class Graph:
|
|
1240
1240
|
return bot_graph.serialize(format=format)
|
1241
1241
|
|
1242
1242
|
@staticmethod
|
1243
|
-
def BetweennessCentrality(graph, key: str = "betweenness_centrality", mantissa: int = 6, tolerance: float = 0.001, silent: bool = False):
|
1243
|
+
def BetweennessCentrality(graph, key: str = "betweenness_centrality", method="vertex", mantissa: int = 6, tolerance: float = 0.001, silent: bool = False):
|
1244
1244
|
"""
|
1245
|
-
Returns the betweenness centrality
|
1245
|
+
Returns the betweenness centrality of the input graph. The order of the returned list is the same as the order of vertices/edges. See https://en.wikipedia.org/wiki/Betweenness_centrality.
|
1246
1246
|
|
1247
1247
|
Parameters
|
1248
1248
|
----------
|
@@ -1250,6 +1250,8 @@ class Graph:
|
|
1250
1250
|
The input graph.
|
1251
1251
|
key : str , optional
|
1252
1252
|
The dictionary key under which to store the betweeness centrality score. The default is "betweenness_centrality".
|
1253
|
+
method : str , optional
|
1254
|
+
The method of computing the betweenness centrality. The options are "vertex" or "edge". The default is "vertex".
|
1253
1255
|
mantissa : int , optional
|
1254
1256
|
The desired length of the mantissa. The default is 6.
|
1255
1257
|
tolerance : float , optional
|
@@ -1276,7 +1278,7 @@ class Graph:
|
|
1276
1278
|
shortest_paths[path[-1]].append(path)
|
1277
1279
|
return shortest_paths
|
1278
1280
|
|
1279
|
-
def
|
1281
|
+
def calculate_vertex_betweenness():
|
1280
1282
|
betweenness = {v: 0.0 for v in py_graph}
|
1281
1283
|
for s in py_graph:
|
1282
1284
|
shortest_paths = shortest_paths_count(s)
|
@@ -1291,6 +1293,56 @@ class Graph:
|
|
1291
1293
|
betweenness[v] += dependency[v]
|
1292
1294
|
return betweenness
|
1293
1295
|
|
1296
|
+
def calculate_edge_betweenness(graph_adj_matrix):
|
1297
|
+
n = len(graph_adj_matrix)
|
1298
|
+
edge_betweenness_scores = {}
|
1299
|
+
|
1300
|
+
# Iterate over all node pairs as source and target nodes
|
1301
|
+
for source in range(n):
|
1302
|
+
# Initialize the 'distance' and 'predecessors' for each node
|
1303
|
+
distance = [-1] * n
|
1304
|
+
predecessors = [[] for _ in range(n)]
|
1305
|
+
distance[source] = 0
|
1306
|
+
stack = []
|
1307
|
+
queue = [source]
|
1308
|
+
|
1309
|
+
# Breadth-first search to find shortest paths
|
1310
|
+
while queue:
|
1311
|
+
current_node = queue.pop(0)
|
1312
|
+
stack.append(current_node)
|
1313
|
+
for neighbor in range(n):
|
1314
|
+
if graph_adj_matrix[current_node][neighbor] == 1:
|
1315
|
+
if distance[neighbor] == -1: # First time visiting neighbor
|
1316
|
+
distance[neighbor] = distance[current_node] + 1
|
1317
|
+
queue.append(neighbor)
|
1318
|
+
if distance[neighbor] == distance[current_node] + 1: # Shortest path
|
1319
|
+
predecessors[neighbor].append(current_node)
|
1320
|
+
|
1321
|
+
# Initialize the dependency values for each node
|
1322
|
+
dependency = [0] * n
|
1323
|
+
|
1324
|
+
# Process the nodes in reverse order of discovery
|
1325
|
+
while stack:
|
1326
|
+
current_node = stack.pop()
|
1327
|
+
for pred in predecessors[current_node]:
|
1328
|
+
dependency[pred] += (1 + dependency[current_node]) / len(predecessors[current_node])
|
1329
|
+
|
1330
|
+
# Update edge betweenness scores
|
1331
|
+
if pred < current_node:
|
1332
|
+
edge = (pred, current_node)
|
1333
|
+
else:
|
1334
|
+
edge = (current_node, pred)
|
1335
|
+
|
1336
|
+
if edge not in edge_betweenness_scores:
|
1337
|
+
edge_betweenness_scores[edge] = 0
|
1338
|
+
edge_betweenness_scores[edge] += dependency[current_node]
|
1339
|
+
|
1340
|
+
# Normalize edge betweenness scores by dividing by 2 (since each edge is counted twice)
|
1341
|
+
for edge in edge_betweenness_scores:
|
1342
|
+
edge_betweenness_scores[edge] /= 2
|
1343
|
+
|
1344
|
+
return edge_betweenness_scores
|
1345
|
+
|
1294
1346
|
from topologicpy.Topology import Topology
|
1295
1347
|
from topologicpy.Dictionary import Dictionary
|
1296
1348
|
|
@@ -1299,38 +1351,75 @@ class Graph:
|
|
1299
1351
|
print("Graph.BetweenessCentrality - Error: The input graph is not a valid graph. Returning None.")
|
1300
1352
|
return None
|
1301
1353
|
|
1302
|
-
|
1303
|
-
|
1304
|
-
if len(vertices) < 1:
|
1305
|
-
if not silent:
|
1306
|
-
print("Graph.BetweenessCentrality - Error: The input graph does not contain valid vertices. Returning None.")
|
1307
|
-
return None
|
1308
|
-
if len(vertices) == 1:
|
1309
|
-
d = Topology.Dictionary(vertices[0])
|
1310
|
-
d = Dictionary.SetValueAtKey(d, key, 1.0)
|
1311
|
-
vertices[0] = Topology.SetDictionary(vertices[0], d)
|
1312
|
-
return [1.0]
|
1313
|
-
|
1314
|
-
py_graph = Graph.AdjacencyDictionary(graph)
|
1315
|
-
betweenness = calculate_betweenness()
|
1316
|
-
for v in betweenness:
|
1317
|
-
betweenness[v] /= 2.0 # Each shortest path is counted twice
|
1354
|
+
if "v" in method.lower():
|
1355
|
+
vertices = Graph.Vertices(graph)
|
1318
1356
|
|
1319
|
-
|
1320
|
-
|
1321
|
-
|
1322
|
-
|
1323
|
-
|
1324
|
-
|
1325
|
-
|
1326
|
-
|
1327
|
-
|
1328
|
-
|
1329
|
-
|
1330
|
-
|
1331
|
-
|
1332
|
-
|
1333
|
-
|
1357
|
+
if len(vertices) < 1:
|
1358
|
+
if not silent:
|
1359
|
+
print("Graph.BetweenessCentrality - Error: The input graph does not contain valid vertices. Returning None.")
|
1360
|
+
return None
|
1361
|
+
if len(vertices) == 1:
|
1362
|
+
d = Topology.Dictionary(vertices[0])
|
1363
|
+
d = Dictionary.SetValueAtKey(d, key, 1.0)
|
1364
|
+
vertices[0] = Topology.SetDictionary(vertices[0], d)
|
1365
|
+
return [1.0]
|
1366
|
+
|
1367
|
+
py_graph = Graph.AdjacencyDictionary(graph)
|
1368
|
+
vertex_betweenness = calculate_vertex_betweenness()
|
1369
|
+
for v in vertex_betweenness:
|
1370
|
+
vertex_betweenness[v] /= 2.0 # Each shortest path is counted twice
|
1371
|
+
|
1372
|
+
min_betweenness = min(vertex_betweenness.values())
|
1373
|
+
max_betweenness = max(vertex_betweenness.values())
|
1374
|
+
if (max_betweenness - min_betweenness) > 0:
|
1375
|
+
for v in vertex_betweenness:
|
1376
|
+
vertex_betweenness[v] = (vertex_betweenness[v] - min_betweenness)/ (max_betweenness - min_betweenness) # Normalize to [0, 1]
|
1377
|
+
|
1378
|
+
|
1379
|
+
vertex_betweenness_scores = [0]*len(vertices)
|
1380
|
+
for i, score in vertex_betweenness.items():
|
1381
|
+
vertex = vertices[int(i)]
|
1382
|
+
d = Topology.Dictionary(vertex)
|
1383
|
+
d = Dictionary.SetValueAtKey(d, key, round(score, mantissa))
|
1384
|
+
vertex = Topology.SetDictionary(vertex, d)
|
1385
|
+
vertex_betweenness_scores[int(i)] = round(score, mantissa)
|
1386
|
+
|
1387
|
+
return vertex_betweenness_scores
|
1388
|
+
else:
|
1389
|
+
graph_edges = Graph.Edges(graph)
|
1390
|
+
adj_matrix = Graph.AdjacencyMatrix(graph)
|
1391
|
+
meshData = Graph.MeshData(graph)
|
1392
|
+
edges = meshData["edges"]
|
1393
|
+
if len(graph_edges) < 1:
|
1394
|
+
if not silent:
|
1395
|
+
print("Graph.BetweenessCentrality - Error: The input graph does not contain any edges. Returning None.")
|
1396
|
+
return None
|
1397
|
+
if len(graph_edges) == 1:
|
1398
|
+
d = Topology.Dictionary(graph_edges[0])
|
1399
|
+
d = Dictionary.SetValueAtKey(d, key, 1.0)
|
1400
|
+
graph_edges[0] = Topology.SetDictionary(graph_edges[0], d)
|
1401
|
+
return [1.0]
|
1402
|
+
|
1403
|
+
edge_betweenness = calculate_edge_betweenness(adj_matrix)
|
1404
|
+
keys = list(edge_betweenness.keys())
|
1405
|
+
values = list(edge_betweenness.values())
|
1406
|
+
min_value = min(values)
|
1407
|
+
max_value = max(values)
|
1408
|
+
edge_betweenness_scores = []
|
1409
|
+
for i, edge in enumerate(edges):
|
1410
|
+
u,v = edge
|
1411
|
+
if (u,v) in keys:
|
1412
|
+
score = edge_betweenness[(u,v)]
|
1413
|
+
elif (v,u) in keys:
|
1414
|
+
score = edge_betweenness[(v,u)]
|
1415
|
+
else:
|
1416
|
+
score = 0
|
1417
|
+
score = (score - min_value)/(max_value - min_value)
|
1418
|
+
edge_betweenness_scores.append(round(score, mantissa))
|
1419
|
+
d = Topology.Dictionary(graph_edges[i])
|
1420
|
+
d = Dictionary.SetValueAtKey(d, key, round(score, mantissa))
|
1421
|
+
graph_edges[i] = Topology.SetDictionary(graph_edges[i], d)
|
1422
|
+
return edge_betweenness_scores
|
1334
1423
|
|
1335
1424
|
@staticmethod
|
1336
1425
|
def Bridges(graph, key: str = "bridge", silent: bool = False):
|
@@ -1345,59 +1434,91 @@ class Graph:
|
|
1345
1434
|
The edge dictionary key under which to store the bridge status. 0 means the edge is NOT a bridge. 1 means that the edge IS a bridge. The default is "bridge".
|
1346
1435
|
silent : bool , optional
|
1347
1436
|
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
1348
|
-
|
1437
|
+
|
1349
1438
|
Returns
|
1350
1439
|
-------
|
1351
1440
|
list
|
1352
1441
|
The list of bridge edges in the input graph.
|
1353
|
-
|
1354
1442
|
"""
|
1355
|
-
import os
|
1356
|
-
import warnings
|
1357
1443
|
from topologicpy.Topology import Topology
|
1358
1444
|
from topologicpy.Dictionary import Dictionary
|
1445
|
+
from topologicpy.Graph import Graph
|
1359
1446
|
|
1360
|
-
try:
|
1361
|
-
import igraph as ig
|
1362
|
-
except:
|
1363
|
-
print("Graph.Bridges - Installing required pyhon-igraph library.")
|
1364
|
-
try:
|
1365
|
-
os.system("pip install python-igraph")
|
1366
|
-
except:
|
1367
|
-
os.system("pip install python-igraph --user")
|
1368
|
-
try:
|
1369
|
-
import igraph as ig
|
1370
|
-
print("Graph.Bridges - python-igraph library installed correctly.")
|
1371
|
-
except:
|
1372
|
-
warnings.warn("Graph.Bridges - Error: Could not import python-igraph. Please install manually.")
|
1373
|
-
|
1374
1447
|
if not Topology.IsInstance(graph, "graph"):
|
1375
1448
|
if not silent:
|
1376
1449
|
print("Graph.Bridges - Error: The input graph parameter is not a valid topologic graph. Returning None")
|
1377
1450
|
return None
|
1378
1451
|
|
1379
|
-
|
1380
|
-
|
1381
|
-
return []
|
1382
|
-
if len(edges) == 1:
|
1452
|
+
graph_edges = Graph.Edges(graph)
|
1453
|
+
for edge in graph_edges:
|
1383
1454
|
d = Topology.Dictionary(edge)
|
1384
|
-
d = Dictionary.SetValueAtKey(d, key,
|
1455
|
+
d = Dictionary.SetValueAtKey(d, key, 0)
|
1385
1456
|
edge = Topology.SetDictionary(edge, d)
|
1386
|
-
return [edge]
|
1387
1457
|
mesh_data = Graph.MeshData(graph)
|
1388
|
-
|
1389
|
-
|
1458
|
+
mesh_edges = mesh_data['edges']
|
1459
|
+
|
1460
|
+
# Get adjacency dictionary
|
1461
|
+
adjacency_dict = Graph.AdjacencyDictionary(graph)
|
1462
|
+
if not adjacency_dict:
|
1463
|
+
if not silent:
|
1464
|
+
print("Graph.Bridges - Error: Failed to retrieve adjacency dictionary. Returning None")
|
1465
|
+
return None
|
1466
|
+
|
1467
|
+
# Helper function to perform DFS and find bridges
|
1468
|
+
def dfs(vertex, parent, time, low, disc, visited, adjacency_dict, bridges, edge_map):
|
1469
|
+
visited[int(vertex)] = True
|
1470
|
+
disc[int(vertex)] = low[int(vertex)] = time[0]
|
1471
|
+
time[0] += 1
|
1472
|
+
for neighbor in adjacency_dict[vertex]:
|
1473
|
+
if not visited[int(neighbor)]:
|
1474
|
+
dfs(neighbor, vertex, time, low, disc, visited, adjacency_dict, bridges, edge_map)
|
1475
|
+
low[int(vertex)] = min(low[int(vertex)], low[int(neighbor)])
|
1476
|
+
|
1477
|
+
# Check if edge is a bridge
|
1478
|
+
if low[int(neighbor)] > disc[int(vertex)]:
|
1479
|
+
bridges.add((vertex, neighbor))
|
1480
|
+
elif neighbor != parent:
|
1481
|
+
low[int(vertex)] = min(low[int(vertex)], disc[int(neighbor)])
|
1390
1482
|
|
1391
|
-
|
1483
|
+
# Prepare adjacency list and edge mapping
|
1484
|
+
vertices = list(adjacency_dict.keys())
|
1485
|
+
num_vertices = len(vertices)
|
1486
|
+
visited = [False] * num_vertices
|
1487
|
+
disc = [-1] * num_vertices
|
1488
|
+
low = [-1] * num_vertices
|
1489
|
+
time = [0]
|
1490
|
+
bridges = set()
|
1491
|
+
|
1492
|
+
# Map edges to indices
|
1493
|
+
edge_map = {}
|
1494
|
+
index = 0
|
1495
|
+
for vertex, neighbors in adjacency_dict.items():
|
1496
|
+
for neighbor in neighbors:
|
1497
|
+
if (neighbor, vertex) not in edge_map: # Avoid duplicating edges in undirected graphs
|
1498
|
+
edge_map[(vertex, neighbor)] = index
|
1499
|
+
index += 1
|
1500
|
+
|
1501
|
+
# Run DFS from all unvisited vertices
|
1502
|
+
for i, vertex in enumerate(vertices):
|
1503
|
+
if not visited[i]:
|
1504
|
+
dfs(vertex, -1, time, low, disc, visited, adjacency_dict, bridges, edge_map)
|
1505
|
+
|
1506
|
+
# Mark bridges in the edges' dictionaries
|
1392
1507
|
bridge_edges = []
|
1393
|
-
for
|
1508
|
+
for edge in bridges:
|
1509
|
+
i, j = edge
|
1510
|
+
i = int(i)
|
1511
|
+
j = int(j)
|
1512
|
+
try:
|
1513
|
+
edge_index = mesh_edges.index([i,j])
|
1514
|
+
except:
|
1515
|
+
edge_index = mesh_edges.index([j,i])
|
1516
|
+
bridge_edges.append(graph_edges[edge_index])
|
1517
|
+
for edge in bridge_edges:
|
1394
1518
|
d = Topology.Dictionary(edge)
|
1395
|
-
|
1396
|
-
d = Dictionary.SetValueAtKey(d, key, 1)
|
1397
|
-
bridge_edges.append(edge)
|
1398
|
-
else:
|
1399
|
-
d = Dictionary.SetValueAtKey(d, key, 0)
|
1519
|
+
d = Dictionary.SetValueAtKey(d, key, 1)
|
1400
1520
|
edge = Topology.SetDictionary(edge, d)
|
1521
|
+
|
1401
1522
|
return bridge_edges
|
1402
1523
|
|
1403
1524
|
@staticmethod
|
@@ -1486,7 +1607,8 @@ class Graph:
|
|
1486
1607
|
for i in range(len(adjacencyMatrix)):
|
1487
1608
|
x, y, z = random.uniform(xMin,xMax), random.uniform(yMin,yMax), random.uniform(zMin,zMax)
|
1488
1609
|
v = Vertex.ByCoordinates(x, y, z)
|
1489
|
-
|
1610
|
+
if isinstance(dictionaries, list):
|
1611
|
+
v = Topology.SetDictionary(v, dictionaries[i])
|
1490
1612
|
vertices.append(v)
|
1491
1613
|
|
1492
1614
|
# Create the graph using vertices and edges
|
@@ -4153,6 +4275,82 @@ class Graph:
|
|
4153
4275
|
v = Topology.SetDictionary(v, d)
|
4154
4276
|
return graph
|
4155
4277
|
|
4278
|
+
@staticmethod
|
4279
|
+
def Complement(graph, tolerance=0.0001, silent=False):
|
4280
|
+
"""
|
4281
|
+
Creates the complement graph of the input graph. See https://en.wikipedia.org/wiki/Complement_graph
|
4282
|
+
|
4283
|
+
Parameters
|
4284
|
+
----------
|
4285
|
+
graph : topologicpy.Graph
|
4286
|
+
The input topologic graph.
|
4287
|
+
tolerance : float , optional
|
4288
|
+
The desired tolerance. The default is 0.0001.
|
4289
|
+
silent : bool , optional
|
4290
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
4291
|
+
|
4292
|
+
Returns
|
4293
|
+
-------
|
4294
|
+
topologicpy.Graph
|
4295
|
+
The created complement topologic graph.
|
4296
|
+
|
4297
|
+
"""
|
4298
|
+
def complement_graph(adj_dict):
|
4299
|
+
"""
|
4300
|
+
Creates the complement graph from an input adjacency dictionary.
|
4301
|
+
|
4302
|
+
Parameters:
|
4303
|
+
adj_dict (dict): The adjacency dictionary where keys are nodes and
|
4304
|
+
values are lists of connected nodes.
|
4305
|
+
|
4306
|
+
Returns:
|
4307
|
+
list of tuples: A list of edge index tuples representing the complement graph.
|
4308
|
+
"""
|
4309
|
+
# Get all nodes in the graph
|
4310
|
+
nodes = list(adj_dict.keys())
|
4311
|
+
# Initialize a set to store edges of the complement graph
|
4312
|
+
complement_edges = set()
|
4313
|
+
# Convert adjacency dictionary to a set of existing edges
|
4314
|
+
existing_edges = set()
|
4315
|
+
for node, neighbors in adj_dict.items():
|
4316
|
+
for neighbor in neighbors:
|
4317
|
+
# Add the edge as an ordered tuple to ensure no duplicates
|
4318
|
+
existing_edges.add(tuple(sorted((node, neighbor))))
|
4319
|
+
# Generate all possible edges and check if they exist in the original graph
|
4320
|
+
for i, node1 in enumerate(nodes):
|
4321
|
+
for j in range(i + 1, len(nodes)):
|
4322
|
+
node2 = nodes[j]
|
4323
|
+
edge = tuple(sorted((node1, node2)))
|
4324
|
+
# Add the edge if it's not in the original graph
|
4325
|
+
if edge not in existing_edges:
|
4326
|
+
complement_edges.add(edge)
|
4327
|
+
# Return the complement edges as a sorted list of tuples
|
4328
|
+
return sorted(complement_edges)
|
4329
|
+
|
4330
|
+
from topologicpy.Graph import Graph
|
4331
|
+
from topologicpy.Edge import Edge
|
4332
|
+
from topologicpy.Vertex import Vertex
|
4333
|
+
from topologicpy.Topology import Topology
|
4334
|
+
|
4335
|
+
if not Topology.IsInstance(graph, "graph"):
|
4336
|
+
if not silent:
|
4337
|
+
print("Graph.Complement - Error: The input graph parameter is not a valid topologic graph. Returning None.")
|
4338
|
+
return None
|
4339
|
+
adj_dict = Graph.AdjacencyDictionary(graph)
|
4340
|
+
py_edges = complement_graph(adj_dict)
|
4341
|
+
vertices = Graph.Vertices(graph)
|
4342
|
+
adjusted_vertices = Vertex.Separate(vertices, minDistance=tolerance)
|
4343
|
+
edges = []
|
4344
|
+
for py_edge in py_edges:
|
4345
|
+
start, end = py_edge
|
4346
|
+
sv = adjusted_vertices[int(start)]
|
4347
|
+
ev = adjusted_vertices[int(end)]
|
4348
|
+
edge = Edge.ByVertices(sv, ev, tolerance=tolerance, silent=silent)
|
4349
|
+
if Topology.IsInstance(edge, "edge"):
|
4350
|
+
edges.append(edge)
|
4351
|
+
return_graph = Graph.ByVerticesEdges(adjusted_vertices, edges)
|
4352
|
+
return return_graph
|
4353
|
+
|
4156
4354
|
@staticmethod
|
4157
4355
|
def Complete(graph, silent: bool = False):
|
4158
4356
|
"""
|
@@ -4793,6 +4991,28 @@ class Graph:
|
|
4793
4991
|
scores.append(degree)
|
4794
4992
|
return scores
|
4795
4993
|
|
4994
|
+
@staticmethod
|
4995
|
+
def DegreeMatrix(graph):
|
4996
|
+
"""
|
4997
|
+
Returns the degree matrix of the input graph. See https://en.wikipedia.org/wiki/Degree_matrix.
|
4998
|
+
|
4999
|
+
Parameters
|
5000
|
+
----------
|
5001
|
+
graph : topologic_core.Graph
|
5002
|
+
The input graph.
|
5003
|
+
|
5004
|
+
Returns
|
5005
|
+
-------
|
5006
|
+
list
|
5007
|
+
The degree matrix of the input graph.
|
5008
|
+
|
5009
|
+
"""
|
5010
|
+
import numpy as np
|
5011
|
+
adj_matrix = Graph.AdjacencyMatrix(g)
|
5012
|
+
np_adj_matrix = np.array(adj_matrix)
|
5013
|
+
degree_matrix = np.diag(np_adj_matrix.sum(axis=1))
|
5014
|
+
return degree_matrix.tolist()
|
5015
|
+
|
4796
5016
|
@staticmethod
|
4797
5017
|
def DegreeSequence(graph):
|
4798
5018
|
"""
|
@@ -6161,6 +6381,37 @@ class Graph:
|
|
6161
6381
|
return False
|
6162
6382
|
return False
|
6163
6383
|
|
6384
|
+
@staticmethod
|
6385
|
+
def FiedlerVector(graph, mantissa = 6, silent: bool = False):
|
6386
|
+
"""
|
6387
|
+
Computes the Fiedler vector of a graph. See https://en.wikipedia.org/wiki/Algebraic_connectivity.
|
6388
|
+
|
6389
|
+
Parameters
|
6390
|
+
----------
|
6391
|
+
graph : topologic_core.Graph
|
6392
|
+
The input graph
|
6393
|
+
mantissa : int , optional
|
6394
|
+
The desired length of the mantissa. The default is 6.
|
6395
|
+
silent : bool , optional
|
6396
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
6397
|
+
|
6398
|
+
Returns
|
6399
|
+
-------
|
6400
|
+
list
|
6401
|
+
The Fiedler vector (eigenvector corresponding to the second smallest eigenvalue).
|
6402
|
+
"""
|
6403
|
+
from topologicpy.Topology import Topology
|
6404
|
+
from topologicpy.Matrix import Matrix
|
6405
|
+
import numpy as np
|
6406
|
+
|
6407
|
+
if not Topology.IsInstance(graph, "graph"):
|
6408
|
+
if not silent:
|
6409
|
+
print("Graph.FiedlerVector - Error: The input graph parameter is not a valid graph. Returning None.")
|
6410
|
+
|
6411
|
+
laplacian = Graph.Laplacian(graph)
|
6412
|
+
eigenvalues, eigenvectors = Matrix.EigenvaluesAndVectors(laplacian, mantissa=mantissa)
|
6413
|
+
return eigenvectors[1]
|
6414
|
+
|
6164
6415
|
@staticmethod
|
6165
6416
|
def IsIsomorphic(graphA, graphB, maxIterations=10, silent=False):
|
6166
6417
|
"""
|
@@ -7511,6 +7762,59 @@ class Graph:
|
|
7511
7762
|
return None
|
7512
7763
|
return graph.IsComplete()
|
7513
7764
|
|
7765
|
+
@staticmethod
|
7766
|
+
def IsConnected(graph, vertexA, vertexB, silent: bool = False):
|
7767
|
+
"""
|
7768
|
+
Returns True if the two input vertices are directly connected by an edge. Returns False otherwise.
|
7769
|
+
|
7770
|
+
Parameters
|
7771
|
+
----------
|
7772
|
+
graph : topologic_core.Graph
|
7773
|
+
The input graph.
|
7774
|
+
vertexA : topologic_core.Vertex
|
7775
|
+
The first input vertex.
|
7776
|
+
vertexB : topologic_core.Vertex
|
7777
|
+
The second input vertex
|
7778
|
+
silent : bool , optional
|
7779
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
7780
|
+
|
7781
|
+
Returns
|
7782
|
+
-------
|
7783
|
+
bool
|
7784
|
+
True if the input vertices are connected by an edge. False otherwise.
|
7785
|
+
|
7786
|
+
"""
|
7787
|
+
from topologicpy.Topology import Topology
|
7788
|
+
|
7789
|
+
if not Topology.IsInstance(graph, "graph"):
|
7790
|
+
if not silent:
|
7791
|
+
print("Graph.IsConnected - Error: The input graph parameter is not a valid graph. Returning None.")
|
7792
|
+
return None
|
7793
|
+
|
7794
|
+
if not Topology.IsInstance(vertexA, "vertex"):
|
7795
|
+
if not silent:
|
7796
|
+
print("Graph.IsConnected - Error: The input vertexA parameter is not a valid vertex. Returning None.")
|
7797
|
+
return None
|
7798
|
+
|
7799
|
+
if not Topology.IsInstance(vertexB, "vertex"):
|
7800
|
+
if not silent:
|
7801
|
+
print("Graph.IsConnected - Error: The input vertexB parameter is not a valid vertex. Returning None.")
|
7802
|
+
return None
|
7803
|
+
|
7804
|
+
if vertexA == vertexB:
|
7805
|
+
if not silent:
|
7806
|
+
print("Graph.IsConnected - Warrning: The two input vertices are the same vertex. Returning False.")
|
7807
|
+
return False
|
7808
|
+
shortest_path = Graph.ShortestPath(graph, vertexA, vertexB)
|
7809
|
+
if shortest_path == None:
|
7810
|
+
return False
|
7811
|
+
else:
|
7812
|
+
edges = Topology.Edges(shortest_path)
|
7813
|
+
if len(edges) == 1:
|
7814
|
+
return True
|
7815
|
+
else:
|
7816
|
+
return False
|
7817
|
+
|
7514
7818
|
@staticmethod
|
7515
7819
|
def IsErdoesGallai(graph, sequence):
|
7516
7820
|
"""
|
@@ -7777,6 +8081,61 @@ class Graph:
|
|
7777
8081
|
json_string = json.dumps(json_data, indent=indent, sort_keys=sortKeys)
|
7778
8082
|
return json_string
|
7779
8083
|
|
8084
|
+
def Laplacian(graph, silent: bool = False, normalized: bool = False):
|
8085
|
+
"""
|
8086
|
+
Returns the Laplacian matrix of the input graph. See https://en.wikipedia.org/wiki/Laplacian_matrix.
|
8087
|
+
|
8088
|
+
Parameters
|
8089
|
+
----------
|
8090
|
+
graph : topologic_core.Graph
|
8091
|
+
The input graph.
|
8092
|
+
silent : bool , optional
|
8093
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
8094
|
+
normalized : bool , optional
|
8095
|
+
If set to True, the returned Laplacian matrix is normalized. The default is False.
|
8096
|
+
|
8097
|
+
Returns
|
8098
|
+
-------
|
8099
|
+
list
|
8100
|
+
The Laplacian matrix as a nested list.
|
8101
|
+
"""
|
8102
|
+
from topologicpy.Topology import Topology
|
8103
|
+
import numpy as np
|
8104
|
+
|
8105
|
+
if not Topology.IsInstance(graph, "graph"):
|
8106
|
+
if not silent:
|
8107
|
+
print("Graph.Laplacian - Error: The input graph parameter is not a valid graph. Returning None.")
|
8108
|
+
return None
|
8109
|
+
|
8110
|
+
# Get vertices of the graph
|
8111
|
+
vertices = Graph.Vertices(graph)
|
8112
|
+
n = len(vertices)
|
8113
|
+
|
8114
|
+
# Initialize Laplacian matrix
|
8115
|
+
laplacian = np.zeros((n, n))
|
8116
|
+
|
8117
|
+
# Fill Laplacian matrix
|
8118
|
+
for i, v1 in enumerate(vertices):
|
8119
|
+
for j, v2 in enumerate(vertices):
|
8120
|
+
if i == j:
|
8121
|
+
laplacian[i][j] = float(Graph.VertexDegree(graph, v1))
|
8122
|
+
elif Graph.IsConnected(graph, v1, v2):
|
8123
|
+
laplacian[i][j] = -1.0
|
8124
|
+
else:
|
8125
|
+
laplacian[i][j] = 0.0
|
8126
|
+
|
8127
|
+
# Normalize the Laplacian if requested
|
8128
|
+
if normalized:
|
8129
|
+
degree_matrix = np.diag(laplacian.diagonal())
|
8130
|
+
with np.errstate(divide='ignore'): # Suppress warnings for division by zero
|
8131
|
+
d_inv_sqrt = np.diag(1.0 / np.sqrt(degree_matrix.diagonal()))
|
8132
|
+
d_inv_sqrt[np.isinf(d_inv_sqrt)] = 0 # Replace infinities with zero
|
8133
|
+
|
8134
|
+
normalized_laplacian = d_inv_sqrt @ laplacian @ d_inv_sqrt
|
8135
|
+
return normalized_laplacian.tolist()
|
8136
|
+
|
8137
|
+
return laplacian.tolist()
|
8138
|
+
|
7780
8139
|
@staticmethod
|
7781
8140
|
def Leaves(graph, edgeKey: str = None, tolerance: float = 0.0001, silent: bool = False):
|
7782
8141
|
"""
|
@@ -9519,6 +9878,86 @@ class Graph:
|
|
9519
9878
|
return None
|
9520
9879
|
return len(Graph.Edges(graph))
|
9521
9880
|
|
9881
|
+
@staticmethod
|
9882
|
+
def Subgraph(graph, vertices, vertexKey="cutVertex", edgeKey="cutEdge", tolerance=0.0001, silent: bool = False):
|
9883
|
+
"""
|
9884
|
+
Returns a subgraph of the input graph as defined by the input vertices.
|
9885
|
+
|
9886
|
+
Parameters
|
9887
|
+
----------
|
9888
|
+
graph : topologic_core.Graph
|
9889
|
+
The input graph.
|
9890
|
+
vertexKey : str , optional
|
9891
|
+
The dictionary key under which to store the cut vertex status of each vertex. See https://en.wikipedia.org/wiki/Cut_(graph_theory).
|
9892
|
+
vertex cuts are indicated with a value of 1. The default is "cutVertex".
|
9893
|
+
edgeKey : str , optional
|
9894
|
+
The dictionary key under which to store the cut edge status of each edge. See https://en.wikipedia.org/wiki/Cut_(graph_theory).
|
9895
|
+
edge cuts are indicated with a value of 1. The default is "cutVertex".
|
9896
|
+
tolerance : float , optional
|
9897
|
+
The desired tolerance. The default is 0.0001.
|
9898
|
+
silent : bool , optional
|
9899
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
9900
|
+
|
9901
|
+
Returns
|
9902
|
+
-------
|
9903
|
+
topologic_core.Graph
|
9904
|
+
The created subgraph.
|
9905
|
+
|
9906
|
+
"""
|
9907
|
+
from topologicpy.Vertex import Vertex
|
9908
|
+
from topologicpy.Edge import Edge
|
9909
|
+
from topologicpy.Dictionary import Dictionary
|
9910
|
+
from topologicpy.Topology import Topology
|
9911
|
+
|
9912
|
+
if not Topology.IsInstance(graph, "graph"):
|
9913
|
+
if not silent:
|
9914
|
+
print("Graph.Subgraph - Error: The input graph parameter is not a valid graph. Returning None.")
|
9915
|
+
return None
|
9916
|
+
|
9917
|
+
if not isinstance(vertices, list):
|
9918
|
+
if not silent:
|
9919
|
+
print("Graph.Subgraph - Error: The input vertices parameter is not a valid list. Returning None.")
|
9920
|
+
return None
|
9921
|
+
|
9922
|
+
vertex_list = [v for v in vertices if Topology.IsInstance(v, "vertex")]
|
9923
|
+
if len(vertex_list) < 1:
|
9924
|
+
if not silent:
|
9925
|
+
print("Graph.Subgraph - Error: The input vertices parameter does not contain any valid vertices. Returning None.")
|
9926
|
+
return None
|
9927
|
+
|
9928
|
+
edges = Graph.Edges(graph, vertices=vertex_list)
|
9929
|
+
# Set the vertexCut status to 0 for all input vertices
|
9930
|
+
for v in vertex_list:
|
9931
|
+
d = Topology.Dictionary(v)
|
9932
|
+
d = Dictionary.SetValueAtKey(d, vertexKey, 0)
|
9933
|
+
v = Topology.SetDictionary(v, d)
|
9934
|
+
|
9935
|
+
final_edges = []
|
9936
|
+
if not edges == None:
|
9937
|
+
for edge in edges:
|
9938
|
+
sv = Edge.StartVertex(edge)
|
9939
|
+
status_1 = any([Vertex.IsCoincident(sv, v, tolerance=tolerance) for v in vertices])
|
9940
|
+
ev = Edge.EndVertex(edge)
|
9941
|
+
status_2 = any([Vertex.IsCoincident(ev, v, tolerance=tolerance) for v in vertices])
|
9942
|
+
if status_1 and status_2:
|
9943
|
+
cutEdge = 0
|
9944
|
+
else:
|
9945
|
+
cutEdge = 1
|
9946
|
+
d = Topology.Dictionary(edge)
|
9947
|
+
d = Dictionary.SetValueAtKey(d, edgeKey, cutEdge)
|
9948
|
+
edge = Topology.SetDictionary(edge, d)
|
9949
|
+
final_edges.append(edge)
|
9950
|
+
return_graph = Graph.ByVerticesEdges(vertex_list, final_edges)
|
9951
|
+
graph_vertices = Graph.Vertices(return_graph)
|
9952
|
+
# Any vertex in the final graph that does not have a vertexCut of 0 is a new vertex and as such needs to have a vertexCut of 1.
|
9953
|
+
for v in graph_vertices:
|
9954
|
+
d = Topology.Dictionary(v)
|
9955
|
+
value = Dictionary.ValueAtKey(d, vertexKey)
|
9956
|
+
if not value == 0:
|
9957
|
+
d = Dictionary.SetValueAtKey(d, vertexKey, 1)
|
9958
|
+
v = Topology.SetDictionary(v, d)
|
9959
|
+
return return_graph
|
9960
|
+
|
9522
9961
|
@staticmethod
|
9523
9962
|
def _topological_distance(g, start, target):
|
9524
9963
|
from collections import deque
|
topologicpy/Matrix.py
CHANGED
@@ -164,6 +164,69 @@ class Matrix:
|
|
164
164
|
[0,0,1,0],
|
165
165
|
[translateX,translateY,translateZ,1]]
|
166
166
|
|
167
|
+
@staticmethod
|
168
|
+
def EigenvaluesAndVectors(matrix, mantissa: int = 6, silent: bool = False):
|
169
|
+
import numpy as np
|
170
|
+
"""
|
171
|
+
Returns the eigenvalues and eigenvectors of the input matrix. See https://en.wikipedia.org/wiki/Eigenvalues_and_eigenvectors
|
172
|
+
|
173
|
+
Parameters
|
174
|
+
----------
|
175
|
+
matrix : list
|
176
|
+
The input matrix. Assumed to be a laplacian matrix.
|
177
|
+
mantissa : int , optional
|
178
|
+
The desired length of the mantissa. The default is 6.
|
179
|
+
silent : bool , optional
|
180
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
181
|
+
|
182
|
+
Returns
|
183
|
+
-------
|
184
|
+
list
|
185
|
+
The list of eigenvalues and eigenvectors of the input matrix.
|
186
|
+
|
187
|
+
"""
|
188
|
+
from topologicpy.Helper import Helper
|
189
|
+
import numpy as np
|
190
|
+
|
191
|
+
if not isinstance(matrix, list):
|
192
|
+
if not silent:
|
193
|
+
print("Matrix.Eigenvalues - Error: The input matrix parameter is not a valid matrix. Returning None.")
|
194
|
+
return None
|
195
|
+
|
196
|
+
np_matrix = np.array(matrix)
|
197
|
+
if not isinstance(np_matrix, np.ndarray):
|
198
|
+
if not silent:
|
199
|
+
print("Matrix.Eigenvalues - Error: The input matrix parameter is not a valid matrix. Returning None.")
|
200
|
+
return None
|
201
|
+
|
202
|
+
# Square check
|
203
|
+
if np_matrix.shape[0] != np_matrix.shape[1]:
|
204
|
+
if not silent:
|
205
|
+
print("Matrix.Eigenvalues - Error: The input matrix parameter is not a square matrix. Returning None.")
|
206
|
+
return None
|
207
|
+
|
208
|
+
# Symmetry check
|
209
|
+
if not np.allclose(np_matrix, np_matrix.T):
|
210
|
+
if not silent:
|
211
|
+
print("Matrix.Eigenvalues - Error: The input matrix is not symmetric. Returning None.")
|
212
|
+
return None
|
213
|
+
|
214
|
+
# # Degree matrix
|
215
|
+
# degree_matrix = np.diag(np_matrix.sum(axis=1))
|
216
|
+
|
217
|
+
# # Laplacian matrix
|
218
|
+
# laplacian_matrix = degree_matrix - np_matrix
|
219
|
+
|
220
|
+
# Eigenvalues
|
221
|
+
eigenvalues, eigenvectors = np.linalg.eig(np_matrix)
|
222
|
+
|
223
|
+
e_values = [round(x, mantissa) for x in list(np.sort(eigenvalues))]
|
224
|
+
e_vectors = []
|
225
|
+
for eigenvector in eigenvectors:
|
226
|
+
e_vectors.append([round(x, mantissa) for x in eigenvector])
|
227
|
+
e_vectors = Helper.Sort(e_vectors, list(eigenvalues))
|
228
|
+
return e_values, e_vectors
|
229
|
+
|
167
230
|
@staticmethod
|
168
231
|
def Multiply(matA, matB):
|
169
232
|
"""
|
topologicpy/Vertex.py
CHANGED
@@ -1710,6 +1710,89 @@ class Vertex():
|
|
1710
1710
|
pt = project_point_onto_plane(Vertex.Coordinates(vertex), [eq["a"], eq["b"], eq["c"], eq["d"]], direction)
|
1711
1711
|
return Vertex.ByCoordinates(pt[0], pt[1], pt[2])
|
1712
1712
|
|
1713
|
+
@staticmethod
|
1714
|
+
def Separate(*vertices, minDistance: float = 0.0001, silent: bool = False):
|
1715
|
+
"""
|
1716
|
+
Separates the input vertices such that no two vertices are within the input minimum distance.
|
1717
|
+
|
1718
|
+
Parameters
|
1719
|
+
----------
|
1720
|
+
vertices : *topologicpy.Vertex
|
1721
|
+
One or more instances of a topologic vertex to be processed.
|
1722
|
+
minDistance : float , optional
|
1723
|
+
The desired minimum distance. The default is 0.0001.
|
1724
|
+
silent : bool , optional
|
1725
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
1726
|
+
|
1727
|
+
Returns
|
1728
|
+
-------
|
1729
|
+
list
|
1730
|
+
The list of vertices with adjusted positions
|
1731
|
+
|
1732
|
+
"""
|
1733
|
+
from topologicpy.Topology import Topology
|
1734
|
+
from topologicpy.Helper import Helper
|
1735
|
+
from topologicpy.Dictionary import Dictionary
|
1736
|
+
from math import sqrt
|
1737
|
+
from scipy.spatial import KDTree
|
1738
|
+
import numpy as np
|
1739
|
+
|
1740
|
+
if len(vertices) == 0:
|
1741
|
+
if not silent:
|
1742
|
+
print("Vertex.Separate - Error: The input vertices parameter is an empty list. Returning None.")
|
1743
|
+
return None
|
1744
|
+
if len(vertices) == 1:
|
1745
|
+
vertices = vertices[0]
|
1746
|
+
if isinstance(vertices, list):
|
1747
|
+
if len(vertices) == 0:
|
1748
|
+
if not silent:
|
1749
|
+
print("Vertex.Separate - Error: The input vertices parameter is an empty list. Returning None.")
|
1750
|
+
return None
|
1751
|
+
else:
|
1752
|
+
vertexList = [x for x in vertices if Topology.IsInstance(x, "Vertex")]
|
1753
|
+
if len(vertexList) == 0:
|
1754
|
+
if not silent:
|
1755
|
+
print("Vertex.Separate - Error: The input vertices parameter does not contain any valid vertices. Returning None.")
|
1756
|
+
return None
|
1757
|
+
else:
|
1758
|
+
if not silent:
|
1759
|
+
print("Vertex.Separate - Warning: The input vertices parameter contains only one vertex. Returning the same vertex.")
|
1760
|
+
return vertices
|
1761
|
+
else:
|
1762
|
+
vertexList = Helper.Flatten(list(vertices))
|
1763
|
+
vertexList = [x for x in vertexList if Topology.IsInstance(x, "Vertex")]
|
1764
|
+
if len(vertexList) == 0:
|
1765
|
+
if not silent:
|
1766
|
+
print("Vertex.Separate - Error: The input parameters do not contain any valid vertices. Returning None.")
|
1767
|
+
return None
|
1768
|
+
|
1769
|
+
coords = np.array([[v.X(), v.Y(), v.Z()] for v in vertexList]) # Extract coordinates
|
1770
|
+
tree = KDTree(coords) # Build k-d tree for efficient neighbor search
|
1771
|
+
|
1772
|
+
for i, vertex in enumerate(coords):
|
1773
|
+
neighbors = tree.query_ball_point(vertex, minDistance)
|
1774
|
+
for neighbor_index in neighbors:
|
1775
|
+
if neighbor_index != i: # Avoid self-comparison
|
1776
|
+
direction = coords[neighbor_index] - vertex
|
1777
|
+
distance = np.linalg.norm(direction)
|
1778
|
+
if distance < minDistance:
|
1779
|
+
# Move current vertex away from its neighbor
|
1780
|
+
adjustment = (minDistance - distance) / 2
|
1781
|
+
unit_vector = direction / distance if distance != 0 else np.random.rand(3)
|
1782
|
+
coords[i] -= unit_vector * adjustment
|
1783
|
+
coords[neighbor_index] += unit_vector * adjustment
|
1784
|
+
|
1785
|
+
# Rebuild the k-d tree after adjustment
|
1786
|
+
tree = KDTree(coords)
|
1787
|
+
|
1788
|
+
# Convert adjusted coordinates back to Vertex objects
|
1789
|
+
separated_vertices = [Vertex.ByCoordinates(x, y, z) for x, y, z in coords]
|
1790
|
+
for i, vertex in enumerate(vertexList):
|
1791
|
+
d = Topology.Dictionary(vertex)
|
1792
|
+
if len(Dictionary.Keys(d)) > 0:
|
1793
|
+
separated_vertices[i] = Topology.SetDictionary(separated_vertices[i], d)
|
1794
|
+
return separated_vertices
|
1795
|
+
|
1713
1796
|
@staticmethod
|
1714
1797
|
def X(vertex, mantissa: int = 6) -> float:
|
1715
1798
|
"""
|
topologicpy/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = '0.7.
|
1
|
+
__version__ = '0.7.98'
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: topologicpy
|
3
|
-
Version: 0.7.
|
3
|
+
Version: 0.7.98
|
4
4
|
Summary: An AI-Powered Spatial Modelling and Analysis Software Library for Architecture, Engineering, and Construction.
|
5
5
|
Author-email: Wassim Jabi <wassim.jabi@gmail.com>
|
6
6
|
License: AGPL v3 License
|
@@ -11,11 +11,11 @@ topologicpy/Dictionary.py,sha256=t0O7Du-iPq46FyKqZfcjHfsUK1E8GS_e67R2V5cpkbw,331
|
|
11
11
|
topologicpy/Edge.py,sha256=gaLqyjFOqFHpw69Ftr4rc-kvakYpauQwhOK4ZO-V35g,67287
|
12
12
|
topologicpy/EnergyModel.py,sha256=UoQ9Jm-hYsN383CbcLKw-y6BKitRHj0uyh84yQ-8ACg,53856
|
13
13
|
topologicpy/Face.py,sha256=wczXpMcfub8Eb10lA4rrXksvi5YYCbRjBdp3lOTUwK0,172618
|
14
|
-
topologicpy/Graph.py,sha256=
|
14
|
+
topologicpy/Graph.py,sha256=IL7htimFIpRIFSGlNgXqdIkq6KgHFUe9T144yGIk2Qc,479404
|
15
15
|
topologicpy/Grid.py,sha256=2s9cSlWldivn1i9EUz4OOokJyANveqmRe_vR93CAndI,18245
|
16
16
|
topologicpy/Helper.py,sha256=F3h4_qcOD_PHAoVe0tEbEE7_jYyVcaHjtwVs4QHOZuI,23978
|
17
17
|
topologicpy/Honeybee.py,sha256=Y_El6M8x3ixvvIe_VcRiwj_4C89ZZg5_WlT7adbCkpw,21849
|
18
|
-
topologicpy/Matrix.py,sha256=
|
18
|
+
topologicpy/Matrix.py,sha256=tiPum1gTvkKxOyxHBDviH4BwLbdlAusBwMe7ZZfu6Po,10510
|
19
19
|
topologicpy/Neo4j.py,sha256=BKOF29fRgXmdpMGkrNzuYbyqgCJ6ElPPMYlfTxXiVbc,22392
|
20
20
|
topologicpy/Plotly.py,sha256=Tvo0_zKVEHtPhsMNNvLy5G0HIys5FPAOyp_o4QN_I_A,115760
|
21
21
|
topologicpy/Polyskel.py,sha256=EFsuh2EwQJGPLiFUjvtXmAwdX-A4r_DxP5hF7Qd3PaU,19829
|
@@ -25,12 +25,12 @@ topologicpy/Speckle.py,sha256=AlsGlSDuKRtX5jhVsPNSSjjbZis079HbUchDH_5RJmE,18187
|
|
25
25
|
topologicpy/Sun.py,sha256=42tDWMYpwRG7Z2Qjtp94eRgBuqySq7k8TgNUZDK7QxQ,36837
|
26
26
|
topologicpy/Topology.py,sha256=kAnJrVyrwJX8c-C4q1cewJ80byG8uaoBWUuk0T6U4SY,441788
|
27
27
|
topologicpy/Vector.py,sha256=Cl7besf20cAGmyNPh-9gbFAHnRU5ZWSMChJ3VyFIDs4,35416
|
28
|
-
topologicpy/Vertex.py,sha256=
|
28
|
+
topologicpy/Vertex.py,sha256=tv6C-rbuNgXHDGgVLT5fbalynLdXqlUuiCDKtkeQ0vk,77814
|
29
29
|
topologicpy/Wire.py,sha256=bX8wO96gFa7HZPY0CFlmYQBOUP_1e0jCb02BPxaY-ao,222981
|
30
30
|
topologicpy/__init__.py,sha256=vlPCanUbxe5NifC4pHcnhSzkmmYcs_UrZrTlVMsxcFs,928
|
31
|
-
topologicpy/version.py,sha256=
|
32
|
-
topologicpy-0.7.
|
33
|
-
topologicpy-0.7.
|
34
|
-
topologicpy-0.7.
|
35
|
-
topologicpy-0.7.
|
36
|
-
topologicpy-0.7.
|
31
|
+
topologicpy/version.py,sha256=edt03iPp-fWm-GyKIDUNJimGnmtCRb6Pjv3dsEvhru4,23
|
32
|
+
topologicpy-0.7.98.dist-info/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
|
33
|
+
topologicpy-0.7.98.dist-info/METADATA,sha256=U6xzVbESx3zzAX4CakRjfE90LNZLqEdPZMN3SKbRBdQ,10513
|
34
|
+
topologicpy-0.7.98.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
35
|
+
topologicpy-0.7.98.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
|
36
|
+
topologicpy-0.7.98.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|