topologicpy 0.8.20__py3-none-any.whl → 0.8.21__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
@@ -4384,12 +4384,16 @@ class Graph:
4384
4384
  weightMetrics: float = 1.0,
4385
4385
  weightStructure: float = 1.0,
4386
4386
  weightWL: float = 1.0,
4387
+ weightJaccard: float = 1.0,
4388
+ vertexIDKey: str = "id",
4389
+ edgeWeightKey: str = None,
4387
4390
  iterations: int = 3,
4388
4391
  mantissa: int = 6,
4389
4392
  silent: bool = False):
4390
4393
  """
4391
- Compares two graphs and returns a similarity score based on attributres, geometry, metrics, structure, and
4394
+ Compares two graphs and returns a similarity score based on attributres, geometry, metrics, structure,
4392
4395
  the Weisfeiler-Lehman graph kernel. See https://en.wikipedia.org/wiki/Weisfeiler_Leman_graph_isomorphism_test
4396
+ , and the weight Jaccard Similarity. See https://www.statology.org/jaccard-similarity/
4393
4397
 
4394
4398
  Parameters
4395
4399
  ----------
@@ -4408,6 +4412,14 @@ class Graph:
4408
4412
  The desired weight for structural similarity (number of vertices and edges). Default is 1.0.
4409
4413
  weightWL : float , optional
4410
4414
  The desired weight for Weisfeiler-Lehman kernel similarity (iterative label propagation). Default is 1.0.
4415
+ weightJaccard: float , optional
4416
+ The desired weight for the Weighted Jaccard similarity. Default is 1.0.
4417
+ vertexIDKey: str , optional
4418
+ The dictionary key under which to find the unique vertex ID. The default is "id".
4419
+ edgeWeightKey: str , optional
4420
+ The dictionary key under which to find the weight of the edge for weighted graphs.
4421
+ If this parameter is specified as "length" or "distance" then the length of the edge is used as its weight.
4422
+ The default is None which means all edges are treated as if they have a weight of 1.
4411
4423
  iterations : int , optional
4412
4424
  The desired number of Weisfeiler-Lehman iterations. Default is 3.
4413
4425
  mantissa : int , optional
@@ -4473,6 +4485,72 @@ class Graph:
4473
4485
  avg_dist = total_dist / len(v1)
4474
4486
  return round(1 / (1 + avg_dist), mantissa) # Inverse average distance
4475
4487
 
4488
+ def weighted_jaccard_similarity(graph1, graph2, vertexIDKey="id", edgeWeightKey=None, mantissa=6):
4489
+ """
4490
+ Computes weighted Jaccard similarity between two graphs by comparing their edge weights.
4491
+
4492
+ Parameters
4493
+ ----------
4494
+ graph1 : topologic Graph
4495
+ First graph.
4496
+ graph2 : topologic Graph
4497
+ Second graph.
4498
+ vertexIDKey: str , optional
4499
+ The dictionary key under which to find the unique vertex ID. The default is "id".
4500
+ edgeWeightKey: str , optional
4501
+ The dictionary key under which to find the weight of the edge for weighted graphs.
4502
+ If this parameter is specified as "length" or "distance" then the length of the edge is used as its weight.
4503
+ The default is None which means all edges are treated as if they have a weight of 1.
4504
+ iterations : int , optional
4505
+ The desired number of Weisfeiler-Lehman iterations. Default is 3.
4506
+ mantissa : int , optional
4507
+ The desired length of the mantissa. The default is 6.
4508
+
4509
+ Returns
4510
+ -------
4511
+ float
4512
+ Similarity score between 0 and 1.
4513
+ """
4514
+ from topologicpy.Vertex import Vertex
4515
+ from topologicpy.Graph import Graph
4516
+ from topologicpy.Topology import Topology
4517
+ from topologicpy.Dictionary import Dictionary
4518
+ from topologicpy.Edge import Edge
4519
+
4520
+
4521
+ def edge_id(edge, vertexIDKey="id", mantissa=6):
4522
+ v1 = Edge.StartVertex(edge)
4523
+ v2 = Edge.EndVertex(edge)
4524
+ d1 = Topology.Dictionary(v1)
4525
+ d2 = Topology.Dictionary(v2)
4526
+ v1_id = Dictionary.ValueAtKey(d1, vertexIDKey) if d1 and Dictionary.ValueAtKey(d1, vertexIDKey) is not None else str(sorted(Vertex.Coordinates(v1, mantissa=mantissa)))
4527
+ v2_id = Dictionary.ValueAtKey(d2, vertexIDKey) if d2 and Dictionary.ValueAtKey(d2, vertexIDKey) is not None else str(sorted(Vertex.Coordinates(v2, mantissa=mantissa)))
4528
+
4529
+ return tuple(sorted(tuple([v1_id, v2_id])))
4530
+
4531
+ def edge_weights(graph, edgeWeightKey=None, mantissa=6):
4532
+ weights = {}
4533
+ for edge in Graph.Edges(graph):
4534
+ if edgeWeightKey == None:
4535
+ weight = 1
4536
+ elif edgeWeightKey.lower() == "length" or edgeWeightKey.lower() == "distance":
4537
+ weight = Edge.Length(edge)
4538
+ else:
4539
+ d = Topology.Dictionary(edge)
4540
+ weight = Dictionary.ValueAtKey(d, edgeWeightKey) if d and Dictionary.ValueAtKey(d, edgeWeightKey) is not None else 1.0
4541
+ eid = edge_id(edge, vertexIDKey=vertexIDKey, mantissa=mantissa)
4542
+ weights[eid] = weight
4543
+ return weights
4544
+
4545
+ w1 = edge_weights(graph1, edgeWeightKey=edgeWeightKey)
4546
+ w2 = edge_weights(graph2, edgeWeightKey=edgeWeightKey)
4547
+ keys = set(w1.keys()) | set(w2.keys())
4548
+
4549
+ numerator = sum(min(w1.get(k, 0), w2.get(k, 0)) for k in keys)
4550
+ denominator = sum(max(w1.get(k, 0), w2.get(k, 0)) for k in keys)
4551
+
4552
+ return numerator / denominator if denominator > 0 else 0.0
4553
+
4476
4554
  def metrics_similarity(graphA, graphB, mantissa=6):
4477
4555
  # Example using global metrics + mean of node metrics
4478
4556
  def safe_mean(lst):
@@ -4562,10 +4640,11 @@ class Graph:
4562
4640
  print("Graph.Compare - Error: The graphB input parameter is not a valid topologic graph. Returning None.")
4563
4641
  return
4564
4642
 
4565
- total_weight = weightAttributes + weightGeometry + weightMetrics + weightStructure + weightWL
4643
+ total_weight = weightAttributes + weightGeometry + weightMetrics + weightStructure + weightWL + weightJaccard
4566
4644
 
4567
4645
  attribute_score = attribute_similarity(graphA, graphB, mantissa=mantissa) if weightAttributes else 0
4568
4646
  geometry_score = geometry_similarity(graphA, graphB, mantissa=mantissa) if weightGeometry else 0
4647
+ jaccard_score = weighted_jaccard_similarity(graphA, graphB, vertexIDKey=vertexIDKey, edgeWeightKey=edgeWeightKey, mantissa=mantissa) if weightJaccard else 0
4569
4648
  metrics_score = metrics_similarity(graphA, graphB, mantissa=mantissa) if weightMetrics else 0
4570
4649
  structure_score = structure_similarity(graphA, graphB, mantissa=mantissa) if weightStructure else 0
4571
4650
  wl_score = weisfeiler_lehman_similarity(graphA, graphB, iterations, mantissa=mantissa) if weightWL else 0
@@ -4573,6 +4652,7 @@ class Graph:
4573
4652
  weighted_sum = (
4574
4653
  attribute_score * weightAttributes +
4575
4654
  geometry_score * weightGeometry +
4655
+ jaccard_score * weightJaccard +
4576
4656
  metrics_score * weightMetrics +
4577
4657
  structure_score * weightStructure +
4578
4658
  wl_score * weightWL
@@ -4583,12 +4663,14 @@ class Graph:
4583
4663
  else:
4584
4664
  overall_score = weighted_sum / total_weight
4585
4665
 
4586
- return { "attribute": round(attribute_score, mantissa),
4587
- "geometry": round(geometry_score, mantissa),
4588
- "metrics": round(metrics_score, mantissa),
4589
- "structure": round(structure_score, mantissa),
4590
- "wl": round(wl_score, mantissa),
4591
- "overall": round(overall_score, mantissa)
4666
+ return {
4667
+ "attribute": round(attribute_score, mantissa),
4668
+ "geometry": round(geometry_score, mantissa),
4669
+ "jaccard": round(jaccard_score, mantissa),
4670
+ "metrics": round(metrics_score, mantissa),
4671
+ "structure": round(structure_score, mantissa),
4672
+ "wl": round(wl_score, mantissa),
4673
+ "overall": round(overall_score, mantissa)
4592
4674
  }
4593
4675
 
4594
4676
  @staticmethod
@@ -11018,3 +11100,85 @@ class Graph:
11018
11100
  g = Graph.ByVerticesEdges(final_vertices, final_edges)
11019
11101
  return g
11020
11102
  return None
11103
+
11104
+ @staticmethod
11105
+ def WeightedJaccardSimilarity(graphA, graphB, vertexA, vertexB, vertexIDKey="id", edgeWeightKey=None, mantissa=6, silent=False):
11106
+ """
11107
+ Computes the weighted Jaccard similarity between two vertices based on their neighbors and
11108
+ edge weights. Accepts either one graph (both vertices are in the same graph) or two graphs
11109
+ (each vertex is in a separate graph).
11110
+
11111
+ Parameters
11112
+ ----------
11113
+ graphA : topologic_core.Graph
11114
+ The first graph
11115
+ graphB : topologic_core.Graph
11116
+ The second graph (this can be the same as the first graph)
11117
+ vertexA : topologic_core.Vertex
11118
+ The first vertex.
11119
+ vertexB : topologic_core.Vertex
11120
+ The second vertex.
11121
+ vertexIDKey : str , optional
11122
+ The dictionary key under which to find the unique vertex ID. The default is "id".
11123
+ edgeWeightKey : str , optional
11124
+ The dictionary key under which to find the weight of the edge for weighted graphs.
11125
+ If this parameter is specified as "length" or "distance" then the length of the edge is used as its weight.
11126
+ The default is None which means all edges are treated as if they have a weight of 1.
11127
+ mantissa : int , optional
11128
+ The desired length of the mantissa. The default is 6.
11129
+ silent : bool , optional
11130
+ If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
11131
+
11132
+ Returns
11133
+ -------
11134
+ float
11135
+ Weighted Jaccard similarity score between 0 (no overlap) and 1 (perfect match).
11136
+
11137
+ """
11138
+ from topologicpy.Graph import Graph
11139
+ from topologicpy.Topology import Topology
11140
+ from topologicpy.Dictionary import Dictionary
11141
+ from topologicpy.Vertex import Vertex
11142
+ from topologicpy.Edge import Edge
11143
+
11144
+ if graphB == None:
11145
+ graphB = graphA
11146
+
11147
+ def edge_id(edge, vertexIDKey="id", mantissa=6):
11148
+ v1 = Edge.StartVertex(edge)
11149
+ v2 = Edge.EndVertex(edge)
11150
+ d1 = Topology.Dictionary(v1)
11151
+ d2 = Topology.Dictionary(v2)
11152
+ v1_id = Dictionary.ValueAtKey(d1, vertexIDKey) if d1 and Dictionary.ValueAtKey(d1, vertexIDKey) is not None else str(sorted(Vertex.Coordinates(v1, mantissa=mantissa)))
11153
+ v2_id = Dictionary.ValueAtKey(d2, vertexIDKey) if d2 and Dictionary.ValueAtKey(d2, vertexIDKey) is not None else str(sorted(Vertex.Coordinates(v2, mantissa=mantissa)))
11154
+ return tuple(sorted([v1_id, v2_id]))
11155
+
11156
+ def get_neighbors_with_weights(graph, vertex, vertexIDKey="id", edgeWeightKey=None, mantissa=6):
11157
+ weights = {}
11158
+ for edge in Graph.Edges(graph, [vertex]):
11159
+ eid = edge_id(edge, vertexIDKey=vertexIDKey, mantissa=mantissa)
11160
+ if edgeWeightKey == None:
11161
+ weight = 1.0
11162
+ elif edgeWeightKey.lower() == "length" or edgeWeightKey.lower() == "distance":
11163
+ weight = Edge.Length(edge, mantissa=mantissa)
11164
+ else:
11165
+ d = Topology.Dictionary(edge)
11166
+ d_weight = Dictionary.ValueAtKey(d, edgeWeightKey, silent=silent)
11167
+ if not d or d_weight is None:
11168
+ if not silent:
11169
+ print(f"Graph.WeightedJaccardSimilarity - Warning: The dictionary of edge {eid} is missing '{edgeWeightKey}' key. Defaulting the edge weight to 1.0.")
11170
+ weight = 1.0
11171
+ else:
11172
+ weight = d_weight
11173
+ weights[eid] = weight
11174
+ return weights
11175
+
11176
+ weights1 = get_neighbors_with_weights(graphA, vertexA, vertexIDKey=vertexIDKey, edgeWeightKey=edgeWeightKey, mantissa=mantissa)
11177
+ weights2 = get_neighbors_with_weights(graphB, vertexB, vertexIDKey=vertexIDKey, edgeWeightKey=edgeWeightKey, mantissa=mantissa)
11178
+
11179
+ keys = set(weights1.keys()) | set(weights2.keys())
11180
+
11181
+ numerator = sum(min(weights1.get(k, 0), weights2.get(k, 0)) for k in keys)
11182
+ denominator = sum(max(weights1.get(k, 0), weights2.get(k, 0)) for k in keys)
11183
+
11184
+ return round(numerator / denominator, mantissa) if denominator != 0 else 0.0
topologicpy/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = '0.8.20'
1
+ __version__ = '0.8.21'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: topologicpy
3
- Version: 0.8.20
3
+ Version: 0.8.21
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,7 +11,7 @@ topologicpy/Dictionary.py,sha256=7h-Gszgnt2OEOvOSADJ4pa-mTNlhQ9cuIiB5WHEW6aY,339
11
11
  topologicpy/Edge.py,sha256=yxkCVDYBflJNEYxnjMmlyvbkpg8TNy7y5bSH3yQ4jzs,71418
12
12
  topologicpy/EnergyModel.py,sha256=UoQ9Jm-hYsN383CbcLKw-y6BKitRHj0uyh84yQ-8ACg,53856
13
13
  topologicpy/Face.py,sha256=SlhB8L7BpDjd4a9YZE4UJ3zoGuF1oq9MSpuesEWro_Q,184847
14
- topologicpy/Graph.py,sha256=fsWgbYp1di1HtDipsXY9d2i42Fn_3dXsVKTac9vl7bU,515137
14
+ topologicpy/Graph.py,sha256=Iq2UI-CdW3usxpgxRi-caxQeUmkFfGhD37c6mPT2Gd8,523908
15
15
  topologicpy/Grid.py,sha256=2s9cSlWldivn1i9EUz4OOokJyANveqmRe_vR93CAndI,18245
16
16
  topologicpy/Helper.py,sha256=4H5KPiv_eiEs489UOOyGLe9RaeoZIfmMh3mk_YCHmXg,29100
17
17
  topologicpy/Honeybee.py,sha256=uDVtDbloydNoaBFcSNukKL_2PLyD6XKkCp1VHz1jtaU,21751
@@ -28,9 +28,9 @@ topologicpy/Vector.py,sha256=GkGt-aJ591IJ2IPffMAudvITLDPi2qZibZc4UAav6m8,42407
28
28
  topologicpy/Vertex.py,sha256=q99IrWwdNlvVfUXz6iP8icmP8NzP2nsiqtgnF9Jnpx8,80913
29
29
  topologicpy/Wire.py,sha256=IVPzBKsckuxC-rHvwxmvtVF7PpApz2lhPHomtQMo_kg,228499
30
30
  topologicpy/__init__.py,sha256=vlPCanUbxe5NifC4pHcnhSzkmmYcs_UrZrTlVMsxcFs,928
31
- topologicpy/version.py,sha256=uWqZIJblLUrle0kYZkrKO4aVSNXiyYenL0G_TSUJllE,23
32
- topologicpy-0.8.20.dist-info/licenses/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
33
- topologicpy-0.8.20.dist-info/METADATA,sha256=0_3vo7ubfFiV0woZaJt8RCFHF9MZZ_Sc7VnqYZX4Mao,10535
34
- topologicpy-0.8.20.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
35
- topologicpy-0.8.20.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
36
- topologicpy-0.8.20.dist-info/RECORD,,
31
+ topologicpy/version.py,sha256=zZ7tNz8qiJpD43ZZY6gDLsQEo04V3AXFbZKZwwi4Pww,23
32
+ topologicpy-0.8.21.dist-info/licenses/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
33
+ topologicpy-0.8.21.dist-info/METADATA,sha256=uin9-ogEkyhyjro0BGZPrFB4abynjZPuv7G3lTk6XzA,10535
34
+ topologicpy-0.8.21.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
35
+ topologicpy-0.8.21.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
36
+ topologicpy-0.8.21.dist-info/RECORD,,