topologicpy 0.8.19__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
@@ -4377,6 +4377,302 @@ class Graph:
4377
4377
  v = Topology.SetDictionary(v, d)
4378
4378
  return graph
4379
4379
 
4380
+ @staticmethod
4381
+ def Compare(graphA, graphB,
4382
+ weightAttributes: float = 1.0,
4383
+ weightGeometry: float = 1.0,
4384
+ weightMetrics: float = 1.0,
4385
+ weightStructure: float = 1.0,
4386
+ weightWL: float = 1.0,
4387
+ weightJaccard: float = 1.0,
4388
+ vertexIDKey: str = "id",
4389
+ edgeWeightKey: str = None,
4390
+ iterations: int = 3,
4391
+ mantissa: int = 6,
4392
+ silent: bool = False):
4393
+ """
4394
+ Compares two graphs and returns a similarity score based on attributres, geometry, metrics, structure,
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/
4397
+
4398
+ Parameters
4399
+ ----------
4400
+ graphA : topologic Graph
4401
+ The first input graph.
4402
+ graphB : topologic Graph
4403
+ The second input graph.
4404
+ weightAttributes : float , optional
4405
+ The desired weight for attribute similarity (dictionary key overlap at vertices). Default is 1.0.
4406
+ weightGeometry : float , optional
4407
+ The desired weight for geometric similarity (vertex positions). Default is 1.0.
4408
+ weightMetrics : float , optional
4409
+ The desired weight for metric similarity (graph-level and node-level). Default is 1.0.
4410
+ The compared metrics are: betweenness centrality, closeness centrality, clustering coefficient, degree, diameter, and pagerank.
4411
+ weightStructure : float , optional
4412
+ The desired weight for structural similarity (number of vertices and edges). Default is 1.0.
4413
+ weightWL : float , optional
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.
4423
+ iterations : int , optional
4424
+ The desired number of Weisfeiler-Lehman iterations. Default is 3.
4425
+ mantissa : int , optional
4426
+ The desired length of the mantissa. The default is 6.
4427
+ silent : bool , optional
4428
+ If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
4429
+
4430
+ Returns
4431
+ -------
4432
+ dict
4433
+ A dictionary of similarity scores between 0 (completely dissimilar) and 1 (identical), based on weighted components.
4434
+ The keys in the dictionary are:
4435
+ "attribute"
4436
+ "geometry"
4437
+ "metrics"
4438
+ "structure"
4439
+ "wl"
4440
+ "overall"
4441
+
4442
+ """
4443
+
4444
+ import hashlib
4445
+ from collections import Counter
4446
+ from topologicpy.Graph import Graph
4447
+ from topologicpy.Vertex import Vertex
4448
+ from topologicpy.Topology import Topology
4449
+ from topologicpy.Dictionary import Dictionary
4450
+
4451
+ def attribute_similarity(graphA, graphB, mantissa=6):
4452
+ v1 = Graph.Vertices(graphA)
4453
+ v2 = Graph.Vertices(graphB)
4454
+ if len(v1) != len(v2) or len(v1) == 0:
4455
+ return 0
4456
+
4457
+ match_score = 0
4458
+ for a, b in zip(v1, v2):
4459
+ dict_a = Topology.Dictionary(a)
4460
+ dict_b = Topology.Dictionary(b)
4461
+
4462
+ keys_a = set(Dictionary.Keys(dict_a)) if dict_a else set()
4463
+ keys_b = set(Dictionary.Keys(dict_b)) if dict_b else set()
4464
+
4465
+ if not keys_a and not keys_b:
4466
+ match_score += 1
4467
+ else:
4468
+ intersection = len(keys_a & keys_b)
4469
+ union = len(keys_a | keys_b)
4470
+ match_score += intersection / union if union > 0 else 0
4471
+
4472
+ return round(match_score / len(v1), mantissa)
4473
+
4474
+ def geometry_similarity(graphA, graphB, mantissa=6):
4475
+ v1 = Graph.Vertices(graphA)
4476
+ v2 = Graph.Vertices(graphB)
4477
+ if len(v1) != len(v2) or len(v1) == 0:
4478
+ return 0
4479
+
4480
+ total_dist = 0
4481
+ for a, b in zip(v1, v2): # assumes same order
4482
+ p1 = Vertex.Coordinates(a, mantissa=mantissa)
4483
+ p2 = Vertex.Coordinates(b, mantissa=mantissa)
4484
+ total_dist += sum((i - j) ** 2 for i, j in zip(p1, p2)) ** 0.5
4485
+ avg_dist = total_dist / len(v1)
4486
+ return round(1 / (1 + avg_dist), mantissa) # Inverse average distance
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
+
4554
+ def metrics_similarity(graphA, graphB, mantissa=6):
4555
+ # Example using global metrics + mean of node metrics
4556
+ def safe_mean(lst):
4557
+ return sum(lst)/len(lst) if lst else 0
4558
+
4559
+ metrics1 = {
4560
+ "closeness": safe_mean(Graph.ClosenessCentrality(graphA)),
4561
+ "betweenness": safe_mean(Graph.BetweennessCentrality(graphA)),
4562
+ "degree": safe_mean(Graph.DegreeCentrality(graphA)),
4563
+ "pagerank": safe_mean(Graph.PageRank(graphA)),
4564
+ "clustering": Graph.GlobalClusteringCoefficient(graphA),
4565
+ "diameter": Graph.Diameter(graphA)
4566
+ }
4567
+
4568
+ metrics2 = {
4569
+ "closeness": safe_mean(Graph.ClosenessCentrality(graphB)),
4570
+ "betweenness": safe_mean(Graph.BetweennessCentrality(graphB)),
4571
+ "degree": safe_mean(Graph.DegreeCentrality(graphB)),
4572
+ "pagerank": safe_mean(Graph.PageRank(graphB)),
4573
+ "clustering": Graph.GlobalClusteringCoefficient(graphB),
4574
+ "diameter": Graph.Diameter(graphB)
4575
+ }
4576
+
4577
+ # Compute similarity as 1 - normalized absolute difference
4578
+ similarities = []
4579
+ for key in metrics1:
4580
+ v1, v2 = metrics1[key], metrics2[key]
4581
+ if v1 == 0 and v2 == 0:
4582
+ similarities.append(1)
4583
+ else:
4584
+ diff = abs(v1 - v2) / max(abs(v1), abs(v2), 1e-6)
4585
+ similarities.append(1 - diff)
4586
+
4587
+ return round(sum(similarities) / len(similarities), mantissa)
4588
+
4589
+ def structure_similarity(graphA, graphB, mantissa=6):
4590
+ v1 = Graph.Vertices(graphA)
4591
+ v2 = Graph.Vertices(graphB)
4592
+ e1 = Graph.Edges(graphA)
4593
+ e2 = Graph.Edges(graphB)
4594
+
4595
+ vertex_score = 1 - abs(len(v1) - len(v2)) / max(len(v1), len(v2), 1)
4596
+ edge_score = 1 - abs(len(e1) - len(e2)) / max(len(e1), len(e2), 1)
4597
+
4598
+ return round((vertex_score + edge_score) / 2, mantissa)
4599
+
4600
+ def weisfeiler_lehman_fingerprint(graph, iterations=3):
4601
+ vertices = Graph.Vertices(graph)
4602
+ labels = {}
4603
+
4604
+ for v in vertices:
4605
+ d = Topology.Dictionary(v)
4606
+ label = str(Dictionary.ValueAtKey(d, "label")) if d and Dictionary.ValueAtKey(d, "label") else "0"
4607
+ labels[v] = label
4608
+
4609
+ all_label_counts = Counter()
4610
+
4611
+ for _ in range(iterations):
4612
+ new_labels = {}
4613
+ for v in vertices:
4614
+ neighbors = Graph.AdjacentVertices(graph, v)
4615
+ neighbor_labels = sorted(labels.get(n, "0") for n in neighbors)
4616
+ long_label = labels[v] + "_" + "_".join(neighbor_labels)
4617
+ hashed_label = hashlib.md5(long_label.encode()).hexdigest()
4618
+ new_labels[v] = hashed_label
4619
+ all_label_counts[hashed_label] += 1
4620
+ labels = new_labels
4621
+
4622
+ return all_label_counts
4623
+
4624
+ def weisfeiler_lehman_similarity(graphA, graphB, iterations=3, mantissa=6):
4625
+ f1 = weisfeiler_lehman_fingerprint(graphA, iterations)
4626
+ f2 = weisfeiler_lehman_fingerprint(graphB, iterations)
4627
+
4628
+ common_labels = set(f1.keys()) & set(f2.keys())
4629
+ score = sum(min(f1[label], f2[label]) for label in common_labels)
4630
+ norm = max(sum(f1.values()), sum(f2.values()), 1)
4631
+
4632
+ return round(score / norm, mantissa)
4633
+
4634
+ if not Topology.IsInstance(graphA, "graph"):
4635
+ if not silent:
4636
+ print("Graph.Compare - Error: The graphA input parameter is not a valid topologic graph. Returning None.")
4637
+ return None
4638
+ if not Topology.IsInstance(graphB, "graph"):
4639
+ if not silent:
4640
+ print("Graph.Compare - Error: The graphB input parameter is not a valid topologic graph. Returning None.")
4641
+ return
4642
+
4643
+ total_weight = weightAttributes + weightGeometry + weightMetrics + weightStructure + weightWL + weightJaccard
4644
+
4645
+ attribute_score = attribute_similarity(graphA, graphB, mantissa=mantissa) if weightAttributes else 0
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
4648
+ metrics_score = metrics_similarity(graphA, graphB, mantissa=mantissa) if weightMetrics else 0
4649
+ structure_score = structure_similarity(graphA, graphB, mantissa=mantissa) if weightStructure else 0
4650
+ wl_score = weisfeiler_lehman_similarity(graphA, graphB, iterations, mantissa=mantissa) if weightWL else 0
4651
+
4652
+ weighted_sum = (
4653
+ attribute_score * weightAttributes +
4654
+ geometry_score * weightGeometry +
4655
+ jaccard_score * weightJaccard +
4656
+ metrics_score * weightMetrics +
4657
+ structure_score * weightStructure +
4658
+ wl_score * weightWL
4659
+ )
4660
+
4661
+ if total_weight <= 0:
4662
+ overall_score = 0
4663
+ else:
4664
+ overall_score = weighted_sum / total_weight
4665
+
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)
4674
+ }
4675
+
4380
4676
  @staticmethod
4381
4677
  def Complement(graph, tolerance=0.0001, silent=False):
4382
4678
  """
@@ -10804,3 +11100,85 @@ class Graph:
10804
11100
  g = Graph.ByVerticesEdges(final_vertices, final_edges)
10805
11101
  return g
10806
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.19'
1
+ __version__ = '0.8.21'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: topologicpy
3
- Version: 0.8.19
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=FOG3TyXfA1K9c4V3PXKP2vbO2L7HOnGj28pAVnuVRqo,505547
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=-CEaM_uLk3M3OyXlLqkLmTj4IViw6vFkTKIRWVX31LI,23
32
- topologicpy-0.8.19.dist-info/licenses/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
33
- topologicpy-0.8.19.dist-info/METADATA,sha256=pFjIJ2TfH28CMrW4rFY9OZMo-4ROp3hzpRns8o6CF08,10535
34
- topologicpy-0.8.19.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
35
- topologicpy-0.8.19.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
36
- topologicpy-0.8.19.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,,