topologicpy 0.7.42__py3-none-any.whl → 0.7.44__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.
@@ -20,7 +20,7 @@ import math
20
20
  from collections import OrderedDict
21
21
  import os
22
22
  from os.path import exists
23
- from datetime import datetime
23
+ from datetime import datetime, timezone
24
24
  import warnings
25
25
 
26
26
  try:
@@ -249,18 +249,24 @@ class EnergyModel:
249
249
  weatherFilePath = os.path.join(os.path.dirname(os.path.realpath(__file__)), "assets", "EnergyModel", "GBR_London.Gatwick.037760_IWEC.epw")
250
250
  designDayFilePath = os.path.join(os.path.dirname(os.path.realpath(__file__)), "assets", "EnergyModel", "GBR_London.Gatwick.037760_IWEC.ddy")
251
251
  translator = openstudio.osversion.VersionTranslator()
252
- osmFile = openstudio.openstudioutilitiescore.toPath(osModelPath)
253
- osModel = translator.loadModel(osmFile)
252
+ # DEBUGGING
253
+ #osmFile = openstudio.openstudioutilitiescore.toPath(osModelPath)
254
+ #osModel = translator.loadModel(osmFile)
255
+ osModel = translator.loadModel(osModelPath)
254
256
  if osModel.isNull():
255
257
  print("EnergyModel.ByTopology - Error: The openstudio model is null. Returning None.")
256
258
  return None
257
259
  else:
258
260
  osModel = osModel.get()
259
- osEPWFile = openstudio.openstudioutilitiesfiletypes.EpwFile.load(openstudio.toPath(weatherFilePath))
261
+ # DEBUGGING
262
+ #osEPWFile = openstudio.openstudioutilitiesfiletypes.EpwFile.load(openstudio.toPath(weatherFilePath))
263
+ osEPWFile = openstudio.openstudioutilitiesfiletypes.EpwFile.load(weatherFilePath)
260
264
  if osEPWFile.is_initialized():
261
265
  osEPWFile = osEPWFile.get()
262
266
  openstudio.model.WeatherFile.setWeatherFile(osModel, osEPWFile)
263
- ddyModel = openstudio.openstudioenergyplus.loadAndTranslateIdf(openstudio.toPath(designDayFilePath))
267
+ # DEBUGGING
268
+ #ddyModel = openstudio.openstudioenergyplus.loadAndTranslateIdf(openstudio.toPath(designDayFilePath))
269
+ ddyModel = openstudio.openstudioenergyplus.loadAndTranslateIdf(designDayFilePath)
264
270
  if ddyModel.is_initialized():
265
271
  ddyModel = ddyModel.get()
266
272
  for ddy in ddyModel.getObjectsByType(openstudio.IddObjectType("OS:SizingPeriod:DesignDay")):
@@ -578,8 +584,9 @@ class EnergyModel:
578
584
  if not overwrite and exists(path):
579
585
  print("EnergyModel.ExportToGBXML - Error: a file already exists at the specified path and overwrite is set to False. Returning None.")
580
586
  return None
581
-
582
- return openstudio.gbxml.GbXMLForwardTranslator().modelToGbXML(model, openstudio.openstudioutilitiescore.toPath(path))
587
+ # DEBUGGING
588
+ #return openstudio.gbxml.GbXMLForwardTranslator().modelToGbXML(model, openstudio.openstudioutilitiescore.toPath(path))
589
+ return openstudio.gbxml.GbXMLForwardTranslator().modelToGbXML(model, path)
583
590
 
584
591
 
585
592
  @staticmethod
@@ -629,8 +636,10 @@ class EnergyModel:
629
636
  print("EnergyModel.ExportToOSM - Error: a file already exists at the specified path and overwrite is set to False. Returning None.")
630
637
  return None
631
638
  osCondition = False
632
- osPath = openstudio.openstudioutilitiescore.toPath(path)
633
- osCondition = model.save(osPath, overwrite)
639
+ # DEBUGGING
640
+ #osPath = openstudio.openstudioutilitiescore.toPath(path)
641
+ #osCondition = model.save(osPath, overwrite)
642
+ osCondition = model.save(path, overwrite)
634
643
  return osCondition
635
644
 
636
645
  @staticmethod
@@ -821,7 +830,7 @@ class EnergyModel:
821
830
  if removeFiles:
822
831
  deleteOldFiles(outputFolder)
823
832
  pbar = tqdm(desc='Running Simulation', total=100, leave=False)
824
- utcnow = datetime.utcnow()
833
+ utcnow = datetime.now(timezone.utc)
825
834
  timestamp = utcnow.strftime("UTC-%Y-%m-%d-%H-%M-%S")
826
835
  if not outputFolder:
827
836
  home = os.path.expanduser('~')
@@ -831,28 +840,32 @@ class EnergyModel:
831
840
  os.mkdir(outputFolder)
832
841
  pbar.update(10)
833
842
  osmPath = os.path.join(outputFolder, model.getBuilding().name().get() + ".osm")
834
- model.save(openstudio.openstudioutilitiescore.toPath(osmPath), True)
843
+ # DEBUGGING
844
+ #model.save(openstudio.openstudioutilitiescore.toPath(osmPath), True)
845
+ model.save(osmPath, True)
835
846
  oswPath = os.path.join(outputFolder, model.getBuilding().name().get() + ".osw")
836
847
  pbar.update(20)
837
- #print("oswPath = "+oswPath)
838
848
  workflow = model.workflowJSON()
839
- workflow.setSeedFile(openstudio.openstudioutilitiescore.toPath(osmPath))
849
+ # DEBUGGING
850
+ #workflow.setSeedFile(openstudio.openstudioutilitiescore.toPath(osmPath))
851
+ workflow.setSeedFile(osmPath)
840
852
  pbar.update(30)
841
- #print("Seed File Set")
842
- workflow.setWeatherFile(openstudio.openstudioutilitiescore.toPath(weatherFilePath))
853
+ # DEBUGGING
854
+ #workflow.setWeatherFile(openstudio.openstudioutilitiescore.toPath(weatherFilePath))
855
+ workflow.setWeatherFile(weatherFilePath)
843
856
  pbar.update(40)
