serJD commited on
Commit
ce3dfc6
1 Parent(s): 30b39ff

ini commit

Browse files
.DS_Store ADDED
Binary file (6.15 kB). View file
 
Trip Generation.xlsx ADDED
Binary file (300 kB). View file
 
__pycache__/tripGenerationFunc.cpython-312.pyc ADDED
Binary file (33.6 kB). View file
 
config.json ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "STREAM_ID": "ebcfc50abe",
3
+ "BRANCH_NAME_LAND_USES": "graph_geometry/activity_nodes_with_land_use",
4
+ "BRANCH_NAME_DISTANCE_MATRIX": "graph_geometry/distance_matrix",
5
+ "BRANCH_NAME_METRIC_DIST_MATRIX": "graph_geometry/metric_matrix",
6
+ "TARGET_BRANCH_TM": "graph_geometry/trip_matrix",
7
+ "distanceMatrixName": "activity_node+distance_matrix_ped_mm_art_noEntr",
8
+ "SCENARIO_NAME": "PED-MM-DRT_noEntryTime",
9
+ "TARGET_TRIP_RATE": 3.2,
10
+ "SCALING_FACTOR": 1,
11
+ "ALPHA_LOW": 0.0023,
12
+ "ALPHA_MED": 0.0038,
13
+ "ALPHA_HIGH": 0.0076,
14
+ "ALPHA_UNIFORM": 0.0038,
15
+ "ALPHA": 0.00066,
16
+ "XLS_FILE_PATH":"Trip Generation.xlsx",
17
+
18
+ "F_VALUES_MANUAL": {
19
+ "activity_node+distance_matrix_ped_mm_art_noEntr": 0,
20
+ "activity_node+distance_matrix_ped_mm_noEntr": 0.55,
21
+ "activity_node+distance_matrix_ped_noEntr": -0.08,
22
+ "activity_node+distance_matrix_ped_art_noEntr": -0.5
23
+ },
24
+ "distance_matrices_of_interest": [
25
+ "activity_node+distance_matrix_ped_mm_art_noEntr",
26
+ "activity_node+distance_matrix_ped_mm_noEntr",
27
+ "activity_node+distance_matrix_ped_art_noEntr",
28
+ "activity_node+distance_matrix_ped_noEntr"
29
+ ],
30
+ "metric_matrices_of_interest": [
31
+ "activity_node+metric_matrix_ped_mm_art",
32
+ "activity_node+metric_matrix_ped_mm",
33
+ "activity_node+metric_matrix_ped_art",
34
+ "activity_node+metric_matrix_ped"
35
+ ],
36
+ "redistributeTrips": [
37
+ {
38
+ "from": "activity_node+distance_matrix_ped_mm_art_noEntr",
39
+ "to": [
40
+ "activity_node+distance_matrix_ped_mm_noEntr",
41
+ "activity_node+distance_matrix_ped_art_noEntr",
42
+ "activity_node+distance_matrix_ped_noEntr"
43
+ ]
44
+ },
45
+ {
46
+ "from": "activity_node+distance_matrix_ped_art_noEntr",
47
+ "to": [
48
+ "activity_node+distance_matrix_ped_noEntr"
49
+ ]
50
+ },
51
+ {
52
+ "from": "activity_node+distance_matrix_ped_mm_noEntr",
53
+ "to": [
54
+ "activity_node+distance_matrix_ped_noEntr"
55
+ ]
56
+ }
57
+ ],
58
+ "DISTANCE_BRACKETS": [
59
+ 800,
60
+ 2400,
61
+ 4800
62
+ ]
63
+ }
64
+
main copy.py ADDED
@@ -0,0 +1,333 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import pandas as pd
4
+ import copy
5
+ from functools import wraps
6
+ from specklepy.api.client import SpeckleClient
7
+ from tripGenerationFunc import *
8
+ import speckle_utils
9
+ import data_utils
10
+
11
+
12
+ # get config file:# Parse JSON
13
+ current_directory = os.path.dirname(os.path.abspath(__file__))
14
+ # Path to the config.json file
15
+ config_file_path = os.path.join(current_directory, "config.json")
16
+
17
+
18
+ #def runAll():
19
+
20
+ speckle_token = os.environ.get("SPECKLE_TOKEN")
21
+ # Check if the config.json file exists
22
+ if os.path.exists(config_file_path):
23
+ # Load the JSON data from config.json
24
+ with open(config_file_path, 'r') as f:
25
+ config = json.load(f)
26
+
27
+ # Convert to Python variables with the same names as the keys in the JSON
28
+ locals().update(config)
29
+ print("varaibles from json")
30
+ # Now you can access the variables directly
31
+ print(STREAM_ID)
32
+ print(BRANCH_NAME_LAND_USES)
33
+ print(TARGET_TRIP_RATE)
34
+ print(ALPHA_LOW)
35
+ print(F_VALUES_MANUAL)
36
+ print(distance_matrices_of_interest)
37
+ print(redistributeTrips)
38
+ print(DISTANCE_BRACKETS)
39
+ print(XLS_FILE_PATH)
40
+ print("==================")
41
+ else:
42
+ print("Error: config.json file not found in the current directory.")
43
+
44
+
45
+
46
+ xls_file_path = os.path.join(current_directory, XLS_FILE_PATH)
47
+ print("full path", xls_file_path)
48
+ # fetch speckle data
49
+ CLIENT = SpeckleClient(host="https://speckle.xyz/")
50
+ CLIENT.authenticate_with_token(token="52566d1047b881764e16ad238356abeb2fc35d8b42")
51
+
52
+ # get land use stream
53
+ stream_land_use = speckle_utils.getSpeckleStream(STREAM_ID,
54
+ BRANCH_NAME_LAND_USES,
55
+ CLIENT,
56
+ commit_id = "")
57
+ # navigate to list with speckle objects of interest
58
+ stream_data = stream_land_use["@Data"]["@{0}"]
59
+
60
+ # transform stream_data to dataframe (create a backup copy of this dataframe)
61
+ df_speckle_lu = speckle_utils.get_dataframe(stream_data, return_original_df=False)
62
+ df_main = df_speckle_lu.copy()
63
+
64
+ # set index column
65
+ df_main = df_main.set_index("ids", drop=False)
66
+
67
+
68
+ # get distance matrix stream
69
+ stream_distance_matrice = speckle_utils.getSpeckleStream(STREAM_ID,
70
+ BRANCH_NAME_DISTANCE_MATRIX,
71
+ CLIENT,
72
+ commit_id = "")
73
+
74
+ # navigate to list with speckle objects of interest
75
+ distance_matrices = {}
76
+ for distM in stream_distance_matrice["@Data"]['@{0}']:
77
+ for kk in distM.__dict__.keys():
78
+ try:
79
+ if kk.split("+")[1].startswith("distance_matrix"):
80
+ distance_matrix_dict = json.loads(distM[kk])
81
+ origin_ids = distance_matrix_dict["origin_uuid"]
82
+ destination_ids = distance_matrix_dict["destination_uuid"]
83
+ distance_matrix = distance_matrix_dict["matrix"]
84
+ # Convert the distance matrix to a DataFrame
85
+ df_distances = pd.DataFrame(distance_matrix, index=origin_ids, columns=destination_ids)
86
+
87
+ # i want to add the index & colum names to dist_m_csv
88
+ #distance_matrices[kk] = dist_m_csv[kk]
89
+ distance_matrices[kk] = df_distances
90
+
91
+ except:
92
+ pass
93
+
94
+
95
+ # get metric matrix stream
96
+ stream_metric_matrice = speckle_utils.getSpeckleStream(STREAM_ID,
97
+ BRANCH_NAME_METRIC_DIST_MATRIX,
98
+ CLIENT,
99
+ commit_id = "")
100
+
101
+
102
+ # navigate to list with speckle objects of interest
103
+ metric_matrices = {}
104
+ for distM in stream_metric_matrice["@Data"]['@{0}']:
105
+ print(distM.__dict__.keys())
106
+ for kk in distM.__dict__.keys():
107
+ try:
108
+ if kk.split("+")[1].startswith("metric_matrix"):
109
+ metric_matrix_dict = json.loads(distM[kk])
110
+ origin_ids = metric_matrix_dict["origin_uuid"]
111
+ destination_ids = metric_matrix_dict["destination_uuid"]
112
+ metric_matrix = metric_matrix_dict["matrix"]
113
+ # Convert the distance matrix to a DataFrame
114
+ df_metric_dist = pd.DataFrame(metric_matrix, index=origin_ids, columns=destination_ids)
115
+ metric_matrices[kk] = df_metric_dist*10 #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
116
+
117
+ print("metric_matrix_dict", metric_matrix_dict.keys())
118
+ except:
119
+ pass
120
+
121
+ metric_matrices = extract_distance_matrices(stream_metric_matrice, metric_matrices_of_interest)
122
+
123
+
124
+ sourceCommits = {
125
+ "landuseCommitID": stream_land_use.id,
126
+ "distanceMatrixCommitID": stream_distance_matrice.id,
127
+ "metricMatrixCommitID": stream_metric_matrice.id
128
+ }
129
+
130
+
131
+ # READ XLS FILE ======================================
132
+ # Read Excel file into Pandas DataFrame
133
+ #Production
134
+ # Load Excel file separately
135
+ #xls_file_path = os.path.join(current_directory, XLS_FILE_PATH)
136
+ if os.path.exists(xls_file_path):
137
+ # Production
138
+ df_production = pd.read_excel(xls_file_path, sheet_name='Production')
139
+ df_production_transposed = df_production.T
140
+ df_production = preprocess_dataFrame(df_production, headerRow_idx=2, numRowsStart_idx=3)
141
+ df_production_transposed = preprocess_dataFrame(df_production_transposed, headerRow_idx=0, numRowsStart_idx=4,
142
+ numColsStart_idx=4, rowNames_idx=2)
143
+
144
+ # Attraction
145
+ df_attraction = pd.read_excel(xls_file_path, sheet_name='Attraction')
146
+ df_attraction = preprocess_dataFrame(df_attraction, headerRow_idx=0, numRowsStart_idx=2)
147
+
148
+ # Distribution_Matrix
149
+ df_distributionMatrix = pd.read_excel(xls_file_path, sheet_name='Distribution_Matrix')
150
+ df_distributionMatrix = preprocess_dataFrame(df_distributionMatrix, headerRow_idx=0, numRowsStart_idx=2,
151
+ numRowsEnd_idx=None, numColsStart_idx=2, numColsEnd_idx=None,
152
+ rowNames_idx=0)
153
+
154
+ # Alphas
155
+ df_alphas = pd.read_excel(xls_file_path, sheet_name='Alphas')
156
+ df_alphas.columns = df_alphas.iloc[1]
157
+ df_alphas = df_alphas.iloc[0, 2:]
158
+
159
+ # Land use
160
+ df_lu = pd.read_excel(xls_file_path, sheet_name='Example_Land_Use')
161
+ df_lu = preprocess_dataFrame(df_lu, headerRow_idx=0, numRowsStart_idx=1)
162
+ df_lu["nameCombined"] = df_lu.iloc[:, 1].astype(str) + "+" + df_lu.iloc[:, 0].astype(str)
163
+
164
+ # Distance Matrix
165
+ df_distMatrix = pd.read_excel(xls_file_path, sheet_name='Example_Distance_Matrix')
166
+ df_distMatrix = preprocess_dataFrame(df_distMatrix, headerRow_idx=0, numRowsStart_idx=1, numRowsEnd_idx=None,
167
+ numColsStart_idx=1, numColsEnd_idx=None, rowNames_idx=0)
168
+ else:
169
+ print("Error: Excel file specified in config.json not found.")
170
+
171
+
172
+
173
+ # Land use strucutre =======
174
+ # THIS IS THE DISTANCE MATRIX THATS USED DOWN THE ROAD
175
+ df_distances_aligned, df_lu_stream_aligned = align_dataframes(distance_matrices[distanceMatrixName], df_main, 'ids')
176
+
177
+ #Create a df with lanuses
178
+ lu_cols = [col for col in df_lu_stream_aligned.columns if col.startswith("lu+")]
179
+ df_lu_stream = df_lu_stream_aligned[lu_cols]
180
+
181
+ # Remove "lu+" from the beginning of column names
182
+ df_lu_stream.columns = df_lu_stream.columns.str.lstrip('lu+')
183
+ df_lu_stream = df_lu_stream.T
184
+
185
+ df_lu_stream_t = df_lu_stream.T
186
+
187
+ df_lu_stream_with_nameLu_column = df_lu_stream.reset_index(drop=False).rename(columns={'index': 'nameLu'})
188
+
189
+ #---
190
+ df_lu_names_xlsx = pd.concat([df_lu.iloc[:, 0:2], df_lu.iloc[:, -1]], axis=1)
191
+ df_lu_names_xlsx.index = df_lu_names_xlsx.iloc[:, 1]
192
+ column_names = ['nameTripType', 'nameLu', 'nameCombined']
193
+ df_lu_names_xlsx.columns = column_names
194
+ print(f"df_lu_names_xlsx shape: {df_lu_names_xlsx.shape}")
195
+ df_lu_names_xlsx.head()
196
+
197
+ #--
198
+
199
+ # Merge DataFrames using an outer join
200
+ merged_df = pd.merge(df_lu_stream_with_nameLu_column, df_lu_names_xlsx, on='nameLu', how='outer')
201
+
202
+ # Get the unique names and their counts from df_lu_names_xlsx
203
+ name_counts = df_lu_names_xlsx['nameLu'].value_counts()
204
+ #print(name_counts)
205
+
206
+ # Identify names in df_lu_stream_with_nameLu_column that are not in df_lu_names_xlsx
207
+ missing_names = df_lu_stream_with_nameLu_column.loc[~df_lu_stream_with_nameLu_column['nameLu'].isin(df_lu_names_xlsx['nameLu'])]
208
+
209
+ # Append missing rows to df_lu_stream_with_nameLu_column
210
+ df_lu_stream_duplicated = pd.concat([merged_df, missing_names], ignore_index=True)
211
+
212
+
213
+ #--
214
+ # Find names in df_lu_names_xlsx that are not in df_lu_stream_with_nameLu_column
215
+ missing_names = df_lu_names_xlsx.loc[~df_lu_names_xlsx['nameLu'].isin(df_lu_stream_with_nameLu_column['nameLu'])]
216
+
217
+ #--
218
+ # print existing names (?)
219
+ df_lu_names_sorted = df_lu_names_xlsx.sort_values(by='nameLu')
220
+ df_lu_stream_duplicated_sorted = df_lu_stream_duplicated.sort_values(by='nameLu')
221
+ #--
222
+ # Merge DataFrames to get the order of names
223
+ merged_order = pd.merge(df_lu_names_xlsx[['nameCombined']], df_lu_stream_duplicated[['nameCombined']], on='nameCombined', how='inner')
224
+
225
+ # Sort df_lu_stream_duplicated based on the order of names in df_lu_names_xlsx
226
+ df_lu_stream_sorted = df_lu_stream_duplicated.sort_values(by='nameCombined', key=lambda x: pd.Categorical(x, categories=merged_order['nameCombined'], ordered=True))
227
+
228
+ # Reorganize columns
229
+ column_order = ['nameTripType', 'nameCombined'] + [col for col in df_lu_stream_sorted.columns if col not in ['nameTripType', 'nameCombined']]
230
+
231
+ # Create a new DataFrame with the desired column order
232
+ df_lu_stream_reordered = df_lu_stream_sorted[column_order]
233
+
234
+ df_lu_stream_reordered_t = df_lu_stream_reordered.T
235
+
236
+ #--
237
+ df_lu_stream_with_index = df_lu_stream_reordered_t.reset_index(drop=False).rename(columns={'index': 'ids'})
238
+ df_lu_stream_with_index.index = df_lu_stream_reordered_t.index
239
+
240
+ df_lu_num_t_index = df_lu_stream_with_index.iloc[3:]
241
+
242
+ df_distances_aligned_index = df_distances_aligned.reset_index(drop=False).rename(columns={'index': 'ids'})
243
+ df_distances_aligned_index.index = df_distances_aligned.index
244
+
245
+ df_lu_namesCombined = df_lu_stream_with_index.loc["nameCombined"].iloc[1:]
246
+
247
+ # Sort df_lu_stream_with_index based on the 'ids' column in df_distances_aligned_index
248
+ df_lu_stream_sorted = df_lu_stream_with_index.sort_values(by=['ids'], key=lambda x: pd.Categorical(x, categories=df_distances_aligned_index['ids'], ordered=True))
249
+
250
+
251
+ df_lu_num = df_lu_stream_sorted.T.iloc[1:, :-3]
252
+ df_lu_num.index = df_lu_namesCombined
253
+
254
+ df_distMatrix_speckle = df_distances_aligned
255
+
256
+ df_attraction_num = df_attraction.reset_index().iloc[:-1, 6:]
257
+
258
+ # =============================================================================
259
+ # TRIP GENERATION
260
+
261
+ # ATTRACTION & PRODUCTION ======================================================
262
+ """
263
+ INPUTS
264
+ df_attraction_num
265
+ df_lu_num
266
+ df_production
267
+ df_lu
268
+ df_production_transposed
269
+ """
270
+
271
+ df_attraction_proNode_sum_total = attraction_proNode_full_iter(df_attraction_num, df_lu_num, True)
272
+
273
+ #Get the sqmProPerson
274
+ df_sqmProPerson = df_production.iloc[0, 4:].reset_index()[3]
275
+
276
+ #Get the trip rate
277
+ df_tripRate = copy.deepcopy(df_production) # create a copy ensures df_tripRate doenst point to df_production
278
+ df_tripRate.index = df_tripRate.iloc[:, 0] #Set the row names
279
+ df_tripRate = df_tripRate.iloc[1:, 2]
280
+
281
+ #Numerical df from production ==============================================
282
+ df_production_num = df_production.iloc[1:, 4:]
283
+ df_production_transposed1 = df_production_num.T
284
+
285
+ df_total_trips_allNodes = production_proNode_total(df_lu,
286
+ df_sqmProPerson,
287
+ df_tripRate,
288
+ df_production_num,
289
+ df_production_transposed,
290
+ df_lu_num, printSteps=False)
291
+ # Convert data types to float
292
+ df_total_trips_allNodes = df_total_trips_allNodes.astype(float)
293
+ df_tripRate = df_tripRate.astype(float)
294
+
295
+ df_total_trips_allNodes_sumPerson = df_total_trips_allNodes.div(df_tripRate, axis=0).sum()
296
+ df_total_trips_allNodes_sumPerson_proCat = df_total_trips_allNodes.div(df_tripRate, axis=0)
297
+ df_total_trips_allNodes_sumPerson_proCat_t = df_total_trips_allNodes_sumPerson_proCat.T
298
+ df_total_trips_allNodes_sumPerson_proCat_t_sum = df_total_trips_allNodes_sumPerson_proCat_t.sum()
299
+
300
+ # get total population
301
+ total_population = df_total_trips_allNodes_sumPerson_proCat_t_sum["Tot_Res"] + df_total_trips_allNodes_sumPerson_proCat_t_sum["Tot_tou"]
302
+
303
+
304
+
305
+ # =============================================================================
306
+ distance_matrices = extract_distance_matrices(stream_distance_matrice, distance_matrices_of_interest)
307
+ metric_matrices_ = extract_distance_matrices(stream_metric_matrice, metric_matrices_of_interest)
308
+ metric_matrices = { k:v*10 for k, v in metric_matrices_.items()} # scale (speckle issue)
309
+
310
+ logs = computeTrips(
311
+ df_distributionMatrix,
312
+ df_total_trips_allNodes,
313
+ df_distMatrix_speckle,
314
+ df_alphas,
315
+ df_attraction_proNode_sum_total,
316
+ df_distances_aligned,
317
+ TARGET_TRIP_RATE,
318
+ SCALING_FACTOR,
319
+ total_population,
320
+ df_total_trips_allNodes_sumPerson_proCat_t_sum["Tot_Res"],
321
+ df_total_trips_allNodes_sumPerson_proCat_t_sum["Tot_tou"],
322
+ distance_matrices,
323
+ metric_matrices,
324
+ redistributeTrips,
325
+ DISTANCE_BRACKETS,
326
+ ALPHA_LOW, ALPHA_MED, ALPHA_HIGH, ALPHA, ALPHA_UNIFORM, F_VALUES_MANUAL,
327
+ CLIENT,
328
+ STREAM_ID,
329
+ TARGET_BRANCH_TM,
330
+ sourceCommits
331
+ )
332
+
333
+ print(logs)
main.py ADDED
@@ -0,0 +1,385 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import pandas as pd
4
+ import copy
5
+ from functools import wraps
6
+ from specklepy.api.client import SpeckleClient
7
+ from tripGenerationFunc import *
8
+ import speckle_utils
9
+ import data_utils
10
+ import gradio as gr
11
+ import requests
12
+ from huggingface_hub import webhook_endpoint, WebhookPayload
13
+ from fastapi import Request
14
+ import datetime
15
+
16
+ current_directory = os.path.dirname(os.path.abspath(__file__))
17
+ # Path to the config.json file
18
+ config_file_path = os.path.join(current_directory, "config.json")
19
+
20
+ # Check if the config.json file exists
21
+ if os.path.exists(config_file_path):
22
+ # Load the JSON data from config.json
23
+ with open(config_file_path, 'r') as f:
24
+ config = json.load(f)
25
+
26
+ # Convert to Python variables with the same names as the keys in the JSON
27
+ locals().update(config)
28
+ print("varaibles from json")
29
+ # Now you can access the variables directly
30
+ print(STREAM_ID)
31
+ print(BRANCH_NAME_LAND_USES)
32
+ print(TARGET_TRIP_RATE)
33
+ print(ALPHA_LOW)
34
+ print(F_VALUES_MANUAL)
35
+ print(distance_matrices_of_interest)
36
+ print(redistributeTrips)
37
+ print(DISTANCE_BRACKETS)
38
+ print(XLS_FILE_PATH)
39
+ print("==================")
40
+ else:
41
+ print("Error: config.json file not found in the current directory.")
42
+
43
+
44
+ # checks payload of webhook and runs the main code if webhook was triggered by specified stream + one of the branches
45
+ listendStreams = [STREAM_ID]
46
+ listendBranchNames = [BRANCH_NAME_LAND_USES,BRANCH_NAME_DISTANCE_MATRIX,BRANCH_NAME_METRIC_DIST_MATRIX]
47
+
48
+ @webhook_endpoint
49
+ async def update_streams(request: Request):
50
+ # Initialize flag
51
+ should_continue = False
52
+
53
+ # Read the request body as JSON
54
+ payload = await request.json()
55
+
56
+ # Check if the payload structure matches the expected format
57
+ if "event" in payload and "data" in payload["event"]:
58
+ event_data = payload["event"]["data"]
59
+
60
+ # Check if the event type is "commit_create"
61
+ if "type" in event_data and event_data["type"] == "commit_create":
62
+ # Check if the stream name matches the specified list
63
+ if "stream" in event_data and event_data["stream"] in listendStreams:
64
+ # Check if the branch name matches the specified list
65
+ if "commit" in event_data and "branchName" in event_data["commit"]:
66
+ if event_data["commit"]["branchName"] in listendBranchNames:
67
+ should_continue = True
68
+ else:
69
+ print("Branch name not found in payload.")
70
+ else:
71
+ print("Stream name not found or not in the specified list.")
72
+ else:
73
+ print("Event type is not 'commit_create'.")
74
+ else:
75
+ print("Payload structure does not match the expected format.")
76
+
77
+ # If the flag is True, continue running the main part of the code
78
+ if should_continue:
79
+ # Your main code logic goes here
80
+ runAll()
81
+ else:
82
+ print("Flag is False. Skipping further execution.")
83
+
84
+ return "Webhook processing complete."
85
+
86
+
87
+
88
+ def runAll():
89
+ # get config file:# Parse JSON
90
+
91
+ speckle_token = os.environ.get("SPECKLE_TOKEN")
92
+
93
+
94
+
95
+
96
+
97
+ xls_file_path = os.path.join(current_directory, XLS_FILE_PATH)
98
+ print("full path", xls_file_path)
99
+ # fetch speckle data
100
+ CLIENT = SpeckleClient(host="https://speckle.xyz/")
101
+ CLIENT.authenticate_with_token(token="52566d1047b881764e16ad238356abeb2fc35d8b42")
102
+
103
+ # get land use stream
104
+ stream_land_use = speckle_utils.getSpeckleStream(STREAM_ID,
105
+ BRANCH_NAME_LAND_USES,
106
+ CLIENT,
107
+ commit_id = "")
108
+ # navigate to list with speckle objects of interest
109
+ stream_data = stream_land_use["@Data"]["@{0}"]
110
+
111
+ # transform stream_data to dataframe (create a backup copy of this dataframe)
112
+ df_speckle_lu = speckle_utils.get_dataframe(stream_data, return_original_df=False)
113
+ df_main = df_speckle_lu.copy()
114
+
115
+ # set index column
116
+ df_main = df_main.set_index("ids", drop=False)
117
+
118
+
119
+ # get distance matrix stream
120
+ stream_distance_matrice = speckle_utils.getSpeckleStream(STREAM_ID,
121
+ BRANCH_NAME_DISTANCE_MATRIX,
122
+ CLIENT,
123
+ commit_id = "")
124
+
125
+ # navigate to list with speckle objects of interest
126
+ distance_matrices = {}
127
+ for distM in stream_distance_matrice["@Data"]['@{0}']:
128
+ for kk in distM.__dict__.keys():
129
+ try:
130
+ if kk.split("+")[1].startswith("distance_matrix"):
131
+ distance_matrix_dict = json.loads(distM[kk])
132
+ origin_ids = distance_matrix_dict["origin_uuid"]
133
+ destination_ids = distance_matrix_dict["destination_uuid"]
134
+ distance_matrix = distance_matrix_dict["matrix"]
135
+ # Convert the distance matrix to a DataFrame
136
+ df_distances = pd.DataFrame(distance_matrix, index=origin_ids, columns=destination_ids)
137
+
138
+ # i want to add the index & colum names to dist_m_csv
139
+ #distance_matrices[kk] = dist_m_csv[kk]
140
+ distance_matrices[kk] = df_distances
141
+
142
+ except:
143
+ pass
144
+
145
+
146
+ # get metric matrix stream
147
+ stream_metric_matrice = speckle_utils.getSpeckleStream(STREAM_ID,
148
+ BRANCH_NAME_METRIC_DIST_MATRIX,
149
+ CLIENT,
150
+ commit_id = "")
151
+
152
+
153
+ # navigate to list with speckle objects of interest
154
+ metric_matrices = {}
155
+ for distM in stream_metric_matrice["@Data"]['@{0}']:
156
+ print(distM.__dict__.keys())
157
+ for kk in distM.__dict__.keys():
158
+ try:
159
+ if kk.split("+")[1].startswith("metric_matrix"):
160
+ metric_matrix_dict = json.loads(distM[kk])
161
+ origin_ids = metric_matrix_dict["origin_uuid"]
162
+ destination_ids = metric_matrix_dict["destination_uuid"]
163
+ metric_matrix = metric_matrix_dict["matrix"]
164
+ # Convert the distance matrix to a DataFrame
165
+ df_metric_dist = pd.DataFrame(metric_matrix, index=origin_ids, columns=destination_ids)
166
+ metric_matrices[kk] = df_metric_dist*10 #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
167
+
168
+ print("metric_matrix_dict", metric_matrix_dict.keys())
169
+ except:
170
+ pass
171
+
172
+ metric_matrices = extract_distance_matrices(stream_metric_matrice, metric_matrices_of_interest)
173
+
174
+
175
+ sourceCommits = {
176
+ "landuseCommitID": stream_land_use.id,
177
+ "distanceMatrixCommitID": stream_distance_matrice.id,
178
+ "metricMatrixCommitID": stream_metric_matrice.id
179
+ }
180
+
181
+
182
+ # READ XLS FILE ======================================
183
+ # Read Excel file into Pandas DataFrame
184
+ #Production
185
+ # Load Excel file separately
186
+ #xls_file_path = os.path.join(current_directory, XLS_FILE_PATH)
187
+ if os.path.exists(xls_file_path):
188
+ # Production
189
+ df_production = pd.read_excel(xls_file_path, sheet_name='Production')
190
+ df_production_transposed = df_production.T
191
+ df_production = preprocess_dataFrame(df_production, headerRow_idx=2, numRowsStart_idx=3)
192
+ df_production_transposed = preprocess_dataFrame(df_production_transposed, headerRow_idx=0, numRowsStart_idx=4,
193
+ numColsStart_idx=4, rowNames_idx=2)
194
+
195
+ # Attraction
196
+ df_attraction = pd.read_excel(xls_file_path, sheet_name='Attraction')
197
+ df_attraction = preprocess_dataFrame(df_attraction, headerRow_idx=0, numRowsStart_idx=2)
198
+
199
+ # Distribution_Matrix
200
+ df_distributionMatrix = pd.read_excel(xls_file_path, sheet_name='Distribution_Matrix')
201
+ df_distributionMatrix = preprocess_dataFrame(df_distributionMatrix, headerRow_idx=0, numRowsStart_idx=2,
202
+ numRowsEnd_idx=None, numColsStart_idx=2, numColsEnd_idx=None,
203
+ rowNames_idx=0)
204
+
205
+ # Alphas
206
+ df_alphas = pd.read_excel(xls_file_path, sheet_name='Alphas')
207
+ df_alphas.columns = df_alphas.iloc[1]
208
+ df_alphas = df_alphas.iloc[0, 2:]
209
+
210
+ # Land use
211
+ df_lu = pd.read_excel(xls_file_path, sheet_name='Example_Land_Use')
212
+ df_lu = preprocess_dataFrame(df_lu, headerRow_idx=0, numRowsStart_idx=1)
213
+ df_lu["nameCombined"] = df_lu.iloc[:, 1].astype(str) + "+" + df_lu.iloc[:, 0].astype(str)
214
+
215
+ # Distance Matrix
216
+ df_distMatrix = pd.read_excel(xls_file_path, sheet_name='Example_Distance_Matrix')
217
+ df_distMatrix = preprocess_dataFrame(df_distMatrix, headerRow_idx=0, numRowsStart_idx=1, numRowsEnd_idx=None,
218
+ numColsStart_idx=1, numColsEnd_idx=None, rowNames_idx=0)
219
+ else:
220
+ print("Error: Excel file specified in config.json not found.")
221
+
222
+
223
+
224
+ # Land use strucutre =======
225
+ # THIS IS THE DISTANCE MATRIX THATS USED DOWN THE ROAD
226
+ df_distances_aligned, df_lu_stream_aligned = align_dataframes(distance_matrices[distanceMatrixName], df_main, 'ids')
227
+
228
+ #Create a df with lanuses
229
+ lu_cols = [col for col in df_lu_stream_aligned.columns if col.startswith("lu+")]
230
+ df_lu_stream = df_lu_stream_aligned[lu_cols]
231
+
232
+ # Remove "lu+" from the beginning of column names
233
+ df_lu_stream.columns = df_lu_stream.columns.str.lstrip('lu+')
234
+ df_lu_stream = df_lu_stream.T
235
+
236
+ df_lu_stream_t = df_lu_stream.T
237
+
238
+ df_lu_stream_with_nameLu_column = df_lu_stream.reset_index(drop=False).rename(columns={'index': 'nameLu'})
239
+
240
+ #---
241
+ df_lu_names_xlsx = pd.concat([df_lu.iloc[:, 0:2], df_lu.iloc[:, -1]], axis=1)
242
+ df_lu_names_xlsx.index = df_lu_names_xlsx.iloc[:, 1]
243
+ column_names = ['nameTripType', 'nameLu', 'nameCombined']
244
+ df_lu_names_xlsx.columns = column_names
245
+ print(f"df_lu_names_xlsx shape: {df_lu_names_xlsx.shape}")
246
+ df_lu_names_xlsx.head()
247
+
248
+ #--
249
+
250
+ # Merge DataFrames using an outer join
251
+ merged_df = pd.merge(df_lu_stream_with_nameLu_column, df_lu_names_xlsx, on='nameLu', how='outer')
252
+
253
+ # Get the unique names and their counts from df_lu_names_xlsx
254
+ name_counts = df_lu_names_xlsx['nameLu'].value_counts()
255
+ #print(name_counts)
256
+
257
+ # Identify names in df_lu_stream_with_nameLu_column that are not in df_lu_names_xlsx
258
+ missing_names = df_lu_stream_with_nameLu_column.loc[~df_lu_stream_with_nameLu_column['nameLu'].isin(df_lu_names_xlsx['nameLu'])]
259
+
260
+ # Append missing rows to df_lu_stream_with_nameLu_column
261
+ df_lu_stream_duplicated = pd.concat([merged_df, missing_names], ignore_index=True)
262
+
263
+
264
+ #--
265
+ # Find names in df_lu_names_xlsx that are not in df_lu_stream_with_nameLu_column
266
+ missing_names = df_lu_names_xlsx.loc[~df_lu_names_xlsx['nameLu'].isin(df_lu_stream_with_nameLu_column['nameLu'])]
267
+
268
+ #--
269
+ # print existing names (?)
270
+ df_lu_names_sorted = df_lu_names_xlsx.sort_values(by='nameLu')
271
+ df_lu_stream_duplicated_sorted = df_lu_stream_duplicated.sort_values(by='nameLu')
272
+ #--
273
+ # Merge DataFrames to get the order of names
274
+ merged_order = pd.merge(df_lu_names_xlsx[['nameCombined']], df_lu_stream_duplicated[['nameCombined']], on='nameCombined', how='inner')
275
+
276
+ # Sort df_lu_stream_duplicated based on the order of names in df_lu_names_xlsx
277
+ df_lu_stream_sorted = df_lu_stream_duplicated.sort_values(by='nameCombined', key=lambda x: pd.Categorical(x, categories=merged_order['nameCombined'], ordered=True))
278
+
279
+ # Reorganize columns
280
+ column_order = ['nameTripType', 'nameCombined'] + [col for col in df_lu_stream_sorted.columns if col not in ['nameTripType', 'nameCombined']]
281
+
282
+ # Create a new DataFrame with the desired column order
283
+ df_lu_stream_reordered = df_lu_stream_sorted[column_order]
284
+
285
+ df_lu_stream_reordered_t = df_lu_stream_reordered.T
286
+
287
+ #--
288
+ df_lu_stream_with_index = df_lu_stream_reordered_t.reset_index(drop=False).rename(columns={'index': 'ids'})
289
+ df_lu_stream_with_index.index = df_lu_stream_reordered_t.index
290
+
291
+ df_lu_num_t_index = df_lu_stream_with_index.iloc[3:]
292
+
293
+ df_distances_aligned_index = df_distances_aligned.reset_index(drop=False).rename(columns={'index': 'ids'})
294
+ df_distances_aligned_index.index = df_distances_aligned.index
295
+
296
+ df_lu_namesCombined = df_lu_stream_with_index.loc["nameCombined"].iloc[1:]
297
+
298
+ # Sort df_lu_stream_with_index based on the 'ids' column in df_distances_aligned_index
299
+ df_lu_stream_sorted = df_lu_stream_with_index.sort_values(by=['ids'], key=lambda x: pd.Categorical(x, categories=df_distances_aligned_index['ids'], ordered=True))
300
+
301
+
302
+ df_lu_num = df_lu_stream_sorted.T.iloc[1:, :-3]
303
+ df_lu_num.index = df_lu_namesCombined
304
+
305
+ df_distMatrix_speckle = df_distances_aligned
306
+
307
+ df_attraction_num = df_attraction.reset_index().iloc[:-1, 6:]
308
+
309
+ # =============================================================================
310
+ # TRIP GENERATION
311
+
312
+ # ATTRACTION & PRODUCTION ======================================================
313
+ """
314
+ INPUTS
315
+ df_attraction_num
316
+ df_lu_num
317
+ df_production
318
+ df_lu
319
+ df_production_transposed
320
+ """
321
+
322
+ df_attraction_proNode_sum_total = attraction_proNode_full_iter(df_attraction_num, df_lu_num, True)
323
+
324
+ #Get the sqmProPerson
325
+ df_sqmProPerson = df_production.iloc[0, 4:].reset_index()[3]
326
+
327
+ #Get the trip rate
328
+ df_tripRate = copy.deepcopy(df_production) # create a copy ensures df_tripRate doenst point to df_production
329
+ df_tripRate.index = df_tripRate.iloc[:, 0] #Set the row names
330
+ df_tripRate = df_tripRate.iloc[1:, 2]
331
+
332
+ #Numerical df from production ==============================================
333
+ df_production_num = df_production.iloc[1:, 4:]
334
+ df_production_transposed1 = df_production_num.T
335
+
336
+ df_total_trips_allNodes = production_proNode_total(df_lu,
337
+ df_sqmProPerson,
338
+ df_tripRate,
339
+ df_production_num,
340
+ df_production_transposed,
341
+ df_lu_num, printSteps=False)
342
+ # Convert data types to float
343
+ df_total_trips_allNodes = df_total_trips_allNodes.astype(float)
344
+ df_tripRate = df_tripRate.astype(float)
345
+
346
+ df_total_trips_allNodes_sumPerson = df_total_trips_allNodes.div(df_tripRate, axis=0).sum()
347
+ df_total_trips_allNodes_sumPerson_proCat = df_total_trips_allNodes.div(df_tripRate, axis=0)
348
+ df_total_trips_allNodes_sumPerson_proCat_t = df_total_trips_allNodes_sumPerson_proCat.T
349
+ df_total_trips_allNodes_sumPerson_proCat_t_sum = df_total_trips_allNodes_sumPerson_proCat_t.sum()
350
+
351
+ # get total population
352
+ total_population = df_total_trips_allNodes_sumPerson_proCat_t_sum["Tot_Res"] + df_total_trips_allNodes_sumPerson_proCat_t_sum["Tot_tou"]
353
+
354
+
355
+
356
+ # =============================================================================
357
+ distance_matrices = extract_distance_matrices(stream_distance_matrice, distance_matrices_of_interest)
358
+ metric_matrices_ = extract_distance_matrices(stream_metric_matrice, metric_matrices_of_interest)
359
+ metric_matrices = { k:v*10 for k, v in metric_matrices_.items()} # scale (speckle issue)
360
+
361
+ logs = computeTrips(
362
+ df_distributionMatrix,
363
+ df_total_trips_allNodes,
364
+ df_distMatrix_speckle,
365
+ df_alphas,
366
+ df_attraction_proNode_sum_total,
367
+ df_distances_aligned,
368
+ TARGET_TRIP_RATE,
369
+ SCALING_FACTOR,
370
+ total_population,
371
+ df_total_trips_allNodes_sumPerson_proCat_t_sum["Tot_Res"],
372
+ df_total_trips_allNodes_sumPerson_proCat_t_sum["Tot_tou"],
373
+ distance_matrices,
374
+ metric_matrices,
375
+ redistributeTrips,
376
+ DISTANCE_BRACKETS,
377
+ ALPHA_LOW, ALPHA_MED, ALPHA_HIGH, ALPHA, ALPHA_UNIFORM, F_VALUES_MANUAL,
378
+ CLIENT,
379
+ STREAM_ID,
380
+ TARGET_BRANCH_TM,
381
+ sourceCommits
382
+ )
383
+
384
+ print(logs)
385
+
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ pandas==1.3.3
2
+ numpy==1.21.2
3
+ numba==0.54.1
4
+ gradio
5
+ specklepy
6
+ requests
tripGenerationFunc.py ADDED
@@ -0,0 +1,910 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import pandas as pd
3
+ from numba import jit
4
+ import math
5
+ import json
6
+ import os
7
+ import sys
8
+ from specklepy.api.client import SpeckleClient
9
+ from specklepy.api.credentials import get_default_account, get_local_accounts
10
+ from specklepy.transports.server import ServerTransport
11
+ from specklepy.api import operations
12
+ from specklepy.objects.geometry import Polyline, Point
13
+ from specklepy.objects import Base
14
+ from specklepy.api import operations, models
15
+ from specklepy.transports.server import ServerTransport
16
+ import time
17
+ from functools import wraps
18
+
19
+
20
+ import speckle_utils
21
+ import data_utils
22
+
23
+
24
+
25
+
26
+ # !!! lots of hard coded values in computeTrips !!!
27
+
28
+ # UTILS
29
+ def reconstruct_dataframe(alpha_low, alpha_med, alpha_high, original_df):
30
+ # Define the mapping from original values to new alpha parameters
31
+ value_to_alpha = {
32
+ 0.00191: alpha_low,
33
+ 0.00767: alpha_high,
34
+ 0.0038: alpha_med
35
+ }
36
+
37
+ # Check if each value is present at least once in the DataFrame
38
+ for original_value in value_to_alpha.keys():
39
+ if not (original_df == original_value).any().any():
40
+ raise ValueError(f"Value {original_value} not found in the input DataFrame.")
41
+
42
+ # Create a new DataFrame based on the original one
43
+ new_df = original_df.copy()
44
+
45
+ # Apply the mapping to each element in the DataFrame
46
+ for original_value, new_value in value_to_alpha.items():
47
+ new_df = new_df.replace(original_value, new_value)
48
+
49
+ return new_df
50
+
51
+ def preprocess_dataFrame(df, headerRow_idx=0, numRowsStart_idx = None, numRowsEnd_idx=None, numColsStart_idx=None, numColsEnd_idx=None, rowNames_idx=None):
52
+ df.columns = df.iloc[headerRow_idx] #Set the header
53
+ if rowNames_idx is not None:
54
+ df.index = df.iloc[:, rowNames_idx] #Set the row names
55
+ df = df.iloc[numRowsStart_idx : numRowsEnd_idx, numColsStart_idx:numColsEnd_idx] #Slice the dataset to numerical data
56
+ return df
57
+
58
+
59
+
60
+
61
+ def timeit(f):
62
+ def timed(*args, **kw):
63
+ ts = time.time()
64
+ result = f(*args, **kw)
65
+ te = time.time()
66
+ print ('func:%r args:[%r, %r] took: %2.4f sec' % \
67
+ (f.__name__, te-ts))
68
+ #(f.__name__, args, kw, te-ts))
69
+ return result
70
+ return timed
71
+
72
+
73
+
74
+
75
+
76
+ def timing_decorator(func):
77
+ @wraps(func)
78
+ def wrapper(*args, **kwargs):
79
+ start_time = time.time()
80
+ result = func(*args, **kwargs)
81
+ end_time = time.time()
82
+
83
+ duration = end_time - start_time
84
+ timestamp = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(end_time))
85
+
86
+ print(f"{func.__name__} took {duration:.4f} seconds. Finished at {timestamp}")
87
+ return result
88
+
89
+ return wrapper
90
+
91
+
92
+ # Function to compare two dataframes after converting and rounding
93
+ def compare_dataframes(df1, df2, decimals=8):
94
+ # Function to convert DataFrame columns to float and then round
95
+ def convert_and_round_dataframe(df, decimals):
96
+ # Convert all columns to float
97
+ df_float = df.astype(float)
98
+ # Round to the specified number of decimals
99
+ return df_float.round(decimals)
100
+
101
+ rounded_df1 = convert_and_round_dataframe(df1, decimals)
102
+ rounded_df2 = convert_and_round_dataframe(df2, decimals)
103
+
104
+ are_equal = rounded_df1.equals(rounded_df2)
105
+
106
+ print("Both methods are equal:", are_equal)
107
+
108
+ print("Numba shape:", df2.shape)
109
+ print("Original shape:", df1.shape)
110
+
111
+ print("======== ORIGINAL OUTPUT (first item in output list, head() for the first 5 columns)")
112
+ print(df1.iloc[0:5].head(2))
113
+
114
+ print("======== New method OUTPUT (first item in output list, head() for the first 5 columns)")
115
+ print(df2.iloc[0:5].head(2))
116
+
117
+
118
+ def align_dataframes(df1, df2, key):
119
+ """
120
+ Align two dataframes based on a common key, ensuring that both dataframes
121
+ have only the rows with matching keys.
122
+
123
+ Parameters:
124
+ - df1: First dataframe.
125
+ - df2: Second dataframe.
126
+ - key: Column name to align dataframes on.
127
+
128
+ Returns:
129
+ - df1_aligned, df2_aligned: Tuple of aligned dataframes.
130
+ """
131
+ common_ids = df1.index.intersection(df2[key])
132
+ df1_aligned = df1.loc[common_ids]
133
+ df2_aligned = df2[df2[key].isin(common_ids)].set_index(key, drop=False)
134
+ return df1_aligned, df2_aligned
135
+
136
+
137
+
138
+ #==================================================================================================
139
+
140
+ def attraction_proNode(df_attraction_num, df_lu, df_lu_anName=None, printSteps=False):
141
+ #lu_proNode
142
+ df_lu_proNode = df_lu.reset_index()[df_lu_anName]
143
+ if printSteps:
144
+ print(df_lu_proNode.shape)
145
+ df_lu_proNode.head(50)
146
+
147
+ #attraction_proNode
148
+ if printSteps:
149
+ print("df_attraction_num:", df_attraction_num.shape)
150
+ print("df_lu_proNode:", df_lu_proNode.shape)
151
+ df_attraction_proNode = df_attraction_num.mul(df_lu_proNode, axis=0)
152
+ if printSteps:
153
+ print("df_attraction_proNode:", df_attraction_proNode.shape)
154
+ df_attraction_proNode.head(100)
155
+
156
+ # Sum the values of each column
157
+ df_attraction_proNode_sum = pd.DataFrame(df_attraction_proNode.sum(), columns=['Sum'])
158
+ if printSteps:
159
+ print("df_attraction_proNode_sum:", df_attraction_proNode_sum.shape)
160
+ df_attraction_proNode_sum.head(100)
161
+
162
+ return df_attraction_proNode_sum
163
+
164
+ #Non vectorized iterative function
165
+ def attraction_proNode_full_iter(df_attraction_num, df_lu_num, printSteps=False):
166
+
167
+ # Initialize an empty DataFrame
168
+ df_attraction_proNode_sum_total = pd.DataFrame()
169
+
170
+ for column_name, column_data in df_lu_num.items():
171
+ df_attraction_proNode_sum = attraction_proNode(df_attraction_num, df_lu_num, df_lu_anName=column_name)
172
+
173
+ # Concatenate DataFrames along columns
174
+ df_attraction_proNode_sum_total = pd.concat([df_attraction_proNode_sum_total, df_attraction_proNode_sum], axis=1)
175
+
176
+ # Rename columns in df_distBasedAttr_step2 with the same column names as in df_distributionMatrix_step1
177
+ df_attraction_proNode_sum_total.columns = df_lu_num.columns
178
+
179
+ return df_attraction_proNode_sum_total
180
+
181
+
182
+
183
+ # PRODUCTION ================================================
184
+
185
+ def production_proNode(df_lu, df_sqmProPerson, df_tripRate, df_production_num, df_production_transposed, printSteps=False, df_lu_anName=None):
186
+
187
+ #lu_proNode - reset index
188
+ df_lu_proNode = df_lu.reset_index()[df_lu_anName]
189
+ if printSteps:
190
+ print(df_lu_proNode.shape)
191
+ df_lu_proNode.head(50)
192
+
193
+ #Get the person count - Divide corresponding values of one DataFrame by another
194
+ df_personCount = df_lu_proNode.div(df_sqmProPerson)
195
+ if printSteps:
196
+ print(df_personCount.shape)
197
+ print(df_personCount)
198
+
199
+ # Ensure the index is unique in df_personCount
200
+ df_personCount = df_personCount.reset_index(drop=True)
201
+ df_production_transposed = df_production_transposed.reset_index(drop=True)
202
+ if printSteps:
203
+ df_production_transposed.head()
204
+
205
+ if printSteps:
206
+ df_personCount.head()
207
+ df_tripRate.head()
208
+
209
+
210
+ #Calculate trip production pro node
211
+
212
+ df_production_proNode = df_production_transposed
213
+ df_production_proNode = df_production_proNode.mul(df_personCount, axis=0)
214
+
215
+
216
+ df_production_proNode = df_production_proNode.T
217
+ df_production_proNode = df_production_proNode.mul(df_tripRate, axis=0)
218
+
219
+
220
+ #Total trips
221
+ df_production_proNode_rowSum = df_production_proNode.sum(axis=1)
222
+ df_total_trips = df_production_proNode_rowSum
223
+ #if printSteps:
224
+ #df_total_trips.head(50)
225
+
226
+ return df_total_trips
227
+
228
+ #Non vectorized iterative function
229
+ def production_proNode_total(df_lu, df_sqmProPerson, df_tripRate, df_production_num, df_production_transposed, df_lu_num, printSteps=False):
230
+
231
+ # Initialize an empty DataFrame
232
+ df_total_trips_allNodes = pd.DataFrame()
233
+
234
+ for column_name, column_data in df_lu_num.items():
235
+ df_total_trips_proNode = production_proNode(df_lu_num, df_sqmProPerson, df_tripRate, df_production_num, df_production_transposed, printSteps=False, df_lu_anName=column_name)
236
+
237
+ # Concatenate DataFrames along columns
238
+ df_total_trips_allNodes = pd.concat([df_total_trips_allNodes, df_total_trips_proNode], axis=1)
239
+
240
+ # Rename columns in df_distBasedAttr_step2 with the same column names as in df_distributionMatrix_step1
241
+ df_total_trips_allNodes.columns = df_lu_num.columns
242
+
243
+ return df_total_trips_allNodes
244
+
245
+
246
+ #df_total_trips_allNodes = production_proNode_total(df_lu, df_sqmProPerson, df_tripRate, df_production_num, df_production_transposed, df_lu_num, printSteps=False)
247
+ #df_total_trips_allNodes.head(50)
248
+
249
+ #==================================================================================================
250
+
251
+ #STEP 1
252
+ def step_1(df_distributionMatrix, df_total_trips_allNodes):
253
+ l = []
254
+ #counter=0
255
+ for column_name, column_data in df_total_trips_allNodes.items():
256
+ df_distributionMatrix_step1_proNode = df_distributionMatrix.mul(column_data, axis = 0)
257
+ l.append(df_distributionMatrix_step1_proNode)
258
+
259
+ return l
260
+
261
+ #STEP 2
262
+ def step_2_vectorized(df_distMatrix, df_alphas):
263
+ # Convert df_distMatrix to a 2D array: Shape (1464, 1464)
264
+ distMatrix_array = df_distMatrix.values
265
+
266
+ # Convert df_alphas to a 1D array: Shape (26,)
267
+ alphas_array = df_alphas.values
268
+
269
+ # Initialize an empty array to store results: Shape (1464, 1464, 26)
270
+ result_3d = np.zeros((distMatrix_array.shape[0], distMatrix_array.shape[1], len(alphas_array)))
271
+
272
+ # Loop over alphas and perform element-wise multiplication followed by exponential function
273
+ for i in range(len(alphas_array)):
274
+ result_3d[:, :, i] = np.exp(-distMatrix_array * alphas_array[i])
275
+
276
+ # Construct the final list of DataFrames
277
+ final_list = [pd.DataFrame(result_3d[i, :, :], columns=df_alphas.index, index=df_distMatrix.index) for i in range(result_3d.shape[0])]
278
+
279
+ return final_list
280
+
281
+ # Step 3
282
+ @jit(nopython=True)
283
+ def multiply_and_sum(arr, attraction_arr):
284
+ # Element-wise multiplication
285
+ multiplied_arr = arr * attraction_arr
286
+ # Sum the values of each column
287
+ summed_arr = multiplied_arr.sum(axis=0)
288
+ return multiplied_arr, summed_arr
289
+
290
+ def step_3_numba(df_attraction_proNode_sum_total, df_step_2):
291
+ # Convert df_attraction_proNode_sum_total to a NumPy array and transpose it
292
+ attraction_array = df_attraction_proNode_sum_total.values.T.astype(np.float64) # Ensure float64 dtype
293
+
294
+ multiplied_results = []
295
+ summed_results = []
296
+
297
+ for df in df_step_2:
298
+ # Convert DataFrame to NumPy array with float64 dtype
299
+ df_array = df.values.astype(np.float64)
300
+
301
+ # Perform element-wise multiplication and summing
302
+ multiplied_arr, summed_arr = multiply_and_sum(df_array, attraction_array)
303
+
304
+ # Convert results back to DataFrames
305
+ df_multiplied = pd.DataFrame(multiplied_arr, columns=df.columns, index=df.index)
306
+
307
+ # Reshape summed_arr to have shape (26,1) and then convert to DataFrame
308
+ df_summed = pd.DataFrame(summed_arr.reshape(-1, 1), index=df.columns, columns=['Sum'])
309
+
310
+ multiplied_results.append(df_multiplied)
311
+ summed_results.append(df_summed)
312
+
313
+ return multiplied_results, summed_results
314
+
315
+
316
+ # step 4
317
+ @jit(nopython=True)
318
+ def divide_and_sum(arr, divisor_arr):
319
+ # Ensure divisor_arr is broadcastable to arr's shape
320
+ divisor_arr_expanded = divisor_arr.reshape((divisor_arr.shape[0], 1, divisor_arr.shape[1]))
321
+
322
+ # Initialize arrays to store results
323
+ divided_result = np.zeros_like(arr)
324
+ summed_result = np.zeros((arr.shape[0], arr.shape[2]))
325
+
326
+ for i in range(arr.shape[0]):
327
+ for j in range(arr.shape[1]):
328
+ for k in range(arr.shape[2]):
329
+ if divisor_arr_expanded[i, 0, k] != 0:
330
+ divided_result[i, j, k] = arr[i, j, k] / divisor_arr_expanded[i, 0, k]
331
+ summed_result[i, k] += divided_result[i, j, k]
332
+
333
+ return divided_result, summed_result
334
+
335
+ def step_4_numba(distAndAreaBasedAttr_step3, distAndAreaBasedAttr_step3_sum):
336
+ # Convert lists of DataFrames to 3D arrays with dtype float64
337
+ array_step3 = np.array([df.values for df in distAndAreaBasedAttr_step3]).astype(np.float64)
338
+ array_step3_sum = np.array([df.values for df in distAndAreaBasedAttr_step3_sum]).astype(np.float64)
339
+
340
+ # Perform division and summation using Numba
341
+ divided_result, summed_result = divide_and_sum(array_step3, array_step3_sum)
342
+
343
+ # Convert results back to lists of DataFrames
344
+ df_distAndAreaBasedAttr_step4 = [pd.DataFrame(divided_result[i], columns=distAndAreaBasedAttr_step3[0].columns, index=distAndAreaBasedAttr_step3[0].index) for i in range(divided_result.shape[0])]
345
+
346
+ # Correct the creation of the summed DataFrame to avoid setting the 'Sum' index
347
+ df_distAndAreaBasedAttr_step4_sum = [pd.DataFrame(summed_result[i]).T.set_axis(['Sum'], axis='index').set_axis(distAndAreaBasedAttr_step3[0].columns, axis='columns') for i in range(summed_result.shape[0])]
348
+
349
+ return df_distAndAreaBasedAttr_step4, df_distAndAreaBasedAttr_step4_sum
350
+
351
+ # step 5
352
+ @jit(nopython=True)
353
+ def tripsPerArctivity_numba(matrix, attrs):
354
+ rows, cols = attrs.shape[0], matrix.shape[0] # 1464, 26
355
+ result = np.zeros((cols, rows), dtype=np.float64) # Prepare result matrix (26, 1464)
356
+
357
+ for i in range(rows): # Iterate over each area
358
+ for j in range(cols): # Iterate over each land use category
359
+ sum_val = 0.0
360
+ for k in range(cols): # Iterate over each element in the distribution matrix row
361
+ sum_val += matrix[j, k] * attrs[i, k]
362
+ result[j, i] = sum_val
363
+
364
+ return result
365
+
366
+ def step_5_numba(distributionMatrix_step1, distAndAreaBasedAttr_step4):
367
+ sums = []
368
+ count = 0
369
+ total_count = len(distributionMatrix_step1)
370
+
371
+ for df_distributionMatrix_step1, df_distAndAreaBasedAttr_step4 in zip(distributionMatrix_step1, distAndAreaBasedAttr_step4):
372
+ # Convert DataFrames to NumPy arrays with dtype float64
373
+ matrix = df_distributionMatrix_step1.values.astype(np.float64)
374
+ attrs = df_distAndAreaBasedAttr_step4.values.astype(np.float64)
375
+
376
+ result = tripsPerArctivity_numba(matrix, attrs)
377
+ df_result = pd.DataFrame(result, index=df_distributionMatrix_step1.columns, columns=df_distAndAreaBasedAttr_step4.index)
378
+
379
+ sums.append(df_result)
380
+
381
+ count += 1
382
+ #print(f"Iteration {count} out of {total_count} is finished.")
383
+ #print("---------")
384
+
385
+ return sums
386
+
387
+
388
+ # step 6&7
389
+ def step_6_7_vectorized(df_trips_proNode_proActivity_total):
390
+ # Convert each DataFrame to a NumPy array and stack them to form a 3D array
391
+ array_3d = np.array([df.values for df in df_trips_proNode_proActivity_total])
392
+
393
+ # Sum across the middle axis (columns of each DataFrame)
394
+ summed_array = array_3d.sum(axis=1)
395
+
396
+ # Convert the summed array back to a DataFrame
397
+ final_matrix = pd.DataFrame(summed_array, index=df_trips_proNode_proActivity_total[0].columns, columns=df_trips_proNode_proActivity_total[0].columns)
398
+
399
+ return final_matrix
400
+
401
+
402
+ # step 8
403
+
404
+ def adjTripRate_adjFactor(tripMatrix,df_total_trips_allNodes_sumPerson, targetRate=1, factor=1 ):
405
+ df_tripMatrix_total_sum = tripMatrix.sum().sum()
406
+ df_total_trips_allNodes_sumPerson_total = df_total_trips_allNodes_sumPerson.sum()
407
+
408
+ # scale to target trip rate
409
+ tripRateBeforeAdjustment = df_tripMatrix_total_sum/df_total_trips_allNodes_sumPerson_total
410
+ print("tripRateBeforeAdjustment",tripRateBeforeAdjustment)
411
+ adjustmentRate = targetRate/tripRateBeforeAdjustment
412
+ print("adjustmentRate",adjustmentRate)
413
+
414
+ # scale by ... scale factor (outdated, was hardcoded )
415
+ df_tripMatrix_adjusted = tripMatrix * adjustmentRate
416
+ #df_tripMatrix_adjusted_scaled = df_tripMatrix_adjusted.div(factor)
417
+ return df_tripMatrix_adjusted, df_tripMatrix_adjusted # df_tripMatrix_adjusted_scaled
418
+
419
+ # Uniform Matrix
420
+ def decay(d, alpha):
421
+ return math.exp(d * alpha * -1)
422
+
423
+ def distanceDecay(df, alpha):
424
+ return df.applymap(lambda x: decay(x, alpha))
425
+
426
+ def matrix_reduce_add(df):
427
+ return df[df != sys.float_info.max].sum().sum()
428
+
429
+ def replace_maxValue(df):
430
+ return df.replace(sys.float_info.max, 0)
431
+
432
+
433
+ #Trip gen matrix is used to scale the distance matrix
434
+ def getUniformMatrix(distanceMatrix, tripGenMatrix, alpha):
435
+
436
+ distanceMatrix_withDecay = distanceDecay(distanceMatrix, alpha)
437
+ distanceMatrix_sum = matrix_reduce_add(distanceMatrix_withDecay)
438
+ tripGenMatrix_sum = matrix_reduce_add(tripGenMatrix)
439
+ ratio = distanceMatrix_sum / tripGenMatrix_sum
440
+
441
+ uniformMatrix = distanceMatrix_withDecay.div(ratio)
442
+
443
+ return replace_maxValue(uniformMatrix)
444
+
445
+
446
+ #==================================================================================================
447
+ #Modal Split functions
448
+
449
+ def computeModalShare(trip_matrix, dist_matrices, alpha, f_values=None):
450
+ """
451
+ Process matrices or DataFrames with exponentiation and normalization.
452
+
453
+ Args:
454
+ trip_matrix (np.ndarray or pd.DataFrame): The trip matrix.
455
+ dist_matrices (dict of np.ndarray or pd.DataFrame): Dictionary of distance matrices.
456
+ alpha (float): The alpha coefficient.
457
+ f_values (dict of float, optional): Dictionary of f coefficients for each matrix. If None, defaults to 0 for each matrix.
458
+
459
+ Returns:
460
+ dict: Normalized matrices.
461
+ """
462
+
463
+ # Default f_values to 0 for each key in dist_matrices if not provided
464
+ if not f_values:
465
+ f_values = {key: 0 for key in dist_matrices.keys()}
466
+
467
+ exp_matrices = {}
468
+ for key, matrix in dist_matrices.items():
469
+ f = f_values.get(key, 0)
470
+
471
+ # Convert DataFrame to numpy array if needed
472
+ if isinstance(matrix, pd.DataFrame):
473
+ matrix = matrix.values
474
+
475
+ exp_matrix = np.exp(-1 * (matrix * alpha + f))
476
+ exp_matrices[key] = exp_matrix
477
+
478
+ # Calculate the sum of all exponentials
479
+ sum_exp = sum(exp_matrices.values())
480
+
481
+ # Normalize each matrix & multiply by trip matrix and update the matrices
482
+ normalized_matrices = {key: (exp_matrix / sum_exp) * trip_matrix for key, exp_matrix in exp_matrices.items()}
483
+
484
+ return normalized_matrices
485
+
486
+
487
+ def redistributeModalShares(dist_matrices, trip_matrices, redistribution_rules, threshold=0.5):
488
+ """
489
+ Redistribute trips among mobility networks based on given redistribution rules and when travel times are within a specified threshold.
490
+
491
+ Args:
492
+ dist_matrices (dict): Dictionary of distance matrices (travel times) for different mobility networks, keyed by identifier.
493
+ trip_matrices (dict): Dictionary of matrices representing the number of trips for each mobility network, keyed by identifier.
494
+ redistribution_rules (list): List of redistribution rules with "from" and "to" network identifiers.
495
+ threshold (float): The threshold for considering travel times as similar.
496
+
497
+ Returns:
498
+ dict: Updated dictionary of trip matrices with transferred trips.
499
+ """
500
+
501
+ # Verify that all specified matrices exist in the input dictionaries
502
+ for rule in redistribution_rules:
503
+ if rule["from"] not in dist_matrices or rule["from"] not in trip_matrices:
504
+ raise ValueError(f"Matrix ID {rule['from']} not found in the inputs.")
505
+ for to_id in rule["to"]:
506
+ if to_id not in dist_matrices or to_id not in trip_matrices:
507
+ raise ValueError(f"Matrix ID {to_id} not found in the inputs.")
508
+
509
+ # Copy the trip_matrices to avoid modifying the input directly
510
+ updated_trip_matrices = {k: v.copy() for k, v in trip_matrices.items()}
511
+
512
+ # Redistribute trips based on the rules and the threshold
513
+ for rule in redistribution_rules:
514
+ from_matrix_id = rule["from"]
515
+ from_matrix_trips = updated_trip_matrices[from_matrix_id]
516
+ from_matrix_dist = dist_matrices[from_matrix_id]
517
+
518
+ for to_matrix_id in rule["to"]:
519
+ to_matrix_dist = dist_matrices[to_matrix_id]
520
+
521
+ # Create a boolean array where the absolute difference in travel times is less than or equal to the threshold
522
+ similar_travel_time = np.abs(from_matrix_dist - to_matrix_dist) <= threshold
523
+
524
+ # Find the indices where there are trips to transfer under the new condition
525
+ indices_to_transfer = similar_travel_time & (from_matrix_trips > 0)
526
+
527
+ # Transfer trips where the condition is True
528
+ updated_trip_matrices[to_matrix_id][indices_to_transfer] += from_matrix_trips[indices_to_transfer]
529
+
530
+ # Zero out the transferred trips in the from_matrix
531
+ from_matrix_trips[indices_to_transfer] = 0
532
+
533
+ # Return the updated trip matrices dictionary
534
+ return updated_trip_matrices
535
+
536
+
537
+
538
+ def computeDistanceBrackets(trip_matrices, metric_dist_matrices, dist_brackets=[800, 2400, 4800]):
539
+ # Transform the keys of metric_dist_matrices to match with trip_matrices
540
+ transformed_metric_keys = {key.replace("metric_matrix", "distance_matrix")+"_noEntr": matrix
541
+ for key, matrix in metric_dist_matrices.items()}
542
+
543
+ # Initialize dictionary to store aggregated trips per distance bracket
544
+ bracket_totals = {bracket: 0 for bracket in dist_brackets}
545
+
546
+ # Iterate over each pair of trip matrix and distance matrix
547
+ for key, trip_matrix in trip_matrices.items():
548
+ # Find the corresponding distance matrix
549
+ dist_matrix = transformed_metric_keys.get(key)
550
+ if dist_matrix is None:
551
+ print("no matrxi found")
552
+ continue # Skip if no corresponding distance matrix found
553
+
554
+ # Calculate trips for each distance bracket
555
+ for i, bracket in enumerate(dist_brackets):
556
+ if i == 0:
557
+ # For the first bracket, count trips with distance <= bracket
558
+ bracket_totals[bracket] += (trip_matrix[dist_matrix <= bracket]).sum().sum()
559
+ else:
560
+ # For subsequent brackets, count trips within the bracket range
561
+ prev_bracket = dist_brackets[i - 1]
562
+ bracket_totals[bracket] += (trip_matrix[(dist_matrix > prev_bracket) & (dist_matrix <= bracket)]).sum().sum()
563
+ brackets_sum = sum(bracket_totals.values())
564
+ brackets_rel = {str(bracket): round(total / brackets_sum, 3) for bracket, total in bracket_totals.items()}
565
+ return brackets_rel
566
+
567
+
568
+ def computeTripStats(trip_matrices, distance_matrices, metric_dist_matrices, pop):
569
+ # Transform the keys of metric_dist_matrices to match with trip_matrices
570
+ transformed_metric_keys = {key.replace("metric_matrix", "distance_matrix")+"_noEntr": matrix
571
+ for key, matrix in metric_dist_matrices.items()}
572
+
573
+ trips = 0
574
+ totalTravelDistance = 0
575
+ totalTravelTime = 0
576
+ # Iterate over each pair of trip matrix and distance matrix
577
+ for key, trip_matrix in trip_matrices.items():
578
+ # Find the corresponding distance matrix
579
+ metric_dist_matrix = transformed_metric_keys.get(key)
580
+ dist_matrix = distance_matrices.get(key)
581
+ if metric_dist_matrix is None:
582
+ print("no matrxi found")
583
+ continue # Skip if no corresponding distance matrix found
584
+
585
+ # compute
586
+ totalTravelTime += (dist_matrix*trip_matrix).sum().sum()
587
+ trips += trip_matrix.sum().sum()
588
+ totalTravelDistance += (metric_dist_matrix*trip_matrix).sum().sum()
589
+
590
+ MeanTripDistance = totalTravelDistance/trips
591
+ MeanTravelDistancePerPerson = totalTravelDistance/pop
592
+
593
+ MeanTravelTime = totalTravelTime/trips
594
+ MeanTravelTimePerPerson = totalTravelTime/pop
595
+
596
+ return totalTravelDistance, totalTravelTime, MeanTripDistance, MeanTravelDistancePerPerson, MeanTravelTime, MeanTravelTimePerPerson
597
+
598
+ def calculate_relative_mode_share(trip_matrices):
599
+ """
600
+ Calculate the relative mode share for a dictionary of trip matrices.
601
+
602
+ Args:
603
+ trip_matrices (dict of np.ndarray or pd.DataFrame): Dictionary of trip matrices.
604
+
605
+ Returns:
606
+ dict: Relative mode distribution for each key in trip_matrices.
607
+ """
608
+
609
+ # Compute the total trips for each mode
610
+ total_trips_per_mode = {key: matrix.sum().sum() for key, matrix in trip_matrices.items()}
611
+
612
+ # Compute the total trips across all modes
613
+ total_trips_all_modes = sum(total_trips_per_mode.values())
614
+
615
+ # Calculate the relative mode distribution
616
+ rel_mode_distribution = {key: trips_per_mode / total_trips_all_modes for key, trips_per_mode in total_trips_per_mode.items()}
617
+
618
+ return rel_mode_distribution
619
+
620
+
621
+ def extract_distance_matrices(stream, distance_matrices_of_interest):
622
+ """
623
+ Extract distance matrices from the stream and convert them to pandas DataFrames.
624
+ Args:
625
+ stream (dict): Stream data containing distance matrices.
626
+ distance_matrices_of_interest (list of str): List of keys for the distance matrices of interest.
627
+ Returns:
628
+ dict: A dictionary of pandas DataFrames, where each key is a distance matrix kind.
629
+ """
630
+ distance_matrices = {}
631
+ for distMK in distance_matrices_of_interest:
632
+ for distM in stream["@Data"]['@{0}']:
633
+ #print( distM.__dict__.keys())
634
+ try:
635
+ distMdict = distM.__dict__[distMK]
636
+
637
+ distance_matrix_dict = json.loads(distMdict)
638
+ origin_ids = distance_matrix_dict["origin_uuid"]
639
+ destination_ids = distance_matrix_dict["destination_uuid"]
640
+ distance_matrix = distance_matrix_dict["matrix"]
641
+
642
+ # Convert the distance matrix to a DataFrame
643
+ df_distances = pd.DataFrame(distance_matrix, index=origin_ids, columns=destination_ids)
644
+ distance_matrices[distMK] = df_distances
645
+ except Exception as e:
646
+ pass
647
+ return distance_matrices
648
+ #==================================================================================================
649
+
650
+
651
+
652
+ def computeTrips(
653
+ df_distributionMatrix,
654
+ df_total_trips_allNodes,
655
+ df_distMatrix_speckle,
656
+ df_alphas,
657
+ df_attraction_proNode_sum_total,
658
+ df_distances_aligned,
659
+ TARGET_TRIP_RATE,
660
+ SCALING_FACTOR,
661
+ total_population,
662
+ tot_res,
663
+ tot_vis,
664
+
665
+
666
+ distance_matrices,
667
+ metric_matrices,
668
+ redistributeTrips,
669
+ DISTANCE_BRACKETS,
670
+
671
+ alpha_low, alpha_med, alpha_high,
672
+ alpha_mode,
673
+ alpha_uniform,
674
+ NEW_F_VALUES,
675
+
676
+ CLIENT,
677
+ TARGET_STREAM,
678
+ TARGET_BRANCH,
679
+ sourceInfo="",
680
+ ):
681
+
682
+ NEW_ALPHAS = reconstruct_dataframe(alpha_low, alpha_med, alpha_high, df_alphas)
683
+ NEW_MODE_ALPHA = alpha_mode
684
+
685
+
686
+ # ====
687
+ #step 1
688
+ distributionMatrix_step1M = step_1(df_distributionMatrix,
689
+ df_total_trips_allNodes)
690
+
691
+ #step 2
692
+ df_step_2M = step_2_vectorized(df_distMatrix_speckle,
693
+ NEW_ALPHAS)
694
+
695
+
696
+
697
+
698
+ #step 3
699
+ distAndAreaBasedAttr_step3M, distAndAreaBasedAttr_step3_sumM = step_3_numba(df_attraction_proNode_sum_total,
700
+ df_step_2M)
701
+
702
+
703
+ #step 4
704
+ distAndAreaBasedAttr_step4M, distAndAreaBasedAttr_step4_sumM = step_4_numba(distAndAreaBasedAttr_step3M,
705
+ distAndAreaBasedAttr_step3_sumM)
706
+
707
+
708
+
709
+ #step 5
710
+ df_trips_proNode_proActivity_totalM = step_5_numba(distributionMatrix_step1M,
711
+ distAndAreaBasedAttr_step4M)
712
+
713
+ #step 6 & 7
714
+ df_tripMatrixM = step_6_7_vectorized(df_trips_proNode_proActivity_totalM)
715
+
716
+
717
+ #step 8
718
+ df_tripMatrix_adjustedM, df_tripMatrix_adjusted_scaledM = adjTripRate_adjFactor(df_tripMatrixM,
719
+ total_population,
720
+ TARGET_TRIP_RATE,
721
+ SCALING_FACTOR )
722
+ #------
723
+ #MAIN 1 compute trip matrice per mode
724
+ trip_matricesM = computeModalShare(df_tripMatrix_adjusted_scaledM,
725
+ distance_matrices,
726
+ NEW_MODE_ALPHA,
727
+ f_values=NEW_F_VALUES)
728
+
729
+ #MAIN 2 compute modal shares (redistribute trips in case of identical travel time)
730
+ trip_matrices_redisM = redistributeModalShares(distance_matrices,
731
+ trip_matricesM,
732
+ redistributeTrips)
733
+
734
+ #POST 1 compute mode shares
735
+ rel_mode_distributionM = calculate_relative_mode_share(trip_matrices_redisM)
736
+
737
+
738
+ #POST 2 distance brackets
739
+ dist_sharesM = computeDistanceBrackets(trip_matrices_redisM,
740
+ metric_matrices,
741
+ DISTANCE_BRACKETS)
742
+
743
+ #POST 3 compute more stats
744
+ (totalTravelDistance, totalTravelTime,
745
+ MeanTripDistance,MeanTravelDistancePerPerson,
746
+ MeanTripTime, MeanTravelTimePerPerson) = computeTripStats(trip_matrices_redisM,
747
+ distance_matrices,
748
+ metric_matrices,
749
+ total_population)
750
+
751
+
752
+ uniform_tripmatrix = getUniformMatrix(df_distances_aligned, df_tripMatrix_adjustedM, alpha_uniform)
753
+
754
+ #add to dataframe
755
+ # Define your parameter and target values
756
+ newdata = {
757
+ # Model Parameter==
758
+
759
+ # Alpha - Routing
760
+ "alpha_low": alpha_low,
761
+ "alpha_med": alpha_med,
762
+ "alpha_high": alpha_high,
763
+ "alpha_uniform":alpha_uniform,
764
+
765
+ "fvalues":NEW_F_VALUES,
766
+
767
+
768
+ "alpha_mode":NEW_MODE_ALPHA,
769
+
770
+ # Model Indicators ==
771
+
772
+ # Modal Shares
773
+ "share_ped_mm_art": rel_mode_distributionM['activity_node+distance_matrix_ped_mm_art_noEntr'],
774
+ "share_ped_mm": rel_mode_distributionM['activity_node+distance_matrix_ped_mm_noEntr'],
775
+ "share_ped": rel_mode_distributionM['activity_node+distance_matrix_ped_noEntr'],
776
+ "share_ped_art": rel_mode_distributionM['activity_node+distance_matrix_ped_art_noEntr'],
777
+
778
+ # Tripshares by Distance Brackets
779
+ "800": dist_sharesM["800"],
780
+ "2400": dist_sharesM["2400"],
781
+ "4800": dist_sharesM["4800"],
782
+
783
+ # Travel Time & Distances
784
+ "totalTravelDistance":totalTravelDistance,
785
+ "totalTravelTime":totalTravelTime,
786
+ "MeanTravelTimePerPerson":MeanTravelTimePerPerson,
787
+
788
+ # Trip Distances
789
+ "MeanTripDistance":MeanTripDistance,
790
+ "MeanTripTime":MeanTripTime,
791
+ "MeanTravelDistancePerPerson":MeanTravelDistancePerPerson,
792
+
793
+ }
794
+
795
+
796
+
797
+ trip_matrice_adjName = {k.replace("distance", "trip"):v for k, v in trip_matricesM.items()}
798
+ trip_matrice_adjName["tripMatrix_landuse"] = df_tripMatrix_adjusted_scaledM
799
+ trip_matrice_adjName["tripMatrix_uniform"] = uniform_tripmatrix
800
+
801
+ extraData = {"population":total_population,
802
+ "residents":tot_res,
803
+ "visitors":tot_vis,
804
+ "parameter":newdata,
805
+ }
806
+
807
+ commitMsg = "automatic update"
808
+ try:
809
+ commitMsg += " using these commits: #+ "
810
+ for k,v in sourceInfo.items():
811
+ commitMsg += f" {k}: {v}"
812
+ except:
813
+ pass
814
+ print(commitMsg)
815
+
816
+ commit_id = send_matrices_and_create_commit(
817
+ trip_matrice_adjName,
818
+ CLIENT,
819
+ TARGET_STREAM,
820
+ TARGET_BRANCH,
821
+ commitMsg,
822
+ rows_per_chunk=300,
823
+ containerMetadata=extraData
824
+ )
825
+ print ("===============================")
826
+ return newdata
827
+
828
+
829
+ #==================================================================================================
830
+ # speckle send
831
+
832
+ def send_row_bundle(rows, indices, transport):
833
+ bundle_object = Base()
834
+ bundle_object.rows = rows
835
+ bundle_object.indices = indices
836
+ bundle_id = operations.send(base=bundle_object, transports=[transport])
837
+ return bundle_id
838
+
839
+ def send_matrix(matrix_df, transport, rows_per_chunk):
840
+ matrix_object = Base(metaData="Some metadata")
841
+ batch_index = 0 # Maintain a separate counter for batch indexing
842
+
843
+ # Bundle rows together
844
+ rows = []
845
+ indices = []
846
+ for index, row in matrix_df.iterrows():
847
+ rows.append([round(r,4) for r in row.tolist()])
848
+ indices.append(str(index))
849
+ if len(rows) == rows_per_chunk:
850
+ bundle_id = send_row_bundle(rows, indices, transport)
851
+ # Set the reference to the bundle in the matrix object using setattr
852
+ setattr(matrix_object, f"@batch_{batch_index}", {"referencedId": bundle_id})
853
+ rows, indices = [], [] # Reset for the next bundle
854
+ batch_index += 1 # Increment the batch index
855
+ print( str(rows_per_chunk) +" rows has been sent")
856
+
857
+ # Don't forget to send the last bundle if it's not empty
858
+ if rows:
859
+ bundle_id = send_row_bundle(rows, indices, transport)
860
+ setattr(matrix_object, f"@batch_{batch_index}", {"referencedId": bundle_id})
861
+
862
+ # Send the matrix object to Speckle
863
+ matrix_object_id = operations.send(base=matrix_object, transports=[transport])
864
+ return matrix_object_id
865
+
866
+
867
+
868
+
869
+
870
+ # Main function to send all matrices and create a commit
871
+ def send_matrices_and_create_commit(matrices, client, stream_id, branch_name, commit_message, rows_per_chunk, containerMetadata):
872
+ transport = ServerTransport(client=client, stream_id=stream_id)
873
+ matrix_ids = {}
874
+
875
+ # Send each matrix row by row and store its object ID
876
+ for k, df in matrices.items():
877
+ matrix_ids[k] = send_matrix(df, transport, rows_per_chunk)
878
+ print("object: " + k + " has been sent")
879
+
880
+ # Create a container object that will hold references to all the matrix objects
881
+ container_object = Base()
882
+
883
+ for k, v in containerMetadata.items():
884
+ container_object[k] = v
885
+
886
+ # Assuming you have a way to reference matrix objects by their IDs in Speckle
887
+ for k, obj_id in matrix_ids.items():
888
+ print("obj_id", obj_id)
889
+ container_object[k] = obj_id
890
+
891
+
892
+ # Dynamically add references to the container object
893
+ for matrix_name, matrix_id in matrix_ids.items():
894
+ # This assigns a reference to the matrix object by its ID
895
+ # You might need to adjust this based on how your Speckle server expects to receive references
896
+ setattr(container_object, matrix_name, {"referencedId": matrix_id})
897
+
898
+
899
+
900
+ # Send the container object
901
+ container_id = operations.send(base=container_object, transports=[transport])
902
+
903
+
904
+ # Now use the container_id when creating the commit
905
+ commit_id = client.commit.create(
906
+ stream_id=stream_id,
907
+ object_id=container_id, # Use the container's ID here
908
+ branch_name=branch_name,
909
+ message=commit_message,
910
+ )
utils.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import time
3
+ from functools import wraps
4
+
5
+ def reconstruct_dataframe(alpha_low, alpha_med, alpha_high, original_df):
6
+ # Define the mapping from original values to new alpha parameters
7
+ value_to_alpha = {
8
+ 0.00191: alpha_low,
9
+ 0.00767: alpha_high,
10
+ 0.0038: alpha_med
11
+ }
12
+
13
+ # Check if each value is present at least once in the DataFrame
14
+ for original_value in value_to_alpha.keys():
15
+ if not (original_df == original_value).any().any():
16
+ raise ValueError(f"Value {original_value} not found in the input DataFrame.")
17
+
18
+ # Create a new DataFrame based on the original one
19
+ new_df = original_df.copy()
20
+
21
+ # Apply the mapping to each element in the DataFrame
22
+ for original_value, new_value in value_to_alpha.items():
23
+ new_df = new_df.replace(original_value, new_value)
24
+
25
+ return new_df
26
+
27
+ def preprocess_dataFrame(df, headerRow_idx=0, numRowsStart_idx = None, numRowsEnd_idx=None, numColsStart_idx=None, numColsEnd_idx=None, rowNames_idx=None):
28
+ df.columns = df.iloc[headerRow_idx] #Set the header
29
+ if rowNames_idx is not None:
30
+ df.index = df.iloc[:, rowNames_idx] #Set the row names
31
+ df = df.iloc[numRowsStart_idx : numRowsEnd_idx, numColsStart_idx:numColsEnd_idx] #Slice the dataset to numerical data
32
+ return df
33
+
34
+
35
+
36
+
37
+ def timeit(f):
38
+ def timed(*args, **kw):
39
+ ts = time.time()
40
+ result = f(*args, **kw)
41
+ te = time.time()
42
+ print ('func:%r args:[%r, %r] took: %2.4f sec' % \
43
+ (f.__name__, te-ts))
44
+ #(f.__name__, args, kw, te-ts))
45
+ return result
46
+ return timed
47
+
48
+
49
+
50
+
51
+
52
+ def timing_decorator(func):
53
+ @wraps(func)
54
+ def wrapper(*args, **kwargs):
55
+ start_time = time.time()
56
+ result = func(*args, **kwargs)
57
+ end_time = time.time()
58
+
59
+ duration = end_time - start_time
60
+ timestamp = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(end_time))
61
+
62
+ print(f"{func.__name__} took {duration:.4f} seconds. Finished at {timestamp}")
63
+ return result
64
+
65
+ return wrapper
66
+
67
+
68
+ # Function to compare two dataframes after converting and rounding
69
+ def compare_dataframes(df1, df2, decimals=8):
70
+ # Function to convert DataFrame columns to float and then round
71
+ def convert_and_round_dataframe(df, decimals):
72
+ # Convert all columns to float
73
+ df_float = df.astype(float)
74
+ # Round to the specified number of decimals
75
+ return df_float.round(decimals)
76
+
77
+ rounded_df1 = convert_and_round_dataframe(df1, decimals)
78
+ rounded_df2 = convert_and_round_dataframe(df2, decimals)
79
+
80
+ are_equal = rounded_df1.equals(rounded_df2)
81
+
82
+ print("Both methods are equal:", are_equal)
83
+
84
+ print("Numba shape:", df2.shape)
85
+ print("Original shape:", df1.shape)
86
+
87
+ print("======== ORIGINAL OUTPUT (first item in output list, head() for the first 5 columns)")
88
+ print(df1.iloc[0:5].head(2))
89
+
90
+ print("======== New method OUTPUT (first item in output list, head() for the first 5 columns)")
91
+ print(df2.iloc[0:5].head(2))
92
+
93
+
94
+ def align_dataframes(df1, df2, key):
95
+ """
96
+ Align two dataframes based on a common key, ensuring that both dataframes
97
+ have only the rows with matching keys.
98
+
99
+ Parameters:
100
+ - df1: First dataframe.
101
+ - df2: Second dataframe.
102
+ - key: Column name to align dataframes on.
103
+
104
+ Returns:
105
+ - df1_aligned, df2_aligned: Tuple of aligned dataframes.
106
+ """
107
+ common_ids = df1.index.intersection(df2[key])
108
+ df1_aligned = df1.loc[common_ids]
109
+ df2_aligned = df2[df2[key].isin(common_ids)].set_index(key, drop=False)
110
+ return df1_aligned, df2_aligned
111
+