from PyQt5 import QtCore, QtWidgets import networkx as nx import matplotlib.pyplot as plt import numpy as np import random from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from prettytable import PrettyTable def is_square(matrix): return all(len(row) == len(matrix) for row in matrix) def validate_values(matrix): return all(all(("1" == element) or ("0" == element) for element in row) for row in matrix) class Window(QtWidgets.QWidget): def __init__(self): super(QtWidgets.QWidget, self).__init__() self.setWindowTitle("Вікно 2") self.grid_layout = QtWidgets.QGridLayout(self) self.adjacency_input_label = QtWidgets.QLabel(self) self.adjacency_input_label.setText("Введіть матрицю суміжності графа") self.grid_layout.addWidget(self.adjacency_input_label, 0, 0, 1, 1) self.adjacency_input = QtWidgets.QTextEdit(self) self.grid_layout.addWidget(self.adjacency_input, 1, 0, 2, 1) self.adjacency_output_label = QtWidgets.QLabel(self) self.adjacency_output_label.setText("Матриця суміжності") self.grid_layout.addWidget(self.adjacency_output_label, 0, 1, 1, 1) self.adjacency_output = QtWidgets.QLabel(self) self.grid_layout.addWidget(self.adjacency_output, 1, 1, 1, 1) self.incidence_output_label = QtWidgets.QLabel(self) self.incidence_output_label.setText("Матриця інцидентності") self.grid_layout.addWidget(self.incidence_output_label, 0, 2, 1, 1) self.incidence_output = QtWidgets.QLabel(self) self.grid_layout.addWidget(self.incidence_output, 1, 2, 1, 1) self.edges_dictionary_output_label = QtWidgets.QLabel(self) self.edges_dictionary_output_label.setText("Словник граней") self.grid_layout.addWidget(self.edges_dictionary_output_label, 0, 3, 1, 1) self.edges_dictionary_output = QtWidgets.QLabel(self) self.grid_layout.addWidget(self.edges_dictionary_output, 1, 3, 1, 1) self.canvas_grid = QtWidgets.QGridLayout() self.figure = plt.figure() self.canvas = FigureCanvas(self.figure) self.canvas_grid.addWidget(self.canvas, 0, 0, 1, 1) self.grid_layout.addLayout(self.canvas_grid, 2, 1, 1, 3) self.build_graph_button = QtWidgets.QPushButton(self) self.build_graph_button.setText("Побудувати граф") self.grid_layout.addWidget(self.build_graph_button, 3, 0, 1, 2) self.open_file_button = QtWidgets.QPushButton(self) self.open_file_button.setText("Відкрити файл") self.grid_layout.addWidget(self.open_file_button, 3, 2, 1, 2) QtCore.QMetaObject.connectSlotsByName(self) self.set_functions() def parse_matrix(self) -> (np.ndarray | None, str | None): text_matrix = list(map(lambda x: x.split(" "), self.adjacency_input.toPlainText().splitlines())) if not is_square(text_matrix): return None, "square_error" elif not validate_values(text_matrix): return None, "value_error" else: return np.array(list([list(map(int, row)) for row in text_matrix])), None def build_graphs(self): adjacency_matrix, error = self.parse_matrix() if adjacency_matrix is None and error == "square_error": self.show_error( "Матриця суміжності має бути квадратною. Перевірте чи кожен елемент рядка відділений пробілом і чи " "дорівнює кількість рядків кількості стовпців." ) elif adjacency_matrix is None and error == "value_error": self.show_error( "Матриця суміжності мусить складатись лише з нулів і одиниць розділених пробілом. Перевірте ваш ввід." ) else: adjacency_graph = self.create_adjacency_graph(adjacency_matrix) self.display_adjacency_matrix(adjacency_graph, self.adjacency_output) incidence_nodes, incidence_edges, incidence_matrix = self.create_incidence_matrix(adjacency_graph) self.display_incidence_matrix(incidence_nodes, incidence_edges, incidence_matrix, self.incidence_output) incidence_graph = self.create_incidence_graph(incidence_nodes, incidence_edges) self.draw_graph(adjacency_graph, incidence_graph) self.display_edge_dictionary(adjacency_graph, self.edges_dictionary_output) @staticmethod def show_error(error_text: str): error_message = QtWidgets.QMessageBox() error_message.setIcon(QtWidgets.QMessageBox.Critical) error_message.setText("Помилка вводу") error_message.setInformativeText(error_text) error_message.setWindowTitle("Помилка") error_message.exec_() @staticmethod def create_adjacency_graph(adjacency_matrix: np.ndarray) -> nx.DiGraph: graph = nx.DiGraph() nodes = [f"X{i}" for i in range(len(adjacency_matrix))] edge_indices = np.argwhere(adjacency_matrix) edges = [(nodes[edge_index[0]], nodes[edge_index[1]]) for edge_index in edge_indices] graph.add_nodes_from(nodes) graph.add_edges_from(edges) return graph @staticmethod def create_incidence_graph(nodes: list, edges: list) -> nx.DiGraph: graph = nx.DiGraph() graph.add_nodes_from(nodes) graph.add_edges_from(edges) return graph def draw_graph(self, adjacency_graph: nx.DiGraph, incidence_graph: nx.DiGraph): random.seed(0) np.random.seed(0) self.figure.clf() pos = nx.planar_layout(adjacency_graph, scale=0.5) nx.draw( adjacency_graph, pos=pos, with_labels=True, node_color="blueviolet", node_size=700, font_color="white", font_size=14, font_family="Liberation Mono", font_weight="bold", width=2 ) pos = nx.planar_layout(incidence_graph, scale=0.5) for i in pos: pos[i][0] += 2 nx.draw( adjacency_graph, pos=pos, with_labels=True, node_color="blueviolet", node_size=700, font_color="white", font_size=14, font_family="Liberation Mono", font_weight="bold", width=2 ) self.canvas.draw_idle() @staticmethod def display_adjacency_matrix(graph: nx.DiGraph, label: QtWidgets.QLabel): matrix = nx.adjacency_matrix(graph).todense().tolist() table = PrettyTable() nodes = [f"X{i}" for i in range(len(matrix))] table.field_names = [" "] + nodes for i in range(len(matrix)): table.add_row([nodes[i]] + matrix[i]) label.setText(table.get_string()) @staticmethod def display_incidence_matrix(nodes: list, edges: list, incidence_matrix: list, label: QtWidgets.QLabel): table = PrettyTable() edge_names = [f"E{i}" for i in range(len(edges))] table.field_names = [" "] + edge_names for i in range(len(incidence_matrix)): table.add_row([nodes[i]] + incidence_matrix[i]) label.setText(table.get_string()) @staticmethod def create_incidence_matrix(graph: nx.DiGraph) -> (list, list, list): nodes = list(graph.nodes) edges = list(graph.edges) edge_names = [f"E{i}" for i in range(len(edges))] matrix = [["0" for j in range(len(edges))] for i in range(len(nodes))] matrix = [ ["1" if nodes[i] == edges[j][0] else matrix[i][j] for j in range(len(matrix[i]))] for i in range(len(matrix))] matrix = [ ["-1" if nodes[i] == edges[j][1] else matrix[i][j] for j in range(len(matrix[i]))] for i in range(len(matrix))] matrix = [ ["±1" if (nodes[i] == edges[j][0] and nodes[i] == edges[j][1]) else matrix[i][j] for j in range(len(matrix[i]))] for i in range(len(matrix))] return nodes, edges, matrix @staticmethod def display_edge_dictionary(graph: nx.DiGraph, label: QtWidgets.QLabel): edge_dictionary = "" edges = list(graph.edges) for i, edge in enumerate(edges): edge_dictionary += f"E{i} = ({edge[0]} -> {edge[1]})\n" label.setText(edge_dictionary) def browse_files(self): file_name = QtWidgets.QFileDialog.getOpenFileName(self, "Відкрити файл", "~/", "Текстові файли (*.txt)") with open(file_name[0]) as f: text_read = f.read() self.adjacency_input.setText(text_read) def set_functions(self): self.build_graph_button.clicked.connect(lambda: self.build_graphs()) self.open_file_button.clicked.connect(lambda: self.browse_files())