844
- #print("Weather File Set")
845
- workflow.saveAs(openstudio.openstudioutilitiescore.toPath(oswPath))
857
+ # DEBUGGING
858
+ #workflow.saveAs(openstudio.openstudioutilitiescore.toPath(oswPath))
859
+ workflow.saveAs(oswPath)
846
860
  pbar.update(50)
847
- #print("OSW File Saved")
848
861
  cmd = osBinaryPath+" run -w " + "\"" + oswPath + "\""
849
862
  pbar.update(60)
850
863
  os.system(cmd)
851
- #print("Simulation DONE")
852
864
  sqlPath = os.path.join(os.path.join(outputFolder,"run"), "eplusout.sql")
853
865
  pbar.update(100)
854
- #print("sqlPath = "+sqlPath)
855
- osSqlFile = openstudio.SqlFile(openstudio.openstudioutilitiescore.toPath(sqlPath))
866
+ # DEBUGGING
867
+ #osSqlFile = openstudio.SqlFile(openstudio.openstudioutilitiescore.toPath(sqlPath))
868
+ osSqlFile = openstudio.SqlFile(sqlPath)
856
869
  model.setSqlFile(osSqlFile)
857
870
  pbar.close()
858
871
  return model
topologicpy/Plotly.py CHANGED
@@ -994,8 +994,8 @@ class Plotly:
994
994
  minValue=None,
995
995
  maxValue=None,
996
996
  title="Confusion Matrix",
997
- xTitle = "Actual",
998
- yTitle = "Predicted",
997
+ xTitle = "Actual Categories",
998
+ yTitle = "Predicted Categories",
999
999
  width=950,
1000
1000
  height=500,
1001
1001
  showScale = True,
@@ -1022,9 +1022,9 @@ class Plotly:
1022
1022
  title : str , optional
1023
1023
  The desired title to display. The default is "Confusion Matrix".
1024
1024
  xTitle : str , optional
1025
- The desired X-axis title to display. The default is "Actual".
1025
+ The desired X-axis title to display. The default is "Actual Categories".
1026
1026
  yTitle : str , optional
1027
- The desired Y-axis title to display. The default is "Predicted".
1027
+ The desired Y-axis title to display. The default is "Predicted Categories".
1028
1028
  width : int , optional
1029
1029
  The desired width of the figure. The default is 950.
1030
1030
  height : int , optional
@@ -1051,7 +1051,11 @@ class Plotly:
1051
1051
  The desired top margin in pixels. The default is 40.
1052
1052
  marginBottom : int , optional
1053
1053
  The desired bottom margin in pixels. The default is 0.
1054
-
1054
+
1055
+ Returns
1056
+ -------
1057
+ plotly.Figure
1058
+ The created plotly figure.
1055
1059
  """
1056
1060
  try:
1057
1061
  import numpy as np
@@ -1265,7 +1269,158 @@ class Plotly:
1265
1269
  fig.update_xaxes( tickvals=xCategories)
1266
1270
  fig.update_yaxes( tickvals=yCategories)
1267
1271
  return fig
1272
+
1273
+ @staticmethod
1274
+ def FigureByCorrelation(actual,
1275
+ predicted,
1276
+ title="Correlation between Actual and Predicted Values",
1277
+ xTitle = "Actual Values",
1278
+ yTitle = "Predicted Values",
1279
+ dotColor = "blue",
1280
+ lineColor = "red",
1281
+ width=800,
1282
+ height=600,
1283
+ theme='default',
1284
+ backgroundColor='rgba(0,0,0,0)',
1285
+ marginLeft=0,
1286
+ marginRight=0,
1287
+ marginTop=40,
1288
+ marginBottom=0):
1289
+ """
1290
+ Returns a Plotly Figure showing the correlation between the input actual and predicted values. Actual values are displayed on the X-Axis, Predicted values are displayed on the Y-Axis.
1291
+
1292
+ Parameters
1293
+ ----------
1294
+ actual : list
1295
+ The actual values to display.
1296
+ predicted : list
1297
+ The predicted values to display.
1298
+ title : str , optional
1299
+ The desired title to display. The default is "Correlation between Actual and Predicted Values".
1300
+ xTitle : str , optional
1301
+ The desired X-axis title to display. The default is "Actual Values".
1302
+ yTitle : str , optional
1303
+ The desired Y-axis title to display. The default is "Predicted Values".
1304
+ dotColor : str , optional
1305
+ The desired color of the dots. This can be any plotly color string and may be specified as:
1306
+ - A hex string (e.g. '#ff0000')
1307
+ - An rgb/rgba string (e.g. 'rgb(255,0,0)')
1308
+ - An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
1309
+ - An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
1310
+ - A named CSS color.
1311
+ The default is 'blue'.
1312
+ lineColor : str , optional
1313
+ The desired color of the best fit line. This can be any plotly color string and may be specified as:
1314
+ - A hex string (e.g. '#ff0000')
1315
+ - An rgb/rgba string (e.g. 'rgb(255,0,0)')
1316
+ - An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
1317
+ - An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
1318
+ - A named CSS color.
1319
+ The default is 'red'.
1320
+ width : int , optional
1321
+ The desired width of the figure. The default is 800.
1322
+ height : int , optional
1323
+ The desired height of the figure. The default is 600.
1324
+ theme : str , optional
1325
+ The plotly color scheme to use. The options are "dark", "light", "default". The default is "default".
1326
+ backgroundColor : str , optional
1327
+ The desired background color. This can be any plotly color string and may be specified as:
1328
+ - A hex string (e.g. '#ff0000')
1329
+ - An rgb/rgba string (e.g. 'rgb(255,0,0)')
1330
+ - An hsl/hsla string (e.g. 'hsl(0,100%,50%)')
1331
+ - An hsv/hsva string (e.g. 'hsv(0,100%,100%)')
1332
+ - A named CSS color.
1333
+ The default is 'rgba(0,0,0,0)' (transparent).
1334
+ marginLeft : int , optional
1335
+ The desired left margin in pixels. The default is 0.
1336
+ marginRight : int , optional
1337
+ The desired right margin in pixels. The default is 0.
1338
+ marginTop : int , optional
1339
+ The desired top margin in pixels. The default is 40.
1340
+ marginBottom : int , optional
1341
+ The desired bottom margin in pixels. The default is 0.
1342
+
1343
+ Returns
1344
+ -------
1345
+ plotly.Figure
1346
+ The created plotly figure.
1268
1347
 
