435 lines
12 KiB
Python
435 lines
12 KiB
Python
import sys
|
|
from PyQt6.QtWidgets import (
|
|
QMessageBox,
|
|
QMainWindow,
|
|
QLabel,
|
|
QComboBox,
|
|
QPushButton,
|
|
QFormLayout,
|
|
QWidget,
|
|
QLineEdit,
|
|
QHBoxLayout,
|
|
QLayout,
|
|
QApplication,
|
|
)
|
|
from PyQt6.QtCore import Qt
|
|
import numpy as np
|
|
|
|
import task
|
|
import algorithm
|
|
import solution
|
|
|
|
|
|
class DisplayField(QWidget):
|
|
label_text: str
|
|
|
|
def __init__(self, parent, label_text="", text=""):
|
|
super().__init__(parent)
|
|
|
|
self._layout = QHBoxLayout(self)
|
|
self._layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
self.label_text = label_text
|
|
self.text = QLabel(f"{self.label_text}{text}")
|
|
self.text.setAlignment(Qt.AlignmentFlag.AlignLeft)
|
|
|
|
self._layout.addWidget(self.text)
|
|
|
|
def set_visible(self, visible):
|
|
self.text.setVisible(visible)
|
|
|
|
def set_text(self, text):
|
|
self.text.setText(f"{self.label_text}{text}")
|
|
|
|
|
|
class InputField(QWidget):
|
|
value: str
|
|
change_func: callable = None
|
|
|
|
def __init__(
|
|
self,
|
|
parent,
|
|
label_text="",
|
|
placeholder_text="",
|
|
lineedit_text="",
|
|
label_position="left",
|
|
):
|
|
super().__init__(parent)
|
|
|
|
self._layout = QHBoxLayout(self)
|
|
self._layout.setContentsMargins(0, 0, 0, 0)
|
|
self.label = QLabel(label_text)
|
|
self.lineedit = QLineEdit(lineedit_text)
|
|
self.lineedit.setPlaceholderText(placeholder_text)
|
|
self.lineedit.setMinimumWidth(50)
|
|
|
|
if label_position == "left":
|
|
self._layout.addWidget(self.label)
|
|
self._layout.addWidget(self.lineedit)
|
|
elif label_position == "right":
|
|
self._layout.addWidget(self.lineedit)
|
|
self._layout.addWidget(self.label)
|
|
elif label_position == "none":
|
|
self._layout.addWidget(self.lineedit)
|
|
|
|
self.value = self.get_value()
|
|
self.lineedit.textChanged.connect(self._on_change)
|
|
|
|
def get_value(self):
|
|
return self.lineedit.text()
|
|
|
|
def set_value(self, value):
|
|
self.lineedit.setText(value)
|
|
|
|
def on_change(self, func):
|
|
self.change_func = func
|
|
|
|
def _on_change(self):
|
|
if self.change_func:
|
|
self.change_func(self)
|
|
|
|
self.value = self.get_value()
|
|
|
|
|
|
def on_input_float_number(widget: InputField):
|
|
try:
|
|
str_value = widget.get_value()
|
|
if not (str_value == "" or str_value == "-"):
|
|
widget.value = str(float(str_value))
|
|
except:
|
|
widget.set_value(str(widget.value))
|
|
|
|
|
|
def on_input_int_number(widget: InputField):
|
|
try:
|
|
str_value = widget.get_value()
|
|
if not (str_value == "" or str_value == "-"):
|
|
widget.value = str(int(str_value))
|
|
except:
|
|
widget.set_value(str(widget.value))
|
|
|
|
|
|
class MatrixRowWidget(QWidget):
|
|
x_inputs: list[InputField]
|
|
b_input: InputField
|
|
|
|
def __init__(self, parent, i: int, n: int):
|
|
super().__init__(parent)
|
|
|
|
self._layout = QHBoxLayout()
|
|
self._layout.setContentsMargins(0, 0, 0, 0)
|
|
self.x_inputs = []
|
|
for j in range(n):
|
|
inp = InputField(self, f"x{j + 1}", "", "0.0", "right")
|
|
inp.lineedit.setAlignment(Qt.AlignmentFlag.AlignRight)
|
|
self.x_inputs.append(inp)
|
|
self._layout.addWidget(inp)
|
|
if j != n - 1:
|
|
self._layout.addWidget(QLabel(" + "))
|
|
|
|
self._layout.addWidget(QLabel(" = "))
|
|
self.b_input = InputField(self, "", "", "0.0", "none")
|
|
self.b_input.lineedit.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
self._layout.addWidget(self.b_input)
|
|
|
|
self.setLayout(self._layout)
|
|
|
|
def get_value_x(self, i: int):
|
|
return self.x_inputs[i].get_value()
|
|
|
|
def get_value_b(self):
|
|
return self.b_input.get_value()
|
|
|
|
def on_change(self, func):
|
|
for inp in self.x_inputs:
|
|
inp.on_change(func)
|
|
|
|
self.b_input.on_change(func)
|
|
|
|
|
|
class MatrixWidget(QWidget):
|
|
rows: list[MatrixRowWidget]
|
|
_layout: QFormLayout
|
|
on_change_func = None
|
|
|
|
def __init__(self, parent, n: int):
|
|
super().__init__(parent)
|
|
|
|
self._initialize()
|
|
self._create(n)
|
|
|
|
def _initialize(self):
|
|
self.rows = []
|
|
self._layout = QFormLayout()
|
|
self._layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
def _create(self, n: int):
|
|
for i in range(n):
|
|
self.rows.append(MatrixRowWidget(self, i, n))
|
|
|
|
for row in self.rows:
|
|
self._layout.addRow(row)
|
|
if self.on_change_func:
|
|
row.on_change(self.on_change_func)
|
|
|
|
self.setLayout(self._layout)
|
|
|
|
def recreate(self, n: int):
|
|
for row in self.rows:
|
|
row.deleteLater()
|
|
|
|
self.rows = []
|
|
self._create(n)
|
|
|
|
def set_value_x(self, i: int, j: int, value: float):
|
|
self.rows[i].x_inputs[j].set_value(value)
|
|
|
|
def set_value_b(self, i: int, value: float):
|
|
self.rows[i].b_input.set_value(value)
|
|
|
|
def get_value_x(self, i: int, j: int):
|
|
return self.rows[i].get_value_x(j)
|
|
|
|
def get_value_b(self, i: int):
|
|
return self.rows[i].get_value_b()
|
|
|
|
def on_change(self, func):
|
|
self.on_change_func = func
|
|
|
|
for row in self.rows:
|
|
if self.on_change_func:
|
|
row.on_change(self.on_change_func)
|
|
|
|
|
|
class MainWindow(QMainWindow):
|
|
initialized = False
|
|
|
|
matrix_a: np.matrix
|
|
matrix_b: np.matrix
|
|
|
|
eps_input: InputField
|
|
n_input: InputField
|
|
det_display: DisplayField
|
|
cond_display: DisplayField
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
self.setupUI()
|
|
|
|
self.initialized = True
|
|
self.update_stats()
|
|
|
|
def setupUI(self):
|
|
self.setWindowTitle("Лабораторная работа №3")
|
|
self.resize(700, 200)
|
|
|
|
self.central_widget = QWidget()
|
|
self.setCentralWidget(self.central_widget)
|
|
|
|
layout = QFormLayout()
|
|
|
|
label1 = QLabel("Вариант 6", self)
|
|
layout.addWidget(label1)
|
|
|
|
inputs_row = QWidget()
|
|
inputs_row_layout = QHBoxLayout()
|
|
|
|
self.n_input = InputField(self, " n: ", "число строк", str(4), "left")
|
|
self.n_input.on_change(lambda w: self.on_n_input_changed(w))
|
|
inputs_row_layout.addWidget(self.n_input)
|
|
|
|
self.eps_input = InputField(self, " eps: ", "точность", str(0.001), "left")
|
|
self.eps_input.on_change(lambda w: self.on_eps_input_changed(w))
|
|
inputs_row_layout.addWidget(self.eps_input)
|
|
|
|
inputs_row.setLayout(inputs_row_layout)
|
|
layout.addWidget(inputs_row)
|
|
|
|
buttons_row = QWidget()
|
|
buttons_row_layout = QHBoxLayout()
|
|
buttons_row_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
self.reset_button = QPushButton("Сбросить", self)
|
|
self.reset_button.clicked.connect(self.on_reset_button_clicked)
|
|
buttons_row_layout.addWidget(self.reset_button)
|
|
|
|
self.paste_button = QPushButton("Вставить", self)
|
|
self.paste_button.clicked.connect(self.on_paste_button_clicked)
|
|
buttons_row_layout.addWidget(self.paste_button)
|
|
|
|
self.random_button = QPushButton("Случайная", self)
|
|
self.random_button.clicked.connect(self.on_random_button_clicked)
|
|
buttons_row_layout.addWidget(self.random_button)
|
|
|
|
buttons_row.setLayout(buttons_row_layout)
|
|
layout.addWidget(buttons_row)
|
|
|
|
self.matrix = MatrixWidget(self, 4)
|
|
self.matrix.on_change(lambda w: self.on_matrix_changed(w))
|
|
self.set_matrix(task.A(), task.B())
|
|
layout.addWidget(self.matrix)
|
|
|
|
self.det_display = DisplayField(self, "Определитель: ", "")
|
|
layout.addWidget(self.det_display)
|
|
|
|
self.cond_display = DisplayField(self, "Число обусловленности: ", "")
|
|
layout.addWidget(self.cond_display)
|
|
|
|
find_solution_button = QPushButton("Решить", self)
|
|
find_solution_button.clicked.connect(self.on_find_solution_clicked)
|
|
layout.addWidget(find_solution_button)
|
|
|
|
self.solution_display = DisplayField(self, "Решение: ", "")
|
|
layout.addWidget(self.solution_display)
|
|
self.solution_display.set_visible(False)
|
|
|
|
self.central_widget.setLayout(layout)
|
|
|
|
def on_eps_input_changed(self, widget: InputField):
|
|
on_input_float_number(widget)
|
|
|
|
eps = widget.get_value()
|
|
try:
|
|
if float(eps) < 0:
|
|
eps = 0
|
|
self.eps_input.set_value(eps)
|
|
except:
|
|
return
|
|
|
|
def on_random_button_clicked(self):
|
|
n = int(self.n_input.get_value())
|
|
self.matrix_a = np.round(np.random.rand(n, n) * 10, 2)
|
|
self.matrix_b = np.round(np.random.rand(n, 1) * 10, 2)
|
|
|
|
self.set_matrix(self.matrix_a, self.matrix_b)
|
|
|
|
def on_find_solution_clicked(self):
|
|
try:
|
|
print(f"Numpy solution: {np.linalg.solve(self.matrix_a, self.matrix_b)}")
|
|
except:
|
|
print("Numpy could not solve the system")
|
|
|
|
try:
|
|
eps = float(self.eps_input.get_value())
|
|
except:
|
|
eps = 0
|
|
x = solution.iterative_method(self.matrix_a, self.matrix_b, eps)
|
|
print(f"Iterative solution: {x}")
|
|
|
|
if x is None:
|
|
self.solution_display.set_visible(True)
|
|
self.solution_display.set_text("Нет решения")
|
|
else:
|
|
self.solution_display.set_visible(True)
|
|
solution_text = "\n"
|
|
for i in range(x.shape[0]):
|
|
solution_text += f"x{i + 1} = {x[i, 0]:.5f}"
|
|
if i < x.shape[0] - 1:
|
|
solution_text += "\n"
|
|
self.solution_display.set_text(solution_text)
|
|
|
|
def on_paste_button_clicked(self):
|
|
text = QApplication.clipboard().text()
|
|
try:
|
|
input_np = np.fromstring(text, dtype=np.float64, sep=" ")
|
|
|
|
n = 0.5 * (np.sqrt(4.0 * input_np.shape[0] + 1.0) - 1.0)
|
|
if n % 1.0 != 0.0:
|
|
return
|
|
n = int(n)
|
|
|
|
input_np = np.reshape(input_np, (n, n + 1))
|
|
|
|
self.n_input.set_value(str(n))
|
|
|
|
self.matrix_a = np.copy(input_np[:n, :n])
|
|
self.matrix_b = np.copy(input_np[:n, n:])
|
|
|
|
self.set_matrix(self.matrix_a, self.matrix_b)
|
|
|
|
except Exception as e:
|
|
print(e)
|
|
return
|
|
|
|
def on_n_input_changed(self, widget: InputField):
|
|
MAX_N = 8
|
|
|
|
on_input_int_number(widget)
|
|
try:
|
|
n = int(widget.get_value())
|
|
if n < 1:
|
|
widget.set_value(str(1))
|
|
return
|
|
if n > MAX_N:
|
|
widget.set_value(str(MAX_N))
|
|
return
|
|
|
|
self.matrix.recreate(int(self.n_input.get_value()))
|
|
self.input_matrix_from_widget()
|
|
self.update_stats()
|
|
except:
|
|
return
|
|
|
|
def on_reset_button_clicked(self):
|
|
self.n_input.set_value(str(4))
|
|
self.eps_input.set_value(str(0.001))
|
|
self.set_matrix(task.A(), task.B())
|
|
|
|
def on_matrix_changed(self, widget: InputField):
|
|
on_input_float_number(widget)
|
|
|
|
try:
|
|
self.input_matrix_from_widget()
|
|
self.update_stats()
|
|
except:
|
|
return
|
|
|
|
def input_matrix_from_widget(self):
|
|
try:
|
|
self.matrix_a = np.matrix(
|
|
[
|
|
[
|
|
self.matrix.get_value_x(i, j)
|
|
for j in range(self.matrix.rows[0].x_inputs.__len__())
|
|
]
|
|
for i in range(self.matrix.rows.__len__())
|
|
],
|
|
dtype=np.float64,
|
|
)
|
|
self.matrix_b = np.matrix(
|
|
[
|
|
[self.matrix.get_value_b(i)]
|
|
for i in range(self.matrix.rows.__len__())
|
|
],
|
|
dtype=np.float64,
|
|
)
|
|
except:
|
|
return
|
|
|
|
def update_stats(self):
|
|
if not self.initialized:
|
|
return
|
|
|
|
self.solution_display.set_visible(False)
|
|
|
|
try:
|
|
self.det_display.set_text(f"{algorithm.det(self.matrix_a):.5f}")
|
|
except:
|
|
self.det_display.set_text("Невозможно вычислить")
|
|
|
|
try:
|
|
self.cond_display.set_text(f"{algorithm.cond(self.matrix_a):.5f}")
|
|
except:
|
|
self.cond_display.set_text("Невозможно вычислить")
|
|
|
|
def set_matrix(self, A: np.matrix, B: np.matrix):
|
|
for i in range(A.shape[0]):
|
|
for j in range(A.shape[1]):
|
|
self.matrix.set_value_x(i, j, str(A[i, j]))
|
|
|
|
for i in range(B.shape[0]):
|
|
self.matrix.set_value_b(i, str(B[i, 0]))
|
|
|
|
self.update_stats()
|