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 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(graph, vertices=None, tolerance=0.0001):
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 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.
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 : topologic_core.Graph
6950
+ graph : topologicpy.Graph
6726
6951
  The input graph.
6727
- vertices : list , optional
6728
- An optional list of vertices to restrict the returned list of edges only to those connected to this list.
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 in the graph.
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
- print("Graph.Edges - Error: The input graph is not a valid graph. Returning None.")
6742
- return None
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
- edges = []
6745
- _ = graph.Edges(edges, tolerance) # Hook to Core
6746
- if not edges:
6747
- return []
6748
- return list(dict.fromkeys(edges)) # remove duplicates
6749
- else:
6750
- vertices = [v for v in vertices if Topology.IsInstance(v, "Vertex")]
6751
- if len(vertices) < 1:
6752
- print("Graph.Edges - Error: The input list of vertices does not contain any valid vertices. Returning None.")
6753
- return None
6754
- edges = []
6755
- _ = graph.Edges(vertices, tolerance, edges) # Hook to Core
6756
- return list(dict.fromkeys(edges)) # remove duplicates
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.Flatten - Error: The input graph is not a valid topologic graph. Returning None.")
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, vertexKey=None, reverse=False):
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
- vertexKey : str , optional
14062
- If set, the returned list of vertices is sorted according to the dicitonary values stored under this key. Default is None.
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 vertices are sorted in reverse order (only if vertexKey is set). Otherwise, they are not. Default is False.
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 vertexKey == None:
14086
- sorting_values = []
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, vertexKey)
14090
- sorting_values.append(value)
14091
- vertices = Helper.Sort(vertices, sorting_values)
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