1348
+ """
1349
+
1350
+ import numpy as np
1351
+ import plotly.graph_objs as go
1352
+ from sklearn.linear_model import LinearRegression
1353
+ import plotly.io as pio
1354
+ actual_values = np.array(actual)
1355
+ predicted_values = np.array(predicted)
1356
+
1357
+ # Validate the theme input
1358
+ if theme == 'light':
1359
+ pio.templates.default = "plotly_white"
1360
+ backgroundColor='white'
1361
+ elif theme == 'dark':
1362
+ pio.templates.default = "plotly_dark"
1363
+ backgroundColor='black'
1364
+ else:
1365
+ pio.templates.default = None # Use default Plotly theme
1366
+
1367
+ # Calculate the best-fit line using linear regression
1368
+ regressor = LinearRegression()
1369
+ regressor.fit(np.array(actual_values).reshape(-1, 1), np.array(predicted_values))
1370
+ line = regressor.predict(np.array(actual_values).reshape(-1, 1))
1371
+
1372
+ # Determine the range and tick step
1373
+ combined_values = np.concatenate([actual_values, predicted_values])
1374
+ min_value = np.min(combined_values)
1375
+ max_value = np.max(combined_values)
1376
+ margin = 0.1 * (max_value - min_value)
1377
+ tick_range = [min_value - margin, max_value + margin]
1378
+ tick_step = (max_value - min_value) / 10 # Adjust as needed for a different number of ticks
1379
+
1380
+ # Create the scatter plot for actual vs predicted values
1381
+ scatter_trace = go.Scatter(
1382
+ x=actual_values,
1383
+ y=predicted_values,
1384
+ mode='markers',
1385
+ name='Actual vs. Predicted',
1386
+ marker=dict(color=dotColor)
1387
+ )
1388
+
1389
+ # Create the line of best fit
1390
+ line_trace = go.Scatter(
1391
+ x=actual_values,
1392
+ y=line,
1393
+ mode='lines',
1394
+ name='Best Fit Line',
1395
+ line=dict(color=lineColor)
1396
+ )
1397
+
1398
+ # Create the 45-degree line
1399
+ line_45_trace = go.Scatter(
1400
+ x=tick_range,
1401
+ y=tick_range,
1402
+ mode='lines',
1403
+ name='45-Degree Line',
1404
+ line=dict(color='green', dash='dash')
1405
+ )
1406
+
1407
+ # Combine the traces into a single figure
1408
+ layout = {
1409
+ "title": title,
1410
+ "width": width,
1411
+ "height": height,
1412
+ "xaxis": {"title": xTitle, "range":tick_range, "dtick":tick_step},
1413
+ "yaxis": {"title": yTitle, "range":tick_range, "dtick":tick_step},
1414
+ "showlegend": True,
1415
+ "paper_bgcolor": backgroundColor,
1416
+ "plot_bgcolor": backgroundColor,
1417
+ "margin":dict(l=marginLeft, r=marginRight, t=marginTop, b=marginBottom)
1418
+ }
1419
+
1420
+ fig = go.Figure(data=[scatter_trace, line_trace, line_45_trace], layout=layout)
1421
+
1422
+ return fig
1423
+
1269
1424
  @staticmethod
1270
1425
  def FigureByDataFrame(dataFrame,
1271
1426
  labels=[],
topologicpy/PyG.py CHANGED
@@ -30,11 +30,27 @@ from sklearn.metrics import accuracy_score
30
30
  from tqdm.auto import tqdm
31
31
  import gc
32
32
 
33
+ import torch
34
+ from torch.utils.data import Dataset
35
+
36
+ import torch
37
+ from torch.utils.data import Dataset
33
38
 
34
39
  class CustomGraphDataset(Dataset):
35
- def __init__(self, root, node_level=False, graph_level=True, node_attr_key='feat',
36
- edge_attr_key='feat', transform=None, pre_transform=None):
37
- super(CustomGraphDataset, self).__init__(root, transform, pre_transform)
40
+ def __init__(self, root=None, data_list=None, indices=None, node_level=False, graph_level=True,
41
+ node_attr_key='feat', edge_attr_key='feat'):
42
+ """
43
+ Initializes the CustomGraphDataset.
44
+
45
+ Parameters:
46
+ - root: Root directory of the dataset (used only if data_list is None)
47
+ - data_list: List of preprocessed data objects (used if provided)
48
+ - indices: List of indices to select a subset of the data
49
+ - node_level: Boolean flag indicating if the dataset is node-level
50
+ - graph_level: Boolean flag indicating if the dataset is graph-level
51
+ - node_attr_key: Key for node attributes
52
+ - edge_attr_key: Key for edge attributes
53
+ """
38
54
  assert not (node_level and graph_level), "Both node_level and graph_level cannot be True at the same time"
39
55
  assert node_level or graph_level, "Both node_level and graph_level cannot be False at the same time"
40
56
 
@@ -43,15 +59,20 @@ class CustomGraphDataset(Dataset):
43
59
  self.node_attr_key = node_attr_key
44
60
  self.edge_attr_key = edge_attr_key
45
61
 
46
- self.graph_df = pd.read_csv(os.path.join(root, 'graphs.csv'))
47
- self.nodes_df = pd.read_csv(os.path.join(root, 'nodes.csv'))
48
- self.edges_df = pd.read_csv(os.path.join(root, 'edges.csv'))
49
-
50
- self.data_list = self.process_all()
62
+ if data_list is not None:
63
+ self.data_list = data_list # Use the provided data list
64
+ elif root is not None:
65
+ # Load and process data from root directory if data_list is not provided
66
+ self.graph_df = pd.read_csv(os.path.join(root, 'graphs.csv'))
67
+ self.nodes_df = pd.read_csv(os.path.join(root, 'nodes.csv'))
68
+ self.edges_df = pd.read_csv(os.path.join(root, 'edges.csv'))
69
+ self.data_list = self.process_all()
70
+ else:
71
+ raise ValueError("Either a root directory or a data_list must be provided.")
51
72
 
52
- @property
53
- def raw_file_names(self):
54
- return ['graphs.csv', 'nodes.csv', 'edges.csv']
73
+ # Filter data_list based on indices if provided
74
+ if indices is not None:
75
+ self.data_list = [self.data_list[i] for i in indices]
55
76
 
56
77
  def process_all(self):
57
78
  data_list = []
@@ -74,9 +95,24 @@ class CustomGraphDataset(Dataset):
74
95
  edge_attr = None
75
96
 
76
97
  if self.graph_level:
77
- y = torch.tensor([self.graph_df[self.graph_df['graph_id'] == graph_id]['label'].values[0]], dtype=torch.long)
98
+ label_value = self.graph_df[self.graph_df['graph_id'] == graph_id]['label'].values[0]
99
+
100
+ if isinstance(label_value, int):
101
+ y = torch.tensor([label_value], dtype=torch.long)
102
+ elif isinstance(label_value, float):
103
+ y = torch.tensor([label_value], dtype=torch.float)
104
+ else:
105
+ raise ValueError(f"Unexpected label type: {type(label_value)}. Expected int or float.")
106
+
78
107
  elif self.node_level:
79
- y = torch.tensor(graph_nodes['label'].values, dtype=torch.long)
108
+ label_values = graph_nodes['label'].values
109
+
110
+ if issubclass(label_values.dtype.type, int):
111
+ y = torch.tensor(label_values, dtype=torch.long)
112
+ elif issubclass(label_values.dtype.type, float):
113
+ y = torch.tensor(label_values, dtype=torch.float)
114
+ else:
115
+ raise ValueError(f"Unexpected label types: {label_values.dtype}. Expected int or float.")
80
116
 
81
117
  data = Data(x=x, edge_index=edge_index, y=y)
82
118
  if edge_attr is not None:
@@ -86,14 +122,103 @@ class CustomGraphDataset(Dataset):
86
122
 
87
123
  return data_list
88
124
 
89
- def len(self):
125
+ def __len__(self):
90
126
  return len(self.data_list)
91
127
 
92
- def get(self, idx):
128
+ def __getitem__(self, idx):
93
129
  return self.data_list[idx]
94
130
 
95
- def __getitem__(self, idx):
96
- return self.get(idx)
131
+
132
+
133
+
134
+ # class CustomGraphDataset(Dataset):
135
+ # def __init__(self, root, node_level=False, graph_level=True, node_attr_key='feat',
136
+ # edge_attr_key='feat', transform=None, pre_transform=None):
137
+ # super(CustomGraphDataset, self).__init__(root, transform, pre_transform)
138
+ # assert not (node_level and graph_level), "Both node_level and graph_level cannot be True at the same time"
139
+ # assert node_level or graph_level, "Both node_level and graph_level cannot be False at the same time"
140
+
141
+ # self.node_level = node_level
142
+ # self.graph_level = graph_level
143
+ # self.node_attr_key = node_attr_key
144
+ # self.edge_attr_key = edge_attr_key
145
+
146
+ # self.graph_df = pd.read_csv(os.path.join(root, 'graphs.csv'))
147
+ # self.nodes_df = pd.read_csv(os.path.join(root, 'nodes.csv'))
148
+ # self.edges_df = pd.read_csv(os.path.join(root, 'edges.csv'))
149
+
150
+ # self.data_list = self.process_all()
151
+
152
+ # @property
153
+ # def raw_file_names(self):
154
+ # return ['graphs.csv', 'nodes.csv', 'edges.csv']
155
+
156
+ # def process_all(self):
157
+ # data_list = []
158
+ # for graph_id in self.graph_df['graph_id'].unique():
159
+ # graph_nodes = self.nodes_df[self.nodes_df['graph_id'] == graph_id]
160
+ # graph_edges = self.edges_df[self.edges_df['graph_id'] == graph_id]
161
+
162
+ # if self.node_attr_key in graph_nodes.columns and not graph_nodes[self.node_attr_key].isnull().all():
163
+ # x = torch.tensor(graph_nodes[self.node_attr_key].values.tolist(), dtype=torch.float)
164
+ # if x.ndim == 1:
165
+ # x = x.unsqueeze(1) # Ensure x has shape [num_nodes, *]
166
+ # else:
167
+ # x = None
168
+
169
+ # edge_index = torch.tensor(graph_edges[['src_id', 'dst_id']].values.T, dtype=torch.long)
170
+
171
+ # if self.edge_attr_key in graph_edges.columns and not graph_edges[self.edge_attr_key].isnull().all():
172
+ # edge_attr = torch.tensor(graph_edges[self.edge_attr_key].values.tolist(), dtype=torch.float)
173
+ # else:
174
+ # edge_attr = None
175
+
176
+
177
+
178
+ # if self.graph_level:
179
+ # label_value = self.graph_df[self.graph_df['graph_id'] == graph_id]['label'].values[0]
180
+
181
+ # # Check if the label is an integer or a float and cast accordingly
182
+ # if isinstance(label_value, int):
183
+ # y = torch.tensor([label_value], dtype=torch.long)
184
+ # elif isinstance(label_value, float):
185
+ # y = torch.tensor([label_value], dtype=torch.float)
186
+ # else:
187
+ # raise ValueError(f"Unexpected label type: {type(label_value)}. Expected int or float.")
188
+
189
+ # elif self.node_level:
190
+ # label_values = graph_nodes['label'].values
191
+
192
+ # # Check if the labels are integers or floats and cast accordingly
193
+ # if issubclass(label_values.dtype.type, int):
194
+ # y = torch.tensor(label_values, dtype=torch.long)
195
+ # elif issubclass(label_values.dtype.type, float):
196
+ # y = torch.tensor(label_values, dtype=torch.float)
197
+ # else:
198
+ # raise ValueError(f"Unexpected label types: {label_values.dtype}. Expected int or float.")
199
+
200
+
201
+ # # if self.graph_level:
202
+ # # y = torch.tensor([self.graph_df[self.graph_df['graph_id'] == graph_id]['label'].values[0]], dtype=torch.long)
203
+ # # elif self.node_level:
204
+ # # y = torch.tensor(graph_nodes['label'].values, dtype=torch.long)
205
+
206
+ # data = Data(x=x, edge_index=edge_index, y=y)
207
+ # if edge_attr is not None:
208
+ # data.edge_attr = edge_attr
209
+
210
+ # data_list.append(data)
211
+
212
+ # return data_list
213
+
214
+ # def len(self):
215
+ # return len(self.data_list)
216
+
217
+ # def get(self, idx):
218
+ # return self.data_list[idx]
219
+
220
+ # def __getitem__(self, idx):
221
+ # return self.get(idx)
97
222
 
98
223
  class _Hparams:
99
224
  def __init__(self, model_type="ClassifierHoldout", optimizer_str="Adam", amsgrad=False, betas=(0.9, 0.999), eps=1e-6, lr=0.001, lr_decay= 0, maximize=False, rho=0.9, weight_decay=0, cv_type="Holdout", split=[0.8,0.1, 0.1], k_folds=5, hl_widths=[32], conv_layer_type='SAGEConv', pooling="AvgPooling", batch_size=32, epochs=1,
@@ -350,7 +475,8 @@ class _GraphRegressorKFold:
350
475
 
351
476
  def _initialize_model(self, hparams, dataset):
352
477
  if hparams.conv_layer_type.lower() == 'sageconv':
353
- return _SAGEConv(dataset.num_node_features, hparams.hl_widths, 1, hparams.pooling).to(self.device)
478
+ return _SAGEConv(dataset[0].num_node_features, hparams.hl_widths, 1, hparams.pooling).to(self.device)
479
+ #return _SAGEConv(dataset.num_node_features, hparams.hl_widths, 1, hparams.pooling).to(self.device)
354
480
  else:
355
481
  raise NotImplementedError
356
482
 
@@ -1411,6 +1537,83 @@ class PyG:
1411
1537
 
1412
1538
  return CustomGraphDataset(root=path, node_level=node_level, graph_level=graph_level, node_attr_key=nodeATTRKey, edge_attr_key=edgeATTRKey)
1413
1539
 
1540
+ @staticmethod
1541
+ def DatasetGraphLabels(dataset, graphLabelHeader="label"):
1542
+ """
1543
+ Returns the labels of the graphs in the input dataset
1544
+
1545
+ Parameters
1546
+ ----------
1547
+ dataset : CustomDataset
1548
+ The input dataset
1549
+ graphLabelHeader: str , optional
1550
+ The key string under which the graph labels are stored. The default is "label".
1551
+
1552
+ Returns
1553
+ -------
1554
+ list
1555
+ The list of graph labels.
1556
+ """
1557
+ import torch
1558
+
1559
+ graph_labels = []
1560
+ for g in dataset:
1561
+ # Get the label of the graph
1562
+ label = g.y
1563
+ graph_labels.append(label.item())
1564
+ return graph_labels
1565
+
1566
+ @staticmethod
1567
+ def DatasetSplit(dataset, split=[0.8,0.1,0.1], shuffle=True, randomState=42):
1568
+ """
1569
+ Splits the dataset into three subsets.
1570
+
1571
+ Parameters
1572
+ ----------
1573
+ dataset : CustomDataset
1574
+ The input dataset
1575
+ split: list , optional
1576
+ The list of ratios. This list must be made out of three numbers adding to 1.
1577
+ shuffle: boolean , optional
1578
+ If set to True, the subsets are created from random indices. Otherwise, they are split sequentially. The default is True.
1579
+ randomState : int , optional
1580
+ The random seed to use for reproducibility. The default is 42.
1581
+
1582
+ Returns
1583
+ -------
1584
+ list
1585
+ The list of three subset datasets.
1586
+ """
1587
+
1588
+ import torch
1589
+ from torch.utils.data import random_split
1590
+ train_ratio, val_ratio, test_ratio = split
1591
+ assert abs(train_ratio + val_ratio + test_ratio - 1.0) < 1e-6, "Ratios must add up to 1."
1592
+
1593
+ # Calculate the number of samples for each split
1594
+ dataset_len = len(dataset)
1595
+ train_len = int(train_ratio * dataset_len)
1596
+ val_len = int(val_ratio * dataset_len)
1597
+ test_len = dataset_len - train_len - val_len # Ensure it adds up correctly
1598
+
1599
+ ## Generate indices for the split
1600
+ indices = list(range(dataset_len))
1601
+ if shuffle:
1602
+ torch.manual_seed(randomState) # For reproducibility
1603
+ indices = torch.randperm(dataset_len).tolist() # Shuffled indices
1604
+
1605
+ # Create splits
1606
+ train_indices = indices[:train_len]
1607
+ val_indices = indices[train_len:train_len + val_len]
1608
+ test_indices = indices[train_len + val_len:train_len + val_len + test_len]
1609
+
1610
+ # Create new instances of CustomGraphDataset using the indices
1611
+ train_dataset = CustomGraphDataset(data_list=dataset.data_list, indices=train_indices)
1612
+ val_dataset = CustomGraphDataset(data_list=dataset.data_list, indices=val_indices)
1613
+ test_dataset = CustomGraphDataset(data_list=dataset.data_list, indices=test_indices)
1614
+
1615
+ return train_dataset, val_dataset, test_dataset
1616
+
1414
1617
  @staticmethod
1415
1618
  def Optimizer(name="Adam", amsgrad=True, betas=(0.9,0.999), eps=0.000001, lr=0.001, maximize=False, weightDecay=0.0, rho=0.9, lr_decay=0.0):
1416
1619
  """
