228 lines
8.8 KiB
Python
228 lines
8.8 KiB
Python
|
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())
|