def __setCriticalLinks(self, save=False, queries={}, crit_res_result=None): a = AequilibraeMatrix() if save: if crit_res_result is None: warnings.warn( "Critical Link analysis not set properly. Need to specify output file too" ) else: if crit_res_result[-3:].lower() != "aem": crit_res_result += ".aes" if self.nodes > 0 and self.zones > 0: if ["elements", "labels", "type"] in queries.keys(): if len(queries["labels"]) == len( queries["elements"]) == len(queries["type"]): a.create_empty(file_name=crit_res_result, zones=self.zones, matrix_names=queries["labels"]) else: raise ValueError( "Queries are inconsistent. 'Labels', 'elements' and 'type' need to have same dimensions" ) else: raise ValueError( "Queries are inconsistent. It needs to contain the following elements: 'Labels', 'elements' and 'type'" ) else: a.create_empty(file_name=a.random_name(), zones=self.zones, matrix_names=["empty", "nothing"]) a.computational_view() if len(a.matrix_view.shape[:]) == 2: a.matrix_view = a.matrix_view.reshape((self.zones, self.zones, 1)) self.critical_links = {"save": save, "queries": queries, "results": a}
def test_set_pce(self): mat_name = AequilibraeMatrix().random_name() g = Graph() g.load_from_disk(test_graph) g.set_graph(cost_field="distance") # Creates the matrix for assignment args = { "file_name": os.path.join(gettempdir(), mat_name), "zones": g.num_zones, "matrix_names": ["cars", "trucks"], "index_names": ["my indices"], } matrix = AequilibraeMatrix() matrix.create_empty(**args) matrix.index[:] = g.centroids[:] matrix.cars.fill(1.1) matrix.trucks.fill(2.2) matrix.computational_view() tc = TrafficClass(graph=g, matrix=matrix) self.assertIsInstance(tc.results, AssignmentResults, 'Results have the wrong type') self.assertIsInstance(tc._aon_results, AssignmentResults, 'Results have the wrong type') with self.assertRaises(ValueError): tc.set_pce('not a number') tc.set_pce(1) tc.set_pce(3.9)
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_execute(self): # Loads and prepares the graph g = Graph() g.load_from_disk(test_graph) g.set_graph(cost_field="distance", skim_fields=None) # None implies that only the cost field will be skimmed # Prepares the matrix for assignment args = { "file_name": os.path.join(gettempdir(), "my_matrix.aem"), "zones": g.num_zones, "matrix_names": ["cars", "trucks"], "index_names": ["my indices"], } matrix = AequilibraeMatrix() matrix.create_empty(**args) matrix.index[:] = g.centroids[:] matrix.cars.fill(1) matrix.trucks.fill(2) matrix.computational_view(["cars"]) # Performs assignment res = AssignmentResults() res.prepare(g, matrix) assig = allOrNothing(matrix, g, res) assig.execute() res.save_to_disk(os.path.join(gettempdir(), "link_loads.aed")) res.save_to_disk(os.path.join(gettempdir(), "link_loads.csv")) matrix.computational_view() # Performs assignment res = AssignmentResults() res.prepare(g, matrix) assig = allOrNothing(matrix, g, res) assig.execute() res.save_to_disk(os.path.join(gettempdir(), "link_loads_2_classes.aed")) res.save_to_disk(os.path.join(gettempdir(), "link_loads_2_classes.csv"))
def test_execute(self): # Loads and prepares the graph g = Graph() g.load_from_disk(test_graph) g.set_graph(cost_field='distance', skim_fields=None) # None implies that only the cost field will be skimmed # Prepares the matrix for assignment args = { 'file_name': '/tmp/my_matrix.aem', 'zones': g.num_zones, 'matrix_names': ['cars', 'trucks'], 'index_names': ['my indices'] } matrix = AequilibraeMatrix() matrix.create_empty(**args) matrix.index[:] = g.centroids[:] matrix.cars.fill(1) matrix.trucks.fill(2) matrix.computational_view(['cars']) # Performs assignment res = AssignmentResults() res.prepare(g, matrix) assig = allOrNothing(matrix, g, res) assig.execute() res.save_to_disk('/tmp/link_loads.aed') res.save_to_disk('/tmp/link_loads.csv') matrix.computational_view() # Performs assignment res = AssignmentResults() res.prepare(g, matrix) assig = allOrNothing(matrix, g, res) assig.execute() res.save_to_disk('/tmp/link_loads_2_classes.aed') res.save_to_disk('/tmp/link_loads_2_classes.csv')
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_execute(self): # Loads and prepares the graph g = Graph() g.load_from_disk(test_graph) g.set_graph(cost_field='distance', skim_fields=None) # None implies that only the cost field will be skimmed # Prepares the matrix for assignment args = {'file_name': os.path.join(gettempdir(),'my_matrix.aem'), 'zones': g.num_zones, 'matrix_names': ['cars', 'trucks'], 'index_names': ['my indices']} matrix = AequilibraeMatrix() matrix.create_empty(**args) matrix.index[:] = g.centroids[:] matrix.cars.fill(1) matrix.trucks.fill(2) matrix.computational_view(['cars']) # Performs assignment res = AssignmentResults() res.prepare(g, matrix) assig = allOrNothing(matrix, g, res) assig.execute() res.save_to_disk(os.path.join(gettempdir(),'link_loads.aed')) res.save_to_disk(os.path.join(gettempdir(),'link_loads.csv')) matrix.computational_view() # Performs assignment res = AssignmentResults() res.prepare(g, matrix) assig = allOrNothing(matrix, g, res) assig.execute() res.save_to_disk(os.path.join(gettempdir(),'link_loads_2_classes.aed')) res.save_to_disk(os.path.join(gettempdir(),'link_loads_2_classes.csv'))
def setUp(self) -> None: self.mat_name = AequilibraeMatrix().random_name() self.g = Graph() self.g.load_from_disk(test_graph) self.g.set_graph(cost_field="distance") # Creates the matrix for assignment args = { "file_name": os.path.join(gettempdir(), self.mat_name), "zones": self.g.num_zones, "matrix_names": ["cars", "trucks"], "index_names": ["my indices"], } matrix = AequilibraeMatrix() matrix.create_empty(**args) matrix.index[:] = self.g.centroids[:] matrix.cars.fill(1.1) matrix.trucks.fill(2.2) # Exports matrix to OMX in order to have two matrices to work with matrix.export(os.path.join(gettempdir(), "my_matrix.omx")) matrix.close()
class AssignmentResults: """ Assignment result holder for a single :obj:`TrafficClass` with multiple user classes """ def __init__(self): self.link_loads = None # type: np.array # The actual results for assignment self.total_link_loads = None # type: np.array # The result of the assignment for all user classes summed self.skims = None # The array of skims self.no_path = None # The list os paths self.num_skims = None # number of skims that will be computed. Depends on the setting of the graph provided p = Parameters().parameters['system']['cpus'] if not isinstance(p, int): p = 0 self.set_cores(p) self.classes = {"number": 1, "names": ["flow"]} self.critical_links = { "save": False, "queries": {}, "results": False } # Queries are a dictionary self.link_extraction = { "save": False, "queries": {}, "output": None } # Queries are a dictionary self.path_file = {"save": False, "results": None} self.nodes = -1 self.zones = -1 self.links = -1 self.__graph_id__ = None self.__float_type = None self.__integer_type = None self.lids = None self.direcs = None # In case we want to do by hand, we can prepare each method individually def prepare(self, graph: Graph, matrix: AequilibraeMatrix) -> None: """ Prepares the object with dimensions corresponding to the assignment matrix and graph objects Args: *graph* (:obj:`Graph`): Needs to have been set with number of centroids and list of skims (if any) *matrix* (:obj:`AequilibraeMatrix`): Matrix properly set for computation with matrix.computational_view(:obj:`list`) """ self.__float_type = graph.default_types("float") self.__integer_type = graph.default_types("int") if matrix.view_names is None: raise ("Please set the matrix_procedures computational view") else: self.classes["number"] = 1 if len(matrix.matrix_view.shape) > 2: self.classes["number"] = matrix.matrix_view.shape[2] self.classes["names"] = matrix.view_names if graph is None: raise ("Please provide a graph") else: self.nodes = graph.num_nodes self.zones = graph.num_zones self.centroids = graph.centroids self.links = graph.num_links self.num_skims = len(graph.skim_fields) self.skim_names = [x for x in graph.skim_fields] self.lids = graph.graph["link_id"] self.direcs = graph.graph["direction"] self.__redim() self.__graph_id__ = graph.__id__ # TODO: Enable these methods when the work for select link analysis and saving path files is completed self.__setSavePathFile(False) self.__setCriticalLinks(False) def reset(self) -> None: """ Resets object to prepared and pre-computation state """ if self.num_skims > 0: self.skims.matrices.fill(0) if self.link_loads is not None: self.no_path.fill(0) self.link_loads.fill(0) self.total_link_loads.fill(0) else: raise ValueError( "Exception: Assignment results object was not yet prepared/initialized" ) def __redim(self): self.link_loads = np.zeros((self.links, self.classes["number"]), self.__float_type) self.total_link_loads = np.zeros(self.links, self.__float_type) self.no_path = np.zeros((self.zones, self.zones), dtype=self.__integer_type) if self.num_skims > 0: self.skims = AequilibraeMatrix() self.skims.create_empty(file_name=self.skims.random_name(), zones=self.zones, matrix_names=self.skim_names) self.skims.index[:] = self.centroids[:] self.skims.computational_view() if len(self.skims.matrix_view.shape[:]) == 2: self.skims.matrix_view = self.skims.matrix_view.reshape( (self.zones, self.zones, 1)) else: self.skims = AequilibraeMatrix() self.skims.matrix_view = np.array((1, 1, 1)) self.reset() def total_flows(self) -> None: """ Totals all link flows for this class into a single link load Results are placed into *total_link_loads* class member """ sum_axis1(self.total_link_loads, self.link_loads, self.cores) def set_cores(self, cores: int) -> None: """ Sets number of cores (threads) to be used in computation Value of zero sets number of threads to all available in the system, while negative values indicate the number of threads to be left out of the computational effort. Resulting number of cores will be adjusted to a minimum of zero or the maximum available in the system if the inputs result in values outside those limits Args: *cores* (:obj:`int`): Number of cores to be used in computation """ if isinstance(cores, int): if cores < 0: self.cores = max(1, mp.cpu_count() + cores) if cores == 0: self.cores = mp.cpu_count() elif cores > 0: cores = max(mp.cpu_count(), cores) if self.cores != cores: self.cores = cores if self.link_loads is not None: self.__redim() else: raise ValueError("Number of cores needs to be an integer") def __setCriticalLinks(self, save=False, queries={}, crit_res_result=None): a = AequilibraeMatrix() if save: if crit_res_result is None: warnings.warn( "Critical Link analysis not set properly. Need to specify output file too" ) else: if crit_res_result[-3:].lower() != "aem": crit_res_result += ".aes" if self.nodes > 0 and self.zones > 0: if ["elements", "labels", "type"] in queries.keys(): if len(queries["labels"]) == len( queries["elements"]) == len(queries["type"]): a.create_empty(file_name=crit_res_result, zones=self.zones, matrix_names=queries["labels"]) else: raise ValueError( "Queries are inconsistent. 'Labels', 'elements' and 'type' need to have same dimensions" ) else: raise ValueError( "Queries are inconsistent. It needs to contain the following elements: 'Labels', 'elements' and 'type'" ) else: a.create_empty(file_name=a.random_name(), zones=self.zones, matrix_names=["empty", "nothing"]) a.computational_view() if len(a.matrix_view.shape[:]) == 2: a.matrix_view = a.matrix_view.reshape((self.zones, self.zones, 1)) self.critical_links = {"save": save, "queries": queries, "results": a} def __setSavePathFile(self, save=False, path_result=None): # Fields: Origin, Node, Predecessor # Number of records: Origins * Nodes a = AequilibraeData() d1 = max(1, self.zones) d2 = 1 memory_mode = True if save: if path_result is None: warnings.warn( "Path file not set properly. Need to specify output file too" ) else: # This is the only place where we keep 32bits, as going 64 bits would explode the file size if self.nodes > 0 and self.zones > 0: d1 = self.zones d2 = self.nodes memory_mode = False a.create_empty( file_path=path_result, entries=d1 * d2, field_names=["origin", "node", "predecessor", "connector"], data_types=[np.uint32, np.uint32, np.uint32, np.uint32], memory_mode=memory_mode, ) self.path_file = {"save": save, "results": a} def get_load_results(self) -> AequilibraeData: """ Translates the assignment results from the graph format into the network format Returns: dataset (:obj:`AequilibraeData`): AequilibraE data with the traffic class assignment results """ fields = ['link_id'] for n in self.classes['names']: fields.extend([f'{n}_ab', f'{n}_ba', f'{n}_tot']) types = [np.float64] * len(fields) entries = int(np.unique(self.lids).shape[0]) res = AequilibraeData() res.create_empty(memory_mode=True, entries=entries, field_names=fields, data_types=types) res.data.fill(np.nan) res.index[:] = np.unique(self.lids)[:] res.link_id[:] = res.index[:] indexing = np.zeros(int(self.lids.max()) + 1, np.uint64) indexing[res.index[:]] = np.arange(entries) # Indices of links BA and AB ABs = self.direcs > 0 BAs = self.direcs < 0 ab_ids = indexing[self.lids[ABs]] ba_ids = indexing[self.lids[BAs]] # Link flows link_flows = self.link_loads[:, :] for i, n in enumerate(self.classes["names"]): # AB Flows res.data[n + "_ab"][ab_ids] = np.nan_to_num(link_flows[ABs, i]) # BA Flows res.data[n + "_ba"][ba_ids] = np.nan_to_num(link_flows[BAs, i]) # Tot Flow res.data[n + "_tot"] = np.nan_to_num( res.data[n + "_ab"]) + np.nan_to_num(res.data[n + "_ba"]) return res def save_to_disk(self, file_name=None, output="loads") -> None: """ Function to write to disk all outputs computed during assignment Args: *file_name* (:obj:`str`): Name of the file, with extension. Valid extensions are: ['aed', 'csv', 'sqlite'] *output* (:obj:`str`, optional): Type of output ('loads', 'path_file'). Defaults to 'loads' """ if output == "loads": res = self.get_load_results() res.export(file_name) # TODO: Re-factor the exporting of the path file within the AequilibraeData format elif output == "path_file": pass
class MatrixReblocking(WorkerThread): def __init__(self, parentThread, **kwargs): WorkerThread.__init__(self, parentThread) self.matrix = AequilibraeMatrix() self.matrices = kwargs.get('matrices') self.sparse = kwargs.get('sparse', False) self.file_name = kwargs.get('file_name', AequilibraeMatrix().random_name()) self.num_matrices = len(self.matrices.keys()) self.matrix_hash = {} self.titles = [] self.report = [] def doWork(self): if self.sparse: # Builds the hash self.ProgressMaxValue.emit(self.num_matrices) self.ProgressValue.emit(0) self.ProgressText.emit("Building correspondence") indices = None p = 0 for mat_name, mat in self.matrices.items(): # Gets all non-zero coordinates and makes sure that they are considered froms = mat['from'] tos = mat['to'] if indices is None: all_indices = np.hstack((froms, tos)) else: all_indices = np.hstack((indices, froms, tos)) indices = np.unique(all_indices) p += 1 self.ProgressValue.emit(p) compact_shape = int(indices.shape[0]) else: compact_shape = 0 for mat_name, mat in self.matrices.items(): compact_shape = np.max(compact_shape, mat.shape[0]) indices = np.arange(compact_shape) new_index = {k: i for i, k in enumerate(indices)} names = [str(n) for n in self.matrices.keys()] self.matrix.create_empty(file_name=self.file_name, zones=compact_shape, matrix_names=names, data_type=np.float64) self.matrix.index[:] = indices[:] k = 0 self.ProgressMaxValue.emit(self.num_matrices) self.ProgressText.emit("Reblocking matrices") new_mat = None for mat_name, mat in self.matrices.items(): if self.sparse: new_mat = np.copy(mat) for j, v in new_index.items(): new_mat['from'][mat['from'] == j] = v new_mat['to'][mat['to'] == j] = v k += 1 self.ProgressValue.emit(k) else: k += 1 self.ProgressValue.emit(1) # In order to differentiate the zeros from the NaNs in the future matrix if new_mat is None: raise ValueError('Could not create reblocked matrix.') new_mat['flow'][new_mat['flow'] == 0] = np.inf # Uses SciPy Sparse matrices to build the compact one self.matrix.matrix[mat_name][:, :] = coo_matrix( (new_mat['flow'], (new_mat['from'], new_mat['to'])), shape=(compact_shape, compact_shape)).toarray().astype(np.float64) # In order to differentiate the zeros from the NaNs in the future matrix self.matrix.matrix[mat_name][self.matrix.matrix[mat_name] == 0] = np.nan self.matrix.matrix[mat_name][self.matrix.matrix[mat_name] == np.inf] = 0 del (mat) del (new_mat) self.ProgressText.emit("Matrix Reblocking finalized") self.finished_threaded_procedure.emit("REBLOCKED MATRICES")
class TrafficAssignmentDialog(QtWidgets.QDialog, FORM_CLASS): def __init__(self, iface): QtWidgets.QDialog.__init__(self) self.iface = iface self.setupUi(self) self.path = standard_path() self.output_path = None self.temp_path = None self.error = None self.report = None self.method = {} self.matrices = OrderedDict() self.skims = [] self.matrix = None self.graph = Graph() self.results = AssignmentResults() self.block_centroid_flows = None self.worker_thread = None # Signals for the matrix_procedures tab self.but_load_new_matrix.clicked.connect(self.find_matrices) # Signals from the Network tab self.load_graph_from_file.clicked.connect(self.load_graph) # Signals for the algorithm tab self.progressbar0.setVisible(False) self.progressbar0.setValue(0) self.progress_label0.setVisible(False) self.do_assignment.clicked.connect(self.run) self.cancel_all.clicked.connect(self.exit_procedure) self.select_output_folder.clicked.connect(self.choose_folder_for_outputs) self.cb_choose_algorithm.addItem('All-Or-Nothing') self.cb_choose_algorithm.currentIndexChanged.connect(self.changing_algorithm) # slots for skim tab self.but_build_query.clicked.connect(partial(self.build_query, 'select link')) self.changing_algorithm() # path file self.path_file = OutputType() # Queries tables = [self.select_link_list, self.list_link_extraction] for table in tables: table.setColumnWidth(0, 280) table.setColumnWidth(1, 40) table.setColumnWidth(2, 150) table.setColumnWidth(3, 40) self.graph_properties_table.setColumnWidth(0, 190) self.graph_properties_table.setColumnWidth(1, 240) # critical link self.but_build_query.clicked.connect(partial(self.build_query, 'select link')) self.do_select_link.stateChanged.connect(self.set_behavior_special_analysis) self.tot_crit_link_queries = 0 self.critical_output = OutputType() # link flow extraction self.but_build_query_extract.clicked.connect(partial(self.build_query, 'Link flow extraction')) self.do_extract_link_flows.stateChanged.connect(self.set_behavior_special_analysis) self.tot_link_flow_extract = 0 self.link_extract = OutputType() # Disabling resources not yet implemented self.do_select_link.setEnabled(False) self.but_build_query.setEnabled(False) self.select_link_list.setEnabled(False) self.skim_list_table.setEnabled(False) self.do_extract_link_flows.setEnabled(False) self.but_build_query_extract.setEnabled(False) self.list_link_extraction.setEnabled(False) self.new_matrix_to_assign() self.table_matrix_list.setColumnWidth(0, 135) self.table_matrix_list.setColumnWidth(1, 135) self.table_matrices_to_assign.setColumnWidth(0, 125) self.table_matrices_to_assign.setColumnWidth(1, 125) self.skim_list_table.setColumnWidth(0, 70) self.skim_list_table.setColumnWidth(1, 490) def choose_folder_for_outputs(self): new_name = GetOutputFolderName(self.path, 'Output folder for traffic assignment') if new_name: self.output_path = new_name self.lbl_output.setText(new_name) else: self.output_path = None self.lbl_output.setText(new_name) def load_graph(self): self.lbl_graphfile.setText('') file_types = ["AequilibraE graph(*.aeg)"] default_type = '.aeg' box_name = 'Traffic Assignment' graph_file, _ = GetOutputFileName(self, box_name, file_types, default_type, self.path) if graph_file is not None: self.graph.load_from_disk(graph_file) fields = list(set(self.graph.graph.dtype.names) - set(self.graph.required_default_fields)) self.minimizing_field.addItems(fields) self.update_skim_list(fields) self.lbl_graphfile.setText(graph_file) cores = get_parameter_chain(['system', 'cpus']) self.results.set_cores(cores) # show graph properties def centers_item(qt_item): cell_widget = QWidget() lay_out = QHBoxLayout(cell_widget) lay_out.addWidget(qt_item) lay_out.setAlignment(Qt.AlignCenter) lay_out.setContentsMargins(0, 0, 0, 0) cell_widget.setLayout(lay_out) return cell_widget items = [['Graph ID', self.graph.__id__], ['Number of links', self.graph.num_links], ['Number of nodes', self.graph.num_nodes], ['Number of centroids', self.graph.num_zones]] self.graph_properties_table.clearContents() self.graph_properties_table.setRowCount(5) for i, item in enumerate(items): self.graph_properties_table.setItem(i, 0, QTableWidgetItem(item[0])) self.graph_properties_table.setItem(i, 1, QTableWidgetItem(str(item[1]))) self.graph_properties_table.setItem(4, 0, QTableWidgetItem('Block flows through centroids')) self.block_centroid_flows = QCheckBox() self.block_centroid_flows.setChecked(self.graph.block_centroid_flows) self.graph_properties_table.setCellWidget(4, 1, centers_item(self.block_centroid_flows)) else: self.graph = Graph() self.set_behavior_special_analysis() def changing_algorithm(self): if self.cb_choose_algorithm.currentText() == 'All-Or-Nothing': self.method['algorithm'] = 'AoN' def run_thread(self): self.worker_thread.assignment.connect(self.signal_handler) # QObject.connect(self.worker_thread, SIGNAL("assignment"), self.signal_handler) self.worker_thread.start() self.exec_() def job_finished_from_thread(self): self.report = self.worker_thread.report self.produce_all_outputs() self.exit_procedure() def run(self): if self.check_data(): self.set_output_names() self.progress_label0.setVisible(True) self.progressbar0.setVisible(True) self.progressbar0.setRange(0, self.graph.num_zones) try: if self.method['algorithm'] == 'AoN': self.worker_thread = allOrNothing(self.matrix, self.graph, self.results) self.run_thread() except ValueError as error: qgis.utils.iface.messageBar().pushMessage("Input error", error.message, level=3) else: qgis.utils.iface.messageBar().pushMessage("Input error", self.error, level=3) def set_output_names(self): self.path_file.temp_file = os.path.join(self.temp_path, 'path_file.aed') self.path_file.output_name = os.path.join(self.output_path, 'path_file') self.path_file.extension = 'aed' if self.do_path_file.isChecked(): self.results.setSavePathFile(save=True, path_result=self.path_file.temp_file) self.link_extract.temp_file = os.path.join(self.temp_path, 'link_extract') self.link_extract.output_name = os.path.join(self.output_path, 'link_extract') self.link_extract.extension = 'aed' self.critical_output.temp_file = os.path.join(self.temp_path, 'critical_output') self.critical_output.output_name = os.path.join(self.output_path, 'critical_output') self.critical_output.extension = 'aed' def check_data(self): self.error = None self.change_graph_settings() if not self.graph.num_links: self.error = 'Graph was not loaded' return False self.matrix = None if not self.prepare_assignable_matrices(): return False if self.matrix is None: self.error = 'Demand matrix missing' return False if self.output_path is None: self.error = 'Parameters for output missing' return False self.temp_path = os.path.join(self.output_path, 'temp') if not os.path.exists(self.temp_path): os.makedirs(self.temp_path) self.results.prepare(self.graph, self.matrix) return True def load_assignment_queries(self): # First we load the assignment queries query_labels = [] query_elements = [] query_types = [] if self.tot_crit_link_queries: for i in range(self.tot_crit_link_queries): links = eval(self.select_link_list.item(i, 0).text()) query_type = self.select_link_list.item(i, 1).text() query_name = self.select_link_list.item(i, 2).text() for l in links: d = directions_dictionary[l[1]] lk = self.graph.ids[(self.graph.graph['link_id'] == int(l[0])) & (self.graph.graph['direction'] == d)] query_labels.append(query_name) query_elements.append(lk) query_types.append(query_type) self.critical_queries = {'labels': query_labels, 'elements': query_elements, ' type': query_types} def signal_handler(self, val): if val[0] == 'zones finalized': self.progressbar0.setValue(val[1]) elif val[0] == 'text AoN': self.progress_label0.setText(val[1]) elif val[0] == 'finished_threaded_procedure': self.job_finished_from_thread() # TODO: Write code to export skims def produce_all_outputs(self): extension = 'aed' if not self.do_output_to_aequilibrae.isChecked(): extension = 'csv' if self.do_output_to_sqlite.isChecked(): extension = 'sqlite' # Save link flows to disk self.results.save_to_disk(os.path.join(self.output_path, 'link_flows.' + extension), output='loads') # save Path file if that is the case if self.do_path_file.isChecked(): if self.method['algorithm'] == 'AoN': if self.do_output_to_sqlite.isChecked(): self.results.save_to_disk(file_name=os.path.join(self.output_path, 'path_file.' + extension), output='path_file') # Saves output skims if self.skim_list_table.rowCount() > 0: self.results.skims.copy(os.path.join(self.output_path, 'skims.aem')) # if self.do_select_link.isChecked(): # if self.method['algorithm'] == 'AoN': # del(self.results.critical_links['results']) # self.results.critical_links = None # # shutil.move(self.critical_output.temp_file + '.aep', self.critical_output.output_name) # shutil.move(self.critical_output.temp_file + '.aed', self.critical_output.output_name[:-3] + 'aed') # # if self.do_extract_link_flows.isChecked(): # if self.method['algorithm'] == 'AoN': # del(self.results.link_extraction['results']) # self.results.link_extraction = None # # shutil.move(self.link_extract.temp_file + '.aep', self.link_extract.output_name) # shutil.move(self.link_extract.temp_file + '.aed', self.link_extract.output_name[:-3] + 'aed') # Procedures related to critical analysis. Not yet fully implemented def build_query(self, purpose): if purpose == 'select link': button = self.but_build_query message = 'Select Link Analysis' table = self.select_link_list counter = self.tot_crit_link_queries else: button = self.but_build_query_extract message = 'Link flow extraction' table = self.list_link_extraction counter = self.tot_link_flow_extract button.setEnabled(False) dlg2 = LoadSelectLinkQueryBuilderDialog(self.iface, self.graph.graph, message) dlg2.exec_() if dlg2.links is not None: table.setRowCount(counter + 1) text = '' for i in dlg2.links: text = text + ', (' + only_str(i[0]) + ', "' + only_str(i[1]) + '")' text = text[2:] table.setItem(counter, 0, QTableWidgetItem(text)) table.setItem(counter, 1, QTableWidgetItem(dlg2.query_type)) table.setItem(counter, 2, QTableWidgetItem(dlg2.query_name)) del_button = QPushButton('X') del_button.clicked.connect(partial(self.click_button_inside_the_list, purpose)) table.setCellWidget(counter, 3, del_button) counter += 1 if purpose == 'select link': self.tot_crit_link_queries = counter elif purpose == 'Link flow extraction': self.tot_link_flow_extract = counter button.setEnabled(True) def click_button_inside_the_list(self, purpose): if purpose == 'select link': table = self.select_link_list else: table = self.list_link_extraction button = self.sender() index = self.select_link_list.indexAt(button.pos()) row = index.row() table.removeRow(row) if purpose == 'select link': self.tot_crit_link_queries -= 1 elif purpose == 'Link flow extraction': self.tot_link_flow_extract -= 1 def set_behavior_special_analysis(self): if self.graph.num_links < 1: behavior = False else: behavior = True self.do_path_file.setEnabled(behavior) # This line of code turns off the features of select link analysis and link flow extraction while these # features are still being developed behavior = False self.do_select_link.setEnabled(behavior) self.do_extract_link_flows.setEnabled(behavior) self.but_build_query.setEnabled(behavior * self.do_select_link.isChecked()) self.select_link_list.setEnabled(behavior * self.do_select_link.isChecked()) self.list_link_extraction.setEnabled(behavior * self.do_extract_link_flows.isChecked()) self.but_build_query_extract.setEnabled(behavior * self.do_extract_link_flows.isChecked()) def update_skim_list(self, skims): self.skim_list_table.clearContents() self.skim_list_table.setRowCount(len(skims)) for i, skm in enumerate(skims): self.skim_list_table.setItem(i, 1, QTableWidgetItem(skm)) chb = QCheckBox() my_widget = QWidget() lay_out = QHBoxLayout(my_widget) lay_out.addWidget(chb) lay_out.setAlignment(Qt.AlignCenter) lay_out.setContentsMargins(0, 0, 0, 0) my_widget.setLayout(lay_out) self.skim_list_table.setCellWidget(i, 0, my_widget) # All Matrix loading and assignables selection def update_matrix_list(self): self.table_matrix_list.clearContents() self.table_matrix_list.clearContents() self.table_matrix_list.setEditTriggers(QAbstractItemView.NoEditTriggers) self.table_matrix_list.setRowCount(len(self.matrices.keys())) for i, data_name in enumerate(self.matrices.keys()): self.table_matrix_list.setItem(i, 0, QTableWidgetItem(data_name)) cbox = QComboBox() for idx in self.matrices[data_name].index_names: cbox.addItem(str(idx)) self.table_matrix_list.setCellWidget(i, 1, cbox) def find_matrices(self): dlg2 = LoadMatrixDialog(self.iface) dlg2.show() dlg2.exec_() if dlg2.matrix is not None: matrix_name = dlg2.matrix.file_path matrix_name = os.path.splitext(os.path.basename(matrix_name))[0] matrix_name = self.find_non_conflicting_name(matrix_name, self.matrices) self.matrices[matrix_name] = dlg2.matrix self.update_matrix_list() row_count = self.table_matrices_to_assign.rowCount() new_matrix = list(self.matrices.keys())[-1] for i in range(row_count): cb = self.table_matrices_to_assign.cellWidget(i, 0) cb.insertItem(-1, new_matrix) def find_non_conflicting_name(self, data_name, dictio): if data_name in dictio: i = 1 new_data_name = data_name + '_' + str(i) while new_data_name in dictio: i += 1 new_data_name = data_name + '_' + str(i) data_name = new_data_name return data_name def changed_assignable_matrix(self, mi): chb = self.sender() mat_name = chb.currentText() table = self.table_matrices_to_assign for row in range(table.rowCount()): if table.cellWidget(row, 0) == chb: break if len(mat_name) == 0: if row + 1 < table.rowCount(): self.table_matrices_to_assign.removeRow(row) else: mat_cores = self.matrices[mat_name].names cbox2 = QComboBox() cbox2.addItems(mat_cores) self.table_matrices_to_assign.setCellWidget(row, 1, cbox2) if row + 1 == table.rowCount(): self.new_matrix_to_assign() def new_matrix_to_assign(self): # We edit ALL the combo boxes to have the current list of matrices row_count = self.table_matrices_to_assign.rowCount() self.table_matrices_to_assign.setRowCount(row_count + 1) cbox = QComboBox() cbox.addItems(list(self.matrices.keys())) cbox.addItem('') cbox.setCurrentIndex(cbox.count() - 1) cbox.currentIndexChanged.connect(self.changed_assignable_matrix) self.table_matrices_to_assign.setCellWidget(row_count, 0, cbox) def prepare_assignable_matrices(self): table = self.table_matrices_to_assign idx = self.graph.centroids mat_names = [] if table.rowCount() > 1: for row in range(table.rowCount() - 1): mat = table.cellWidget(row, 0).currentText() core = table.cellWidget(row, 1).currentText() mat_index = self.matrices[mat].index if not np.array_equal(idx, mat_index): no_zones = [item for item in mat_index if item not in idx] # We only return an error if the matrix has too many centroids if no_zones: self.error = 'Assignable matrix has centroids that do not exist in the network: {}'.format( ','.join([str(x) for x in no_zones])) return False if core in mat_names: self.error = 'Assignable matrices cannot have same names' return False mat_names.append(only_str(core)) self.matrix = AequilibraeMatrix() self.matrix.create_empty(file_name=self.matrix.random_name(), zones=idx.shape[0], matrix_names=mat_names) self.matrix.index[:] = idx[:] for row in range(table.rowCount() - 1): mat = table.cellWidget(row, 0).currentText() core = table.cellWidget(row, 1).currentText() src_mat = self.matrices[mat].matrix[core] dest_mat = self.matrix.matrix[core] rows = src_mat.shape[0] cols = src_mat.shape[1] dest_mat[:rows, :cols] = src_mat[:, :] # Inserts cols and rows that don;t exist if rows != self.matrix.zones: src_index = list(self.matrices[mat].index[:]) for i, row in enumerate(idx): if row not in src_index: dest_mat[i + 1:, :] = dest_mat[i:-1, :] dest_mat[i, :] = 0 if cols != self.matrix.zones: for j, col in enumerate(idx): if col not in src_index: dest_mat[:, j + 1:] = dest_mat[:, j:-1] dest_mat[:, j] = 0 self.matrix.computational_view() else: self.error = 'You need to have at least one matrix to assign' return False return True def change_graph_settings(self): skims = [] table = self.skim_list_table for i in range(table.rowCount()): for chb in table.cellWidget(i, 0).findChildren(QCheckBox): if chb.isChecked(): skims.append(only_str(table.item(i, 1).text())) if len(skims) == 0: skims = False self.graph.set_graph(cost_field=self.minimizing_field.currentText(), skim_fields=skims, block_centroid_flows=self.block_centroid_flows.isChecked()) def exit_procedure(self): self.close() if self.report: dlg2 = ReportDialog(self.iface, self.report) dlg2.show() dlg2.exec_()
args["field_names"] = ["columns"] column_vector = AequilibraeData() column_vector.create_empty(**args) column_vector.index[:] = np.arange(column_vector.entries) + 100 column_vector.columns[:] = column_vector.index[:] + np.random.rand(zones)[:] # balance vectors column_vector.columns[:] = column_vector.columns[:] * (row_vector.rows.sum() / column_vector.columns.sum()) # Impedance matrix_procedures name_test = os.path.join(tempfile.gettempdir(), "aequilibrae_matrix_test.aem") args = {"file_name": name_test, "zones": zones, "matrix_names": ["impedance"]} matrix = AequilibraeMatrix() matrix.create_empty(**args) # randoms = np.random.randint(5, size=(2, 4)) matrix.impedance[:, :] = np.random.rand(zones, zones)[:, :] matrix.index[:] = np.arange(matrix.zones) + 100 matrix.computational_view(["impedance"]) model_expo = SyntheticGravityModel() model_expo.function = "EXPO" model_expo.beta = 0.1 model_gamma = SyntheticGravityModel() model_gamma.function = "GAMMA" model_gamma.beta = 0.1 model_gamma.alpha = -0.2
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()
def save_skims(self, matrix_name: str, which_ones="final", format="omx") -> None: """Saves the skims (if any) to the skim folder and registers in the matrix list Args: name (:obj:`str`): Name of the matrix record to hold this matrix (same name used for file name) which_ones (:obj:`str`,optional): {'final': Results of the final iteration, 'blended': Averaged results for all iterations, 'all': Saves skims for both the final iteration and the blended ones} Default is 'final' *format* (:obj:`str`, `Optional`): File format ('aem' or 'omx'). Default is 'omx' """ mat_format = format.lower() if mat_format not in ["omx", "aem"]: raise ValueError("Matrix needs to be either OMX or native AequilibraE") if mat_format == "omx" and not has_omx: raise ImportError("OpenMatrix is not available on your system") file_name = f"{matrix_name}.{mat_format}" mats = Matrices() export_name = path.join(mats.fldr, file_name) if path.isfile(export_name): raise FileExistsError(f"{file_name} already exists. Choose a different name or matrix format") if mats.check_exists(matrix_name): raise FileExistsError(f"{matrix_name} already exists. Choose a different name") avg_skims = self.classes[0].results.skims # type: AequilibraeMatrix # The ones for the last iteration are here last_skims = self.classes[0]._aon_results.skims # type: AequilibraeMatrix names = [] if which_ones in ["final", "all"]: for core in last_skims.names: names.append(f"{core}_final") if which_ones in ["blended", "all"]: for core in avg_skims.names: names.append(f"{core}_blended") if not names: raise ValueError("No skims to save") # Assembling a single final skim file can be done like this # We will want only the time for the last iteration and the distance averaged out for all iterations working_name = export_name if mat_format == "aem" else AequilibraeMatrix().random_name() kwargs = {"file_name": working_name, "zones": self.classes[0].graph.centroids.shape[0], "matrix_names": names} # Create the matrix to manipulate out_skims = AequilibraeMatrix() out_skims.create_empty(**kwargs) out_skims.index[:] = self.classes[0].graph.centroids[:] out_skims.description = f"Assignment skim from procedure ID {self.procedure_id}" if which_ones in ["final", "all"]: for core in last_skims.names: out_skims.matrix[f"{core}_final"][:, :] = last_skims.matrix[core][:, :] if which_ones in ["blended", "all"]: for core in avg_skims.names: out_skims.matrix[f"{core}_blended"][:, :] = avg_skims.matrix[core][:, :] out_skims.matrices.flush() # Make sure that all data went to the disk # If it were supposed to be an OMX, we export to one if mat_format == "omx": out_skims.export(export_name) # Now we create the appropriate record record = mats.new_record(matrix_name, file_name) record.procedure_id = self.procedure_id record.timestamp = self.procedure_date record.procedure = "Traffic Assignment" record.description = "Skimming for assignment procedure" record.save()
def produce_all_outputs(self): fn = os.path.join(self.output_path, "skims.aem") fn_omx = os.path.join(self.output_path, "skims.omx") if self.cb_choose_algorithm.currentText() == 'all-or-nothing': cls = [x for x in self.traffic_classes.values() if x is not None][0] cls.results.save_to_disk(os.path.join( self.output_path, f"link_flows_{cls.graph.mode}.csv"), output="loads") cls.results.save_to_disk(os.path.join( self.output_path, f"link_flows_{cls.graph.mode}.aed"), output="loads") if has_omx: cls.results.skims.export(fn_omx) else: cls.results.skims.export(fn) return table = self.skim_list_table skim_names = [] for i in range(table.rowCount()): mode = self.all_modes[table.item(i, 0).text()] field = table.item(i, 1).text() last_iter = table.cellWidget(i, 2).isChecked() blended = table.cellWidget(i, 3).isChecked() if last_iter: skim_names.append(f'{field}_{mode}_final') if blended: skim_names.append(f'{field}_{mode}_blended') for cls in self.assignment.classes: cls.results.save_to_disk(os.path.join( self.output_path, f"link_flows_{cls.graph.mode}.csv"), output="loads") cls.results.save_to_disk(os.path.join( self.output_path, f"link_flows_{cls.graph.mode}.aed"), output="loads") # cls.results.skims.export(os.path.join(self.output_path, f'blended_skims_{cls.graph.mode}.aed')) if skim_names: args = { 'file_name': fn, 'zones': self.project.network.count_centroids(), 'matrix_names': skim_names } skims = AequilibraeMatrix() skims.create_empty(**args) for i in range(table.rowCount()): mode_name = table.item(i, 0).text() mode = self.all_modes[mode_name] field = table.item(i, 1).text() last_iter = table.cellWidget(i, 2).isChecked() blended = table.cellWidget(i, 3).isChecked() cls = self.traffic_classes[mode_name] if last_iter: mat_name = f'{field}_{mode}_final' skims.matrix[ mat_name][:, :] = cls._aon_results.skims.matrix[ field][:, :] if blended: mat_name = f'{field}_{mode}_blended' skims.matrix[mat_name][:, :] = cls.results.skims.matrix[ field][:, :] skims.index[:] = cls.matrix.index[:] if has_omx: skims.export(fn_omx) skims.close() del skims os.unlink(fn)
column_vector.create_empty(**args) column_vector.index[:] = np.arange(column_vector.entries) + 100 column_vector.columns[:] = column_vector.index[:] + np.random.rand(zones)[:] # balance vectors column_vector.columns[:] = column_vector.columns[:] * (row_vector.rows.sum() / column_vector.columns.sum()) # Impedance matrix_procedures name_test = os.path.join(tempfile.gettempdir(), 'aequilibrae_matrix_test.aem') args = {'file_name': name_test, 'zones': zones, 'matrix_names': ['impedance']} matrix = AequilibraeMatrix() matrix.create_empty(**args) # randoms = np.random.randint(5, size=(2, 4)) matrix.impedance[:, :] = np.random.rand(zones, zones)[:, :] matrix.index[:] = np.arange(matrix.zones) + 100 matrix.computational_view(['impedance']) model_expo = SyntheticGravityModel() model_expo.function = 'EXPO' model_expo.beta = 0.1 model_gamma = SyntheticGravityModel() model_gamma.function = 'GAMMA' model_gamma.beta = 0.1 model_gamma.alpha = -0.2
from aequilibrae.matrix import AequilibraeMatrix from aequilibrae.distribution import GravityCalibration import numpy as np import os, tempfile zones = 100 # Impedance matrix_procedures name_test = AequilibraeMatrix().random_name() args = {'file_name': name_test, 'zones': zones, 'matrix_names': ['impedance']} impedance = AequilibraeMatrix() impedance.create_empty(**args) impedance.impedance[:, :] = np.random.rand(zones, zones)[:,:] * 1000 impedance.index[:] = np.arange(impedance.zones) + 100 impedance.computational_view(['impedance']) args['matrix_names'] = ['base_matrix'] args['file_name'] = AequilibraeMatrix().random_name() matrix = AequilibraeMatrix() matrix.create_empty(**args) matrix.base_matrix[:, :] = np.random.rand(zones, zones)[:,:] * 1000 matrix.index[:] = np.arange(matrix.zones) + 100 matrix.computational_view(['base_matrix']) class TestGravityCalibration(TestCase):
class AssignmentResults: """ Assignment result holder for a single :obj:`TrafficClass` with multiple user classes """ def __init__(self): self.compact_link_loads = np.array([]) # Results for assignment on simplified graph self.compact_total_link_loads = np.array([]) # Results for all user classes summed on simplified graph self.link_loads = np.array([]) # The actual results for assignment self.total_link_loads = np.array([]) # The result of the assignment for all user classes summed self.crosswalk = np.array([]) # crosswalk between compact graph link IDs and actual link IDs self.skims = AequilibraeMatrix() # The array of skims self.no_path = None # The list os paths self.num_skims = 0 # number of skims that will be computed. Depends on the setting of the graph provided p = Parameters().parameters["system"]["cpus"] if not isinstance(p, int): p = 0 self.set_cores(p) self.classes = {"number": 1, "names": ["flow"]} self.nodes = -1 self.zones = -1 self.links = -1 self.compact_links = -1 self.compact_nodes = -1 self.__graph_id__ = None self.__float_type = None self.__integer_type = None self.lids = None self.direcs = None # In case we want to do by hand, we can prepare each method individually def prepare(self, graph: Graph, matrix: AequilibraeMatrix) -> None: """ Prepares the object with dimensions corresponding to the assignment matrix and graph objects Args: *graph* (:obj:`Graph`): Needs to have been set with number of centroids and list of skims (if any) *matrix* (:obj:`AequilibraeMatrix`): Matrix properly set for computation with matrix.computational_view(:obj:`list`) """ self.__float_type = graph.default_types("float") self.__integer_type = graph.default_types("int") if matrix.view_names is None: raise ("Please set the matrix_procedures computational view") else: self.classes["number"] = 1 if len(matrix.matrix_view.shape) > 2: self.classes["number"] = matrix.matrix_view.shape[2] self.classes["names"] = matrix.view_names if graph is None: raise ("Please provide a graph") else: self.compact_nodes = graph.compact_num_nodes self.compact_links = graph.compact_num_links self.nodes = graph.num_nodes self.zones = graph.num_zones self.centroids = graph.centroids self.links = graph.num_links self.num_skims = len(graph.skim_fields) self.skim_names = [x for x in graph.skim_fields] self.lids = graph.graph.link_id.values self.direcs = graph.graph.direction.values self.crosswalk = np.zeros(graph.graph.shape[0], self.__integer_type) self.crosswalk[graph.graph.__supernet_id__.values] = graph.graph.__compressed_id__.values self.__graph_ids = graph.graph.__supernet_id__.values self.__redim() self.__graph_id__ = graph.__id__ def reset(self) -> None: """ Resets object to prepared and pre-computation state """ if self.num_skims > 0: self.skims.matrices.fill(0) if self.link_loads is not None: self.no_path.fill(0) self.link_loads.fill(0) self.total_link_loads.fill(0) self.compact_link_loads.fill(0) self.compact_total_link_loads.fill(0) else: raise ValueError("Exception: Assignment results object was not yet prepared/initialized") def __redim(self): self.compact_link_loads = np.zeros((self.compact_links + 1, self.classes["number"]), self.__float_type) self.compact_total_link_loads = np.zeros(self.compact_links, self.__float_type) self.link_loads = np.zeros((self.links, self.classes["number"]), self.__float_type) self.total_link_loads = np.zeros(self.links, self.__float_type) self.no_path = np.zeros((self.zones, self.zones), dtype=self.__integer_type) if self.num_skims > 0: self.skims = AequilibraeMatrix() self.skims.create_empty(file_name=self.skims.random_name(), zones=self.zones, matrix_names=self.skim_names) self.skims.index[:] = self.centroids[:] self.skims.computational_view() if len(self.skims.matrix_view.shape[:]) == 2: self.skims.matrix_view = self.skims.matrix_view.reshape((self.zones, self.zones, 1)) else: self.skims = AequilibraeMatrix() self.skims.matrix_view = np.array((1, 1, 1)) self.reset() def total_flows(self) -> None: """ Totals all link flows for this class into a single link load Results are placed into *total_link_loads* class member """ sum_axis1(self.total_link_loads, self.link_loads, self.cores) def set_cores(self, cores: int) -> None: """ Sets number of cores (threads) to be used in computation Value of zero sets number of threads to all available in the system, while negative values indicate the number of threads to be left out of the computational effort. Resulting number of cores will be adjusted to a minimum of zero or the maximum available in the system if the inputs result in values outside those limits Args: *cores* (:obj:`int`): Number of cores to be used in computation """ if not isinstance(cores, int): raise ValueError("Number of cores needs to be an integer") if cores < 0: self.cores = max(1, mp.cpu_count() + cores) elif cores == 0: self.cores = mp.cpu_count() elif cores > 0: cores = min(mp.cpu_count(), cores) if self.cores != cores: self.cores = cores if self.link_loads.shape[0]: self.__redim() def get_load_results(self) -> AequilibraeData: """ Translates the assignment results from the graph format into the network format Returns: dataset (:obj:`AequilibraeData`): AequilibraE data with the traffic class assignment results """ fields = [] for n in self.classes["names"]: fields.extend([f"{n}_ab", f"{n}_ba", f"{n}_tot"]) types = [np.float64] * len(fields) entries = int(np.unique(self.lids).shape[0]) res = AequilibraeData() res.create_empty(memory_mode=True, entries=entries, field_names=fields, data_types=types) res.data.fill(np.nan) res.index[:] = np.unique(self.lids)[:] indexing = np.zeros(int(self.lids.max()) + 1, np.uint64) indexing[res.index[:]] = np.arange(entries) # Indices of links BA and AB ABs = self.direcs > 0 BAs = self.direcs < 0 ab_ids = indexing[self.lids[ABs]] ba_ids = indexing[self.lids[BAs]] # Link flows link_flows = self.link_loads[self.__graph_ids, :] for i, n in enumerate(self.classes["names"]): # AB Flows res.data[n + "_ab"][ab_ids] = np.nan_to_num(link_flows[ABs, i]) # BA Flows res.data[n + "_ba"][ba_ids] = np.nan_to_num(link_flows[BAs, i]) # Tot Flow res.data[n + "_tot"] = np.nan_to_num(res.data[n + "_ab"]) + np.nan_to_num(res.data[n + "_ba"]) return res def save_to_disk(self, file_name=None, output="loads") -> None: """ Function to write to disk all outputs computed during assignment Args: *file_name* (:obj:`str`): Name of the file, with extension. Valid extensions are: ['aed', 'csv', 'sqlite'] *output* (:obj:`str`, optional): Type of output ('loads', 'path_file'). Defaults to 'loads' """ if output == "loads": res = self.get_load_results() res.export(file_name) # TODO: Re-factor the exporting of the path file within the AequilibraeData format elif output == "path_file": raise NotImplementedError
from aequilibrae.matrix import AequilibraeMatrix from aequilibrae.distribution import GravityCalibration import numpy as np import os, tempfile zones = 100 # Impedance matrix_procedures name_test = AequilibraeMatrix().random_name() args = {'file_name': name_test, 'zones': zones, 'matrix_names': ['impedance']} impedance = AequilibraeMatrix() impedance.create_empty(**args) impedance.impedance[:, :] = np.random.rand(zones, zones)[:,:] * 1000 impedance.index[:] = np.arange(impedance.zones) + 100 impedance.computational_view(['impedance']) args['matrix_names'] = ['base_matrix'] matrix = AequilibraeMatrix() matrix.create_empty(**args) matrix.base_matrix[:, :] = np.random.rand(zones, zones)[:,:] * 1000 matrix.index[:] = np.arange(matrix.zones) + 100 matrix.computational_view(['base_matrix']) class TestGravityCalibration(TestCase): def test_calibrate(self):