@@ -1529,11 +1732,11 @@ class PyG:
1529
1732
  ----------
1530
1733
  hparams : HParams
1531
1734
  The input hyperparameters
1532
- trainingDataset : DGLDataset
1735
+ trainingDataset : CustomDataset
1533
1736
  The input training dataset.
1534
- validationDataset : DGLDataset
1737
+ validationDataset : CustomDataset
1535
1738
  The input validation dataset. If not specified, a portion of the trainingDataset will be used for validation according the to the split list as specified in the hyper-parameters.
1536
- testingDataset : DGLDataset
1739
+ testingDataset : CustomDataset
1537
1740
  The input testing dataset. If not specified, a portion of the trainingDataset will be used for testing according the to the split list as specified in the hyper-parameters.
1538
1741
 
1539
1742
  Returns
@@ -1635,13 +1838,13 @@ class PyG:
1635
1838
  import os
1636
1839
 
1637
1840
  if model == None:
1638
- print("DGL.ModelSave - Error: The input model parameter is invalid. Returning None.")
1841
+ print("PyG.ModelSave - Error: The input model parameter is invalid. Returning None.")
1639
1842
  return None
1640
1843
  if path == None:
1641
- print("DGL.ModelSave - Error: The input path parameter is invalid. Returning None.")
1844
+ print("PyG.ModelSave - Error: The input path parameter is invalid. Returning None.")
1642
1845
  return None
