topologicpy 0.8.55__py3-none-any.whl → 0.8.58__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 +568 -36
- topologicpy/Kuzu.py +950 -0
- topologicpy/Vertex.py +78 -48
- topologicpy/version.py +1 -1
- {topologicpy-0.8.55.dist-info → topologicpy-0.8.58.dist-info}/METADATA +1 -1
- {topologicpy-0.8.55.dist-info → topologicpy-0.8.58.dist-info}/RECORD +9 -8
- {topologicpy-0.8.55.dist-info → topologicpy-0.8.58.dist-info}/WHEEL +0 -0
- {topologicpy-0.8.55.dist-info → topologicpy-0.8.58.dist-info}/licenses/LICENSE +0 -0
- {topologicpy-0.8.55.dist-info → topologicpy-0.8.58.dist-info}/top_level.txt +0 -0
topologicpy/Graph.py
CHANGED
@@ -448,6 +448,67 @@ class Graph:
|
|
448
448
|
new_graph = Graph.ByVerticesEdges(graph_vertices, graph_edges)
|
449
449
|
return new_graph
|
450
450
|
|
451
|
+
@staticmethod
|
452
|
+
def AddEdgeByIndex(graph, index: list = None, dictionary = None, silent: bool = False):
|
453
|
+
"""
|
454
|
+
Creates an edge in the input Graph by connecting the two vertices specified by their indices (e.g., [5, 6] connects the 4th and 6th vertices).
|
455
|
+
|
456
|
+
Parameters
|
457
|
+
----------
|
458
|
+
graph : topologic_core.Graph
|
459
|
+
The input graph.
|
460
|
+
index : list or tuple
|
461
|
+
The input list of vertex indices (e.g. [4, 6]).
|
462
|
+
dictionary : topologic_core.Dictionary , optional
|
463
|
+
The input edge dictionary.
|
464
|
+
silent : bool , optional
|
465
|
+
If set to True, error and warning messages are suppressed. Default is False.
|
466
|
+
|
467
|
+
Returns
|
468
|
+
-------
|
469
|
+
topologic_core.Graph
|
470
|
+
The input graph with the input edge added to it.
|
471
|
+
|
472
|
+
"""
|
473
|
+
from topologicpy.Edge import Edge
|
474
|
+
from topologicpy.Topology import Topology
|
475
|
+
|
476
|
+
if not Topology.IsInstance(graph, "Graph"):
|
477
|
+
if not silent:
|
478
|
+
print("Graph.AddEdgeIndex - Error: The input graph parameter is not a valid graph. Returning None.")
|
479
|
+
return None
|
480
|
+
if dictionary:
|
481
|
+
if not Topology.IsInstance(dictionary, "Dictionary"):
|
482
|
+
if not silent:
|
483
|
+
print("Graph.AddEdgeIndex - Error: The input dictionary parameter is not a valid dictionary. Returning None.")
|
484
|
+
return None
|
485
|
+
if not isinstance(index, list):
|
486
|
+
if not silent:
|
487
|
+
print("Graph.AddEdgeIndex - Error: The input index parameter is not a valid list. Returning None.")
|
488
|
+
return None
|
489
|
+
index = [x for x in index if isinstance(x, int)]
|
490
|
+
if not len(index) == 2:
|
491
|
+
if not silent:
|
492
|
+
print("Graph.AddEdgeIndex - Error: The input index parameter should only contain two integer numbers. Returning None.")
|
493
|
+
return None
|
494
|
+
vertices = Graph.Vertices(graph)
|
495
|
+
n = len(vertices)
|
496
|
+
if index[0] < 0 or index[0] > n-1:
|
497
|
+
if not silent:
|
498
|
+
print("Graph.AddEdgeIndex - Error: The first integer in the input index parameter does not exist in the input graph. Returning None.")
|
499
|
+
return None
|
500
|
+
if index[1] < 0 or index[1] > n-1:
|
501
|
+
if not silent:
|
502
|
+
print("Graph.AddEdgeIndex - Error: The second integer in the input index parameter does not exist in the input graph. Returning None.")
|
503
|
+
return None
|
504
|
+
sv = vertices[index[0]]
|
505
|
+
ev = vertices[index[1]]
|
506
|
+
edge = Edge.ByVertices(sv, ev)
|
507
|
+
if dictionary:
|
508
|
+
edge = Topology.SetDictionary(edge, dictionary)
|
509
|
+
graph = Graph.AddEdge(graph,edge)
|
510
|
+
return graph
|
511
|
+
|
451
512
|
@staticmethod
|
452
513
|
def AddVertex(graph, vertex, tolerance: float = 0.0001, silent: bool = False):
|
453
514
|
"""
|
@@ -2835,6 +2896,158 @@ class Graph:
|
|
2835
2896
|
graphs.append(Graph.ByVerticesEdges(vertices, edges))
|
2836
2897
|
return {'graphs':graphs, 'labels':labels}
|
2837
2898
|
|
2899
|
+
@staticmethod
|
2900
|
+
def ByDictionaries(graphDictionary, vertexDictionaries, edgeDictionaries, vertexKey: str = None, edgeKey: str = None, silent: bool = False, tolerance: float = 0.0001):
|
2901
|
+
"""
|
2902
|
+
Creates a graph from input python dictionaries.
|
2903
|
+
|
2904
|
+
Rules:
|
2905
|
+
All vertex dictionaries must contain at least the vertexKey.
|
2906
|
+
All edge dictionaries must contain at least the edgeKey.
|
2907
|
+
The edgeKey must be a tuple or list of two str values.
|
2908
|
+
x,y,z coordinates are optional. However, if a vertex dictionary contains x,y,z coordinates then all vertex dictionaries must contain x,y,z coordinates.
|
2909
|
+
If vertex dictionaries contain x,y,z coordinates they must not overlap and be separated by a distance greater than tolerance.
|
2910
|
+
Keys and values are case sensitive.
|
2911
|
+
x,y,z keys, if present must be lowercase.
|
2912
|
+
|
2913
|
+
Example:
|
2914
|
+
graphDictionary = {"name": "Small Apartment", "location": "123 Main Street"}
|
2915
|
+
vertexDictionaries = [
|
2916
|
+
{"name":"Entry", "type":"Circulation", "x":1, "y":4, "z":0, "area":5},
|
2917
|
+
{"name":"Living Room", "type":"Living Room", "x":3, "y":4 , "z":0, "area":24},
|
2918
|
+
{"name":"Dining Room", "type":"Dining Room", "x":5, "y":2, "z":0, "area":18},
|
2919
|
+
{"name":"Kitchen", "type":"Kitchen", "x":1, "y":2, "z":0, "area":15},
|
2920
|
+
{"name":"Bathroom", "type":"Bathroom", "x":3, "y":6, "z":0, "area":9},
|
2921
|
+
{"name":"Bedroom", "type":"Bedroom", "x":5, "y":4, "z":0, "area":16}
|
2922
|
+
]
|
2923
|
+
edgeDictionaries = [
|
2924
|
+
{"connects": ("Entry","Living Room"), "relationship": "adjacent_to"},
|
2925
|
+
{"connects": ("Living Room","Kitchen"), "relationship": "adjacent_to"},
|
2926
|
+
{"connects": ("Dining Room","Kitchen"), "relationship": "adjacent_to"},
|
2927
|
+
{"connects": ("Living Room","Dining Room"), "relationship": "adjacent_to"},
|
2928
|
+
{"connects": ("Living Room","Bedroom"), "relationship": "adjacent_to"},
|
2929
|
+
{"connects": ("Living Room","Bathroom"), "relationship": "adjacent_to"}
|
2930
|
+
]
|
2931
|
+
vertexKey = "name"
|
2932
|
+
edgeKey = "connects"
|
2933
|
+
|
2934
|
+
Parameters
|
2935
|
+
----------
|
2936
|
+
graphDictionary : dict
|
2937
|
+
The python dictionary to associate with the resulting graph
|
2938
|
+
vertexDictionaries : list
|
2939
|
+
The input list of vertex dictionaries. These must contain the vertexKey. X,Y,Z coordinates are optional.
|
2940
|
+
edgeDictionaries : list
|
2941
|
+
The input list of edge dictionaries. These must have the edgeKey to specify the two vertices they connect (by using the vertexKey)
|
2942
|
+
vertexKey: str
|
2943
|
+
The vertex key used to identify which vertices and edge connects.
|
2944
|
+
edgeKey: str
|
2945
|
+
The edge key under which the pair of vertex keys are listed as a tuple or list.
|
2946
|
+
tolerance: float , optional
|
2947
|
+
The desired tolerance. The default is 0.0001
|
2948
|
+
silent : bool , optional
|
2949
|
+
If set to True, error and warning messages are suppressed. Default is False.
|
2950
|
+
|
2951
|
+
Returns
|
2952
|
+
-------
|
2953
|
+
topologic_core.Graph
|
2954
|
+
The resulting graph
|
2955
|
+
|
2956
|
+
"""
|
2957
|
+
from topologicpy.Vertex import Vertex
|
2958
|
+
from topologicpy.Edge import Edge
|
2959
|
+
from topologicpy.Cluster import Cluster
|
2960
|
+
from topologicpy.Topology import Topology
|
2961
|
+
from topologicpy.Dictionary import Dictionary
|
2962
|
+
|
2963
|
+
def _set_dict(obj, kv: dict):
|
2964
|
+
keys = list(kv.keys())
|
2965
|
+
vals = list(kv.values())
|
2966
|
+
d = Dictionary.ByKeysValues(keys, vals)
|
2967
|
+
Topology.SetDictionary(obj, d)
|
2968
|
+
return obj
|
2969
|
+
|
2970
|
+
def _vertex(vertexDictionary, vertices, vertexKey, tolerance=0.0001, silent=False):
|
2971
|
+
x = vertexDictionary.get("x", 0)
|
2972
|
+
y = vertexDictionary.get("y", 0)
|
2973
|
+
z = vertexDictionary.get("z", 0)
|
2974
|
+
v = Vertex.ByCoordinates(x, y, z)
|
2975
|
+
v = _set_dict(v, vertexDictionary)
|
2976
|
+
if "x" in vertexDictionary.keys(): # Check for overlap only if coords are given.
|
2977
|
+
if len(vertices) > 0:
|
2978
|
+
nv = Vertex.NearestVertex(v, Cluster.ByTopologies(vertices))
|
2979
|
+
d = Topology.Dictionary(nv)
|
2980
|
+
nv_name = Dictionary.ValueAtKey(d, vertexKey, "Unknown")
|
2981
|
+
if Vertex.Distance(v, nv) < tolerance:
|
2982
|
+
if not silent:
|
2983
|
+
v_name = vertexDictionary[vertexKey]
|
2984
|
+
print(f"Graph.ByDictionaries - Warning: Vertices {v_name} and {nv_name} overlap.")
|
2985
|
+
return v
|
2986
|
+
|
2987
|
+
|
2988
|
+
if graphDictionary:
|
2989
|
+
if not isinstance(graphDictionary, dict):
|
2990
|
+
if not silent:
|
2991
|
+
print("Graph.ByDictionaries - Error: The input graphDictionary parameter is not a valid python dictionary. Returning None.")
|
2992
|
+
return None
|
2993
|
+
|
2994
|
+
if not isinstance(vertexDictionaries, list):
|
2995
|
+
if not silent:
|
2996
|
+
print("Graph.ByDictionaries - Error: The input vertexDictionaries parameter is not a valid list. Returning None.")
|
2997
|
+
return None
|
2998
|
+
|
2999
|
+
if not isinstance(edgeDictionaries, list):
|
3000
|
+
if not silent:
|
3001
|
+
print("Graph.ByDictionaries - Error: The input edgeDictionaries parameter is not a valid list. Returning None.")
|
3002
|
+
return None
|
3003
|
+
|
3004
|
+
name_to_vertex = {}
|
3005
|
+
vertices = []
|
3006
|
+
for vd in vertexDictionaries:
|
3007
|
+
v = _vertex(vd, vertices, vertexKey=vertexKey, tolerance=tolerance, silent=silent)
|
3008
|
+
if v:
|
3009
|
+
vertices.append(v)
|
3010
|
+
|
3011
|
+
# If coordinates are not present, make sure you separate the vertices to allow edges to be created.
|
3012
|
+
if "x" not in vertexDictionaries[0].keys():
|
3013
|
+
vertices = Vertex.Separate(vertices, minDistance=max(1, tolerance))
|
3014
|
+
|
3015
|
+
for i, v in enumerate(vertices):
|
3016
|
+
vd = vertexDictionaries[i]
|
3017
|
+
name_to_vertex[vd[vertexKey]] = v
|
3018
|
+
|
3019
|
+
# Create adjacency edges (undirected: one edge per pair)
|
3020
|
+
edges = []
|
3021
|
+
for d in edgeDictionaries:
|
3022
|
+
a, b = d[edgeKey]
|
3023
|
+
va = name_to_vertex.get(a, None)
|
3024
|
+
vb = name_to_vertex.get(b, None)
|
3025
|
+
if not va and not vb:
|
3026
|
+
if not silent:
|
3027
|
+
print(f"Graph.ByDictionaries - Warning: vertices '{a}' and '{b}' are missing. Could not create an edge between them.")
|
3028
|
+
continue
|
3029
|
+
if not va:
|
3030
|
+
if not silent:
|
3031
|
+
print(f"Graph.ByDictionaries - Warning: vertex '{a}' is missing. Could not create an edge between '{a}' and '{b}'.")
|
3032
|
+
continue
|
3033
|
+
if not vb:
|
3034
|
+
if not silent:
|
3035
|
+
print(f"Graph.ByDictionaries - Warning: vertex '{b}' is missing. Could not create an edge between '{a}' and '{b}'.")
|
3036
|
+
continue
|
3037
|
+
e = Edge.ByStartVertexEndVertex(va, vb, silent=True)
|
3038
|
+
if not e:
|
3039
|
+
if not silent:
|
3040
|
+
print(f"Graph.ByDictionaries - Warning: Could not create an edge between '{a}' and '{b}'. Check if the distance betwen '{a}' and '{b}' is kess than the input tolerance.")
|
3041
|
+
continue
|
3042
|
+
edges.append(e)
|
3043
|
+
# Build graph
|
3044
|
+
g = Graph.ByVerticesEdges(vertices, edges)
|
3045
|
+
|
3046
|
+
# Attach graph-level metadata
|
3047
|
+
if graphDictionary:
|
3048
|
+
_set_dict(g, graphDictionary)
|
3049
|
+
return g
|
3050
|
+
|
2838
3051
|
@staticmethod
|
2839
3052
|
def ByIFCFile(file,
|
2840
3053
|
includeTypes: list = [],
|
@@ -6716,44 +6929,146 @@ class Graph:
|
|
6716
6929
|
return graph.Edge(vertexA, vertexB, tolerance) # Hook to Core
|
6717
6930
|
|
6718
6931
|
@staticmethod
|
6719
|
-
def Edges(
|
6932
|
+
def Edges(
|
6933
|
+
graph,
|
6934
|
+
vertices: list = None,
|
6935
|
+
strict: bool = False,
|
6936
|
+
sortBy: str = None,
|
6937
|
+
reverse: bool = False,
|
6938
|
+
silent: bool = False,
|
6939
|
+
tolerance: float = 0.0001
|
6940
|
+
) -> list: # list[topologic_core.Edge]
|
6720
6941
|
"""
|
6721
|
-
Returns the
|
6942
|
+
Returns the list of edges from `graph` whose endpoints match the given `vertices`
|
6943
|
+
according to the `strict` rule.
|
6944
|
+
|
6945
|
+
If `strict` is True, both endpoints of an edge must be in `vertices`.
|
6946
|
+
If `strict` is False, at least one endpoint must be in `vertices`.
|
6722
6947
|
|
6723
6948
|
Parameters
|
6724
6949
|
----------
|
6725
|
-
graph :
|
6950
|
+
graph : topologicpy.Graph
|
6726
6951
|
The input graph.
|
6727
|
-
vertices : list
|
6728
|
-
|
6952
|
+
vertices : list[topologicpy.Vertex]
|
6953
|
+
The list of vertices to test membership against.
|
6954
|
+
strict : bool, optional
|
6955
|
+
If set to True, require both endpoints to be in `vertices`. Otherwise,
|
6956
|
+
require at least one endpoint to be in `vertices`. Default is False.
|
6957
|
+
sortBy : str , optional
|
6958
|
+
The dictionary key to use for sorting the returned edges. Special strings include "length" and "distance" to sort by the length of the edge. Default is None.
|
6959
|
+
reverse : bool , optional
|
6960
|
+
If set to True, the sorted list is reversed. This has no effect if the sortBy parameter is not set. Default is False.
|
6961
|
+
silent : bool, optional
|
6962
|
+
Isilent : bool, optional
|
6963
|
+
If set to True, all errors and warnings are suppressed. Default is False.
|
6729
6964
|
tolerance : float , optional
|
6730
6965
|
The desired tolerance. Default is 0.0001.
|
6731
6966
|
|
6732
6967
|
Returns
|
6733
6968
|
-------
|
6734
|
-
list
|
6735
|
-
The list of edges
|
6969
|
+
list[topologic_core.Edge]
|
6970
|
+
The list of matching edges from the original graph (not recreated).
|
6736
6971
|
|
6737
6972
|
"""
|
6973
|
+
from topologicpy.Vertex import Vertex
|
6738
6974
|
from topologicpy.Topology import Topology
|
6975
|
+
from topologicpy.Edge import Edge
|
6976
|
+
from topologicpy.Helper import Helper
|
6977
|
+
from topologicpy.Dictionary import Dictionary
|
6978
|
+
|
6979
|
+
def sort_edges(edges, sortBy, reverse):
|
6980
|
+
if not sortBy is None:
|
6981
|
+
if "length" in sortBy.lower() or "dist" in sortBy.lower():
|
6982
|
+
edge_values = [Edge.Length(e) for e in edges]
|
6983
|
+
else:
|
6984
|
+
edge_values = [Dictionary.ValueAtKey(Topology.Dictionary(e), sortBy, "0") for e in edges]
|
6985
|
+
edges = Helper.Sort(edges, edge_values)
|
6986
|
+
if reverse:
|
6987
|
+
edges.reverse()
|
6988
|
+
return edges
|
6739
6989
|
|
6740
6990
|
if not Topology.IsInstance(graph, "Graph"):
|
6741
|
-
|
6742
|
-
|
6991
|
+
if not silent:
|
6992
|
+
print("Graph.InducedEdges - Error: The input 'graph' is not a valid Graph. Returning [].")
|
6993
|
+
return []
|
6994
|
+
|
6995
|
+
graph_edges = []
|
6996
|
+
_ = graph.Edges(graph_edges, tolerance) # Hook to Core
|
6997
|
+
graph_edges = list(dict.fromkeys(graph_edges)) # remove duplicates
|
6998
|
+
|
6999
|
+
if not graph_edges:
|
7000
|
+
return []
|
6743
7001
|
if not vertices:
|
6744
|
-
|
6745
|
-
|
6746
|
-
|
6747
|
-
|
6748
|
-
|
6749
|
-
|
6750
|
-
|
6751
|
-
|
6752
|
-
|
6753
|
-
|
6754
|
-
|
6755
|
-
|
6756
|
-
|
7002
|
+
graph_edges = sort_edges(graph_edges, sortBy, reverse)
|
7003
|
+
return graph_edges
|
7004
|
+
|
7005
|
+
if not isinstance(vertices, list):
|
7006
|
+
if not silent:
|
7007
|
+
print("Graph.Edges - Error: The input 'vertices' is not a list. Returning [].")
|
7008
|
+
return []
|
7009
|
+
|
7010
|
+
valid_vertices = [v for v in vertices if Topology.IsInstance(v, "Vertex")]
|
7011
|
+
if not valid_vertices:
|
7012
|
+
if not silent:
|
7013
|
+
print("Graph.Edges - Warning: No valid vertices provided. Returning [].")
|
7014
|
+
return []
|
7015
|
+
|
7016
|
+
return_edges = []
|
7017
|
+
for e in graph_edges:
|
7018
|
+
sv = Edge.StartVertex(e)
|
7019
|
+
ev = Edge.EndVertex(e)
|
7020
|
+
|
7021
|
+
in_start = not Vertex.Index(sv, valid_vertices) is None
|
7022
|
+
in_end = not Vertex.Index(ev, valid_vertices) is None
|
7023
|
+
if strict:
|
7024
|
+
if in_start and in_end:
|
7025
|
+
return_edges.append(e)
|
7026
|
+
else:
|
7027
|
+
if in_start or in_end:
|
7028
|
+
return_edges.append(e)
|
7029
|
+
|
7030
|
+
return_edges = sort_edges(return_edges, sortBy, reverse)
|
7031
|
+
return return_edges
|
7032
|
+
|
7033
|
+
# @staticmethod
|
7034
|
+
# def Edges(graph, vertices=None, tolerance=0.0001):
|
7035
|
+
# """
|
7036
|
+
# Returns the edges found in the input graph. If the input list of vertices is specified, this method returns the edges connected to this list of vertices. Otherwise, it returns all graph edges.
|
7037
|
+
|
7038
|
+
# Parameters
|
7039
|
+
# ----------
|
7040
|
+
# graph : topologic_core.Graph
|
7041
|
+
# The input graph.
|
7042
|
+
# vertices : list , optional
|
7043
|
+
# An optional list of vertices to restrict the returned list of edges only to those connected to this list.
|
7044
|
+
# tolerance : float , optional
|
7045
|
+
# The desired tolerance. Default is 0.0001.
|
7046
|
+
|
7047
|
+
# Returns
|
7048
|
+
# -------
|
7049
|
+
# list
|
7050
|
+
# The list of edges in the graph.
|
7051
|
+
|
7052
|
+
# """
|
7053
|
+
# from topologicpy.Topology import Topology
|
7054
|
+
|
7055
|
+
# if not Topology.IsInstance(graph, "Graph"):
|
7056
|
+
# print("Graph.Edges - Error: The input graph is not a valid graph. Returning None.")
|
7057
|
+
# return None
|
7058
|
+
# if not vertices:
|
7059
|
+
# edges = []
|
7060
|
+
# _ = graph.Edges(edges, tolerance) # Hook to Core
|
7061
|
+
# if not edges:
|
7062
|
+
# return []
|
7063
|
+
# return list(dict.fromkeys(edges)) # remove duplicates
|
7064
|
+
# else:
|
7065
|
+
# vertices = [v for v in vertices if Topology.IsInstance(v, "Vertex")]
|
7066
|
+
# if len(vertices) < 1:
|
7067
|
+
# print("Graph.Edges - Error: The input list of vertices does not contain any valid vertices. Returning None.")
|
7068
|
+
# return None
|
7069
|
+
# edges = []
|
7070
|
+
# _ = graph.Edges(vertices, tolerance, edges) # Hook to Core
|
7071
|
+
# return list(dict.fromkeys(edges)) # remove duplicates
|
6757
7072
|
|
6758
7073
|
@staticmethod
|
6759
7074
|
def EigenVectorCentrality(graph, normalize: bool = False, key: str = "eigen_vector_centrality", colorKey: str = "evc_color", colorScale: str = "viridis", mantissa: int = 6, tolerance: float = 0.0001, silent: bool = False):
|
@@ -8199,6 +8514,57 @@ class Graph:
|
|
8199
8514
|
edge = Topology.SetDictionary(edge, d)
|
8200
8515
|
return graph
|
8201
8516
|
|
8517
|
+
|
8518
|
+
@staticmethod
|
8519
|
+
def InducedSubgraph(graph, vertices: list = None, strict: bool = False, silent: bool = False, tolerance: float = 0.0001):
|
8520
|
+
"""
|
8521
|
+
Returns the subgraph whose edges are connected to the given `vertices`
|
8522
|
+
according to the `strict` rule. Isolated vertices are included as-is.
|
8523
|
+
|
8524
|
+
If `strict` is True, both endpoints of an edge must be in `vertices`.
|
8525
|
+
If `strict` is False, at least one endpoint must be in `vertices`.
|
8526
|
+
|
8527
|
+
Parameters
|
8528
|
+
----------
|
8529
|
+
graph : topologicpy.Graph
|
8530
|
+
The input graph.
|
8531
|
+
vertices : list[topologicpy.Vertex]
|
8532
|
+
The list of vertices to test membership against.
|
8533
|
+
strict : bool, optional
|
8534
|
+
If set to True, require both endpoints to be in `vertices`. Otherwise,
|
8535
|
+
require at least one endpoint to be in `vertices`. Default is False.
|
8536
|
+
silent : bool, optional
|
8537
|
+
Isilent : bool, optional
|
8538
|
+
If set to True, all errors and warnings are suppressed. Default is False
|
8539
|
+
tolerance : float , optional
|
8540
|
+
The desired tolerance. Default is 0.0001.
|
8541
|
+
|
8542
|
+
Returns
|
8543
|
+
-------
|
8544
|
+
list[topologic_core.Edge]
|
8545
|
+
The list of matching edges from the original graph (not recreated).
|
8546
|
+
|
8547
|
+
"""
|
8548
|
+
from topologicpy.Topology import Topology
|
8549
|
+
|
8550
|
+
if not Topology.IsInstance(graph, "Graph"):
|
8551
|
+
if not silent:
|
8552
|
+
print("Graph.InducedSubgraph - Error: The input graph parameter is not a valid graph. Returning None.")
|
8553
|
+
|
8554
|
+
if not isinstance(vertices, list):
|
8555
|
+
if not silent:
|
8556
|
+
print("Graph.InducedSubgraph - Error: The input 'vertices' is not a list. Returning None.")
|
8557
|
+
return None
|
8558
|
+
|
8559
|
+
valid_vertices = [v for v in vertices if Topology.IsInstance(v, "Vertex")]
|
8560
|
+
if not valid_vertices:
|
8561
|
+
if not silent:
|
8562
|
+
print("Graph.InducedSubgraph - Warning: No valid vertices provided. Returning None.")
|
8563
|
+
return None
|
8564
|
+
connected_vertices = [v for v in valid_vertices if Graph.VertexDegree(graph, v) > 0]
|
8565
|
+
edges = Graph.Edges(graph, connected_vertices, strict=strict, tolerance=tolerance)
|
8566
|
+
return Graph.ByVerticesEdges(valid_vertices, edges)
|
8567
|
+
|
8202
8568
|
@staticmethod
|
8203
8569
|
def IsEmpty(graph, silent: bool = False):
|
8204
8570
|
"""
|
@@ -9117,8 +9483,6 @@ class Graph:
|
|
9117
9483
|
|
9118
9484
|
return pos
|
9119
9485
|
|
9120
|
-
|
9121
|
-
|
9122
9486
|
def radial_layout_2d(edge_list, root_index=0):
|
9123
9487
|
import numpy as np
|
9124
9488
|
from collections import deque
|
@@ -9270,9 +9634,15 @@ class Graph:
|
|
9270
9634
|
|
9271
9635
|
if not Topology.IsInstance(graph, "Graph"):
|
9272
9636
|
if not silent:
|
9273
|
-
print("Graph.
|
9637
|
+
print("Graph.Reshape - Error: The input graph is not a valid topologic graph. Returning None.")
|
9274
9638
|
return None
|
9275
9639
|
|
9640
|
+
vertices = Graph.Vertices(graph)
|
9641
|
+
if len(vertices) < 2:
|
9642
|
+
if not silent:
|
9643
|
+
print("Graph.Reshape - Warning: The graph has less than two vertices. It cannot be rehsaped. Returning the original input graph.")
|
9644
|
+
return graph
|
9645
|
+
|
9276
9646
|
if 'circ' in shape.lower():
|
9277
9647
|
return circle_layout_2d(graph, radius=size/2, sides=sides)
|
9278
9648
|
elif 'lin' in shape.lower():
|
@@ -10998,7 +11368,166 @@ class Graph:
|
|
10998
11368
|
print(f'Graph.Kernel - Error: Unsupported method "{method}". '
|
10999
11369
|
f'Supported methods are "WL" and "Hopper". Returning None.')
|
11000
11370
|
return None
|
11001
|
-
|
11371
|
+
|
11372
|
+
@staticmethod
|
11373
|
+
def KHopsSubgraph(
|
11374
|
+
graph,
|
11375
|
+
vertices: list,
|
11376
|
+
k: int = 1,
|
11377
|
+
direction: str = "both",
|
11378
|
+
silent: bool = False,
|
11379
|
+
):
|
11380
|
+
"""
|
11381
|
+
Returns a subgraph consisting of the k-hop neighborhood around the input list of seed vertices.
|
11382
|
+
|
11383
|
+
Parameters
|
11384
|
+
----------
|
11385
|
+
graph : topologicpy.Graph
|
11386
|
+
The input graph.
|
11387
|
+
vertices : list
|
11388
|
+
The input list of seed vertices.
|
11389
|
+
k : int, optional
|
11390
|
+
Number of hops. Default is 1.
|
11391
|
+
direction : str, optional
|
11392
|
+
'both', 'out', or 'in'. Default 'both'.
|
11393
|
+
silent : bool, optional
|
11394
|
+
Suppress warnings/errors. Default False.
|
11395
|
+
|
11396
|
+
Returns
|
11397
|
+
-------
|
11398
|
+
topologicpy.Graph or None
|
11399
|
+
The resulting subgraph, or None on error.
|
11400
|
+
"""
|
11401
|
+
from topologicpy.Vertex import Vertex
|
11402
|
+
from topologicpy.Edge import Edge
|
11403
|
+
from topologicpy.Graph import Graph
|
11404
|
+
from topologicpy.Topology import Topology
|
11405
|
+
from topologicpy.Dictionary import Dictionary
|
11406
|
+
|
11407
|
+
# ---- validate inputs ----
|
11408
|
+
if not Topology.IsInstance(graph, "graph"):
|
11409
|
+
if not silent:
|
11410
|
+
print("Graph.KHopsSubgraph - Error: The input graph parameter is not a valid graph. Returning None.")
|
11411
|
+
return None
|
11412
|
+
|
11413
|
+
if not isinstance(vertices, list):
|
11414
|
+
if not silent:
|
11415
|
+
print("Graph.KHopsSubgraph - Error: The input vertices parameter is not a valid list. Returning None.")
|
11416
|
+
return None
|
11417
|
+
|
11418
|
+
graph_vertices = Graph.Vertices(graph)
|
11419
|
+
if not graph_vertices:
|
11420
|
+
if not silent:
|
11421
|
+
print("Graph.KHopsSubgraph - Error: The input graph does not contain any vertices. Returning None.")
|
11422
|
+
return None
|
11423
|
+
|
11424
|
+
# Keep only valid vertex objects
|
11425
|
+
seed_vertices = [v for v in vertices if Topology.IsInstance(v, "vertex")]
|
11426
|
+
if not seed_vertices:
|
11427
|
+
if not silent:
|
11428
|
+
print("Graph.KHopsSubgraph - Error: The input vertices list does not contain any valid vertices. Returning None.")
|
11429
|
+
return None
|
11430
|
+
|
11431
|
+
# ---- map seeds to vertex indices (prefer identity; fallback to list.index) ----
|
11432
|
+
id_to_index = {Topology.UUID(v): i for i, v in enumerate(graph_vertices)}
|
11433
|
+
seed_indices = []
|
11434
|
+
for sv in seed_vertices:
|
11435
|
+
idx = id_to_index.get(Topology.UUID(sv))
|
11436
|
+
if idx is None:
|
11437
|
+
try:
|
11438
|
+
idx = graph_vertices.index(sv) # fallback if same object not used
|
11439
|
+
except ValueError:
|
11440
|
+
idx = None
|
11441
|
+
if idx is not None:
|
11442
|
+
seed_indices.append(idx)
|
11443
|
+
|
11444
|
+
if not seed_indices:
|
11445
|
+
if not silent:
|
11446
|
+
print("Graph.KHopsSubgraph - Error: None of the seed vertices are found in the graph. Returning None.")
|
11447
|
+
return None
|
11448
|
+
|
11449
|
+
# ---- get mesh data (index-based edge list) ----
|
11450
|
+
# Expect: mesh_data["vertices"] (list), mesh_data["edges"] (list of [a, b] indices)
|
11451
|
+
mesh_data = Graph.MeshData(graph)
|
11452
|
+
edges_idx = mesh_data.get("edges") or []
|
11453
|
+
# Compute number of vertices robustly
|
11454
|
+
n_verts = len(mesh_data.get("vertices") or graph_vertices)
|
11455
|
+
|
11456
|
+
# ---- build adjacency (directed; BFS respects 'direction') ----
|
11457
|
+
adj_out = {i: set() for i in range(n_verts)}
|
11458
|
+
adj_in = {i: set() for i in range(n_verts)}
|
11459
|
+
for (a, b) in edges_idx:
|
11460
|
+
if 0 <= a < n_verts and 0 <= b < n_verts:
|
11461
|
+
adj_out[a].add(b)
|
11462
|
+
adj_in[b].add(a)
|
11463
|
+
|
11464
|
+
# ---- BFS up to k hops ----
|
11465
|
+
dir_norm = (direction or "both").lower()
|
11466
|
+
if dir_norm not in ("both", "out", "in"):
|
11467
|
+
dir_norm = "both"
|
11468
|
+
|
11469
|
+
visited = set(seed_indices)
|
11470
|
+
frontier = set(seed_indices)
|
11471
|
+
for _ in range(max(0, int(k))):
|
11472
|
+
nxt = set()
|
11473
|
+
for v in frontier:
|
11474
|
+
if dir_norm in ("both", "out"):
|
11475
|
+
nxt |= adj_out.get(v, set())
|
11476
|
+
if dir_norm in ("both", "in"):
|
11477
|
+
nxt |= adj_in.get(v, set())
|
11478
|
+
nxt -= visited
|
11479
|
+
if not nxt:
|
11480
|
+
break
|
11481
|
+
visited |= nxt
|
11482
|
+
frontier = nxt
|
11483
|
+
|
11484
|
+
if not visited:
|
11485
|
+
if not silent:
|
11486
|
+
print("Graph.KHopsSubgraph - Warning: No vertices found within the specified k hops. Returning None.")
|
11487
|
+
return None
|
11488
|
+
|
11489
|
+
# ---- assemble subgraph ----
|
11490
|
+
# Vertices: actual TopologicPy Vertex objects
|
11491
|
+
sub_vertex_indices = sorted(visited)
|
11492
|
+
sub_vertices = [graph_vertices[i] for i in sub_vertex_indices]
|
11493
|
+
|
11494
|
+
# Edges: include only those whose endpoints are both in the subgraph
|
11495
|
+
sub_index_set = set(sub_vertex_indices)
|
11496
|
+
# Map from global index -> actual Vertex object for edge reconstruction
|
11497
|
+
idx_to_vertex = {i: graph_vertices[i] for i in sub_vertex_indices}
|
11498
|
+
|
11499
|
+
sub_edges = []
|
11500
|
+
for (a, b) in edges_idx:
|
11501
|
+
if a in sub_index_set and b in sub_index_set:
|
11502
|
+
# Recreate edge to ensure it references the subgraph vertices
|
11503
|
+
ea = idx_to_vertex[a]
|
11504
|
+
eb = idx_to_vertex[b]
|
11505
|
+
try:
|
11506
|
+
e = Edge.ByStartVertexEndVertex(ea, eb)
|
11507
|
+
except Exception:
|
11508
|
+
# If creation fails, skip this edge
|
11509
|
+
continue
|
11510
|
+
# Preserve edge label if present
|
11511
|
+
try:
|
11512
|
+
# Find original edge and copy its dictionary if possible
|
11513
|
+
# (best-effort; safe if Graph.Edges aligns with edges_idx order)
|
11514
|
+
# Otherwise, leave edge as-is.
|
11515
|
+
pass
|
11516
|
+
except Exception:
|
11517
|
+
pass
|
11518
|
+
sub_edges.append(e)
|
11519
|
+
|
11520
|
+
try:
|
11521
|
+
return Graph.ByVerticesEdges(sub_vertices, sub_edges)
|
11522
|
+
except Exception:
|
11523
|
+
# As a fallback, some environments accept edges alone
|
11524
|
+
try:
|
11525
|
+
return Graph.ByEdges(sub_edges)
|
11526
|
+
except Exception:
|
11527
|
+
if not silent:
|
11528
|
+
print("Graph.KHopsSubgraph - Error: Failed to construct the subgraph. Returning None.")
|
11529
|
+
return None
|
11530
|
+
|
11002
11531
|
@staticmethod
|
11003
11532
|
def Laplacian(graph, silent: bool = False, normalized: bool = False):
|
11004
11533
|
"""
|
@@ -14050,7 +14579,7 @@ class Graph:
|
|
14050
14579
|
return round(degree, mantissa)
|
14051
14580
|
|
14052
14581
|
@staticmethod
|
14053
|
-
def Vertices(graph,
|
14582
|
+
def Vertices(graph, sortBy=None, reverse=False):
|
14054
14583
|
"""
|
14055
14584
|
Returns the list of vertices in the input graph.
|
14056
14585
|
|
@@ -14058,11 +14587,14 @@ class Graph:
|
|
14058
14587
|
----------
|
14059
14588
|
graph : topologic_core.Graph
|
14060
14589
|
The input graph.
|
14061
|
-
|
14062
|
-
|
14590
|
+
sortBy : str , optional
|
14591
|
+
The dictionary key to use for sorting the returned edges. Special strings include "length" and "distance" to sort by the length of the edge. Default is None.
|
14063
14592
|
reverse : bool , optional
|
14064
|
-
If set to True, the
|
14065
|
-
|
14593
|
+
If set to True, the sorted list is reversed. This has no effect if the sortBy parameter is not set. Default is False.
|
14594
|
+
silent : bool, optional
|
14595
|
+
Isilent : bool, optional
|
14596
|
+
If set to True, all errors and warnings are suppressed. Default is False.
|
14597
|
+
|
14066
14598
|
Returns
|
14067
14599
|
-------
|
14068
14600
|
list
|
@@ -14082,13 +14614,13 @@ class Graph:
|
|
14082
14614
|
_ = graph.Vertices(vertices) # Hook to Core
|
14083
14615
|
except:
|
14084
14616
|
vertices = []
|
14085
|
-
if not
|
14086
|
-
|
14617
|
+
if not sortBy == None:
|
14618
|
+
vertex_values = []
|
14087
14619
|
for v in vertices:
|
14088
14620
|
d = Topology.Dictionary(v)
|
14089
|
-
value = Dictionary.ValueAtKey(d,
|
14090
|
-
|
14091
|
-
vertices = Helper.Sort(vertices,
|
14621
|
+
value = str(Dictionary.ValueAtKey(d, sortBy, "0"))
|
14622
|
+
vertex_values.append(value)
|
14623
|
+
vertices = Helper.Sort(vertices, vertex_values)
|
14092
14624
|
if reverse == True:
|
14093
14625
|
vertices.reverse()
|
14094
14626
|
return vertices
|