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 +172 -8
- topologicpy/version.py +1 -1
- {topologicpy-0.8.20.dist-info → topologicpy-0.8.21.dist-info}/METADATA +1 -1
- {topologicpy-0.8.20.dist-info → topologicpy-0.8.21.dist-info}/RECORD +7 -7
- {topologicpy-0.8.20.dist-info → topologicpy-0.8.21.dist-info}/WHEEL +0 -0
- {topologicpy-0.8.20.dist-info → topologicpy-0.8.21.dist-info}/licenses/LICENSE +0 -0
- {topologicpy-0.8.20.dist-info → topologicpy-0.8.21.dist-info}/top_level.txt +0 -0
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,
|
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 {
|
4587
|
-
|
4588
|
-
|
4589
|
-
|
4590
|
-
|
4591
|
-
|
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.
|
1
|
+
__version__ = '0.8.21'
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: topologicpy
|
3
|
-
Version: 0.8.
|
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=
|
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=
|
32
|
-
topologicpy-0.8.
|
33
|
-
topologicpy-0.8.
|
34
|
-
topologicpy-0.8.
|
35
|
-
topologicpy-0.8.
|
36
|
-
topologicpy-0.8.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|