1643
1846
  if not overwrite and os.path.exists(path):
1644
- print("DGL.ModelSave - Error: a file already exists at the specified path and overwrite is set to False. Returning None.")
1847
+ print("PyG.ModelSave - Error: a file already exists at the specified path and overwrite is set to False. Returning None.")
1645
1848
  return None
1646
1849
  if overwrite and os.path.exists(path):
1647
1850
  os.remove(path)
@@ -1893,7 +2096,7 @@ class PyG:
1893
2096
  from sklearn import metrics
1894
2097
  from sklearn.metrics import accuracy_score
1895
2098
  except:
1896
- print("DGL - Installing required scikit-learn (sklearn) library.")
2099
+ print("PyG - Installing required scikit-learn (sklearn) library.")
1897
2100
  try:
1898
2101
  os.system("pip install scikit-learn")
1899
2102
  except:
@@ -1901,19 +2104,19 @@ class PyG:
1901
2104
  try:
1902
2105
  from sklearn import metrics
1903
2106
  from sklearn.metrics import accuracy_score
1904
- print("DGL - scikit-learn (sklearn) library installed correctly.")
2107
+ print("PyG - scikit-learn (sklearn) library installed correctly.")
1905
2108
  except:
1906
- warnings.warn("DGL - Error: Could not import scikit-learn (sklearn). Please try to install scikit-learn manually. Returning None.")
2109
+ warnings.warn("PyG - Error: Could not import scikit-learn (sklearn). Please try to install scikit-learn manually. Returning None.")
1907
2110
  return None
