topologicpy 0.7.72__py3-none-any.whl → 0.7.74__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/CellComplex.py +58 -0
- topologicpy/Graph.py +703 -70
- topologicpy/Plotly.py +32 -148
- topologicpy/version.py +1 -1
- {topologicpy-0.7.72.dist-info → topologicpy-0.7.74.dist-info}/METADATA +1 -1
- {topologicpy-0.7.72.dist-info → topologicpy-0.7.74.dist-info}/RECORD +9 -9
- {topologicpy-0.7.72.dist-info → topologicpy-0.7.74.dist-info}/WHEEL +1 -1
- {topologicpy-0.7.72.dist-info → topologicpy-0.7.74.dist-info}/LICENSE +0 -0
- {topologicpy-0.7.72.dist-info → topologicpy-0.7.74.dist-info}/top_level.txt +0 -0
topologicpy/CellComplex.py
CHANGED
@@ -1006,6 +1006,64 @@ class CellComplex():
|
|
1006
1006
|
shells = Topology.Shells(cellComplex)
|
1007
1007
|
return shells
|
1008
1008
|
|
1009
|
+
@staticmethod
|
1010
|
+
def Torus(origin= None, majorRadius: float = 0.5, minorRadius: float = 0.125, uSides: int = 16, vSides: int = 8, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001):
|
1011
|
+
"""
|
1012
|
+
Creates a torus.
|
1013
|
+
|
1014
|
+
Parameters
|
1015
|
+
----------
|
1016
|
+
origin : topologic_core.Vertex , optional
|
1017
|
+
The origin location of the torus. The default is None which results in the torus being placed at (0, 0, 0).
|
1018
|
+
majorRadius : float , optional
|
1019
|
+
The major radius of the torus. The default is 0.5.
|
1020
|
+
minorRadius : float , optional
|
1021
|
+
The minor radius of the torus. The default is 0.1.
|
1022
|
+
uSides : int , optional
|
1023
|
+
The number of sides along the longitude of the torus. The default is 16.
|
1024
|
+
vSides : int , optional
|
1025
|
+
The number of sides along the latitude of the torus. The default is 8.
|
1026
|
+
direction : list , optional
|
1027
|
+
The vector representing the up direction of the torus. The default is [0, 0, 1].
|
1028
|
+
placement : str , optional
|
1029
|
+
The description of the placement of the origin of the torus. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "center".
|
1030
|
+
tolerance : float , optional
|
1031
|
+
The desired tolerance. The default is 0.0001.
|
1032
|
+
|
1033
|
+
Returns
|
1034
|
+
-------
|
1035
|
+
topologic_core.Cell
|
1036
|
+
The created torus.
|
1037
|
+
|
1038
|
+
"""
|
1039
|
+
|
1040
|
+
from topologicpy.Vertex import Vertex
|
1041
|
+
from topologicpy.Wire import Wire
|
1042
|
+
from topologicpy.Face import Face
|
1043
|
+
from topologicpy.Cell import Cell
|
1044
|
+
from topologicpy.Topology import Topology
|
1045
|
+
|
1046
|
+
if not Topology.IsInstance(origin, "Vertex"):
|
1047
|
+
origin = Vertex.ByCoordinates(0, 0, 0)
|
1048
|
+
if not Topology.IsInstance(origin, "Vertex"):
|
1049
|
+
print("Cell.Torus - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
|
1050
|
+
return None
|
1051
|
+
c = Wire.Circle(origin=Vertex.Origin(), radius=minorRadius, sides=vSides, fromAngle=0, toAngle=360, close=False, direction=[0, 1, 0], placement="center")
|
1052
|
+
c = Face.ByWire(c)
|
1053
|
+
c = Topology.Translate(c, abs(majorRadius-minorRadius), 0, 0)
|
1054
|
+
torus = Topology.Spin(c, origin=Vertex.Origin(), triangulate=False, direction=[0, 0, 1], angle=360, sides=uSides, tolerance=tolerance)
|
1055
|
+
if Topology.Type(torus) == Topology.TypeID("Shell"):
|
1056
|
+
faces = Topology.Faces(torus)
|
1057
|
+
torus = CellComplex.ByFaces(faces)
|
1058
|
+
if placement.lower() == "bottom":
|
1059
|
+
torus = Topology.Translate(torus, 0, 0, minorRadius)
|
1060
|
+
elif placement.lower() == "lowerleft":
|
1061
|
+
torus = Topology.Translate(torus, majorRadius, majorRadius, minorRadius)
|
1062
|
+
|
1063
|
+
torus = Topology.Orient(torus, origin=Vertex.Origin(), dirA=[0, 0, 1], dirB=direction)
|
1064
|
+
torus = Topology.Place(torus, originA=Vertex.Origin(), originB=origin)
|
1065
|
+
return torus
|
1066
|
+
|
1009
1067
|
@staticmethod
|
1010
1068
|
def Vertices(cellComplex) -> list:
|
1011
1069
|
"""
|
topologicpy/Graph.py
CHANGED
@@ -742,6 +742,102 @@ class Graph:
|
|
742
742
|
_ = graph.AllPaths(vertexA, vertexB, True, timeLimit, paths) # Hook to Core
|
743
743
|
return paths
|
744
744
|
|
745
|
+
@staticmethod
|
746
|
+
def AreIsomorphic(graphA, graphB, maxIterations=10, silent=False):
|
747
|
+
"""
|
748
|
+
Tests if the two input graphs are isomorphic according to the Weisfeiler Lehman graph isomorphism test. See https://en.wikipedia.org/wiki/Weisfeiler_Leman_graph_isomorphism_test
|
749
|
+
|
750
|
+
Parameters
|
751
|
+
----------
|
752
|
+
graphA : topologic_core.Graph
|
753
|
+
The first input graph.
|
754
|
+
graphB : topologic_core.Graph
|
755
|
+
The second input graph.
|
756
|
+
maxIterations : int , optional
|
757
|
+
This number limits the number of iterations to prevent the function from running indefinitely, particularly for very large or complex graphs.
|
758
|
+
silent : bool , optional
|
759
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
760
|
+
|
761
|
+
Returns
|
762
|
+
-------
|
763
|
+
bool
|
764
|
+
True if the two input graphs are isomorphic. False otherwise
|
765
|
+
|
766
|
+
"""
|
767
|
+
|
768
|
+
from topologicpy.Topology import Topology
|
769
|
+
|
770
|
+
def weisfeiler_lehman_test(graph1, graph2, max_iterations=10):
|
771
|
+
"""
|
772
|
+
Test if two graphs are isomorphic using the Weisfeiler-Leman (WL) algorithm with early stopping.
|
773
|
+
|
774
|
+
Parameters:
|
775
|
+
graph1 (dict): Adjacency list representation of the first graph.
|
776
|
+
graph2 (dict): Adjacency list representation of the second graph.
|
777
|
+
max_iterations (int): Maximum WL iterations allowed (default is 10).
|
778
|
+
|
779
|
+
Returns:
|
780
|
+
bool: True if the graphs are WL-isomorphic, False otherwise.
|
781
|
+
"""
|
782
|
+
|
783
|
+
def wl_iteration(labels, graph):
|
784
|
+
"""Perform one WL iteration and return updated labels."""
|
785
|
+
new_labels = {}
|
786
|
+
for node in graph:
|
787
|
+
neighborhood_labels = sorted([labels[neighbor] for neighbor in graph[node]])
|
788
|
+
new_labels[node] = (labels[node], tuple(neighborhood_labels))
|
789
|
+
unique_labels = {}
|
790
|
+
count = 0
|
791
|
+
for node in sorted(new_labels):
|
792
|
+
if new_labels[node] not in unique_labels:
|
793
|
+
unique_labels[new_labels[node]] = count
|
794
|
+
count += 1
|
795
|
+
new_labels[node] = unique_labels[new_labels[node]]
|
796
|
+
return new_labels
|
797
|
+
|
798
|
+
# Initialize labels
|
799
|
+
labels1 = {node: 1 for node in graph1}
|
800
|
+
labels2 = {node: 1 for node in graph2}
|
801
|
+
|
802
|
+
for i in range(max_iterations):
|
803
|
+
# Perform WL iteration for both graphs
|
804
|
+
new_labels1 = wl_iteration(labels1, graph1)
|
805
|
+
new_labels2 = wl_iteration(labels2, graph2)
|
806
|
+
|
807
|
+
# Check if the label distributions match
|
808
|
+
if sorted(new_labels1.values()) != sorted(new_labels2.values()):
|
809
|
+
return False
|
810
|
+
|
811
|
+
# Check for stability (early stopping)
|
812
|
+
if new_labels1 == labels1 and new_labels2 == labels2:
|
813
|
+
break
|
814
|
+
|
815
|
+
# Update labels for next iteration
|
816
|
+
labels1, labels2 = new_labels1, new_labels2
|
817
|
+
|
818
|
+
return True
|
819
|
+
|
820
|
+
if not Topology.IsInstance(graphA, "Graph") and not Topology.IsInstance(graphB, "Graph"):
|
821
|
+
if not silent:
|
822
|
+
print("Graph.AreIsomorphic - Error: The input graph parameters are not valid graphs. Returning None.")
|
823
|
+
return None
|
824
|
+
if not Topology.IsInstance(graphA, "Graph"):
|
825
|
+
if not silent:
|
826
|
+
print("Graph.AreIsomorphic - Error: The input graphA parameter is not a valid graph. Returning None.")
|
827
|
+
return None
|
828
|
+
if not Topology.IsInstance(graphB, "Graph"):
|
829
|
+
if not silent:
|
830
|
+
print("Graph.AreIsomorphic - Error: The input graphB parameter is not a valid graph. Returning None.")
|
831
|
+
return None
|
832
|
+
if maxIterations <= 0:
|
833
|
+
if not silent:
|
834
|
+
print("Graph.AreIsomorphic - Error: The input maxIterations parameter is not within a valid range. Returning None.")
|
835
|
+
return None
|
836
|
+
|
837
|
+
g1 = Graph.AdjacencyDictionary(graphA)
|
838
|
+
g2 = Graph.AdjacencyDictionary(graphB)
|
839
|
+
return weisfeiler_lehman_test(g1, g2, max_iterations=maxIterations)
|
840
|
+
|
745
841
|
@staticmethod
|
746
842
|
def AverageClusteringCoefficient(graph, mantissa: int = 6, silent: bool = False):
|
747
843
|
"""
|
@@ -1277,6 +1373,7 @@ class Graph:
|
|
1277
1373
|
print("Graph.BetweenessCentrality - Error: The input graph is not a valid graph. Returning None.")
|
1278
1374
|
return None
|
1279
1375
|
graphVertices = Graph.Vertices(graph)
|
1376
|
+
|
1280
1377
|
if not isinstance(vertices, list):
|
1281
1378
|
vertices = graphVertices
|
1282
1379
|
else:
|
@@ -1298,7 +1395,15 @@ class Graph:
|
|
1298
1395
|
if len(destinations) < 1:
|
1299
1396
|
print("Graph.BetweenessCentrality - Error: The input list of destinations does not contain valid vertices. Returning None.")
|
1300
1397
|
return None
|
1301
|
-
|
1398
|
+
graphEdges = Graph.Edges(graph)
|
1399
|
+
if len(graphEdges) == 0:
|
1400
|
+
print("Graph.BetweenessCentrality - Warning: The input graph is a null graph.")
|
1401
|
+
scores = [0 for t in vertices]
|
1402
|
+
for i, v in enumerate(vertices):
|
1403
|
+
d = Topology.Dictionary(v)
|
1404
|
+
d = Dictionary.SetValueAtKey(d, key, scores[i])
|
1405
|
+
v = Topology.SetDictionary(v, d)
|
1406
|
+
return scores
|
1302
1407
|
paths = []
|
1303
1408
|
try:
|
1304
1409
|
for so in tqdm(sources, desc="Computing Shortest Paths", leave=False):
|
@@ -1906,8 +2011,6 @@ class Graph:
|
|
1906
2011
|
for i, vertices, in enumerate(vertices_ds):
|
1907
2012
|
edges = edges_ds[i]
|
1908
2013
|
g = Graph.ByVerticesEdges(vertices, edges)
|
1909
|
-
temp_v = Graph.Vertices(g)
|
1910
|
-
temp_e = Graph.Edges(g)
|
1911
2014
|
if Topology.IsInstance(g, "Graph"):
|
1912
2015
|
if len(graphFeaturesKeys) == 0:
|
1913
2016
|
values = [graph_ids[i], graph_labels[i], graph_features[i]]
|
@@ -5526,19 +5629,35 @@ class Graph:
|
|
5526
5629
|
return False
|
5527
5630
|
|
5528
5631
|
@staticmethod
|
5529
|
-
def
|
5530
|
-
|
5531
|
-
|
5632
|
+
def Reshape(graph,
|
5633
|
+
shape="spring_2d",
|
5634
|
+
k=0.8, seed=None,
|
5635
|
+
iterations=50,
|
5636
|
+
rootVertex=None,
|
5637
|
+
size=1,
|
5638
|
+
sides=16,
|
5639
|
+
key="",
|
5640
|
+
tolerance=0.0001,
|
5641
|
+
silent=False):
|
5642
|
+
"""
|
5643
|
+
Reshapes the input graph according to the desired input shape parameter.
|
5532
5644
|
|
5533
5645
|
Parameters
|
5534
5646
|
----------
|
5535
5647
|
graph : topologic_core.Graph
|
5536
5648
|
The input graph.
|
5537
|
-
|
5538
|
-
The desired
|
5539
|
-
If set to '
|
5540
|
-
If set to '
|
5541
|
-
If set to '
|
5649
|
+
shape : str , optional
|
5650
|
+
The desired shape of the graph.
|
5651
|
+
If set to 'spring_2d' or 'spring_3d', the algorithm uses a simplified version of the Fruchterman-Reingold force-directed algorithm to distribute the vertices.
|
5652
|
+
If set to 'radial_2d', the nodes will be distributed along concentric circles in the XY plane.
|
5653
|
+
If set to 'tree_2d' or 'tree_3d', the nodes will be distributed using the Reingold-Tillford layout.
|
5654
|
+
If set to 'circle_2d', the nodes will be distributed on the cirumference of a segemented circles in the XY plane, based on the size and sides input parameter (radius=size/2).
|
5655
|
+
If set to 'line_2d', the nodes will be distributed on a line in the XY plane based on the size input parameter (length=size).
|
5656
|
+
If set to 'spehere_3d', the nodes will be distributed on the surface of a sphere based on the size input parameter raidus=size/2).
|
5657
|
+
If set to 'grid_2d', the nodes will be distributed on a grid in the XY plane with size based on the size input parameter (length=width=size).
|
5658
|
+
If set to 'grid_3d', the nodes will be distributed on a 3D cubic grid/matrix based on the size input parameter(width=length=height=size).
|
5659
|
+
If set to 'cluster_2d', or 'cluster_3d, the nodes will be clustered according to the 'key' input parameter. The overall radius of the cluster is determined by the size input parameter (radius = size/2)
|
5660
|
+
The default is 'spring_2d'.
|
5542
5661
|
k : float, optional
|
5543
5662
|
The desired spring constant to use for the attractive and repulsive forces. The default is 0.8.
|
5544
5663
|
seed : int , optional
|
@@ -5547,20 +5666,38 @@ class Graph:
|
|
5547
5666
|
The desired maximum number of iterations to solve the forces in the 'spring' mode. The default is 50.
|
5548
5667
|
rootVertex : topologic_core.Vertex , optional
|
5549
5668
|
The desired vertex to use as the root of the tree and radial layouts.
|
5550
|
-
|
5551
|
-
The desired
|
5669
|
+
sides : int , optional
|
5670
|
+
The desired number of sides of the circle layout option. The default is 16
|
5671
|
+
length : float, optional
|
5672
|
+
The desired horizontal length for the line layout option. The default is 1.0.
|
5673
|
+
key : string, optional
|
5674
|
+
The key under which to find the clustering value for the 'cluster_2d' and 'cluster_3d' options. The default is "".
|
5552
5675
|
tolerance : float , optional
|
5553
5676
|
The desired tolerance. The default is 0.0001.
|
5677
|
+
silent : bool , optional
|
5678
|
+
If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
|
5554
5679
|
|
5555
5680
|
Returns
|
5556
5681
|
-------
|
5557
5682
|
topologic_core.Graph
|
5558
|
-
The
|
5683
|
+
The reshaped graph.
|
5559
5684
|
|
5560
5685
|
"""
|
5561
5686
|
from topologicpy.Vertex import Vertex
|
5687
|
+
from topologicpy.Edge import Edge
|
5688
|
+
from topologicpy.Wire import Wire
|
5689
|
+
from topologicpy.Face import Face
|
5690
|
+
from topologicpy.Graph import Graph
|
5691
|
+
from topologicpy.Grid import Grid
|
5692
|
+
from topologicpy.Helper import Helper
|
5693
|
+
from topologicpy.Vector import Vector
|
5562
5694
|
from topologicpy.Topology import Topology
|
5695
|
+
from topologicpy.Dictionary import Dictionary
|
5563
5696
|
import numpy as np
|
5697
|
+
import math
|
5698
|
+
from collections import defaultdict
|
5699
|
+
import random
|
5700
|
+
|
5564
5701
|
|
5565
5702
|
def buchheim(tree):
|
5566
5703
|
dt = firstwalk(_DrawTree(tree))
|
@@ -5674,7 +5811,7 @@ class Graph:
|
|
5674
5811
|
Returns:
|
5675
5812
|
A numpy array representing the adjacency matrix.
|
5676
5813
|
"""
|
5677
|
-
|
5814
|
+
|
5678
5815
|
# Get the number of nodes from the edge list.
|
5679
5816
|
flat_list = Helper.Flatten(edge_list)
|
5680
5817
|
flat_list = [x for x in flat_list if not x == None]
|
@@ -5714,15 +5851,226 @@ class Graph:
|
|
5714
5851
|
new_roots.extend(children)
|
5715
5852
|
old_roots = new_roots
|
5716
5853
|
return root, num_nodes
|
5854
|
+
|
5855
|
+
def generate_cubic_matrix(size, min_points):
|
5856
|
+
# Calculate the minimum points per axis to reach or exceed min_points in total
|
5857
|
+
points_per_axis = int(np.ceil(min_points ** (1/3)))
|
5858
|
+
|
5859
|
+
# Calculate the spacing based on the size and points per axis
|
5860
|
+
spacing = size / (points_per_axis - 1) if points_per_axis > 1 else 0
|
5861
|
+
|
5862
|
+
# Generate linearly spaced points from -size/2 to size/2 along each axis
|
5863
|
+
x = np.linspace(-size / 2, size / 2, points_per_axis)
|
5864
|
+
y = np.linspace(-size / 2, size / 2, points_per_axis)
|
5865
|
+
z = np.linspace(-size / 2, size / 2, points_per_axis)
|
5866
|
+
|
5867
|
+
# Create a meshgrid and stack them to get XYZ coordinates for each point
|
5868
|
+
X, Y, Z = np.meshgrid(x, y, z)
|
5869
|
+
points = np.vstack([X.ravel(), Y.ravel(), Z.ravel()]).T
|
5870
|
+
return points
|
5871
|
+
|
5872
|
+
def vertex_max_degree(graph, vertices):
|
5873
|
+
degrees = [Graph.VertexDegree(graph, vertex) for vertex in vertices]
|
5874
|
+
i = degrees.index(max(degrees))
|
5875
|
+
return vertices[i], i
|
5876
|
+
|
5877
|
+
def circle_layout_2d(graph, radius=0.5, sides=16):
|
5878
|
+
vertices = Graph.Vertices(graph)
|
5879
|
+
edges = Graph.Edges(graph)
|
5880
|
+
edge_dict = {}
|
5717
5881
|
|
5882
|
+
for i, edge in enumerate(edges):
|
5883
|
+
sv = Edge.StartVertex(edge)
|
5884
|
+
ev = Edge.EndVertex(edge)
|
5885
|
+
si = Vertex.Index(sv, vertices)
|
5886
|
+
ei = Vertex.Index(ev, vertices)
|
5887
|
+
edge_dict[str(si)+"_"+str(ei)] = i
|
5888
|
+
edge_dict[str(ei)+"_"+str(si)] = i
|
5889
|
+
n = len(vertices)
|
5890
|
+
c = Wire.Circle(radius=radius, sides=sides)
|
5891
|
+
c_vertices = []
|
5892
|
+
for i in range(n):
|
5893
|
+
u = i*(1/n)
|
5894
|
+
c_vertices.append(Wire.VertexByParameter(c, u))
|
5718
5895
|
|
5719
|
-
|
5720
|
-
|
5721
|
-
|
5722
|
-
|
5723
|
-
|
5724
|
-
|
5896
|
+
for i, c_v in enumerate(c_vertices):
|
5897
|
+
d = Topology.Dictionary(vertices[i])
|
5898
|
+
c_v = Topology.SetDictionary(c_v, d)
|
5899
|
+
adj_dict = Graph.AdjacencyDictionary(graph)
|
5900
|
+
keys = adj_dict.keys()
|
5901
|
+
|
5902
|
+
c_edges = []
|
5903
|
+
used = [[0] * n for _ in range(n)]
|
5904
|
+
for key in keys:
|
5905
|
+
x = int(key)
|
5906
|
+
adj_vertices = [int(v) for v in adj_dict[key]]
|
5907
|
+
for y in adj_vertices:
|
5908
|
+
if used[x][y] == 0:
|
5909
|
+
v1 = Vector.ByCoordinates(Vertex.X(c_vertices[x]), Vertex.Y(c_vertices[x]), Vertex.Z(c_vertices[x]))
|
5910
|
+
v2 = Vector.ByCoordinates(Vertex.X(c_vertices[y]), Vertex.Y(c_vertices[y]), Vertex.Z(c_vertices[y]))
|
5911
|
+
ang1 = Vector.CompassAngle(v1, [0,1,0])
|
5912
|
+
ang2 = Vector.CompassAngle(v2, [0,1,0])
|
5913
|
+
if ang2-ang1 < 180:
|
5914
|
+
e = Edge.ByVertices(c_vertices[x], c_vertices[y])
|
5915
|
+
else:
|
5916
|
+
e = Edge.ByVertices(c_vertices[y], c_vertices[x])
|
5917
|
+
orig_edge_index = edge_dict.get(str(x)+"_"+str(y), edge_dict.get(str(y)+"_"+str(x), None))
|
5918
|
+
if orig_edge_index:
|
5919
|
+
d = Topology.Dictionary(edges[orig_edge_index])
|
5920
|
+
e = Topology.SetDictionary(e, d)
|
5921
|
+
c_edges.append(e)
|
5922
|
+
used[x][y] = 1
|
5923
|
+
used[y][x] = 1
|
5924
|
+
new_g = Graph.ByVerticesEdges(c_vertices, c_edges)
|
5925
|
+
return new_g
|
5926
|
+
|
5927
|
+
def cluster_layout_2d(graph, key, radius=0.5):
|
5928
|
+
|
5929
|
+
d = Graph.MeshData(graph)
|
5930
|
+
edges = d['edges']
|
5931
|
+
v_dicts = d['vertexDictionaries']
|
5932
|
+
e_dicts = d['edgeDictionaries']
|
5933
|
+
vertices = Graph.Vertices(graph)
|
5934
|
+
# Step 1: Group objects by key value while remembering their original indices
|
5935
|
+
grouped_objects = defaultdict(list)
|
5936
|
+
object_indices = [] # Stores original indices of objects in the order they were grouped
|
5937
|
+
|
5938
|
+
for idx, obj in enumerate(vertices):
|
5939
|
+
d = Topology.Dictionary(obj)
|
5940
|
+
value = Dictionary.ValueAtKey(d, key)
|
5941
|
+
grouped_objects[value].append((obj, idx))
|
5942
|
+
object_indices.append((value, idx))
|
5943
|
+
|
5944
|
+
# Step 2: Compute cluster centers on the circumference of a circle
|
5945
|
+
cluster_centers = {}
|
5946
|
+
num_clusters = len(grouped_objects)
|
5947
|
+
|
5948
|
+
# Function to generate cluster center on the circle's circumference
|
5949
|
+
def generate_cluster_center(index, total_clusters, circle_radius):
|
5950
|
+
# Distribute cluster centers evenly along the circumference of a circle
|
5951
|
+
angle = (2 * math.pi * index) / total_clusters # Equal angle separation
|
5952
|
+
x = circle_radius * math.cos(angle)
|
5953
|
+
y = circle_radius * math.sin(angle)
|
5954
|
+
return (x, y)
|
5955
|
+
|
5956
|
+
# Step 3: Compute vertices for each cluster
|
5957
|
+
object_positions = [None] * len(vertices) # Placeholder list for ordered vertices
|
5958
|
+
cluster_index = 0
|
5959
|
+
|
5960
|
+
for value, objs in grouped_objects.items():
|
5961
|
+
intra_cluster_radius = radius*(len(objs)/len(vertices) + 0.1)
|
5962
|
+
# Determine the center of the current cluster
|
5963
|
+
if value not in cluster_centers:
|
5964
|
+
cluster_center = generate_cluster_center(cluster_index, num_clusters, radius)
|
5965
|
+
cluster_centers[value] = cluster_center
|
5966
|
+
cluster_index += 1
|
5967
|
+
else:
|
5968
|
+
cluster_center = cluster_centers[value]
|
5969
|
+
|
5970
|
+
# Step 4: Place objects randomly around the cluster center
|
5971
|
+
for obj, original_index in objs:
|
5972
|
+
# Randomly place the object within the intra-cluster circle
|
5973
|
+
r = intra_cluster_radius * math.sqrt(random.random()) # Random distance with sqrt for uniform distribution
|
5974
|
+
angle = random.uniform(0, 2 * math.pi) # Random angle
|
5975
|
+
|
5976
|
+
# Polar coordinates to Cartesian for local positioning
|
5977
|
+
x = cluster_center[0] + r * math.cos(angle)
|
5978
|
+
y = cluster_center[1] + r * math.sin(angle)
|
5979
|
+
|
5980
|
+
# Save the coordinates in the correct order
|
5981
|
+
object_positions[original_index] = [x, y]
|
5982
|
+
|
5983
|
+
positions = [[p[0], p[1], 0] for p in object_positions]
|
5984
|
+
new_g = Graph.ByMeshData(positions, edges, v_dicts, e_dicts, tolerance=0.001)
|
5985
|
+
return new_g
|
5986
|
+
|
5987
|
+
def cluster_layout_3d(graph, key, radius=0.5):
|
5988
|
+
d = Graph.MeshData(graph)
|
5989
|
+
edges = d['edges']
|
5990
|
+
v_dicts = d['vertexDictionaries']
|
5991
|
+
e_dicts = d['edgeDictionaries']
|
5992
|
+
vertices = Graph.Vertices(graph)
|
5993
|
+
|
5994
|
+
# Step 1: Group objects by key value while remembering their original indices
|
5995
|
+
grouped_objects = defaultdict(list)
|
5996
|
+
object_indices = [] # Stores original indices of objects in the order they were grouped
|
5997
|
+
|
5998
|
+
for idx, obj in enumerate(vertices):
|
5999
|
+
d = Topology.Dictionary(obj)
|
6000
|
+
value = Dictionary.ValueAtKey(d, key)
|
6001
|
+
grouped_objects[value].append((obj, idx))
|
6002
|
+
object_indices.append((value, idx))
|
6003
|
+
|
6004
|
+
# Step 2: Compute cluster centers on the surface of a sphere
|
6005
|
+
cluster_centers = {}
|
6006
|
+
num_clusters = len(grouped_objects)
|
6007
|
+
|
6008
|
+
# Function to generate cluster center on the surface of a sphere
|
6009
|
+
def generate_cluster_center(index, total_clusters, sphere_radius):
|
6010
|
+
# Use a spiral algorithm to distribute cluster centers evenly on a sphere's surface
|
6011
|
+
phi = math.acos(1 - 2 * (index + 0.5) / total_clusters) # Inclination angle
|
6012
|
+
theta = math.pi * (1 + 5**0.5) * index # Azimuthal angle (Golden angle)
|
6013
|
+
|
6014
|
+
x = sphere_radius * math.sin(phi) * math.cos(theta)
|
6015
|
+
y = sphere_radius * math.sin(phi) * math.sin(theta)
|
6016
|
+
z = sphere_radius * math.cos(phi)
|
6017
|
+
return (x, y, z)
|
6018
|
+
|
6019
|
+
# Step 3: Compute vertices for each cluster
|
6020
|
+
object_positions = [None] * len(vertices) # Placeholder list for ordered vertices
|
6021
|
+
cluster_index = 0
|
6022
|
+
|
6023
|
+
for value, objs in grouped_objects.items():
|
6024
|
+
# Determine the center of the current cluster
|
6025
|
+
if value not in cluster_centers:
|
6026
|
+
cluster_center = generate_cluster_center(cluster_index, num_clusters, radius)
|
6027
|
+
cluster_centers[value] = cluster_center
|
6028
|
+
cluster_index += 1
|
6029
|
+
else:
|
6030
|
+
cluster_center = cluster_centers[value]
|
6031
|
+
|
6032
|
+
intra_cluster_radius = radius*(len(objs)/len(vertices) + 0.1)
|
6033
|
+
|
6034
|
+
# Step 4: Place objects randomly within the cluster's spherical volume
|
6035
|
+
for obj, original_index in objs:
|
6036
|
+
# Randomly place the object within the intra-cluster sphere
|
6037
|
+
u = random.random()
|
6038
|
+
v = random.random()
|
6039
|
+
r = intra_cluster_radius * (u ** (1/3)) # Random distance with cube root for uniform distribution
|
6040
|
+
|
6041
|
+
theta = 2 * math.pi * v # Random azimuthal angle
|
6042
|
+
phi = math.acos(2 * u - 1) # Random polar angle
|
6043
|
+
|
6044
|
+
# Spherical to Cartesian for local positioning
|
6045
|
+
x = cluster_center[0] + r * math.sin(phi) * math.cos(theta)
|
6046
|
+
y = cluster_center[1] + r * math.sin(phi) * math.sin(theta)
|
6047
|
+
z = cluster_center[2] + r * math.cos(phi)
|
6048
|
+
|
6049
|
+
# Save the coordinates in the correct order
|
6050
|
+
object_positions[original_index] = [x, y, z]
|
6051
|
+
|
6052
|
+
positions = [[p[0], p[1], p[2]] for p in object_positions]
|
6053
|
+
new_g = Graph.ByMeshData(positions, edges, v_dicts, e_dicts, tolerance=0.001)
|
6054
|
+
return new_g
|
6055
|
+
|
6056
|
+
def sphere_layout_3d(graph, radius=0.5):
|
6057
|
+
def points_on_sphere(n, r):
|
6058
|
+
points = []
|
6059
|
+
phi = math.pi * (3. - math.sqrt(5.)) # Golden angle in radians
|
5725
6060
|
|
6061
|
+
for i in range(n):
|
6062
|
+
y = 1 - (i / float(n - 1)) * 2 # y goes from 1 to -1
|
6063
|
+
radius = math.sqrt(1 - y * y) # radius at y
|
6064
|
+
|
6065
|
+
theta = phi * i # Golden angle increment
|
6066
|
+
|
6067
|
+
x = math.cos(theta) * radius * r
|
6068
|
+
z = math.sin(theta) * radius * r
|
6069
|
+
y *= r
|
6070
|
+
|
6071
|
+
points.append([x, y, z])
|
6072
|
+
return points
|
6073
|
+
|
5726
6074
|
vertices = Graph.Vertices(graph)
|
5727
6075
|
edges = Graph.Edges(graph)
|
5728
6076
|
edge_dict = {}
|
@@ -5734,16 +6082,15 @@ class Graph:
|
|
5734
6082
|
ei = Vertex.Index(ev, vertices)
|
5735
6083
|
edge_dict[str(si)+"_"+str(ei)] = i
|
5736
6084
|
edge_dict[str(ei)+"_"+str(si)] = i
|
5737
|
-
|
5738
6085
|
n = len(vertices)
|
5739
|
-
|
5740
|
-
c_vertices =
|
5741
|
-
|
6086
|
+
c_points = points_on_sphere(n, r=radius)
|
6087
|
+
c_vertices = [Vertex.ByCoordinates(coord) for coord in c_points]
|
5742
6088
|
for i, c_v in enumerate(c_vertices):
|
5743
6089
|
d = Topology.Dictionary(vertices[i])
|
5744
6090
|
c_v = Topology.SetDictionary(c_v, d)
|
5745
6091
|
adj_dict = Graph.AdjacencyDictionary(graph)
|
5746
6092
|
keys = adj_dict.keys()
|
6093
|
+
|
5747
6094
|
c_edges = []
|
5748
6095
|
used = [[0] * n for _ in range(n)]
|
5749
6096
|
for key in keys:
|
@@ -5759,6 +6106,94 @@ class Graph:
|
|
5759
6106
|
e = Edge.ByVertices(c_vertices[x], c_vertices[y])
|
5760
6107
|
else:
|
5761
6108
|
e = Edge.ByVertices(c_vertices[y], c_vertices[x])
|
6109
|
+
orig_edge_index = edge_dict.get(str(x)+"_"+str(y), edge_dict.get(str(y)+"_"+str(x), None))
|
6110
|
+
if orig_edge_index:
|
6111
|
+
d = Topology.Dictionary(edges[orig_edge_index])
|
6112
|
+
e = Topology.SetDictionary(e, d)
|
6113
|
+
c_edges.append(e)
|
6114
|
+
used[x][y] = 1
|
6115
|
+
used[y][x] = 1
|
6116
|
+
new_g = Graph.ByVerticesEdges(c_vertices, c_edges)
|
6117
|
+
return new_g
|
6118
|
+
|
6119
|
+
def grid_layout_2d(graph, size=1):
|
6120
|
+
vertices = Graph.Vertices(graph)
|
6121
|
+
n = len(vertices)
|
6122
|
+
u = int(math.sqrt(n))
|
6123
|
+
if u*u < n:
|
6124
|
+
u += 1
|
6125
|
+
u_range = [t/(u-1) for t in range(u)]
|
6126
|
+
edges = Graph.Edges(graph)
|
6127
|
+
edge_dict = {}
|
6128
|
+
|
6129
|
+
for i, edge in enumerate(edges):
|
6130
|
+
sv = Edge.StartVertex(edge)
|
6131
|
+
ev = Edge.EndVertex(edge)
|
6132
|
+
si = Vertex.Index(sv, vertices)
|
6133
|
+
ei = Vertex.Index(ev, vertices)
|
6134
|
+
edge_dict[str(si)+"_"+str(ei)] = i
|
6135
|
+
edge_dict[str(ei)+"_"+str(si)] = i
|
6136
|
+
f = Face.Rectangle(width=size, length=size)
|
6137
|
+
c = Grid.VerticesByParameters(face=f, uRange=u_range, vRange=u_range)
|
6138
|
+
c_vertices = Topology.Vertices(c)[:len(vertices)]
|
6139
|
+
|
6140
|
+
for i, c_v in enumerate(c_vertices):
|
6141
|
+
d = Topology.Dictionary(vertices[i])
|
6142
|
+
c_v = Topology.SetDictionary(c_v, d)
|
6143
|
+
adj_dict = Graph.AdjacencyDictionary(graph)
|
6144
|
+
keys = adj_dict.keys()
|
6145
|
+
|
6146
|
+
c_edges = []
|
6147
|
+
used = [[0] * n for _ in range(n)]
|
6148
|
+
for key in keys:
|
6149
|
+
x = int(key)
|
6150
|
+
adj_vertices = [int(v) for v in adj_dict[key]]
|
6151
|
+
for y in adj_vertices:
|
6152
|
+
if used[x][y] == 0:
|
6153
|
+
e = Edge.ByVertices(c_vertices[x], c_vertices[y])
|
6154
|
+
orig_edge_index = edge_dict.get(str(x)+"_"+str(y), edge_dict.get(str(y)+"_"+str(x), None))
|
6155
|
+
if orig_edge_index:
|
6156
|
+
d = Topology.Dictionary(edges[orig_edge_index])
|
6157
|
+
e = Topology.SetDictionary(e, d)
|
6158
|
+
c_edges.append(e)
|
6159
|
+
used[x][y] = 1
|
6160
|
+
used[y][x] = 1
|
6161
|
+
new_g = Graph.ByVerticesEdges(c_vertices, c_edges)
|
6162
|
+
return new_g
|
6163
|
+
|
6164
|
+
def line_layout_2d(graph, length=1):
|
6165
|
+
vertices = Graph.Vertices(graph)
|
6166
|
+
edges = Graph.Edges(graph)
|
6167
|
+
edge_dict = {}
|
6168
|
+
|
6169
|
+
for i, edge in enumerate(edges):
|
6170
|
+
sv = Edge.StartVertex(edge)
|
6171
|
+
ev = Edge.EndVertex(edge)
|
6172
|
+
si = Vertex.Index(sv, vertices)
|
6173
|
+
ei = Vertex.Index(ev, vertices)
|
6174
|
+
edge_dict[str(si)+"_"+str(ei)] = i
|
6175
|
+
edge_dict[str(ei)+"_"+str(si)] = i
|
6176
|
+
|
6177
|
+
n = len(vertices)
|
6178
|
+
c = Wire.Line(length=length, sides=n-1)
|
6179
|
+
c_vertices = Topology.Vertices(c)
|
6180
|
+
|
6181
|
+
for i, c_v in enumerate(c_vertices):
|
6182
|
+
d = Topology.Dictionary(vertices[i])
|
6183
|
+
c_v = Topology.SetDictionary(c_v, d)
|
6184
|
+
adj_dict = Graph.AdjacencyDictionary(graph)
|
6185
|
+
keys = adj_dict.keys()
|
6186
|
+
c_edges = []
|
6187
|
+
used = [[0] * n for _ in range(n)]
|
6188
|
+
for key in keys:
|
6189
|
+
x = int(key)
|
6190
|
+
adj_vertices = [int(v) for v in adj_dict[key]]
|
6191
|
+
for y in adj_vertices:
|
6192
|
+
if used[x][y] == 0:
|
6193
|
+
if Vertex.X(c_vertices[x]) < Vertex.X(c_vertices[y]):
|
6194
|
+
e = Edge.ByVertices(c_vertices[x], c_vertices[y])
|
6195
|
+
else:
|
6196
|
+
e = Edge.ByVertices(c_vertices[y], c_vertices[x])
|
5762
6197
|
|
5763
6198
|
orig_edge_index = edge_dict[str(x)+"_"+str(y)]
|
5764
6199
|
d = Topology.Dictionary(edges[orig_edge_index])
|
@@ -5769,10 +6204,51 @@ class Graph:
|
|
5769
6204
|
new_g = Graph.ByVerticesEdges(c_vertices, c_edges)
|
5770
6205
|
return new_g
|
5771
6206
|
|
5772
|
-
def
|
5773
|
-
|
5774
|
-
|
6207
|
+
def grid_layout_3d(graph, size=1):
|
6208
|
+
vertices = Graph.Vertices(graph)
|
6209
|
+
n = len(vertices)
|
6210
|
+
edges = Graph.Edges(graph)
|
6211
|
+
edge_dict = {}
|
5775
6212
|
|
6213
|
+
for i, edge in enumerate(edges):
|
6214
|
+
sv = Edge.StartVertex(edge)
|
6215
|
+
ev = Edge.EndVertex(edge)
|
6216
|
+
si = Vertex.Index(sv, vertices)
|
6217
|
+
ei = Vertex.Index(ev, vertices)
|
6218
|
+
edge_dict[str(si)+"_"+str(ei)] = i
|
6219
|
+
edge_dict[str(ei)+"_"+str(si)] = i
|
6220
|
+
c_coords = generate_cubic_matrix(size, n)
|
6221
|
+
c_vertices = [Vertex.ByCoordinates(list(coord)) for coord in c_coords[:n]]
|
6222
|
+
|
6223
|
+
for i, c_v in enumerate(c_vertices):
|
6224
|
+
d = Topology.Dictionary(vertices[i])
|
6225
|
+
c_v = Topology.SetDictionary(c_v, d)
|
6226
|
+
adj_dict = Graph.AdjacencyDictionary(graph)
|
6227
|
+
keys = adj_dict.keys()
|
6228
|
+
|
6229
|
+
c_edges = []
|
6230
|
+
used = [[0] * n for _ in range(n)]
|
6231
|
+
for key in keys:
|
6232
|
+
x = int(key)
|
6233
|
+
adj_vertices = [int(v) for v in adj_dict[key]]
|
6234
|
+
for y in adj_vertices:
|
6235
|
+
if used[x][y] == 0:
|
6236
|
+
e = Edge.ByVertices(c_vertices[x], c_vertices[y])
|
6237
|
+
orig_edge_index = edge_dict.get(str(x)+"_"+str(y), edge_dict.get(str(y)+"_"+str(x), None))
|
6238
|
+
if orig_edge_index:
|
6239
|
+
d = Topology.Dictionary(edges[orig_edge_index])
|
6240
|
+
e = Topology.SetDictionary(e, d)
|
6241
|
+
c_edges.append(e)
|
6242
|
+
used[x][y] = 1
|
6243
|
+
used[y][x] = 1
|
6244
|
+
new_g = Graph.ByVerticesEdges(c_vertices, c_edges)
|
6245
|
+
return new_g
|
6246
|
+
|
6247
|
+
def spring_layout_2d(edge_list, iterations=500, k=None, seed=None):
|
6248
|
+
# Compute the layout of a graph using the Fruchterman-Reingold algorithm
|
6249
|
+
# with a force-directed
|
6250
|
+
|
6251
|
+
iterations = max(1, iterations)
|
5776
6252
|
adj_matrix = edge_list_to_adjacency_matrix(edge_list)
|
5777
6253
|
# Set the random seed
|
5778
6254
|
if seed is not None:
|
@@ -5820,7 +6296,60 @@ class Graph:
|
|
5820
6296
|
|
5821
6297
|
return pos
|
5822
6298
|
|
5823
|
-
def
|
6299
|
+
def spring_layout_3d(edge_list, iterations=500, k=None, seed=None):
|
6300
|
+
# Compute the layout of a graph using the Fruchterman-Reingold algorithm
|
6301
|
+
# with a force-directed layout approach.
|
6302
|
+
|
6303
|
+
iterations = max(1,iterations)
|
6304
|
+
|
6305
|
+
adj_matrix = edge_list_to_adjacency_matrix(edge_list)
|
6306
|
+
# Set the random seed
|
6307
|
+
if seed is not None:
|
6308
|
+
np.random.seed(seed)
|
6309
|
+
|
6310
|
+
# Set the optimal distance between nodes
|
6311
|
+
if k is None or k <= 0:
|
6312
|
+
k = np.cbrt(1.0 / adj_matrix.shape[0]) # Adjusted for 3D
|
6313
|
+
|
6314
|
+
# Initialize the positions of the nodes randomly in 3D
|
6315
|
+
pos = np.random.rand(adj_matrix.shape[0], 3)
|
6316
|
+
|
6317
|
+
# Compute the initial temperature
|
6318
|
+
t = 0.1 * np.max(pos)
|
6319
|
+
|
6320
|
+
# Compute the cooling factor
|
6321
|
+
cooling_factor = t / iterations
|
6322
|
+
|
6323
|
+
# Iterate over the specified number of iterations
|
6324
|
+
for i in range(iterations):
|
6325
|
+
# Compute the distance between each pair of nodes
|
6326
|
+
delta = pos[:, np.newaxis, :] - pos[np.newaxis, :, :]
|
6327
|
+
distance = np.linalg.norm(delta, axis=-1)
|
6328
|
+
|
6329
|
+
# Avoid division by zero
|
6330
|
+
distance = np.where(distance == 0, 0.1, distance)
|
6331
|
+
|
6332
|
+
# Compute the repulsive force between each pair of nodes
|
6333
|
+
repulsive_force = k ** 2 / distance ** 2
|
6334
|
+
|
6335
|
+
# Compute the attractive force between each pair of adjacent nodes
|
6336
|
+
attractive_force = adj_matrix * distance / k
|
6337
|
+
|
6338
|
+
# Compute the total force acting on each node
|
6339
|
+
force = np.sum((repulsive_force - attractive_force)[:, :, np.newaxis] * delta, axis=1)
|
6340
|
+
|
6341
|
+
# Compute the displacement of each node
|
6342
|
+
displacement = t * force / np.linalg.norm(force, axis=1)[:, np.newaxis]
|
6343
|
+
|
6344
|
+
# Update the positions of the nodes
|
6345
|
+
pos += displacement
|
6346
|
+
|
6347
|
+
# Cool the temperature
|
6348
|
+
t -= cooling_factor
|
6349
|
+
|
6350
|
+
return pos
|
6351
|
+
|
6352
|
+
def tree_layout_2d(edge_list, root_index=0):
|
5824
6353
|
|
5825
6354
|
root, num_nodes = tree_from_edge_list(edge_list, root_index)
|
5826
6355
|
dt = buchheim(root)
|
@@ -5846,8 +6375,46 @@ class Graph:
|
|
5846
6375
|
pos[:, 1] = np.max(pos[:, 1]) - pos[:, 1]
|
5847
6376
|
|
5848
6377
|
return pos
|
6378
|
+
|
6379
|
+
def tree_layout_3d(edge_list, root_index=0, base_radius=1.0, radius_factor=1.5):
|
6380
|
+
root, num_nodes = tree_from_edge_list(edge_list, root_index)
|
6381
|
+
dt = buchheim(root)
|
6382
|
+
pos = np.zeros((num_nodes, 3)) # Initialize 3D positions
|
6383
|
+
|
6384
|
+
pos[int(dt.tree.node), 0] = dt.x
|
6385
|
+
pos[int(dt.tree.node), 1] = dt.y
|
6386
|
+
pos[int(dt.tree.node), 2] = 0 # Root at z = 0
|
6387
|
+
|
6388
|
+
old_roots = [dt]
|
6389
|
+
new_roots = []
|
6390
|
+
depth = 1 # Start at depth level 1 for children
|
6391
|
+
|
6392
|
+
while len(old_roots) > 0:
|
6393
|
+
new_roots = []
|
6394
|
+
for temp_root in old_roots:
|
6395
|
+
children = temp_root.children
|
6396
|
+
num_children = len(children)
|
6397
|
+
if num_children > 0:
|
6398
|
+
# Increase the radius dynamically based on the number of children
|
6399
|
+
dynamic_radius = base_radius + (num_children - 1) * radius_factor * depth
|
6400
|
+
|
6401
|
+
angle_step = 2 * np.pi / num_children # Angle between each child
|
6402
|
+
for i, child in enumerate(children):
|
6403
|
+
angle = i * angle_step
|
6404
|
+
pos[int(child.tree.node), 0] = pos[int(temp_root.tree.node), 0] + dynamic_radius * np.cos(angle) # X position
|
6405
|
+
pos[int(child.tree.node), 1] = pos[int(temp_root.tree.node), 1] + dynamic_radius * np.sin(angle) # Y position
|
6406
|
+
pos[int(child.tree.node), 2] = -dynamic_radius*depth # Z-coordinate based on depth
|
6407
|
+
|
6408
|
+
new_roots.extend(children)
|
6409
|
+
|
6410
|
+
old_roots = new_roots
|
6411
|
+
depth += 1 # Increment depth for the next level
|
6412
|
+
|
6413
|
+
pos[:, 1] = np.max(pos[:, 1]) - pos[:, 1] # Flip y-coordinates if necessary
|
6414
|
+
|
6415
|
+
return pos
|
5849
6416
|
|
5850
|
-
def
|
6417
|
+
def radial_layout_2d(edge_list, root_index=0):
|
5851
6418
|
root, num_nodes = tree_from_edge_list(edge_list, root_index)
|
5852
6419
|
dt = buchheim(root)
|
5853
6420
|
pos = np.zeros((num_nodes, 2))
|
@@ -5888,53 +6455,119 @@ class Graph:
|
|
5888
6455
|
|
5889
6456
|
return new_pos
|
5890
6457
|
|
5891
|
-
def
|
6458
|
+
def spherical_layout_3d(edge_list, root_index=0):
|
6459
|
+
root, num_nodes = tree_from_edge_list(edge_list, root_index)
|
6460
|
+
dt = buchheim(root)
|
5892
6461
|
|
5893
|
-
|
5894
|
-
|
5895
|
-
elif layout == 'spring':
|
5896
|
-
return spring_layout(edge_list, k=k, seed=seed, iterations=iterations)
|
5897
|
-
elif layout == 'radial':
|
5898
|
-
return radial_layout(edge_list, root_index=root_index)
|
5899
|
-
else:
|
5900
|
-
raise NotImplementedError(f"{layout} is not implemented yet. Please choose from ['radial', 'spring', 'tree']")
|
6462
|
+
# Initialize positions with 3 columns for x, y, z coordinates
|
6463
|
+
pos = np.zeros((num_nodes, 3))
|
5901
6464
|
|
5902
|
-
|
5903
|
-
|
5904
|
-
|
5905
|
-
|
6465
|
+
# Set initial coordinates and depth for the root node
|
6466
|
+
pos[int(dt.tree.node), 0] = dt.x
|
6467
|
+
pos[int(dt.tree.node), 1] = dt.y
|
6468
|
+
depth = np.zeros(num_nodes) # To track the depth of each node
|
6469
|
+
|
6470
|
+
old_roots = [(dt, 0)] # Store nodes with their depth levels
|
6471
|
+
new_roots = []
|
6472
|
+
|
6473
|
+
while len(old_roots) > 0:
|
6474
|
+
new_roots = []
|
6475
|
+
for temp_root, current_depth in old_roots:
|
6476
|
+
children = temp_root.children
|
6477
|
+
for child in children:
|
6478
|
+
node_index = int(child.tree.node)
|
6479
|
+
pos[node_index, 0] = child.x
|
6480
|
+
pos[node_index, 1] = child.y
|
6481
|
+
depth[node_index] = current_depth + 1 # Increase depth for children
|
6482
|
+
new_roots.extend([(child, current_depth + 1) for child in children])
|
6483
|
+
|
6484
|
+
old_roots = new_roots
|
6485
|
+
|
6486
|
+
# Normalize x and y coordinates to a [0, 1] range
|
6487
|
+
pos[:, 0] = pos[:, 0] - np.min(pos[:, 0])
|
6488
|
+
pos[:, 1] = pos[:, 1] - np.min(pos[:, 1])
|
6489
|
+
|
6490
|
+
pos[:, 0] = pos[:, 0] / np.max(pos[:, 0])
|
6491
|
+
pos[:, 0] = pos[:, 0] - pos[:, 0][root_index]
|
6492
|
+
|
6493
|
+
range_ = np.max(pos[:, 0]) - np.min(pos[:, 0])
|
6494
|
+
pos[:, 0] = pos[:, 0] / range_
|
6495
|
+
|
6496
|
+
pos[:, 0] = pos[:, 0] * np.pi * 1.98 # Longitude (azimuthal angle)
|
6497
|
+
pos[:, 1] = pos[:, 1] / np.max(pos[:, 1]) # Latitude (polar angle)
|
6498
|
+
|
6499
|
+
# Convert the 2D coordinates to 3D spherical coordinates
|
6500
|
+
new_pos = np.zeros((num_nodes, 3)) # 3D position array
|
6501
|
+
base_radius = 1 # Base radius for the first sphere
|
6502
|
+
radius_increment = 1.3 # Increase in radius per depth level
|
6503
|
+
|
6504
|
+
# pos[:, 0] is the azimuth angle (longitude) in radians
|
6505
|
+
# pos[:, 1] is the polar angle (latitude) in radians (adjusted to go from 0 to pi)
|
6506
|
+
polar_angle = pos[:, 1] * np.pi # Scaling to go from 0 to pi
|
6507
|
+
azimuth_angle = pos[:, 0]
|
6508
|
+
|
6509
|
+
# Calculate the 3D Cartesian coordinates based on depth
|
6510
|
+
for i in range(num_nodes):
|
6511
|
+
r = base_radius + depth[i] * radius_increment # Radius grows with depth
|
6512
|
+
new_pos[i, 0] = r * np.sin(polar_angle[i]) * np.cos(azimuth_angle[i]) # X = r * sin(θ) * cos(φ)
|
6513
|
+
new_pos[i, 1] = r * np.sin(polar_angle[i]) * np.sin(azimuth_angle[i]) # Y = r * sin(θ) * sin(φ)
|
6514
|
+
new_pos[i, 2] = r * np.cos(polar_angle[i]) # Z = r * cos(θ)
|
6515
|
+
|
6516
|
+
return new_pos
|
5906
6517
|
|
5907
6518
|
if not Topology.IsInstance(graph, "Graph"):
|
5908
|
-
|
6519
|
+
if not silent:
|
6520
|
+
print("Graph.Flatten - Error: The input graph is not a valid topologic graph. Returning None.")
|
5909
6521
|
return None
|
5910
6522
|
|
5911
|
-
if 'circ' in
|
5912
|
-
|
5913
|
-
|
5914
|
-
|
5915
|
-
|
5916
|
-
|
5917
|
-
|
5918
|
-
|
5919
|
-
|
5920
|
-
|
5921
|
-
|
6523
|
+
if 'circ' in shape.lower():
|
6524
|
+
return circle_layout_2d(graph, radius=size/2, sides=sides)
|
6525
|
+
elif 'lin' in shape.lower():
|
6526
|
+
return line_layout_2d(graph, length=size)
|
6527
|
+
elif 'grid' in shape.lower() and '2d' in shape.lower():
|
6528
|
+
return grid_layout_2d(graph, size=size)
|
6529
|
+
elif 'sphere' == shape.lower() and '3d' in shape.lower():
|
6530
|
+
return sphere_layout_3d(graph, radius=size/2)
|
6531
|
+
elif 'grid' in shape.lower() and '3d' in shape.lower():
|
6532
|
+
return grid_layout_3d(graph, size=size)
|
6533
|
+
elif 'cluster' in shape.lower() and '2d' in shape.lower():
|
6534
|
+
return cluster_layout_2d(graph, radius=size/2, key=key)
|
6535
|
+
elif 'cluster' in shape.lower() and '3d' in shape.lower():
|
6536
|
+
return cluster_layout_3d(graph, radius=size/2, key=key)
|
5922
6537
|
else:
|
5923
|
-
|
5924
|
-
|
5925
|
-
|
5926
|
-
|
5927
|
-
|
5928
|
-
|
5929
|
-
|
5930
|
-
|
5931
|
-
|
5932
|
-
|
5933
|
-
|
5934
|
-
|
5935
|
-
|
5936
|
-
|
5937
|
-
|
6538
|
+
d = Graph.MeshData(graph)
|
6539
|
+
edges = d['edges']
|
6540
|
+
v_dicts = d['vertexDictionaries']
|
6541
|
+
e_dicts = d['edgeDictionaries']
|
6542
|
+
vertices = Graph.Vertices(graph)
|
6543
|
+
if rootVertex == None:
|
6544
|
+
rootVertex, root_index = vertex_max_degree(graph, vertices)
|
6545
|
+
else:
|
6546
|
+
root_index = Vertex.Index(rootVertex, vertices, tolerance=tolerance)
|
6547
|
+
if root_index == None:
|
6548
|
+
root_index = 0
|
6549
|
+
if 'rad' in shape.lower() and '2d' in shape.lower():
|
6550
|
+
positions = radial_layout_2d(edges, root_index=root_index)
|
6551
|
+
elif 'spherical' in shape.lower() and '3d' in shape.lower():
|
6552
|
+
positions = spherical_layout_3d(edges, root_index=root_index)
|
6553
|
+
elif 'spring' in shape.lower() and "3d" in shape.lower():
|
6554
|
+
positions = spring_layout_3d(edges, k=k, seed=seed, iterations=iterations)
|
6555
|
+
elif 'spring' in shape.lower() and '2d' in shape.lower():
|
6556
|
+
positions = spring_layout_2d(edges, k=k, seed=seed, iterations=iterations)
|
6557
|
+
elif 'tree' in shape.lower() and '2d' in shape.lower():
|
6558
|
+
positions = tree_layout_2d(edges, root_index=root_index)
|
6559
|
+
elif 'tree' in shape.lower() and '3d' in shape.lower():
|
6560
|
+
positions = tree_layout_3d(edges, root_index=root_index, base_radius=1.0, radius_factor=1.5)
|
6561
|
+
else:
|
6562
|
+
if not silent:
|
6563
|
+
print(f"{shape} is not implemented yet. Please choose from ['circle 2D', 'grid 2D', 'line 2D', 'radial 2D', 'spring 2D', 'tree 2D', 'grid 3D', 'sphere 3D', 'tree 3D']. Returning None.")
|
6564
|
+
return None
|
6565
|
+
positions = positions.tolist()
|
6566
|
+
if len(positions[0]) == 3:
|
6567
|
+
positions = [[p[0], p[1], p[2]] for p in positions]
|
6568
|
+
else:
|
6569
|
+
positions = [[p[0], p[1], 0] for p in positions]
|
6570
|
+
return Graph.ByMeshData(positions, edges, v_dicts, e_dicts, tolerance=tolerance)
|
5938
6571
|
|
5939
6572
|
@staticmethod
|
5940
6573
|
def GlobalClusteringCoefficient(graph):
|
topologicpy/Plotly.py
CHANGED
@@ -415,23 +415,24 @@ class Plotly:
|
|
415
415
|
e_cluster = Cluster.ByTopologies(vertices)
|
416
416
|
geo = Topology.Geometry(e_cluster, mantissa=mantissa)
|
417
417
|
vertices = geo['vertices']
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
418
|
+
if len(vertices) > 0:
|
419
|
+
v_data = Plotly.DataByTopology(e_cluster,
|
420
|
+
vertexColor=vertexColor,
|
421
|
+
vertexColorKey=vertexColorKey,
|
422
|
+
vertexSize=vertexSize,
|
423
|
+
vertexSizeKey=vertexSizeKey,
|
424
|
+
vertexLabelKey=vertexLabelKey,
|
425
|
+
showVertexLabel=showVertexLabel,
|
426
|
+
vertexGroupKey=vertexGroupKey,
|
427
|
+
vertexMinGroup=vertexMinGroup,
|
428
|
+
vertexMaxGroup=vertexMaxGroup,
|
429
|
+
vertexGroups=vertexGroups,
|
430
|
+
vertexLegendLabel=vertexLegendLabel,
|
431
|
+
vertexLegendGroup=vertexLegendGroup,
|
432
|
+
vertexLegendRank=vertexLegendRank,
|
433
|
+
colorScale=colorScale)
|
434
|
+
|
435
|
+
data += v_data
|
435
436
|
|
436
437
|
if showEdges:
|
437
438
|
e_dictionaries = []
|
@@ -458,12 +459,12 @@ class Plotly:
|
|
458
459
|
else:
|
459
460
|
new_edges = edges
|
460
461
|
e_dictionaries.append(d)
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
462
|
+
if len(new_edges) > 0:
|
463
|
+
e_cluster = Cluster.ByTopologies(new_edges)
|
464
|
+
geo = Topology.Geometry(e_cluster, mantissa=mantissa)
|
465
|
+
vertices = geo['vertices']
|
466
|
+
edges = geo['edges']
|
467
|
+
data.extend(Plotly.edgeData(vertices, edges, dictionaries=e_dictionaries, color=edgeColor, colorKey=edgeColorKey, width=edgeWidth, widthKey=edgeWidthKey, labelKey=edgeLabelKey, showEdgeLabel=showEdgeLabel, groupKey=edgeGroupKey, minGroup=edgeMinGroup, maxGroup=edgeMaxGroup, groups=edgeGroups, legendLabel=edgeLegendLabel, legendGroup=edgeLegendGroup, legendRank=edgeLegendRank, showLegend=showEdgeLegend, colorScale=colorScale))
|
467
468
|
return data
|
468
469
|
|
469
470
|
@staticmethod
|
@@ -496,7 +497,7 @@ class Plotly:
|
|
496
497
|
x.append(v[0])
|
497
498
|
y.append(v[1])
|
498
499
|
z.append(v[2])
|
499
|
-
label = ""
|
500
|
+
label = " "
|
500
501
|
group = None
|
501
502
|
if len(dictionaries) > 0:
|
502
503
|
d = dictionaries[m]
|
@@ -506,15 +507,13 @@ class Plotly:
|
|
506
507
|
color = Color.AnyToHex(d_color)
|
507
508
|
groupList.append(color)
|
508
509
|
if not labelKey == None:
|
509
|
-
label = str(Dictionary.ValueAtKey(d, key=labelKey)) or ""
|
510
|
+
label = str(Dictionary.ValueAtKey(d, key=labelKey)) or " "
|
510
511
|
if not sizeKey == None:
|
511
512
|
size = Dictionary.ValueAtKey(d, key=sizeKey) or size
|
512
513
|
if not groupKey == None:
|
513
|
-
group = Dictionary.ValueAtKey(d, key=groupKey) or ""
|
514
|
+
group = Dictionary.ValueAtKey(d, key=groupKey) or " "
|
514
515
|
try:
|
515
|
-
if group == "":
|
516
|
-
#color = 'white'
|
517
|
-
#groupList.append(Color.AnyToHex(color))
|
516
|
+
if group == " ":
|
518
517
|
pass
|
519
518
|
elif type(group) == int or type(group) == float:
|
520
519
|
if group < minGroup:
|
@@ -571,8 +570,6 @@ class Plotly:
|
|
571
570
|
from topologicpy.Color import Color
|
572
571
|
from topologicpy.Dictionary import Dictionary
|
573
572
|
from topologicpy.Helper import Helper
|
574
|
-
for d in dictionaries[:30]:
|
575
|
-
print(Dictionary.Keys(d), Dictionary.Values(d))
|
576
573
|
traces = []
|
577
574
|
x = []
|
578
575
|
y = []
|
@@ -855,121 +852,6 @@ class Plotly:
|
|
855
852
|
def closest_index(input_value, values):
|
856
853
|
return int(min(range(len(values)), key=lambda i: abs(values[i] - input_value)))
|
857
854
|
|
858
|
-
def edgeData_old(vertices, edges, dictionaries=None, color="black", colorKey=None, width=1, widthKey=None, labelKey=None, showEdgeLabel = False, groupKey=None, minGroup=None, maxGroup=None, groups=[], legendLabel="Topology Edges", legendGroup=2, legendRank=2, showLegend=True, colorScale="Viridis"):
|
859
|
-
traces = []
|
860
|
-
x = []
|
861
|
-
y = []
|
862
|
-
z = []
|
863
|
-
labels = []
|
864
|
-
groupList = []
|
865
|
-
label = ""
|
866
|
-
group = ""
|
867
|
-
|
868
|
-
if showEdgeLabel == True:
|
869
|
-
mode = "lines+text"
|
870
|
-
else:
|
871
|
-
mode = "lines"
|
872
|
-
|
873
|
-
if showEdgeLabel == True:
|
874
|
-
mode = "lines+text"
|
875
|
-
else:
|
876
|
-
mode = "lines"
|
877
|
-
if groups:
|
878
|
-
if len(groups) > 0:
|
879
|
-
if type(groups[0]) == int or type(groups[0]) == float:
|
880
|
-
if not minGroup:
|
881
|
-
minGroup = min(groups)
|
882
|
-
if not maxGroup:
|
883
|
-
maxGroup = max(groups)
|
884
|
-
else:
|
885
|
-
minGroup = 0
|
886
|
-
maxGroup = len(groups) - 1
|
887
|
-
else:
|
888
|
-
minGroup = 0
|
889
|
-
maxGroup = 1
|
890
|
-
|
891
|
-
if colorKey or widthKey or labelKey or groupKey:
|
892
|
-
keys = [x for x in [colorKey, widthKey, labelKey, groupKey] if not x == None]
|
893
|
-
temp_dict = Helper.ClusterByKeys(edges, dictionaries, keys, silent=False)
|
894
|
-
dict_clusters = temp_dict["dictionaries"]
|
895
|
-
elements_clusters = temp_dict['elements']
|
896
|
-
for j, elements_cluster in enumerate(elements_clusters):
|
897
|
-
d = dict_clusters[j][0] # All dicitonaries have same values in dictionaries, so take first one.
|
898
|
-
if d:
|
899
|
-
if not colorKey == None:
|
900
|
-
d_color = Dictionary.ValueAtKey(d, key=colorKey) or color
|
901
|
-
color = Color.AnyToHex(d_color)
|
902
|
-
if not labelKey == None:
|
903
|
-
label = str(Dictionary.ValueAtKey(d, key=labelKey)) or ""
|
904
|
-
if not widthKey == None:
|
905
|
-
width = Dictionary.ValueAtKey(d, key=edgeWidthKey) or width
|
906
|
-
if not groupKey == None:
|
907
|
-
group = Dictionary.ValueAtKey(d, key=groupKey)
|
908
|
-
if not group == None:
|
909
|
-
if type(group) == int or type(group) == float:
|
910
|
-
if group < minGroup:
|
911
|
-
group = minGroup
|
912
|
-
if group > maxGroup:
|
913
|
-
group = maxGroup
|
914
|
-
d_color = Color.ByValueInRange(group, minValue=minGroup, maxValue=maxGroup, colorScale=colorScale)
|
915
|
-
else:
|
916
|
-
d_color = Color.ByValueInRange(groups.index(group), minValue=minGroup, maxValue=maxGroup, colorScale=colorScale)
|
917
|
-
color = d_color
|
918
|
-
x = []
|
919
|
-
y = []
|
920
|
-
z = []
|
921
|
-
for e in elements_cluster:
|
922
|
-
sv = vertices[e[0]]
|
923
|
-
ev = vertices[e[1]]
|
924
|
-
x+=[sv[0], ev[0], None] # x-coordinates of edge ends
|
925
|
-
y+=[sv[1], ev[1], None] # y-coordinates of edge ends
|
926
|
-
z+=[sv[2], ev[2], None] # z-coordinates of edge ends
|
927
|
-
if showEdgeLabel == True:
|
928
|
-
mode = "lines+text"
|
929
|
-
else:
|
930
|
-
mode = "lines"
|
931
|
-
trace = go.Scatter3d(x=x,
|
932
|
-
y=y,
|
933
|
-
z=z,
|
934
|
-
name=label,
|
935
|
-
showlegend=showLegend,
|
936
|
-
marker_size=0,
|
937
|
-
mode=mode,
|
938
|
-
line=dict(color=color, width=width),
|
939
|
-
legendgroup=legendGroup,
|
940
|
-
legendrank=legendRank,
|
941
|
-
text=label,
|
942
|
-
hoverinfo='text')
|
943
|
-
traces.append(trace)
|
944
|
-
else:
|
945
|
-
x = []
|
946
|
-
y = []
|
947
|
-
z = []
|
948
|
-
for e in edges:
|
949
|
-
sv = vertices[e[0]]
|
950
|
-
ev = vertices[e[1]]
|
951
|
-
x+=[sv[0], ev[0], None] # x-coordinates of edge ends
|
952
|
-
y+=[sv[1], ev[1], None] # y-coordinates of edge ends
|
953
|
-
z+=[sv[2], ev[2], None] # z-coordinates of edge ends
|
954
|
-
if showEdgeLabel == True:
|
955
|
-
mode = "lines+text"
|
956
|
-
else:
|
957
|
-
mode = "lines"
|
958
|
-
trace = go.Scatter3d(x=x,
|
959
|
-
y=y,
|
960
|
-
z=z,
|
961
|
-
name=label,
|
962
|
-
showlegend=showLegend,
|
963
|
-
marker_size=0,
|
964
|
-
mode=mode,
|
965
|
-
line=dict(color=color, width=width),
|
966
|
-
legendgroup=legendGroup,
|
967
|
-
legendrank=legendRank,
|
968
|
-
text=label,
|
969
|
-
hoverinfo='text')
|
970
|
-
traces.append(trace)
|
971
|
-
|
972
|
-
return traces
|
973
855
|
|
974
856
|
def faceData(vertices, faces, dictionaries=None, color="#FAFAFA", colorKey=None,
|
975
857
|
opacity=0.5, labelKey=None, groupKey=None,
|
@@ -1152,7 +1034,8 @@ class Plotly:
|
|
1152
1034
|
geo = Topology.Geometry(e_cluster, mantissa=mantissa)
|
1153
1035
|
vertices = geo['vertices']
|
1154
1036
|
edges = geo['edges']
|
1155
|
-
|
1037
|
+
if len(edges) > 0:
|
1038
|
+
data.extend(Plotly.edgeData(vertices, edges, dictionaries=e_dictionaries, color=edgeColor, colorKey=edgeColorKey, width=edgeWidth, widthKey=edgeWidthKey, labelKey=edgeLabelKey, showEdgeLabel=showEdgeLabel, groupKey=edgeGroupKey, minGroup=edgeMinGroup, maxGroup=edgeMaxGroup, groups=edgeGroups, legendLabel=edgeLegendLabel, legendGroup=edgeLegendGroup, legendRank=edgeLegendRank, showLegend=showEdgeLegend, colorScale=colorScale))
|
1156
1039
|
|
1157
1040
|
if showFaces and Topology.Type(topology) >= Topology.TypeID("Face"):
|
1158
1041
|
if not faceColorKey == None:
|
@@ -1185,7 +1068,8 @@ class Plotly:
|
|
1185
1068
|
geo = Topology.Geometry(f_cluster, mantissa=mantissa)
|
1186
1069
|
vertices = geo['vertices']
|
1187
1070
|
faces = geo['faces']
|
1188
|
-
|
1071
|
+
if len(faces) > 0:
|
1072
|
+
data.append(faceData(vertices, faces, dictionaries=f_dictionaries, color=faceColor, colorKey=faceColorKey, opacity=faceOpacity, labelKey=faceLabelKey, groupKey=faceGroupKey, minGroup=faceMinGroup, maxGroup=faceMaxGroup, groups=faceGroups, legendLabel=faceLegendLabel, legendGroup=faceLegendGroup, legendRank=faceLegendRank, showLegend=showFaceLegend, intensities=intensityList, colorScale=colorScale))
|
1189
1073
|
return data
|
1190
1074
|
|
1191
1075
|
@staticmethod
|
topologicpy/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = '0.7.
|
1
|
+
__version__ = '0.7.74'
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: topologicpy
|
3
|
-
Version: 0.7.
|
3
|
+
Version: 0.7.74
|
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
|
@@ -2,7 +2,7 @@ topologicpy/ANN.py,sha256=XAuUjNvDRK1hhXfo82S-zXmnAPZGEdHJMRdfpu0aJ8I,47901
|
|
2
2
|
topologicpy/Aperture.py,sha256=p9pUzTQSBWoUaDiug1V1R1hnEIEwYSXFg2t7iRAmNRY,2723
|
3
3
|
topologicpy/BVH.py,sha256=mKVCAu9K8qzcWXtPDVH5usXZV1DNNNJl4n3rU5Lh1ZM,12931
|
4
4
|
topologicpy/Cell.py,sha256=2izd-YGqy897_JHHgrGlIo5WwUeEIWVD3KspV1z_sj8,107860
|
5
|
-
topologicpy/CellComplex.py,sha256=
|
5
|
+
topologicpy/CellComplex.py,sha256=5PtnRrDx_3zmtt4aTosxK9XBzZtayzaOC50pkJiIlFY,51170
|
6
6
|
topologicpy/Cluster.py,sha256=51q5G1L5xAzRMfVU8YBXhq0g3g2X9aVNcahU-vYZRrI,55672
|
7
7
|
topologicpy/Color.py,sha256=wPhA7rLr9BTZsWYUUVnQpbmL5ZMkGlDSsa8f3S5B-d4,20250
|
8
8
|
topologicpy/Context.py,sha256=ppApYKngZZCQBFWaxIMi2z2dokY23c935IDCBosxDAE,3055
|
@@ -11,13 +11,13 @@ topologicpy/Dictionary.py,sha256=0AsGoz48pGTye_F4KcJopNjD9STeQ50LHc6PPvERFaA,319
|
|
11
11
|
topologicpy/Edge.py,sha256=9u9SdUxuenLUIK26xwFvPoYV34p0dCfXmHHBxdgvAdM,67164
|
12
12
|
topologicpy/EnergyModel.py,sha256=AqTtmXE35SxvRXhG3vYAQd7GQDW-6HtjYPHua6ME4Eg,53762
|
13
13
|
topologicpy/Face.py,sha256=q7x6auTju6IS3mdOhhXZdU3rqKSuJCE-5EOfxofDDMI,124348
|
14
|
-
topologicpy/Graph.py,sha256=
|
14
|
+
topologicpy/Graph.py,sha256=BuSg8ilbZd2Qqoenmtw5FrkSe1fDLFS6jshljvq4dOs,416822
|
15
15
|
topologicpy/Grid.py,sha256=9N6PE84qCm40TRi2WtlVZSBwXXr47zHpscEpZHg_JW4,18205
|
16
16
|
topologicpy/Helper.py,sha256=Sv35czP_j0oLDeJcN8usswUm4U3auiK1LQ_Z_HBvxxg,21716
|
17
17
|
topologicpy/Honeybee.py,sha256=HfTaEV1R8K1xOVQQy9sBOhBTF_ap8A2RxZOYhirp_Mw,21835
|
18
18
|
topologicpy/Matrix.py,sha256=umgR7An919-wGInXJ1wpqnoQ2jCPdyMe2rcWTZ16upk,8079
|
19
19
|
topologicpy/Neo4j.py,sha256=t52hgE9cVsqkGc7m7fjRsLnyfRHakVHwdvF4ms7ow78,22342
|
20
|
-
topologicpy/Plotly.py,sha256=
|
20
|
+
topologicpy/Plotly.py,sha256=VNUHRUNu4NUmf3HScQRml3TcwHe_ZoqXjqwttGSuU88,112642
|
21
21
|
topologicpy/Polyskel.py,sha256=EFsuh2EwQJGPLiFUjvtXmAwdX-A4r_DxP5hF7Qd3PaU,19829
|
22
22
|
topologicpy/PyG.py,sha256=LU9LCCzjxGPUM31qbaJXZsTvniTtgugxJY7y612t4A4,109757
|
23
23
|
topologicpy/Shell.py,sha256=8OJjlWk9eCZ3uGOTht6ZVrcMczCafw-YWoDGueaz7eg,87673
|
@@ -28,9 +28,9 @@ topologicpy/Vector.py,sha256=A1g83zDHep58iVPY8WQ8iHNrSOfGWFEzvVeDuMnjDNY,33078
|
|
28
28
|
topologicpy/Vertex.py,sha256=ZS6xK89JKokBKc0W8frdRhhuzR8c-dI1TTLt7pTf1iA,71032
|
29
29
|
topologicpy/Wire.py,sha256=eVet2OToVsXi9AkDYo35LpfMPqJ6aKGD6QkiU4-Jvs8,182271
|
30
30
|
topologicpy/__init__.py,sha256=vlPCanUbxe5NifC4pHcnhSzkmmYcs_UrZrTlVMsxcFs,928
|
31
|
-
topologicpy/version.py,sha256=
|
32
|
-
topologicpy-0.7.
|
33
|
-
topologicpy-0.7.
|
34
|
-
topologicpy-0.7.
|
35
|
-
topologicpy-0.7.
|
36
|
-
topologicpy-0.7.
|
31
|
+
topologicpy/version.py,sha256=_XwNUtWN5b4vRt6q08wKnk-7iLHDZXyRKXWBhgnWyP4,23
|
32
|
+
topologicpy-0.7.74.dist-info/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
|
33
|
+
topologicpy-0.7.74.dist-info/METADATA,sha256=QQ-v5awljewQVDPhAMmdZdVtUcKzJh4LRiYGoCePtmk,10493
|
34
|
+
topologicpy-0.7.74.dist-info/WHEEL,sha256=R06PA3UVYHThwHvxuRWMqaGcr-PuniXahwjmQRFMEkY,91
|
35
|
+
topologicpy-0.7.74.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
|
36
|
+
topologicpy-0.7.74.dist-info/RECORD,,
|
File without changes
|
File without changes
|