def test_save(self): a = AequilibraeMatrix() a.load(self.sf_skims) a.computational_view(['distance']) new_mat = np.random.rand(a.zones, a.zones) a.matrix_view *= new_mat res = a.matrix_view.sum() a.save('new_name_for_matrix') self.assertEqual(res, a.matrix_view.sum(), 'Saved wrong result') a.save(['new_name_for_matrix2']) self.assertEqual(a.view_names[0], 'new_name_for_matrix2', 'Did not update computational view') self.assertEqual(len(a.view_names), 1, 'computational view with the wrong number of matrices') a.computational_view(['distance', 'new_name_for_matrix']) with self.assertRaises(ValueError): a.save(['just_one_name']) a.save(['one_name', 'two_names']) with self.assertRaises(ValueError): a.save('distance') b = AequilibraeMatrix() b.load(self.name_test) b.computational_view("seed") b.save() b.computational_view(["mat", "seed", "dist"]) b.save()
def test_save(self): a = AequilibraeMatrix() a.load(self.sf_skims) a.computational_view(["distance"]) new_mat = np.random.rand(a.zones, a.zones) a.matrix_view *= new_mat res = a.matrix_view.sum() a.save("new_name_for_matrix") self.assertEqual(res, a.matrix_view.sum(), "Saved wrong result") a.save(["new_name_for_matrix2"]) self.assertEqual(a.view_names[0], "new_name_for_matrix2", "Did not update computational view") self.assertEqual(len(a.view_names), 1, "computational view with the wrong number of matrices") a.computational_view(["distance", "new_name_for_matrix"]) with self.assertRaises(ValueError): a.save(["just_one_name"]) a.save(["one_name", "two_names"]) with self.assertRaises(ValueError): a.save("distance") b = AequilibraeMatrix() b.load(self.name_test) b.computational_view("seed") b.save() b.computational_view(["mat", "seed", "dist"]) b.save()
def test_skimming_on_assignment(self): matrix = AequilibraeMatrix() matrix.load(os.path.join(gettempdir(), self.mat_name)) matrix.computational_view(["cars"]) res = AssignmentResults() res.prepare(self.g, matrix) self.g.set_skimming([]) self.g.set_blocked_centroid_flows(True) assig = allOrNothing(matrix, self.g, res) assig.execute() if res.skims.distance.sum() > 0: self.fail( "skimming for nothing during assignment returned something different than zero" ) self.g.set_skimming("distance") res.prepare(self.g, matrix) assig = allOrNothing(matrix, self.g, res) assig.execute() if res.skims.distance.sum() != 2914644.0: self.fail("skimming during assignment returned the wrong value") matrix.close()
def new_record(self, name: str, file_name: str, matrix=AequilibraeMatrix()) -> MatrixRecord: """Creates a new record for a matrix in disk, but does not save it If the matrix file is not already on disk, it will fail Args: *name* (:obj:`str`): Name of the matrix *file_name* (:obj:`str`): Name of the file on disk Return: *matrix_record* (:obj:`MatrixRecord`): A matrix record that can be manipulated in memory before saving """ if name in self.__items: raise ValueError( f"There is already a matrix of name ({name}). It must be unique." ) for mat in self.__items.values(): if mat.file_name == file_name: raise ValueError( f"There is already a matrix record for file name ({file_name}). It must be unique." ) if matrix.cores > 0: if isfile(join(self.fldr, file_name)): raise FileExistsError( f"{file_name} already exists. Choose a different name or matrix format" ) mat_format = file_name.split(".")[-1].lower() if mat_format not in ["omx", "aem"]: raise ValueError( "Matrix needs to be either OMX or native AequilibraE") matrix.export(join(self.fldr, file_name)) cores = matrix.cores else: if not isfile(join(self.fldr, file_name)): raise FileExistsError( f"{file_name} does not exist. Cannot create this matrix record" ) mat = AequilibraeMatrix() mat.load(join(self.fldr, file_name)) cores = mat.cores mat.close() del mat tp = {key: None for key in self.__fields} tp["name"] = name tp["file_name"] = file_name tp["cores"] = cores mr = MatrixRecord(tp) mr.save() self.__items[name.lower()] = mr logger.warning("Matrix Record has been saved to the database") return mr
def test_execute(self): # Loads and prepares the graph car_loads = [] two_class_loads = [] for extension in ["omx", "aem"]: matrix = AequilibraeMatrix() if extension == 'omx': mat_name = os.path.join(gettempdir(), "my_matrix." + extension) else: mat_name = self.mat_name matrix.load(mat_name) matrix.computational_view(["cars"]) # Performs assignment res = AssignmentResults() res.prepare(self.g, matrix) assig = allOrNothing(matrix, self.g, res) assig.execute() car_loads.append(res.link_loads) res.save_to_disk( os.path.join(gettempdir(), "link_loads_{}.aed".format(extension))) res.save_to_disk( os.path.join(gettempdir(), "link_loads_{}.csv".format(extension))) matrix.computational_view() # Performs assignment res = AssignmentResults() res.prepare(self.g, matrix) assig = allOrNothing(matrix, self.g, res) assig.execute() two_class_loads.append(res.link_loads) res.save_to_disk( os.path.join(gettempdir(), "link_loads_2_classes_{}.aed".format(extension))) res.save_to_disk( os.path.join(gettempdir(), "link_loads_2_classes_{}.csv".format(extension))) matrix.close() load_diff = two_class_loads[0] - two_class_loads[1] if load_diff.max() > 0.0000000001 or load_diff.max() < -0.0000000001: self.fail( "Loads for two classes differ for OMX and AEM matrix types") load_diff = car_loads[0] - car_loads[1] if load_diff.max() > 0.0000000001 or load_diff.max() < -0.0000000001: self.fail( "Loads for a single class differ for OMX and AEM matrix types")
def test_get_matrix(self): self.test_load() a = AequilibraeMatrix() a.load(siouxfalls_skims) with self.assertRaises(AttributeError): a.get_matrix('does not exist') q = a.get_matrix('distance') self.assertEqual(q.shape[0], 24) a = AequilibraeMatrix() a.load(name_test) print(np.array_equal(a.get_matrix("seed"), a.matrix["seed"]))
def test_get_matrix(self): a = AequilibraeMatrix() a.load(self.sf_skims) with self.assertRaises(AttributeError): a.get_matrix("does not exist") q = a.get_matrix("distance") self.assertEqual(q.shape[0], 24) a = AequilibraeMatrix() a.load(self.name_test) print(np.array_equal(a.get_matrix("seed"), a.matrix["seed"])) del a
def test_calibrate_with_omx(self): imped = AequilibraeMatrix() imped.load(siouxfalls_skims) imped.computational_view(['free_flow_time']) mat = AequilibraeMatrix() mat.load(siouxfalls_demand) mat.computational_view() args = {"impedance": imped, "matrix": mat, "function": "power", "nan_to_zero": False} distributed_matrix = GravityCalibration(**args) distributed_matrix.calibrate() if distributed_matrix.gap > 0.0001: self.fail("Calibration did not converge") args = {"impedance": imped, "matrix": mat, "function": "power", "nan_to_zero": True} distributed_matrix = GravityCalibration(**args) distributed_matrix.calibrate() if distributed_matrix.gap > 0.0001: self.fail("Calibration did not converge")
def update_database(self) -> None: """Adds records to the matrices database for matrix files found on disk""" existing_files = os.listdir(self.fldr) paths_for_existing = [mat.file_name for mat in self.__items.values()] new_files = [x for x in existing_files if x not in paths_for_existing] new_files = [ x for x in new_files if os.path.splitext(x.lower())[1] in [".omx", ".aem"] ] if new_files: logger.warning( f'New matrix found on disk. Added to the database: {",".join(new_files)}' ) for fl in new_files: mat = AequilibraeMatrix() mat.load(join(self.fldr, fl)) name = None if not mat.is_omx(): name = str(mat.name).lower() if not name: name = fl.lower() name = name.replace(".", "_").replace(" ", "_") if name in self.__items: i = 0 while f"{name}_{i}" in self.__items: i += 1 name = f"{name}_{i}" rec = self.new_record(name, fl) rec.save()
class TestAequilibraeMatrix(TestCase): matrix = None def setUp(self) -> None: self.sf_skims = f"/Aequilibrae_matrix_{uuid.uuid4()}.omx" copyfile(siouxfalls_skims, self.sf_skims) temp_folder = gettempdir() self.name_test = temp_folder + f"/Aequilibrae_matrix_{uuid.uuid4()}.aem" self.copy_matrix_name = temp_folder + f"/Aequilibrae_matrix_{uuid.uuid4()}.aem" self.csv_export_name = temp_folder + f"/Aequilibrae_matrix_{uuid.uuid4()}.csv" self.omx_export_name = temp_folder + f"/Aequilibrae_matrix_{uuid.uuid4()}.omx" if self.matrix is not None: return args = { "file_name": self.name_test, "zones": zones, "matrix_names": ["mat", "seed", "dist"], "index_names": ["my indices"], } self.matrix = AequilibraeMatrix() self.matrix.create_empty(**args) self.matrix.index[:] = np.arange(self.matrix.zones) + 100 self.matrix.mat[:, :] = np.random.rand(self.matrix.zones, self.matrix.zones)[:, :] self.matrix.mat[:, :] = self.matrix.mat[:, :] * (1000 / np.sum(self.matrix.mat[:, :])) self.matrix.setName("Test matrix - " + str(random.randint(1, 10))) self.matrix.setDescription("Generated at " + datetime.datetime.now().strftime("%I:%M%p on %B %d, %Y")) self.new_matrix = self.matrix def tearDown(self) -> None: try: del self.matrix os.remove(self.name_test) if os.path.exists(self.name_test) else None os.remove(self.csv_export_name) if os.path.exists(self.csv_export_name) else None os.remove(self.copy_matrix_name) if os.path.exists(self.copy_matrix_name) else None os.remove(self.omx_export_name) if os.path.exists(self.omx_export_name) else None except Exception as e: print(f"Could not delete. {e.args}") def test_load(self): self.new_matrix = AequilibraeMatrix() # Cannot load OMX file with no indices with self.assertRaises(LookupError): self.new_matrix.load(no_index_omx) self.new_matrix = AequilibraeMatrix() self.new_matrix.load(self.name_test) del self.new_matrix def test_computational_view(self): self.new_matrix.computational_view(["mat", "seed"]) self.new_matrix.mat.fill(0) self.new_matrix.seed.fill(0) if self.new_matrix.matrix_view.shape[2] != 2: self.fail("Computational view returns the wrong number of matrices") self.new_matrix.computational_view(["mat"]) self.new_matrix.matrix_view[:, :] = np.arange(zones ** 2).reshape(zones, zones) if np.sum(self.new_matrix.mat) != np.sum(self.new_matrix.matrix_view): self.fail("Assigning to matrix view did not work") self.new_matrix.setName("Test matrix - " + str(random.randint(1, 10))) self.new_matrix.setDescription("Generated at " + datetime.datetime.now().strftime("%I:%M%p on %B %d, %Y")) del self.new_matrix def test_computational_view_with_omx(self): self.new_matrix = AequilibraeMatrix() self.new_matrix.load(omx_example) arrays = ["m1", "m2"] self.new_matrix.computational_view(arrays) total_mats = np.sum(self.new_matrix.matrix_view) self.new_matrix.computational_view([arrays[0]]) total_m1 = np.sum(self.new_matrix.matrix_view) self.new_matrix.close() omx_file = omx.open_file(omx_example, "r") m1 = np.array(omx_file["m1"]).sum() m2 = np.array(omx_file["m2"]).sum() self.assertEqual(m1 + m2, total_mats) self.assertEqual(m1, total_m1) omx_file.close() del omx_file def test_copy(self): # test in-memory matrix_procedures copy matrix_copy = self.new_matrix.copy(self.copy_matrix_name, cores=["mat"]) if not np.array_equal(matrix_copy.mat, self.new_matrix.mat): self.fail("Matrix copy was not perfect") matrix_copy.close() del matrix_copy def test_export_to_csv(self): self.new_matrix.export(self.csv_export_name) df = pd.read_csv(self.csv_export_name) df.fillna(0, inplace=True) self.assertEqual(df.shape[0], 2500, "Exported wrong size") self.assertEqual(df.shape[1], 5, "Exported wrong size") self.assertAlmostEqual(df.mat.sum(), np.nansum(self.new_matrix.matrices), 5, "Exported wrong matrix total") def test_export_to_omx(self): self.new_matrix.export(self.omx_export_name) omxfile = omx.open_file(self.omx_export_name, "r") # Check if matrices values are compatible for m in self.new_matrix.names: sm = np.nansum(self.new_matrix.matrix[m]) sm2 = np.nansum(np.array(omxfile[m])) self.assertEqual(sm, sm2, "Matrix {} was exported with the wrong value".format(m)) del omxfile def test_nan_to_num(self): m = self.new_matrix.mat.sum() - self.new_matrix.mat[1, 1] self.new_matrix.computational_view(["mat", "seed"]) self.new_matrix.nan_to_num() self.new_matrix.mat[1, 1] = np.nan self.new_matrix.computational_view(["mat"]) self.new_matrix.nan_to_num() if abs(m - self.new_matrix.mat.sum()) > 0.000000000001: self.fail("Total for mat matrix not maintained") del self.new_matrix def test_copy_from_omx(self): temp_file = AequilibraeMatrix().random_name() a = AequilibraeMatrix() a.create_from_omx(temp_file, omx_example) omxfile = omx.open_file(omx_example, "r") # Check if matrices values are compatible for m in ["m1", "m2", "m3"]: sm = a.matrix[m].sum() sm2 = np.array(omxfile[m]).sum() if sm != sm2: self.fail("Matrix {} was copied with the wrong value".format(m)) if np.any(a.index[:] != np.array(list(omxfile.mapping("taz").keys()))): self.fail("Index was not created properly") a.close() del a del omxfile def test_copy_from_omx_long_name(self): temp_file = AequilibraeMatrix().random_name() a = AequilibraeMatrix() with self.assertRaises(ValueError): a.create_from_omx(temp_file, omx_example, robust=False) del a def test_copy_omx_wrong_content(self): # Check if we get a result if we try to copy non-existing cores temp_file = AequilibraeMatrix().random_name() a = AequilibraeMatrix() with self.assertRaises(ValueError): a.create_from_omx(temp_file, omx_example, cores=["m1", "m2", "m3", "m4"]) with self.assertRaises(ValueError): a.create_from_omx(temp_file, omx_example, mappings=["wrong index"]) del a def test_get_matrix(self): a = AequilibraeMatrix() a.load(self.sf_skims) with self.assertRaises(AttributeError): a.get_matrix("does not exist") q = a.get_matrix("distance") self.assertEqual(q.shape[0], 24) a = AequilibraeMatrix() a.load(self.name_test) print(np.array_equal(a.get_matrix("seed"), a.matrix["seed"])) del a def test_save(self): a = AequilibraeMatrix() a.load(self.sf_skims) a.computational_view(["distance"]) new_mat = np.random.rand(a.zones, a.zones) a.matrix_view *= new_mat res = a.matrix_view.sum() a.save("new_name_for_matrix") self.assertEqual(res, a.matrix_view.sum(), "Saved wrong result") a.save(["new_name_for_matrix2"]) self.assertEqual(a.view_names[0], "new_name_for_matrix2", "Did not update computational view") self.assertEqual(len(a.view_names), 1, "computational view with the wrong number of matrices") a.computational_view(["distance", "new_name_for_matrix"]) with self.assertRaises(ValueError): a.save(["just_one_name"]) a.save(["one_name", "two_names"]) with self.assertRaises(ValueError): a.save("distance") b = AequilibraeMatrix() b.load(self.name_test) b.computational_view("seed") b.save() b.computational_view(["mat", "seed", "dist"]) b.save()
class LoadMatrixDialog(QtWidgets.QDialog, FORM_CLASS): def __init__(self, iface, **kwargs): QtWidgets.QDialog.__init__(self) self.iface = iface self.setupUi(self) self.path = standard_path() self.sparse = kwargs.get('sparse', False) self.multiple = kwargs.get('multiple', True) self.allow_single_use = kwargs.get('single_use', False) self.output_name = None self.layer = None self.orig = None self.dest = None self.cells = None self.matrix_count = 0 self.matrices = {} self.matrix = None self.error = None self.__current_name = None self.logger = aequilibrae.logger self.radio_layer_matrix.clicked.connect(self.change_matrix_type) self.radio_npy_matrix.clicked.connect(self.change_matrix_type) self.radio_aeq_matrix.clicked.connect(self.change_matrix_type) self.radio_omx_matrix.clicked.connect(self.change_matrix_type) # For changing the network layer self.matrix_layer.currentIndexChanged.connect(self.load_fields_to_combo_boxes) # Buttons self.but_load.clicked.connect(self.load_the_matrix) if self.allow_single_use: self.but_save_for_single_use.clicked.connect(self.prepare_final_matrix) else: self.but_save_for_single_use.setVisible(False) self.but_permanent_save.clicked.connect(self.get_name_and_save_to_disk) # THIRD, we load layers in the canvas to the combo-boxes for layer in all_layers_from_toc(): # We iterate through all layers if 'wkbType' in dir(layer): if layer.wkbType() == 100: self.matrix_layer.addItem(layer.name()) if no_omx: self.radio_omx_matrix.setEnabled(False) self.resizing() def resizing(self): if self.radio_aeq_matrix.isChecked(): self.group_combo.setVisible(False) self.group_list.setVisible(False) self.group_buttons.setVisible(False) self.setMaximumSize(127, 176) self.resize(127, 176) self.but_permanent_save.setVisible(False) else: self.group_combo.setVisible(True) self.group_list.setVisible(True) self.group_buttons.setVisible(True) self.matrix_list_view.setColumnWidth(0, 180) self.matrix_list_view.setColumnWidth(1, 100) self.matrix_list_view.setColumnWidth(2, 125) self.matrix_list_view.itemChanged.connect(self.change_matrix_name) self.matrix_list_view.doubleClicked.connect(self.slot_double_clicked) self.setMaximumSize(QtCore.QSize(100000, 100000)) self.resize(542, 427) self.but_permanent_save.setVisible(True) self.but_save_for_single_use.setEnabled(False) self.but_permanent_save.setEnabled(False) def slot_double_clicked(self, mi): row = mi.row() if row > -1: self.matrix_count -= 1 mat_to_remove = self.matrix_list_view.item(row, 0).text() self.matrices.pop(mat_to_remove, None) self.update_matrix_list() def change_matrix_type(self): self.but_load.setEnabled(True) members = [self.lbl_matrix, self.lbl_from, self.matrix_layer, self.field_from] all_members = members + [self.lbl_to, self.lbl_flow, self.field_to, self.field_cells] # Covers the Numpy option (minimizes the code length this way) for member in all_members: member.setVisible(False) if self.radio_layer_matrix.isChecked(): self.lbl_matrix.setText('Matrix') self.lbl_from.setText('From') for member in all_members: member.setVisible(True) self.load_fields_to_combo_boxes() if self.radio_omx_matrix.isChecked(): self.lbl_matrix.setText('Matrix core') self.lbl_from.setText('Indices') for member in members: member.setVisible(True) self.resizing() def load_fields_to_combo_boxes(self): self.but_load.setEnabled(False) for combo in [self.field_from, self.field_to, self.field_cells]: combo.clear() if self.matrix_layer.currentIndex() >= 0: self.but_load.setEnabled(True) self.layer = get_vector_layer_by_name(self.matrix_layer.currentText()) for field in self.layer.dataProvider().fields().toList(): if field.type() in integer_types: self.field_from.addItem(field.name()) self.field_to.addItem(field.name()) self.field_cells.addItem(field.name()) if field.type() in float_types: self.field_cells.addItem(field.name()) def run_thread(self): self.worker_thread.ProgressValue.connect(self.progress_value_from_thread) self.worker_thread.ProgressMaxValue.connect(self.progress_range_from_thread) self.worker_thread.ProgressText.connect(self.progress_text_from_thread) self.worker_thread.finished_threaded_procedure.connect(self.finished_threaded_procedure) self.but_load.setEnabled(False) self.worker_thread.start() self.exec_() # VAL and VALUE have the following structure: (bar/text ID, value) def progress_range_from_thread(self, val): self.progressbar.setRange(0, val) def progress_value_from_thread(self, val): self.progressbar.setValue(val) def progress_text_from_thread(self, val): self.progress_label.setText(val) def finished_threaded_procedure(self, param): self.but_load.setEnabled(True) if self.worker_thread.report: dlg2 = ReportDialog(self.iface, self.worker_thread.report) dlg2.show() dlg2.exec_() else: if param == 'LOADED-MATRIX': self.compressed.setVisible(True) self.progress_label.setVisible(False) if self.__current_name in self.matrices.keys(): i = 1 while self.__current_name + '_' + str(i) in self.matrices.keys(): i += 1 self.__current_name = self.__current_name + '_' + str(i) self.matrices[self.__current_name] = self.worker_thread.matrix self.matrix_count += 1 self.update_matrix_list() if self.multiple == False: self.update_matrix_hashes() elif param == 'REBLOCKED MATRICES': self.matrix = self.worker_thread.matrix if self.compressed.isChecked(): pass # compression not implemented yet self.exit_procedure() def load_the_matrix(self): self.error = None self.worker_thread = None if self.radio_layer_matrix.isChecked(): if self.field_from.currentIndex() < 0 or self.field_from.currentIndex() < 0 or self.field_cells.currentIndex() < 0: self.error = 'Invalid field chosen' if self.error is None: self.compressed.setVisible(False) self.progress_label.setVisible(True) self.__current_name = self.field_cells.currentText().lower().replace(' ', '_') idx1 = self.layer.dataProvider().fieldNameIndex(self.field_from.currentText()) idx2 = self.layer.dataProvider().fieldNameIndex(self.field_to.currentText()) idx3 = self.layer.dataProvider().fieldNameIndex(self.field_cells.currentText()) idx = [idx1, idx2, idx3] self.worker_thread = LoadMatrix(qgis.utils.iface.mainWindow(), type='layer', layer=self.layer, idx=idx, sparse=self.sparse) if self.radio_npy_matrix.isChecked(): file_types = ["NumPY array(*.npy)"] default_type = '.npy' box_name = 'Matrix Loader' new_name, type = GetOutputFileName(self, box_name, file_types, default_type, self.path) self.__current_name = os.path.split(new_name)[1].split('.')[0] self.worker_thread = LoadMatrix(qgis.utils.iface.mainWindow(), type='numpy', file_path=new_name) if self.radio_aeq_matrix.isChecked(): file_types = ["AequilibraE Matrix(*.aem)"] default_type = '.aem' box_name = 'AequilibraE Matrix' new_name, type = GetOutputFileName(self, box_name, file_types, default_type, self.path) if new_name is not None: self.matrix = AequilibraeMatrix() self.matrix.load(new_name) self.exit_procedure() if self.radio_omx_matrix.isChecked(): pass # Still not implemented if self.worker_thread is not None: self.run_thread() if self.error is not None: qgis.utils.iface.messageBar().pushMessage("Error:", self.error, level=1) def update_matrix_list(self): if self.matrix_count > 0: self.but_save_for_single_use.setEnabled(True) self.but_permanent_save.setEnabled(True) else: self.but_save_for_single_use.setEnabled(False) self.but_permanent_save.setEnabled(False) self.matrix_list_view.clearContents() self.matrix_list_view.setRowCount(self.matrix_count) self.matrix_list_view.blockSignals(True) i = 0 for key, value in self.matrices.items(): r = np.unique(value['from']).shape[0] c = np.unique(value['to']).shape[0] dimensions = "{:,}".format(r) + " x " + "{:,}".format(c) total = "{:,.2f}".format(float(value['flow'].sum())) item_1 = QTableWidgetItem(key) self.matrix_list_view.setItem(i, 0, item_1) item_2 = QTableWidgetItem(dimensions) item_2.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) self.matrix_list_view.setItem(i, 1, item_2) item_3 = QTableWidgetItem(total) item_3.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) self.matrix_list_view.setItem(i, 2, item_3) i += 1 self.matrix_list_view.blockSignals(False) def change_matrix_name(self, item): self.matrix_list_view.blockSignals(True) row = item.row() new_name = self.matrix_list_view.item(row, 0).text().lower().replace(' ', '_') item_1 = QTableWidgetItem(new_name) self.matrix_list_view.setItem(row, 0, item_1) current_names = [] for i in range(self.matrix_count): current_names.append(self.matrix_list_view.item(i, 0).text()) for old_key in self.matrices.keys(): if old_key not in current_names: self.matrices[new_name] = self.matrices.pop(old_key) self.matrix_list_view.blockSignals(False) def get_name_and_save_to_disk(self): self.output_name, _ = GetOutputFileName(self, 'AequilibraE matrix', ["Aequilibrae Matrix(*.aem)"], '.aem', self.path) self.prepare_final_matrix() def prepare_final_matrix(self): self.compressed.setVisible(False) self.progress_label.setVisible(True) if self.output_name is None: self.worker_thread = MatrixReblocking(qgis.utils.iface.mainWindow(), sparse=self.sparse, matrices=self.matrices) else: self.worker_thread = MatrixReblocking(qgis.utils.iface.mainWindow(), sparse=self.sparse, matrices=self.matrices, file_name=self.output_name) self.run_thread() def exit_procedure(self): self.close()
class TestAequilibraeMatrix(TestCase): def test___init__(self): os.remove(name_test) if os.path.exists(name_test) else None args = {'file_name': name_test, 'zones': zones, 'matrix_names': ['mat', 'seed', 'dist'], 'index_names': ['my indices']} matrix = AequilibraeMatrix() matrix.create_empty(**args) matrix.index[:] = np.arange(matrix.zones) + 100 matrix.mat[:, :] = np.random.rand(matrix.zones, matrix.zones)[:, :] matrix.mat[:, :] = matrix.mat[:, :] * (1000 / np.sum(matrix.mat[:, :])) matrix.setName('Test matrix - ' + str(random.randint(1, 10))) matrix.setDescription('Generated at ' + datetime.datetime.now().strftime("%I:%M%p on %B %d, %Y")) matrix.close(True) del (matrix) def test_load(self): # self.test___init__() self.new_matrix = AequilibraeMatrix() self.new_matrix.load(name_test) def test_computational_view(self): self.test_load() self.new_matrix.computational_view(['mat', 'seed']) self.new_matrix.mat.fill(0) self.new_matrix.seed.fill(0) if self.new_matrix.matrix_view.shape[2] != 2: self.fail('Computational view returns the wrong number of matrices') self.new_matrix.computational_view(['mat']) self.new_matrix.matrix_view[:, :] = np.arange(zones ** 2).reshape(zones, zones) if np.sum(self.new_matrix.mat) != np.sum(self.new_matrix.matrix_view): self.fail('Assigning to matrix view did not work') self.new_matrix.setName('Test matrix - ' + str(random.randint(1, 10))) self.new_matrix.setDescription('Generated at ' + datetime.datetime.now().strftime("%I:%M%p on %B %d, %Y")) self.new_matrix.close(True) def test_copy(self): self.test_load() # test in-memory matrix_procedures copy matrix_copy = self.new_matrix.copy(copy_matrix_name, cores=['mat']) if not np.array_equal(matrix_copy.mat, self.new_matrix.mat): self.fail('Matrix copy was not perfect') matrix_copy.close(True) self.new_matrix.close(True) def test_export(self): self.test_load() self.new_matrix.export(csv_export_name) self.new_matrix.close(True) def test_nan_to_num(self): self.test_load() s = self.new_matrix.seed.sum() - self.new_matrix.seed[1, 1] m = self.new_matrix.mat.sum() - self.new_matrix.mat[1, 1] self.new_matrix.seed[1,1] = np.nan self.new_matrix.computational_view(['mat', 'seed']) self.new_matrix.nan_to_num() self.new_matrix.mat[1,1] = np.nan self.new_matrix.computational_view(['mat']) self.new_matrix.nan_to_num() if s != self.new_matrix.seed.sum(): self.fail('Total for seed matrix not maintained') if m != self.new_matrix.mat.sum(): self.fail('Total for mat matrix not maintained')
class TestAequilibraeMatrix(TestCase): def test___init__(self): os.remove(name_test) if os.path.exists(name_test) else None args = { "file_name": name_test, "zones": zones, "matrix_names": ["mat", "seed", "dist"], "index_names": ["my indices"], } matrix = AequilibraeMatrix() matrix.create_empty(**args) matrix.index[:] = np.arange(matrix.zones) + 100 matrix.mat[:, :] = np.random.rand(matrix.zones, matrix.zones)[:, :] matrix.mat[:, :] = matrix.mat[:, :] * (1000 / np.sum(matrix.mat[:, :])) matrix.setName("Test matrix - " + str(random.randint(1, 10))) matrix.setDescription( "Generated at " + datetime.datetime.now().strftime("%I:%M%p on %B %d, %Y")) matrix.close() del matrix def test_load(self): # self.test___init__() self.new_matrix = AequilibraeMatrix() # Cannot load OMX file with no indices with self.assertRaises(LookupError): self.new_matrix.load(no_index_omx) self.new_matrix = AequilibraeMatrix() self.new_matrix.load(name_test) def test_computational_view(self): self.test_load() self.new_matrix.computational_view(["mat", "seed"]) self.new_matrix.mat.fill(0) self.new_matrix.seed.fill(0) if self.new_matrix.matrix_view.shape[2] != 2: self.fail( "Computational view returns the wrong number of matrices") self.new_matrix.computational_view(["mat"]) self.new_matrix.matrix_view[:, :] = np.arange(zones**2).reshape( zones, zones) if np.sum(self.new_matrix.mat) != np.sum(self.new_matrix.matrix_view): self.fail("Assigning to matrix view did not work") self.new_matrix.setName("Test matrix - " + str(random.randint(1, 10))) self.new_matrix.setDescription( "Generated at " + datetime.datetime.now().strftime("%I:%M%p on %B %d, %Y")) self.new_matrix.close() def test_computational_view_with_omx(self): self.new_matrix = AequilibraeMatrix() self.new_matrix.load(omx_example) arrays = ["m1", "m2"] self.new_matrix.computational_view(arrays) total_mats = np.sum(self.new_matrix.matrix_view) self.new_matrix.computational_view([arrays[0]]) total_m1 = np.sum(self.new_matrix.matrix_view) self.new_matrix.close() omx_file = omx.open_file(omx_example, "r") m1 = np.array(omx_file["m1"]).sum() m2 = np.array(omx_file["m2"]).sum() self.assertEqual(m1 + m2, total_mats) self.assertEqual(m1, total_m1) omx_file.close() def test_copy(self): self.test_load() # test in-memory matrix_procedures copy matrix_copy = self.new_matrix.copy(copy_matrix_name, cores=["mat"]) if not np.array_equal(matrix_copy.mat, self.new_matrix.mat): self.fail("Matrix copy was not perfect") matrix_copy.close() self.new_matrix.close() def test_export_to_csv(self): self.test_load() self.new_matrix.export(csv_export_name) self.new_matrix.close() def test_export_to_omx(self): self.test_load() self.new_matrix.export(omx_export_name) omxfile = omx.open_file(omx_export_name, "r") # Check if matrices values are compatible for m in self.new_matrix.names: sm = np.nansum(self.new_matrix.matrix[m]) sm2 = np.nansum(np.array(omxfile[m])) self.assertEqual( sm, sm2, "Matrix {} was exported with the wrong value".format(m)) self.new_matrix.close() def test_nan_to_num(self): self.test_load() s = self.new_matrix.seed.sum() - self.new_matrix.seed[1, 1] m = self.new_matrix.mat.sum() - self.new_matrix.mat[1, 1] self.new_matrix.seed[1, 1] = np.nan self.new_matrix.computational_view(["mat", "seed"]) self.new_matrix.nan_to_num() self.new_matrix.mat[1, 1] = np.nan self.new_matrix.computational_view(["mat"]) self.new_matrix.nan_to_num() if s != self.new_matrix.seed.sum(): self.fail("Total for seed matrix not maintained") if m != self.new_matrix.mat.sum(): self.fail("Total for mat matrix not maintained") def test_copy_from_omx(self): temp_file = AequilibraeMatrix().random_name() a = AequilibraeMatrix() a.create_from_omx(temp_file, omx_example) omxfile = omx.open_file(omx_example, "r") # Check if matrices values are compatible for m in ["m1", "m2", "m3"]: sm = a.matrix[m].sum() sm2 = np.array(omxfile[m]).sum() if sm != sm2: self.fail( "Matrix {} was copied with the wrong value".format(m)) if np.any(a.index[:] != np.array(list(omxfile.mapping("taz").keys()))): self.fail("Index was not created properly") a.close() def test_copy_from_omx_long_name(self): temp_file = AequilibraeMatrix().random_name() a = AequilibraeMatrix() with self.assertRaises(ValueError): a.create_from_omx(temp_file, omx_example, robust=False) def test_copy_omx_wrong_content(self): # Check if we get a result if we try to copy non-existing cores temp_file = AequilibraeMatrix().random_name() a = AequilibraeMatrix() with self.assertRaises(ValueError): a.create_from_omx(temp_file, omx_example, cores=["m1", "m2", "m3", "m4"]) with self.assertRaises(ValueError): a.create_from_omx(temp_file, omx_example, mappings=["wrong index"])
class DisplayAequilibraEFormatsDialog(QtWidgets.QDialog, FORM_CLASS): def __init__(self, iface): QtWidgets.QDialog.__init__(self) self.iface = iface self.setupUi(self) self.error = None self.error = None self.data_path, self.data_type = GetOutputFileName( self, 'AequilibraE custom formats', ["Aequilibrae dataset(*.aed)", "Aequilibrae matrix(*.aem)"], '.aed', standard_path()) if self.data_type is None: self.error = 'Path provided is not a valid dataset' self.exit_with_error() self.data_type = self.data_type.upper() if self.data_type == 'AED': self.data_to_show = AequilibraEData() elif self.data_type == 'AEM': self.data_to_show = AequilibraeMatrix() try: self.data_to_show.load(self.data_path) except: self.error = 'Could not load dataset' self.exit_with_error() # Elements that will be used during the displaying self._layout = QVBoxLayout() self.table = QTableView() self._layout.addWidget(self.table) # Settings for displaying self.show_layout = QHBoxLayout() # Thousand separator self.thousand_separator = QCheckBox() self.thousand_separator.setChecked(True) self.thousand_separator.setText('Thousands separator') self.thousand_separator.toggled.connect(self.format_showing) self.show_layout.addWidget(self.thousand_separator) self.spacer = QSpacerItem(5, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) self.show_layout.addItem(self.spacer) # Decimals txt = QLabel() txt.setText('Decimal places') self.show_layout.addWidget(txt) self.decimals = QSpinBox() self.decimals.valueChanged.connect(self.format_showing) self.decimals.setMinimum(0) self.decimals.setValue(4) self.decimals.setMaximum(10) self.show_layout.addWidget(self.decimals) self._layout.addItem(self.show_layout) # differentiates between matrix and dataset if self.data_type == 'AEM': self.data_to_show.computational_view([self.data_to_show.names[0]]) # Matrices need cores and indices to be set as well self.mat_layout = QHBoxLayout() self.mat_list = QComboBox() for n in self.data_to_show.names: self.mat_list.addItem(n) self.mat_list.currentIndexChanged.connect(self.change_matrix_cores) self.mat_layout.addWidget(self.mat_list) self.idx_list = QComboBox() for i in self.data_to_show.index_names: self.idx_list.addItem(i) self.idx_list.currentIndexChanged.connect(self.change_matrix_cores) self.mat_layout.addWidget(self.idx_list) self._layout.addItem(self.mat_layout) self.change_matrix_cores() self.but_export = QPushButton() self.but_export.setText('Export') self.but_export.clicked.connect(self.export) self.but_close = QPushButton() self.but_close.clicked.connect(self.exit_procedure) self.but_close.setText('Close') self.but_layout = QHBoxLayout() self.but_layout.addWidget(self.but_export) self.but_layout.addWidget(self.but_close) self._layout.addItem(self.but_layout) # We chose to use QTableView. However, if we want to allow the user to edit the dataset # The we need to allow them to switch to the slower QTableWidget # Code below # self.table = QTableWidget(self.data_to_show.entries, self.data_to_show.num_fields) # self.table.setHorizontalHeaderLabels(self.data_to_show.fields) # self.table.setObjectName('data_viewer') # # self.table.setVerticalHeaderLabels([str(x) for x in self.data_to_show.index[:]]) # self.table.clearContents() # # for i in range(self.data_to_show.entries): # for j, f in enumerate(self.data_to_show.fields): # item1 = QTableWidgetItem(str(self.data_to_show.data[f][i])) # item1.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) # self.table.setItem(i, j, item1) self.resize(700, 500) self.setLayout(self._layout) self.format_showing() def format_showing(self): decimals = self.decimals.value() separator = self.thousand_separator.isChecked() if isinstance(self.data_to_show, AequilibraeMatrix): m = NumpyModel(self.data_to_show, separator, decimals) else: m = DatabaseModel(self.data_to_show, separator, decimals) self.table.clearSpans() self.table.setModel(m) def change_matrix_cores(self): self.data_to_show.computational_view([self.mat_list.currentText()]) self.data_to_show.set_index(self.data_to_show.index_names[0]) self.format_showing() def export(self): new_name, file_type = GetOutputFileName( self, self.data_type, ["Comma-separated file(*.csv)"], ".csv", self.data_path) if new_name is not None: self.data_to_show.export(new_name) def exit_with_error(self): qgis.utils.iface.messageBar().pushMessage("Error:", self.error, level=1) self.exit_procedure() def exit_procedure(self): self.close()
class TestTrafficAssignment(TestCase): def setUp(self) -> None: self.matrix = AequilibraeMatrix() self.matrix.load(siouxfalls_demand) self.matrix.computational_view() self.project = Project() self.project.load(siouxfalls_project) self.project.network.build_graphs() self.car_graph = self.project.network.graphs['c'] # type: Graph self.car_graph.set_graph('free_flow_time') self.car_graph.set_blocked_centroid_flows(False) self.assignment = TrafficAssignment() self.assigclass = TrafficClass(self.car_graph, self.matrix) def tearDown(self) -> None: self.matrix.close() self.project.conn.close() def test_set_vdf(self): with self.assertRaises(ValueError): self.assignment.set_vdf('CQS') self.assignment.set_vdf('BPR') def test_set_classes(self): with self.assertRaises(ValueError): self.assignment.set_classes([1, 2]) # The traffic assignment class is unprotected. # Should we protect it? # self.assigclass = TrafficClass(self.car_graph, self.matrix) # self.assigclass.graph = 1 # with self.assertRaises(ValueError): # self.assignment.set_classes(self.assigclass) self.assignment.set_classes(self.assigclass) # self.fail() def test_algorithms_available(self): algs = self.assignment.algorithms_available() real = ['all-or-nothing', 'msa', 'frank-wolfe', 'bfw', 'cfw'] diff = [x for x in real if x not in algs] diff2 = [x for x in algs if x not in real] if len(diff) + len(diff2) > 0: self.fail('list of algorithms raised is wrong') def test_set_cores(self): with self.assertRaises(Exception): self.assignment.set_cores(3) self.assignment.set_classes(self.assigclass) with self.assertRaises(ValueError): self.assignment.set_cores('q') self.assignment.set_cores(3) def test_set_algorithm(self): with self.assertRaises(AttributeError): self.assignment.set_algorithm('not an algo') self.assignment.set_classes(self.assigclass) with self.assertRaises(Exception): self.assignment.set_algorithm('msa') self.assignment.set_vdf("BPR") self.assignment.set_vdf_parameters({"alpha": "b", "beta": "power"}) self.assignment.set_capacity_field("capacity") self.assignment.set_time_field("free_flow_time") self.assignment.max_iter = 10 self.assignment.set_algorithm('bfw') def test_set_vdf_parameters(self): with self.assertRaises(Exception): self.assignment.set_vdf_parameters({"alpha": "b", "beta": "power"}) self.assignment.set_vdf('bpr') self.assignment.set_classes(self.assigclass) self.assignment.set_vdf_parameters({"alpha": "b", "beta": "power"}) def test_set_time_field(self): N = random.randint(1, 50) val = ''.join( random.choices(string.ascii_uppercase + string.digits, k=N)) self.assignment.set_time_field(val) self.assertEqual(self.assignment.time_field, val) def test_set_capacity_field(self): N = random.randint(1, 50) val = ''.join( random.choices(string.ascii_uppercase + string.digits, k=N)) self.assignment.set_capacity_field(val) self.assertEqual(self.assignment.capacity_field, val) def test_execute(self): self.assignment.set_classes(self.assigclass) self.assignment.set_vdf("BPR") self.assignment.set_vdf_parameters({"alpha": 0.15, "beta": 4.0}) self.assignment.set_vdf_parameters({"alpha": "b", "beta": "power"}) self.assignment.set_capacity_field("capacity") self.assignment.set_time_field("free_flow_time") self.assignment.max_iter = 10 self.assignment.set_algorithm('msa') self.assignment.execute() msa10 = self.assignment.assignment.rgap self.assigclass.results.total_flows() correl = np.corrcoef(self.assigclass.results.total_link_loads, self.assigclass.graph.graph['volume'])[0, 1] self.assertLess(0.8, correl) self.assignment.max_iter = 30 self.assignment.set_algorithm('msa') self.assignment.execute() msa25 = self.assignment.assignment.rgap self.assigclass.results.total_flows() correl = np.corrcoef(self.assigclass.results.total_link_loads, self.assigclass.graph.graph['volume'])[0, 1] self.assertLess(0.95, correl) self.assignment.set_algorithm('frank-wolfe') self.assignment.execute() fw25 = self.assignment.assignment.rgap self.assigclass.results.total_flows() correl = np.corrcoef(self.assigclass.results.total_link_loads, self.assigclass.graph.graph['volume'])[0, 1] self.assertLess(0.97, correl) self.assignment.set_algorithm('cfw') self.assignment.execute() cfw25 = self.assignment.assignment.rgap self.assigclass.results.total_flows() correl = np.corrcoef(self.assigclass.results.total_link_loads, self.assigclass.graph.graph['volume'])[0, 1] self.assertLess(0.98, correl) self.assignment.set_algorithm('bfw') self.assignment.execute() bfw25 = self.assignment.assignment.rgap self.assigclass.results.total_flows() correl = np.corrcoef(self.assigclass.results.total_link_loads, self.assigclass.graph.graph['volume'])[0, 1] self.assertLess(0.99, correl) self.assertLess(msa25, msa10) self.assertLess(fw25, msa25) self.assertLess(cfw25, fw25) self.assertLess(bfw25, cfw25)