1908
2111
 
1909
2112
  if not isinstance(actual, list):
1910
- print("DGL.ConfusionMatrix - ERROR: The actual input is not a list. Returning None")
2113
+ print("PyG.ConfusionMatrix - ERROR: The actual input is not a list. Returning None")
1911
2114
  return None
1912
2115
  if not isinstance(predicted, list):
1913
- print("DGL.ConfusionMatrix - ERROR: The predicted input is not a list. Returning None")
2116
+ print("PyG.ConfusionMatrix - ERROR: The predicted input is not a list. Returning None")
1914
2117
  return None
1915
2118
  if len(actual) != len(predicted):
1916
- print("DGL.ConfusionMatrix - ERROR: The two input lists do not have the same length. Returning None")
2119
+ print("PyG.ConfusionMatrix - ERROR: The two input lists do not have the same length. Returning None")
1917
2120
  return None
1918
2121
  if normalize:
1919
2122
  cm = np.transpose(metrics.confusion_matrix(y_true=actual, y_pred=predicted, normalize="true"))
@@ -2065,3 +2268,58 @@ class PyG:
2065
2268
  size = len(predicted)
2066
2269
 
2067
2270
  return {"mse": mse, "size": size}
2271
+
2272
+ @staticmethod
2273
+ def Performance(actual, predicted, mantissa: int = 6):
2274
+ """
2275
+ Computes regression model performance measures. This is to be used only with regression not with classification.
2276
+
2277
+ Parameters
2278
+ ----------
2279
+ actual : list
2280
+ The input list of actual values.
2281
+ predicted : list
2282
+ The input list of predicted values.
2283
+ mantissa : int , optional
2284
+ The desired length of the mantissa. The default is 6.
2285
+
2286
+ Returns
2287
+ -------
2288
+ dict
2289
+ The dictionary containing the performance measures. The keys in the dictionary are: 'mae', 'mape', 'mse', 'r', 'r2', 'rmse'.
2290
+ """
2291
+
2292
+ if not isinstance(actual, list):
2293
+ print("PyG.Performance - ERROR: The actual input is not a list. Returning None")
2294
+ return None
2295
+ if not isinstance(predicted, list):
2296
+ print("PyG.Performance - ERROR: The predicted input is not a list. Returning None")
2297
+ return None
2298
+ if not (len(actual) == len(predicted)):
2299
+ print("PyG.Performance - ERROR: The actual and predicted input lists have different lengths. Returning None")
2300
+ return None
2301
+
2302
+ predicted = np.array(predicted)
2303
+ actual = np.array(actual)
2304
+
2305
+ mae = np.mean(np.abs(predicted - actual))
2306
+ mape = np.mean(np.abs((actual - predicted) / actual))*100
2307
+ mse = np.mean((predicted - actual)**2)
2308
+ correlation_matrix = np.corrcoef(predicted, actual)
2309
+ r = correlation_matrix[0, 1]
2310
+ r2 = r**2
2311
+ absolute_errors = np.abs(predicted - actual)
2312
+ mean_actual = np.mean(actual)
2313
+ if mean_actual == 0:
2314
+ rae = None
2315
+ else:
2316
+ rae = np.mean(absolute_errors) / mean_actual
2317
+ rmse = np.sqrt(mse)
2318
+ return {'mae': round(mae, mantissa),
2319
+ 'mape': round(mape, mantissa),
2320
+ 'mse': round(mse, mantissa),
2321
+ 'r': round(r, mantissa),
2322
+ 'r2': round(r2, mantissa),
2323
+ 'rae': round(rae, mantissa),
2324
+ 'rmse': round(rmse, mantissa)
2325
+ }
topologicpy/Shell.py CHANGED
@@ -1056,6 +1056,110 @@ class Shell():
1056
1056
  """
1057
1057
  return shell.IsClosed()
1058
1058
 
1059
+
1060
+ @staticmethod
1061
+ def ParabolicSurface(origin= None, focalLength=0.125, width: float = 1, length: float = 1, uSides: int = 16, vSides: int = 16,
1062
+ direction: list = [0, 0, 1], placement: str ="center", mantissa: int = 6, tolerance: float = 0.0001):
1063
+ """
1064
+ Creates a parabolic surface.
1065
+
1066
+ Parameters
1067
+ ----------
1068
+ origin : topologic_core.Vertex , optional
1069
+ The origin location of the parabolic surface. The default is None which results in the parabolic surface being placed at (0, 0, 0).
1070
+ focalLength : float , optional
1071
+ The focal length of the parabola. The default is 1.
1072
+ width : float , optional
1073
+ The width of the parabolic surface. The default is 1.
1074
+ length : float , optional
1075
+ The length of the parabolic surface. The default is 1.
1076
+ uSides : int , optional
1077
+ The number of sides along the width. The default is 16.
1078
+ vSides : int , optional
1079
+ The number of sides along the length. The default is 16.
1080
+ direction : list , optional
1081
+ The vector representing the up direction of the parabolic surface. The default is [0, 0, 1].
1082
+ placement : str , optional
1083
+ The description of the placement of the origin of the parabolic surface. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "center".
1084
+ mantissa : int , optional
1085
+ The desired length of the mantissa. The default is 6.
1086
+ tolerance : float , optional
1087
+ The desired tolerance. The default is 0.0001.
1088
+
1089
+ Returns
1090
+ -------
1091
+ topologic_core.Shell
1092
+ The created parabolic surface.
1093
+
1094
+ """
1095
+ from topologicpy.Vertex import Vertex
1096
+ from topologicpy.Wire import Wire
1097
+ from topologicpy.Face import Face
1098
+ from topologicpy.Topology import Topology
1099
+
1100
+ def create_triangulated_mesh(vertices, uSides, vSides):
1101
+ faces = []
1102
+
1103
+ # Iterate over the grid of vertices to form triangular faces
1104
+ for i in range(uSides - 1):
1105
+ for j in range(vSides - 1):
1106
+ # Get the indices of the vertices forming the current grid cell
1107
+ v1 = vertices[i * vSides + j]
1108
+ v2 = vertices[i * vSides + (j + 1)]
1109
+ v3 = vertices[(i + 1) * vSides + j]
1110
+ v4 = vertices[(i + 1) * vSides + (j + 1)]
1111
+
1112
+ # Create two triangles for each grid cell
1113
+ # Triangle 1: (v1, v2, v3)
1114
+ wire1 = Wire.ByVertices([v1, v2, v3])
1115
+ face1 = Face.ByWire(wire1)
1116
+ faces.append(face1)
1117
+
1118
+ # Triangle 2: (v3, v2, v4)
1119
+ wire2 = Wire.ByVertices([v3, v2, v4])
1120
+ face2 = Face.ByWire(wire2)
1121
+ faces.append(face2)
1122
+
1123
+ # Create the mesh (Shell) from the list of faces
1124
+ mesh = Shell.ByFaces(faces)
1125
+ return mesh
1126
+
1127
+ if not Topology.IsInstance(origin, "Vertex"):
1128
+ origin = Vertex.Origin()
1129
+
1130
+ x_range = [-width*0.5, width*0.5]
1131
+ y_range = [-length*0.5, length*0.5]
1132
+ # Generate x and y values
1133
+ x_values = [x_range[0] + i * (x_range[1] - x_range[0]) / (uSides - 1) for i in range(uSides)]
1134
+ y_values = [y_range[0] + i * (y_range[1] - y_range[0]) / (vSides - 1) for i in range(vSides)]
1135
+
1136
+ # Create the grid and calculate Z values
1137
+ vertices = []
1138
+
1139
+ for x in x_values:
1140
+ for y in y_values:
1141
+ z = ((x)**2 + (y)**2) / (4 * focalLength)
1142
+ vertices.append(Vertex.ByCoordinates(x, y, z))
1143
+
1144
+ mesh = create_triangulated_mesh(vertices=vertices, uSides=uSides, vSides=vSides)
1145
+ if not placement.lower() == "bottom":
1146
+ x_list = [Vertex.X(v) for v in vertices]
1147
+ y_list = [Vertex.Y(v) for v in vertices]
1148
+ z_list = [Vertex.Z(v) for v in vertices]
1149
+ x_list.sort()
1150
+ y_list.sort()
1151
+ z_list.sort()
1152
+ width = abs(x_list[-1] - x_list[0])
1153
+ length = abs(y_list[-1] - y_list[0])
1154
+ height = abs(z_list[-1] - z_list[0])
1155
+ if placement.lower() == "center":
1156
+ mesh = Topology.Translate(mesh, 0, 0, -height*0.5)
1157
+ elif placement.lower() == "lowerleft":
1158
+ mesh = Topology.Translate(mesh, width*0.5, length*0.5, 0)
1159
+
1160
+ mesh = Topology.Orient(mesh, origin=origin, dirA=[0, 0, 1], dirB=direction, tolerance=tolerance)
1161
+ return mesh
1162
+
1059
1163
  @staticmethod
1060
1164
  def Pie(origin= None, radiusA: float = 0.5, radiusB: float = 0.0, sides: int = 32, rings: int = 1, fromAngle: float = 0.0, toAngle: float = 360.0, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001):
1061
1165
  """
