diff --git a/Л5-В6/Lab_5.pdf b/Л5-В6/Lab_5.pdf new file mode 100644 index 0000000..aa54a1d Binary files /dev/null and b/Л5-В6/Lab_5.pdf differ diff --git a/Л5-В6/Блоксхема.drawio b/Л5-В6/Блоксхема.drawio new file mode 100644 index 0000000..b8a2221 --- /dev/null +++ b/Л5-В6/Блоксхема.drawio @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Л5-В6/Отчет.docx b/Л5-В6/Отчет.docx new file mode 100644 index 0000000..f54a4b3 Binary files /dev/null and b/Л5-В6/Отчет.docx differ diff --git a/Л5-В6/Программа/.gitignore b/Л5-В6/Программа/.gitignore new file mode 100644 index 0000000..df989ca --- /dev/null +++ b/Л5-В6/Программа/.gitignore @@ -0,0 +1,2 @@ +.venv +*/__pycache__ \ No newline at end of file diff --git a/Л5-В6/Программа/requirements.txt b/Л5-В6/Программа/requirements.txt new file mode 100644 index 0000000..1d0cbea --- /dev/null +++ b/Л5-В6/Программа/requirements.txt @@ -0,0 +1,5 @@ +sympy +numpy +scipy +pyqt6 +matplotlib diff --git a/Л5-В6/Программа/src/algorithm.py b/Л5-В6/Программа/src/algorithm.py new file mode 100644 index 0000000..813de7d --- /dev/null +++ b/Л5-В6/Программа/src/algorithm.py @@ -0,0 +1,45 @@ +import numpy as np +import typing + + +def runge_kutta_4( + x_0: float, + y_0: typing.List[float], + x_n: float, + h: float, + f: typing.List[typing.Callable[[float, typing.List[float]], float]], +) -> typing.Dict[float, np.ndarray]: + count = len(y_0) + + if count != len(f): + raise ValueError("len(y_0) != len(f)") + + x_i = x_0 + y_i = np.array(y_0, dtype=np.float64) + result = {x_i: y_i} + + while x_i < x_n + h / 2: + y_i = calculate_y(x_i, y_i, h, f) + x_i += h + + result[x_i] = y_i + + return result + + +def calculate_y(x_i, y_i, h, f): + k_1 = np.array([f[i](x_i, y_i) for i in range(len(y_i))], dtype=np.float64) + k_2 = np.array( + [f[i](x_i + h / 2, y_i + h * k_1 / 2) for i in range(len(y_i))], + dtype=np.float64, + ) + k_3 = np.array( + [f[i](x_i + h / 2, y_i + h * k_2 / 2) for i in range(len(y_i))], + dtype=np.float64, + ) + k_4 = np.array( + [f[i](x_i + h, y_i + h * k_3) for i in range(len(y_i))], dtype=np.float64 + ) + + y_i = y_i + h / 6 * (k_1 + 2 * k_2 + 2 * k_3 + k_4) + return y_i diff --git a/Л5-В6/Программа/src/main.py b/Л5-В6/Программа/src/main.py new file mode 100644 index 0000000..444749e --- /dev/null +++ b/Л5-В6/Программа/src/main.py @@ -0,0 +1,17 @@ +import sys +from PyQt6.QtWidgets import QApplication +from PyQt6.QtGui import QFont + +import window +import widgets + +if __name__ == "__main__": + widgets.setup_dark_theme() + + app = QApplication(sys.argv) + font = QFont("Cascadia Code", 14) + app.setFont(font) + window = window.MainWindow() + window.show() + + sys.exit(app.exec()) diff --git a/Л5-В6/Программа/src/task.py b/Л5-В6/Программа/src/task.py new file mode 100644 index 0000000..04173e2 --- /dev/null +++ b/Л5-В6/Программа/src/task.py @@ -0,0 +1,68 @@ +import sympy +import typing + +f_str = "y * ln(y) / sin(x)" +f_expr = sympy.sympify(f_str) +f_l = sympy.lambdify("x, y", f_expr, cse=True) + + +def task1_f(x: float, y: typing.List[float]) -> float: + return f_l(x, *y) + + +def task1_f_display() -> str: + return f_str + + +f_1_str = "sin(x**2 + y_2**2)" +f_1_expr = sympy.sympify(f_1_str) +f_1_l = sympy.lambdify("x, y_1, y_2", f_1_expr, cse=True) + +f_2_str = "cos(x * y_1)" +f_2_expr = sympy.sympify(f_2_str) +f_2_l = sympy.lambdify("x, y_1, y_2", f_2_expr, cse=True) + + +def task2_f_1(x: float, y: typing.List[float]) -> float: + return f_1_l(x, *y) + + +def task2_f_2(x: float, y: typing.List[float]) -> float: + return f_2_l(x, *y) + + +def task2_f_1_display() -> str: + return f_1_str + + +def task2_f_2_display() -> str: + return f_2_str + + +def task3_f_display() -> str: + return "y'' + a1 * y' + a2 * y" + + +x, a1, a2 = sympy.symbols("x a1 a2", real=True) +y = sympy.symbols("y", cls=sympy.Function)(x) +yp = y.diff(x) +ypp = y.diff(x, 2) + +f_xy = ypp + a1 * yp + a2 * y + +task3_generic_solution = sympy.dsolve(f_xy, y).rhs + + +def task3_solution_display() -> str: + return str(task3_generic_solution) + + +def task3_make_f_sympy(a1_, a2_, x_0_, y_0_, y_d_0_): + solution = task3_generic_solution.subs({a1: a1_, a2: a2_}) + cnd1 = sympy.Eq(solution.subs(x, x_0_), y_0_) + cnd2 = sympy.Eq(solution.diff(x).subs(x, x_0_), y_d_0_) + + C1, C2 = sympy.symbols("C1 C2") + C1C2_sl = sympy.solve([cnd1, cnd2], (C1, C2)) + + return sympy.simplify(solution.subs(C1C2_sl)) diff --git a/Л5-В6/Программа/src/widgets.py b/Л5-В6/Программа/src/widgets.py new file mode 100644 index 0000000..7b5ad0c --- /dev/null +++ b/Л5-В6/Программа/src/widgets.py @@ -0,0 +1,330 @@ +from io import BytesIO +from PyQt6.QtWidgets import ( + QLabel, + QWidget, + QLineEdit, + QHBoxLayout, + QSlider, + QVBoxLayout, +) +from PyQt6.QtCore import Qt +from PyQt6.QtSvgWidgets import QSvgWidget + +import matplotlib +import matplotlib.figure +import matplotlib.axes +from matplotlib.backends.backend_qtagg import ( + FigureCanvasQTAgg as FigureCanvas, + NavigationToolbar2QT as NavigationToolbar, +) + +import sympy + +import abc +import typing + +import matplotlib.pyplot + + +def setup_dark_theme(): + matplotlib.pyplot.style.use( + { + "figure.facecolor": "#1e1e1e", + "savefig.facecolor": "#1e1e1e", + "text.color": "white", + "xtick.color": "white", + "ytick.color": "white", + "legend.facecolor": "#1e1e1e", + "legend.edgecolor": "white", + "legend.labelcolor": "white", + "grid.color": "white", + "figure.edgecolor": "white", + "axes.facecolor": "#1e1e1e", + "axes.axisbelow": "true", + "axes.edgecolor": "white", + "axes.labelcolor": "white", + "axes.grid": True, + "axes.spines.left": False, + "axes.spines.right": False, + "axes.spines.top": False, + "axes.spines.bottom": False, + } + ) + + +class DisplayField(QWidget): + label: str + + def __init__(self, parent, label="", text="", wrap=False): + super().__init__(parent) + + self._layout = QHBoxLayout(self) + self._layout.setContentsMargins(0, 0, 0, 0) + + self.label = label + self.text = QLabel(f"{self.label}{text}") + self.text.setWordWrap(wrap) + 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}") + + +class InputWidget(QWidget): + value: typing.Any + previous_value: typing.Any + callback: typing.Callable[[typing.Self], bool] | None = None + + def init(self): + self.value = self._get_value_internal() + self.previous_value = self.value + self._set_callback_internal(self._on_change_internal) + + def get_value(self) -> typing.Any: + return self.value + + def get_previous_value(self) -> typing.Any: + return self.previous_value + + def set_value(self, value: typing.Any): + self._set_value_internal(value) + self._set_value_no_sync(value) + + def set_callback(self, callback: typing.Callable[[typing.Self], bool]): + self.callback = callback + + @abc.abstractmethod + def _get_value_internal(self) -> typing.Any: + pass + + @abc.abstractmethod + def _set_value_internal(self, value: typing.Any): + pass + + @abc.abstractmethod + def _set_callback_internal(self, callback: typing.Callable[[], None]): + pass + + def _set_value_no_sync(self, value: typing.Any): + self.previous_value = self.value + self.value = value + + def _on_change_internal(self): + self._set_value_no_sync(self._get_value_internal()) + + if self.callback: + self.callback(self) + + +def make_callback_chain( + callbacks: typing.List[typing.Callable[[InputWidget], bool]] +) -> typing.Callable[[InputWidget], bool]: + def callback(widget: InputWidget): + for callback in callbacks: + if not callback(widget): + break + return True + + return callback + + +def make_min_max_callback( + min: typing.Any, max: typing.Any +) -> typing.Callable[[InputWidget], bool]: + def callback(widget: InputWidget): + value = widget.get_value() + + if type(min)(value) < min: + widget.set_value(min) + if type(max)(value) > max: + widget.set_value(max) + + return True + + return callback + + +def float_check_callback(widget: InputWidget): + try: + internal_value = widget._get_value_internal() + if not (internal_value == "" or internal_value == "-" or internal_value == "."): + _ = float(widget.get_value()) + return True + else: + widget.value = widget.previous_value + return False + except: + widget.set_value(widget.previous_value) + return False + + +def int_check_callback(widget: InputWidget): + try: + internal_value = widget._get_value_internal() + if not (internal_value == "" or internal_value == "-"): + _ = int(widget.get_value()) + return True + else: + widget.value = widget.previous_value + return False + except: + widget.set_value(widget.previous_value) + return False + + +class InputField(InputWidget): + 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) + + super().init() + super().set_value(lineedit_text) + + def _get_value_internal(self): + return self.lineedit.text() + + def _set_value_internal(self, value): + self.lineedit.setText(str(value)) + + def _set_callback_internal(self, callback): + self.lineedit.textChanged.connect(callback) + + +class MathInputField(InputField): + internal_value: typing.Any + + def __init__( + self, + parent, + label_text="", + placeholder_text="", + lineedit_value="0.0", + label_position="left", + ): + super().__init__( + parent, label_text, placeholder_text, lineedit_value, label_position + ) + + def _get_value_internal(self): + try: + self.internal_value = str( + float( + sympy.sympify(self.lineedit.text()).evalf( + subs={ + "e": sympy.E, + "Pi": sympy.pi, + "PI": sympy.pi, + } + ) + ) + ) + except: + pass + + return self.internal_value + + +class InputSlider(InputWidget): + slider: QSlider + + def __init__( + self, + parent, + label_text="", + value=0, + min_value=0, + max_value=100, + step=1, + ticks=False, + ): + super().__init__(parent) + + self._layout = QHBoxLayout(self) + self._layout.setContentsMargins(0, 0, 0, 0) + self.label = QLabel(label_text) + self.slider = QSlider(Qt.Orientation.Horizontal) + if ticks: + self.slider.setTickPosition(QSlider.TickPosition.TicksBelow) + self.slider.setTickInterval(step) + + self.slider.setMinimum(min_value) + self.slider.setMaximum(max_value) + self.slider.setValue(value) + self.slider.setSingleStep(step) + + self._layout.addWidget(self.label) + self._layout.addWidget(self.slider) + + super().init() + super().set_value(value) + + def _get_value_internal(self): + return self.slider.value() + + def _set_value_internal(self, value): + self.slider.setValue(value) + + def _set_callback_internal(self, callback): + self.slider.valueChanged.connect(callback) + + +class PlotWidget(QWidget): + figure: matplotlib.figure.Figure + + toolbar: NavigationToolbar = None + canvas: FigureCanvas + + def __init__(self, parent, figure: matplotlib.figure.Figure, toolbar: bool = False): + super().__init__(parent) + + self._layout = QVBoxLayout(self) + + self.figure = figure + self.canvas = FigureCanvas(self.figure) + self.canvas.setStyleSheet("background-color:transparent;") + + if toolbar: + self.toolbar = NavigationToolbar(self.canvas, self) + self._layout.addWidget(self.toolbar) + + self._layout.addWidget(self.canvas) + + def set_figure(self, figure: matplotlib.figure.Figure): + self.figure = figure + self.canvas = FigureCanvas(self.figure) + self._layout.addWidget(self.canvas) + + self.redraw() + + def get_figure(self) -> matplotlib.figure.Figure: + return self.figure + + def redraw(self): + self.canvas.draw() diff --git a/Л5-В6/Программа/src/window.py b/Л5-В6/Программа/src/window.py new file mode 100644 index 0000000..1bdf656 --- /dev/null +++ b/Л5-В6/Программа/src/window.py @@ -0,0 +1,536 @@ +from PyQt6.QtWidgets import ( + QMainWindow, + QLabel, + QPushButton, + QFormLayout, + QWidget, + QHBoxLayout, + QTabWidget, +) +from PyQt6 import QtWidgets + +import matplotlib +import matplotlib.pyplot as plt + +matplotlib.use("QtAgg") + +import numpy as np +import sympy +import typing + +import widgets +import task +import algorithm + + +class MainWindow(QMainWindow): + def __init__(self): + super().__init__() + + self.setup_ui() + + def setup_ui(self): + self.setWindowTitle("Лабораторная работа №5") + self.setFixedSize(800, 900) + + central_widget = QTabWidget(self) + central_widget.setTabsClosable(False) + central_widget.setContentsMargins(0, 0, 0, 0) + + central_widget.addTab(Tab1(central_widget), "Задание 1") + central_widget.addTab(Tab2(central_widget), "Задание 2") + central_widget.addTab(Tab3(central_widget), "Задание 3") + + self.setCentralWidget(central_widget) + + +class Tab1(QWidget): + initialized = False + + x_0_field: widgets.InputField + y_0_field: widgets.InputField + x_n_field: widgets.InputField + + h_display: widgets.DisplayField + h_field: widgets.InputSlider + + x_0: float + y_0: float + x_n: float + h: float + + solution_plot: widgets.PlotWidget + solution_figure: plt.Figure + solution_axes: plt.Axes + + def __init__(self, parent): + super().__init__(parent) + + self._initialize() + self.reset() + self.initialized = True + self.update_solution() + + def _initialize(self): + self._layout = QFormLayout(self) + + self._function_display = widgets.DisplayField(self, "f(x, y) = ", "") + self._layout.addWidget(self._function_display) + + self.x_0_field = widgets.MathInputField(self, label_text="x_0: ") + self.x_0_field.set_callback( + widgets.make_callback_chain( + [widgets.float_check_callback, self._x_0_field_changed] + ) + ) + + self._layout.addWidget(self.x_0_field) + + self.y_0_field = widgets.MathInputField(self, label_text="y_0: ") + self.y_0_field.set_callback( + widgets.make_callback_chain( + [widgets.float_check_callback, self._y_0_field_changed] + ) + ) + self._layout.addWidget(self.y_0_field) + + self.x_n_field = widgets.MathInputField(self, label_text="x_n: ") + self.x_n_field.set_callback( + widgets.make_callback_chain( + [widgets.float_check_callback, self._x_n_field_changed] + ) + ) + self._layout.addWidget(self.x_n_field) + + self.h_display = widgets.DisplayField(self, "h: ", "") + self._layout.addWidget(self.h_display) + + self.h_field = widgets.InputSlider( + self, label_text="", value=0, min_value=1, max_value=100, step=1 + ) + self.h_field.set_callback(self._h_field_changed) + self._layout.addWidget(self.h_field) + + self.solution_figure = plt.figure() + self.solution_figure.patch.set_facecolor("none") + self.solution_axes = self.solution_figure.add_subplot(111) + self.solution_figure.tight_layout(pad=3.0) + + self.solution_plot = widgets.PlotWidget(self, self.solution_figure, True) + self._layout.addWidget(self.solution_plot) + + def reset(self): + self._function_display.set_text(task.task1_f_display()) + self.x_0_field.set_value("pi / 2") + self.y_0_field.set_value("e") + self.x_n_field.set_value("pi") + self.h_field.set_value(5) + + QtWidgets.QApplication.processEvents() + + def _x_0_field_changed(self, w: widgets.InputWidget) -> bool: + self.x_0 = float(w.get_value()) + + self.update_solution() + + return True + + def _y_0_field_changed(self, w: widgets.InputWidget) -> bool: + self.y_0 = float(w.get_value()) + + return True + + def _x_n_field_changed(self, w: widgets.InputWidget) -> bool: + self.x_n = float(w.get_value()) + + self.update_solution() + + return True + + def _h_field_changed(self, w: widgets.InputWidget) -> bool: + self.h = float(w.get_value()) / 100.0 + self.h_display.set_text(str(self.h)) + + self.update_solution() + + return True + + def update_solution(self): + if not self.initialized: + return + + solution = algorithm.runge_kutta_4( + self.x_0, [self.y_0], self.x_n, self.h, [task.task1_f] + ) + + x = np.array(list(solution.keys())) + y = np.array(list(map(lambda x: x[0], solution.values()))) + + self.solution_axes.clear() + self.solution_axes.set_xlabel("x") + self.solution_axes.set_ylabel("y") + self.solution_axes.grid(True) + + self.solution_axes.plot(x, y, label="y(x)") + + solution = algorithm.runge_kutta_4( + self.x_0, [self.y_0], self.x_n, self.h / 2, [task.task1_f] + ) + + x = np.array(list(solution.keys())) + y = np.array(list(map(lambda x: x[0], solution.values()))) + + self.solution_axes.plot(x, y, label="y(x) (h/2)") + + solution = algorithm.runge_kutta_4( + self.x_0, [self.y_0], self.x_n, self.h * 2, [task.task1_f] + ) + + x = np.array(list(solution.keys())) + y = np.array(list(map(lambda x: x[0], solution.values()))) + + self.solution_axes.plot(x, y, label="y(x) (h*2)") + + self.solution_axes.set_yscale("log") + + self.solution_axes.legend(loc="upper left") + self.solution_plot.redraw() + + +class Tab2(QWidget): + initialized = False + + _function1_display: widgets.DisplayField + _function2_display: widgets.DisplayField + + x_0_field: widgets.InputField + y_10_field: widgets.InputField + y_20_field: widgets.InputField + x_n_field: widgets.InputField + + h_display: widgets.DisplayField + h_field: widgets.InputSlider + + x_0: float + y_10: float + y_20: float + x_n: float + h: float + + def __init__(self, parent): + super().__init__(parent) + + self._initialize() + self.reset() + self.initialized = True + self.update_solution() + + def _initialize(self): + self._layout = QFormLayout(self) + + self._function1_display = widgets.DisplayField(self, "f1(x, y) = ", "") + self._layout.addWidget(self._function1_display) + + self._function2_display = widgets.DisplayField(self, "f2(x, y) = ", "") + self._layout.addWidget(self._function2_display) + + self.x_0_field = widgets.MathInputField(self, label_text="x_0: ") + self.x_0_field.set_callback( + widgets.make_callback_chain( + [widgets.float_check_callback, self._x_0_field_changed] + ) + ) + + self._layout.addWidget(self.x_0_field) + + self.y_10_field = widgets.MathInputField(self, label_text="y_10: ") + self.y_10_field.set_callback( + widgets.make_callback_chain( + [widgets.float_check_callback, self._y_10_field_changed] + ) + ) + self._layout.addWidget(self.y_10_field) + + self.y_20_field = widgets.MathInputField(self, label_text="y_20: ") + self.y_20_field.set_callback( + widgets.make_callback_chain( + [widgets.float_check_callback, self._y_20_field_changed] + ) + ) + self._layout.addWidget(self.y_20_field) + + self.x_n_field = widgets.MathInputField(self, label_text="x_n: ") + self.x_n_field.set_callback( + widgets.make_callback_chain( + [widgets.float_check_callback, self._x_n_field_changed] + ) + ) + self._layout.addWidget(self.x_n_field) + + self.h_display = widgets.DisplayField(self, "h: ", "") + self._layout.addWidget(self.h_display) + + self.h_field = widgets.InputSlider( + self, label_text="", value=0, min_value=1, max_value=100, step=1 + ) + self.h_field.set_callback(self._h_field_changed) + self._layout.addWidget(self.h_field) + + self.solution_figure = plt.figure() + self.solution_figure.patch.set_facecolor("none") + self.solution_axes = self.solution_figure.add_subplot(111) + self.solution_figure.tight_layout(pad=3.0) + + self.solution_plot = widgets.PlotWidget(self, self.solution_figure) + self._layout.addWidget(self.solution_plot) + + def reset(self): + self._function1_display.set_text(task.task2_f_1_display()) + self._function2_display.set_text(task.task2_f_2_display()) + self.x_0_field.set_value("0") + self.y_10_field.set_value("0") + self.y_20_field.set_value("0") + self.x_n_field.set_value("4") + self.h_field.set_value(5) + + QtWidgets.QApplication.processEvents() + + def _x_0_field_changed(self, w: widgets.InputWidget) -> bool: + self.x_0 = float(w.get_value()) + + self.update_solution() + + return True + + def _y_10_field_changed(self, w: widgets.InputWidget) -> bool: + self.y_10 = float(w.get_value()) + + self.update_solution() + + return True + + def _y_20_field_changed(self, w: widgets.InputWidget) -> bool: + self.y_20 = float(w.get_value()) + + self.update_solution() + + return True + + def _x_n_field_changed(self, w: widgets.InputWidget) -> bool: + self.x_n = float(w.get_value()) + + self.update_solution() + + return True + + def _h_field_changed(self, w: widgets.InputWidget) -> bool: + self.h = float(w.get_value()) / 100.0 + self.h_display.set_text(str(self.h)) + + self.update_solution() + + return True + + def update_solution(self): + if not self.initialized: + return + + solution = algorithm.runge_kutta_4( + self.x_0, + [self.y_10, self.y_20], + self.x_n, + self.h, + [task.task2_f_1, task.task2_f_2], + ) + + x = np.array(list(solution.keys())) + y1 = np.array(list(map(lambda x: x[0], solution.values()))) + y2 = np.array(list(map(lambda x: x[1], solution.values()))) + + self.solution_axes.cla() + self.solution_axes.plot(x, y1, label="y_1") + self.solution_axes.plot(x, y2, label="y_2") + + self.solution_axes.legend() + + self.solution_plot.redraw() + + +class Tab3(QWidget): + initialized = False + + a1_field: widgets.InputField + a2_field: widgets.InputField + x_0_field: widgets.InputField + y_0_field: widgets.InputField + y_d_0_field: widgets.InputField + + generic_solution_display: widgets.DisplayField + solution_display: widgets.DisplayField + + a1: float + a2: float + x_0: float + y_0: float + y_d_0: float + + solution_plot: widgets.PlotWidget + solution_figure: plt.Figure + solution_axes: plt.Axes + + def __init__(self, parent): + super().__init__(parent) + + self._initialize() + self.reset() + self.initialized = True + self.update_solution() + + def _initialize(self): + self._layout = QFormLayout(self) + + self._function_display = widgets.DisplayField( + self, task.task3_f_display(), " = 0" + ) + self._layout.addWidget(self._function_display) + + self.a1_field = widgets.InputField( + self, + "a1: ", + ) + self.a1_field.set_callback( + widgets.make_callback_chain( + [widgets.float_check_callback, self._a1_changed] + ) + ) + self._layout.addWidget(self.a1_field) + + self.a2_field = widgets.InputField( + self, + "a2: ", + ) + self.a2_field.set_callback( + widgets.make_callback_chain( + [widgets.float_check_callback, self._a2_changed] + ) + ) + self._layout.addWidget(self.a2_field) + + self.x_0_field = widgets.InputField( + self, + "x_0: ", + ) + self.x_0_field.set_callback( + widgets.make_callback_chain( + [widgets.float_check_callback, self._x_0_field_changed] + ) + ) + self._layout.addWidget(self.x_0_field) + + self.y_0_field = widgets.InputField( + self, + "y_0: ", + ) + self.y_0_field.set_callback( + widgets.make_callback_chain( + [widgets.float_check_callback, self._y_0_field_changed] + ) + ) + self._layout.addWidget(self.y_0_field) + + self.y_d_0_field = widgets.InputField( + self, + "y_d_0: ", + ) + self.y_d_0_field.set_callback( + widgets.make_callback_chain( + [widgets.float_check_callback, self._y_d_0_field_changed] + ) + ) + self._layout.addWidget(self.y_d_0_field) + + self.generic_solution_display = widgets.DisplayField( + self, "Общее решение: ", wrap=True + ) + self._layout.addWidget(self.generic_solution_display) + + self.solution_display = widgets.DisplayField(self, "Решение: ", wrap=True) + self._layout.addWidget(self.solution_display) + + self.solution_figure = plt.figure() + self.solution_figure.patch.set_facecolor("none") + self.solution_axes = self.solution_figure.add_subplot(111) + self.solution_figure.tight_layout(pad=3.0) + + self.solution_plot = widgets.PlotWidget(self, self.solution_figure, True) + self._layout.addWidget(self.solution_plot) + + def reset(self): + self.a1_field.set_value("-4.0") + self.a2_field.set_value("4.0") + self.x_0_field.set_value("0.3") + self.y_0_field.set_value("1.0") + self.y_d_0_field.set_value("1.0") + + QtWidgets.QApplication.processEvents() + + def _a1_changed(self, w: widgets.InputWidget) -> bool: + self.a1 = float(w.get_value()) + + self.update_solution() + + return True + + def _a2_changed(self, w: widgets.InputWidget) -> bool: + self.a2 = float(w.get_value()) + + self.update_solution() + + return True + + def _x_0_field_changed(self, w: widgets.InputWidget) -> bool: + self.x_0 = float(w.get_value()) + + self.update_solution() + + return True + + def _y_0_field_changed(self, w: widgets.InputWidget) -> bool: + self.y_0 = float(w.get_value()) + + self.update_solution() + + return True + + def _y_d_0_field_changed(self, w: widgets.InputWidget) -> bool: + self.y_d_0 = float(w.get_value()) + + self.update_solution() + + return True + + def update_solution(self): + if not self.initialized: + return + + self.generic_solution_display.set_text( + "\n" + str(task.task3_solution_display()) + ) + solution = task.task3_make_f_sympy( + self.a1, self.a2, self.x_0, self.y_0, self.y_d_0 + ) + self.solution_display.set_text(str(solution)) + + try: + f = sympy.lambdify("x", solution, cse=True) + x = np.linspace(self.x_0, self.x_0 + 5, 100) + y = f(x) + + self.solution_axes.clear() + self.solution_axes.grid(True) + self.solution_axes.plot(x, y, label="y") + self.solution_axes.legend() + + self.solution_plot.redraw() + except Exception as e: + print(e)