topologicpy/Topology.py CHANGED
@@ -2627,6 +2627,7 @@ class Topology():
2627
2627
  if ap != None:
2628
2628
  apertures.append(ap)
2629
2629
  context = Context.ByTopologyParameters(topology, u=0.5, v=0.5, w=0.5)
2630
+
2630
2631
  for ap in apertures:
2631
2632
  _ = Aperture.ByTopologyContext(ap, context)
2632
2633
  return topology
@@ -6308,7 +6309,7 @@ class Topology():
6308
6309
  rot_vertices = [Vertex.ByCoordinates(rot_v) for rot_v in rot_vertices]
6309
6310
  new_topology = Topology.ReplaceVertices(topology, verticesA=Topology.Vertices(topology), verticesB=rot_vertices)
6310
6311
  new_topology = Topology.SelfMerge(new_topology, tolerance=tolerance)
6311
- if len(Dictionary.Keys(d) > 0):
6312
+ if len(Dictionary.Keys(d)) > 0:
6312
6313
  new_topology = Topology.SetDictionary(new_topology, d)
6313
6314
  return new_topology
6314
6315
  if len(Dictionary.Keys(d)) > 0:
@@ -7875,7 +7876,6 @@ class Topology():
7875
7876
  if Topology.IsInstance(topology, "Cell"):
7876
7877
  sinkCells = [topology]
7877
7878
  elif hidimSink >= Topology.TypeID("Cell"):
7878
- print("Transfering Dictionaries to Cells")
7879
7879
  sinkCells = Topology.Cells(topology)
7880
7880
  _ = Topology.TransferDictionaries(selectors, sinkCells, tolerance=tolerance, numWorkers=numWorkers)
7881
7881
  return topology
topologicpy/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = '0.7.42'
1
+ __version__ = '0.7.44'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: topologicpy
3
- Version: 0.7.42
3
+ Version: 0.7.44
4
4
  Summary: An Advanced Spatial Modelling and Analysis Software Library for Architecture, Engineering, and Construction.
5
5
  Author-email: Wassim Jabi <wassim.jabi@gmail.com>
6
6
  License: MIT License
@@ -8,7 +8,7 @@ topologicpy/Context.py,sha256=ppApYKngZZCQBFWaxIMi2z2dokY23c935IDCBosxDAE,3055
8
8
  topologicpy/DGL.py,sha256=Dd6O08D-vSxpjHYgKm45JpKiaeGvWlg1BRMzYMAXGNc,138991
9
9
  topologicpy/Dictionary.py,sha256=KqJ29YyE23Y3Xc6XmKLSCZXRfBvm-OEOxlMZ4dt-rfM,27094
10
10
  topologicpy/Edge.py,sha256=vhYHkobSLGSWV-oe2oJFFDobqFToDyb7s71yQ840AAA,65166
11
- topologicpy/EnergyModel.py,sha256=ni0H1JgvLl1-q90yK9Sm1qj5P1fTuidlimEIcwuj6qE,53287
11
+ topologicpy/EnergyModel.py,sha256=XcCP55VW5WHDIIKcURijmBOZEgNUDEn_V9h5EejkntA,53876
12
12
  topologicpy/Face.py,sha256=d1Im4ogxuaVmZyb6hgv5muwQwahLtGzOar_a3og1a_I,115376
13
13
  topologicpy/Graph.py,sha256=p3VoVH6yygNV2wwzpsi4tm6yOfwRbtmsbnsAu2opLjQ,393517
14
14
  topologicpy/Grid.py,sha256=3-sn7CHWGcXk18XCnHjsUttNJTWwmN63g_Insj__p04,18218
@@ -16,20 +16,20 @@ topologicpy/Helper.py,sha256=i-AfI29NMsZXBaymjilfvxQbuS3wpYbpPw4RWu1YCHs,16358
16
16
  topologicpy/Honeybee.py,sha256=vcBECJlgWVjNNdD9ZmjNik_pA1Y_ZNoOorsQb2CiyGA,21965
17
17
  topologicpy/Matrix.py,sha256=umgR7An919-wGInXJ1wpqnoQ2jCPdyMe2rcWTZ16upk,8079
18
18
  topologicpy/Neo4j.py,sha256=YvtF7RYUMATEZ8iHwFwK_MOxEDyARby2DTI2CCK6-cI,19694
19
- topologicpy/Plotly.py,sha256=qMhBMAYoNMsc-cKdNpqM2p9rqAVXWvBNTzmTKw7iU_0,98963
19
+ topologicpy/Plotly.py,sha256=U6Lo7hyDoStRKQXqlhb2LM-rR_bfBulxetRT2wMBmhI,105391
20
20
  topologicpy/Polyskel.py,sha256=EFsuh2EwQJGPLiFUjvtXmAwdX-A4r_DxP5hF7Qd3PaU,19829
21
- topologicpy/PyG.py,sha256=mDEYYGKv-q1B7GI_J7b3pAbJhF0hqMnITZNMzhHycg4,96097
22
- topologicpy/Shell.py,sha256=etLWt2VsKOYE-2N0InKdQxLDHEZWsp59DeToSggrjF0,79914
21
+ topologicpy/PyG.py,sha256=0yeECsMz-dqhhZSv52s_xPCO_3BcEXUK7z1YFDN9qoo,106987
22
+ topologicpy/Shell.py,sha256=NZyHYTvV0pXx18AoMbVs8y-7if8teETItv5ZOJq6gzE,84672
23
23
  topologicpy/Speckle.py,sha256=rUS6PCaxIjEF5_fUruxvMH47FMKg-ohcoU0qAUb-yNM,14267
24
24
  topologicpy/Sun.py,sha256=InnKtX8eKwtAgcScuABH6yp0ljmWh5m_fDR4-n3jJMY,36869
25
- topologicpy/Topology.py,sha256=5KGQ8jSiURMk9syoMLc3mrIONCfj_pfvCfHTKgPcLOM,366047
25
+ topologicpy/Topology.py,sha256=RFZPKx15qM53skLavdMwL_YnJkDIinOnz3CLAY5OsWE,366001
26
26
  topologicpy/Vector.py,sha256=WQQUbwrg7VKImtxuBUi2i-FRiPT77WlrzLP05gdXKM8,33079
27
27
  topologicpy/Vertex.py,sha256=EQdVYHmW85_pZdHZB3N8pEi0GiadCCkF3z_oqohA7B0,71161
28
28
  topologicpy/Wire.py,sha256=9EJE0Iq3nGz5X7Suy6xxjmuOpfV49By6WH98UAL_7m4,153532
29
29
  topologicpy/__init__.py,sha256=D7ky87CAQMiS2KE6YLvcTLkTgA2PY7rASe6Z23pjp9k,872
30
- topologicpy/version.py,sha256=UWZPE9jgOKgWiu1NjdF3GjtgYjFTqvDvq_t-y5kjb-s,23
31
- topologicpy-0.7.42.dist-info/LICENSE,sha256=BRNw73R2WdDBICtwhI3wm3cxsaVqLTAGuRwrTltcfxs,1068
32
- topologicpy-0.7.42.dist-info/METADATA,sha256=NSPc5BzWU_sOnE--u-EnHbDLze91ki3OV6LRtAyEc1k,10916
33
- topologicpy-0.7.42.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
34
- topologicpy-0.7.42.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
35
- topologicpy-0.7.42.dist-info/RECORD,,
30
+ topologicpy/version.py,sha256=2Sw5dX1m7XzP13Ernw7oNdPR5WsUzOMJ7VZ-QyJpf9o,23
31
+ topologicpy-0.7.44.dist-info/LICENSE,sha256=BRNw73R2WdDBICtwhI3wm3cxsaVqLTAGuRwrTltcfxs,1068
32
+ topologicpy-0.7.44.dist-info/METADATA,sha256=N7KPudecCpJl9CdmpNEfoLiXYGpxcXZwegs6AvC1HTo,10916
33
+ topologicpy-0.7.44.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
34
+ topologicpy-0.7.44.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
35
+ topologicpy-0.7.44.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (72.1.0)
2
+ Generator: setuptools (72.2.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5