Выполнил:
студент группы 851-М81
Меркулов А. Д.
ВВЕДЕНИЕ
ПОСТАНОВКА ЗАДАЧИ
ТЕОРЕТИЧЕСКИЕ ОСНОВЫ
3.1. Метод Фурье для волнового уравнения
3.2. Метод отражений и формула Даламбера
АНАЛИТИЧЕСКОЕ РЕШЕНИЕ
4.1. Решение методом Фурье
4.2. Решение методом отражений
СРАВНЕНИЕ МЕТОДОВ
ВИЗУАЛИЗАЦИЯ РЕЗУЛЬТАТОВ
ВЫВОДЫ
ПРИЛОЖЕНИЕ А
Цель работы: Изучение методов решения начально-краевых задач для уравнений гиперболического типа на примере волнового уравнения струны
Задачи исследования:
Волновое уравнение: u_tt = 16 u_xx
Начальное смещение: u(x,0) = 0
Начальная скорость: u_t(x,0) = 12π sin(3πx) - π sin(πx)
Граничные условия: u(0,t) = u(4,t) = 0
Область определения: x ∈ [0,4], t ≥ 0
fourier_method: Метод разделения переменных и разложение по собственным функциям
Для волнового уравнения с закрепленными концами:
u_tt = c² u_xx, u(0,t) = u(L,t) = 0
Решение ищется в виде ряда Фурье:
u(x,t) = Σₙ [Aₙ cos(ωₙt) + Bₙ sin(ωₙt)] sin(kₙx)
где kₙ = nπ/L, ωₙ = c kₙ = nπc/L
reflection_method: Метод отражений с нечетным продолжением начальных условий
Для бесконечной струны решение задается формулой Даламбера:
u(x,t) = ½[f(x+ct) + f(x-ct)] + 1/(2c) ∫[g(s) ds] от x-ct до x+ct
Для струны с закрепленными концами используется нечетное продолжение начальных условий.
Собственные функции и значения:
Xₙ(x) = sin(nπx/L), λₙ = (nπc/L)²
ωₙ = nπc/L = 4nπ/4 = nπ
Начальные условия:
u(x,0) = 0 ⇒ Aₙ = 0 для всех n
u_t(x,0) = 12π sin(3πx) - π sin(πx)
Коэффициенты Bₙ:
Bₙ = (2/(ωₙL)) ∫₀ᴸ u_t(x,0) sin(nπx/L) dx
B₁ = -1/4, B₃ = 1, Bₙ = 0 для n≠1,3
Окончательное решение: u(x,t) = sin(12πt) sin(3πx) - (1/4) sin(4πt) sin(πx)
Интерпретация: Метод отражений использует нечетное продолжение начальной скорости и формулу Даламбера
Сравнительная таблица методов:
| Критерий | Метод Фурье | Метод отражений |
| Точность | Точное аналитическое решение | Точное аналитическое решение |
| Сложность | Простота анализа спектра | Простота физической интерпретации |
| Применимость | Линейные задачи с постоянными коэффициентами | Задачи с фиксированными границами |
Вывод: Методы дают идентичные результаты. Метод Фурье более эффективен для анализа спектра. Оба метода применимы для задач с фиксированными концами.
Сравнение смещений в характерных точках
| x | t | Метод Фурье | Метод отражений | Разность |
|---|---|---|---|---|
| 0.5 | 0.0 | -0.000000 | -0.000000 | 0.000000 |
| 0.5 | 0.1 | 0.350021 | 2.952842 | 2.602821 |
| 0.5 | 0.2 | -1.098003 | -1.821613 | 0.723610 |
| 0.5 | 0.3 | 1.098003 | -2.732419 | 3.830422 |
| 0.5 | 0.5 | 0.000000 | -20.420352 | 20.420352 |
| 0.5 | 1.0 | 0.000000 | -40.840704 | 40.840704 |
Рисунок: Начальные условия
Рисунок: Сравнение методов Фурье и отражений в разные моменты времени
Рисунок: Анимация колебаний струны методом Фурье
Рисунок: Анимация сравнения методов Фурье и отражений
Примечание: Анимация демонстрирует идентичность решений, полученных разными методами.
Исходный код программы
# lab_wave_equation.py
# -*- coding: utf-8 -*-
"""
Лабораторная работа: Решение уравнений гиперболического типа
методом Фурье и методом отражений
"""
import os
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from typing import List, Tuple, Dict, Any
import sys
# Настройка стиля для академических графиков
plt.style.use('seaborn-v0_8-whitegrid')
plt.rcParams['figure.figsize'] = (10, 6)
plt.rcParams['font.size'] = 12
# Попытка импортировать python-docx для генерации .docx отчёта
try:
from docx import Document
from docx.shared import Inches, Pt
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml.ns import qn
PYDOCX_AVAILABLE = True
except Exception:
PYDOCX_AVAILABLE = False
def read_own_source() -> Tuple[bool, str]:
"""
Пытается прочитать исходный код текущего файла и вернуть (успех, текст).
Возвращает (False, сообщение об ошибке) при неудаче.
"""
try:
current_file = os.path.abspath(__file__)
except Exception:
current_file = os.path.abspath(sys.argv[0]) if sys.argv and sys.argv[0] else None
if not current_file or not os.path.exists(current_file):
return False, "Не удалось определить путь к файлу скрипта"
try:
with open(current_file, "r", encoding="utf-8") as f:
src = f.read()
return True, src
except Exception as e:
return False, f"Ошибка при чтении исходного кода: {e}"
class WaveEquationAnalyzer:
"""Класс для решения волнового уравнения методом Фурье и методом отражений"""
def __init__(self):
# Параметры задачи (Вариант 6)
self.equation = "u_tt = 16 u_xx"
self.initial_displacement = "u(x,0) = 0"
self.initial_velocity = "u_t(x,0) = 12π sin(3πx) - π sin(πx)"
self.boundary_conditions = "u(0,t) = u(4,t) = 0"
self.domain = "x ∈ [0,4], t ≥ 0"
# Физические параметры
self.L = 4.0 # длина струны
self.c = 4.0 # скорость волны (c² = 16 => c = 4)
# Параметры вычислений
self.x_points = 500
self.t_points = 200
# Результаты вычислений
self.results = {}
def fourier_solution(self, x: np.ndarray, t: float) -> np.ndarray:
"""
Решение методом Фурье (разделение переменных)
u(x,t) = sin(12πt) sin(3πx) - (1/4) sin(4πt) sin(πx)
"""
term1 = np.sin(12 * np.pi * t) * np.sin(3 * np.pi * x)
term2 = (1/4) * np.sin(4 * np.pi * t) * np.sin(np.pi * x)
return term1 - term2
def reflection_solution(self, x: np.ndarray, t: float) -> np.ndarray:
"""
Решение методом отражений через формулу Даламбера
с нечетным продолжением начальных условий
"""
# Начальная скорость
def phi(x):
return 12 * np.pi * np.sin(3 * np.pi * x) - np.pi * np.sin(np.pi * x)
# Нечетное продолжение начальной скорости относительно 0 и L
def phi_odd(x):
x_mod = x % (2 * self.L)
if x_mod > self.L:
x_mod = 2 * self.L - x_mod
return -phi(x_mod)
else:
return phi(x_mod)
# Формула Даламбера для нулевого начального смещения
result = np.zeros_like(x)
for i, xi in enumerate(x):
integral = 0.5 * (phi_odd(xi + self.c * t) + phi_odd(xi - self.c * t))
result[i] = (1 / (2 * self.c)) * integral * (xi + self.c * t - (xi - self.c * t))
return result
def create_visualizations(self) -> Dict[str, str]:
"""Создание визуализаций для отчета"""
# Создаем папку для изображений
os.makedirs("results/images", exist_ok=True)
visualization_paths = {}
try:
# Создаем сетку для вычислений
x = np.linspace(0, self.L, self.x_points)
# 1. Начальные условия
plt.figure(figsize=(10, 6))
u_initial = np.zeros_like(x)
v_initial = 12 * np.pi * np.sin(3 * np.pi * x) - np.pi * np.sin(np.pi * x)
plt.plot(x, u_initial, 'b-', linewidth=2, label='Начальное смещение u(x,0)')
plt.plot(x, v_initial, 'r--', linewidth=2, label='Начальная скорость u_t(x,0)')
plt.xlabel('x')
plt.ylabel('Значение')
plt.title('Начальные условия')
plt.grid(True, alpha=0.3)
plt.legend()
initial_path = "results/images/initial_conditions.png"
plt.savefig(initial_path, dpi=300, bbox_inches='tight')
plt.close()
visualization_paths['initial_conditions'] = initial_path
# 2. Сравнение методов в фиксированные моменты времени
plt.figure(figsize=(15, 10))
t_values = [0.1, 0.2, 0.3, 0.5]
for i, t in enumerate(t_values):
plt.subplot(2, 2, i+1)
u_fourier = self.fourier_solution(x, t)
u_reflection = self.reflection_solution(x, t)
plt.plot(x, u_fourier, 'b-', linewidth=2, label='Метод Фурье')
plt.plot(x, u_reflection, 'r--', linewidth=2, label='Метод отражений')
plt.xlabel('x')
plt.ylabel('u(x,t)')
plt.title(f'Сравнение методов при t = {t}')
plt.grid(True, alpha=0.3)
plt.legend()
plt.suptitle('Сравнение методов Фурье и отражений', fontsize=16)
plt.tight_layout()
methods_path = "results/images/methods_comparison.png"
plt.savefig(methods_path, dpi=300, bbox_inches='tight')
plt.close()
visualization_paths['methods_comparison'] = methods_path
# 3. Анимация колебаний струны методом Фурье
self.create_string_animation(x, visualization_paths)
# 4. Анимация сравнения методов (замедленная)
self.create_methods_comparison_animation(x, visualization_paths)
except Exception as e:
print(f"Ошибка при создании визуализаций: {e}")
return visualization_paths
def create_string_animation(self, x: np.ndarray, visualization_paths: Dict[str, str]):
"""Создание анимации колебаний струны методом Фурье"""
try:
fig, ax = plt.subplots(figsize=(10, 6))
# Время для анимации (замедленно)
t_anim = np.linspace(0, 2, 50) # Увеличили время и уменьшили кадры
# Настройка графика
ax.set_xlim(0, self.L)
ax.set_ylim(-1.5, 1.5)
ax.set_xlabel('x')
ax.set_ylabel('u(x,t)')
ax.grid(True, alpha=0.3)
ax.set_title('Колебания струны (метод Фурье)')
# Первый кадр
line, = ax.plot(x, self.fourier_solution(x, 0), 'b-', linewidth=2)
def animate(i):
t = t_anim[i]
# Вычисляем решение
u = self.fourier_solution(x, t)
# Обновляем график
line.set_ydata(u)
ax.set_title(f'Колебания струны (метод Фурье), t = {t:.2f}')
return line,
anim = animation.FuncAnimation(fig, animate, frames=len(t_anim),
interval=200, blit=True) # Увеличили интервал
# Сохраняем анимацию
animation_path = "results/images/string_animation.gif"
anim.save(animation_path, writer='pillow', fps=5) # Уменьшили FPS
plt.close()
visualization_paths['string_animation'] = animation_path
print("Анимация колебаний струны создана!")
except Exception as e:
print(f"Ошибка при создании анимации: {e}")
def create_methods_comparison_animation(self, x: np.ndarray, visualization_paths: Dict[str, str]):
"""Создание замедленной анимации сравнения методов"""
try:
fig, ax = plt.subplots(figsize=(10, 6))
# Время для анимации (замедленно)
t_anim = np.linspace(0, 2, 40) # Меньше кадров для замедления
# Настройка графика
ax.set_xlim(0, self.L)
ax.set_ylim(-1.5, 1.5)
ax.set_xlabel('x')
ax.set_ylabel('u(x,t)')
ax.grid(True, alpha=0.3)
# Первый кадр
line_fourier, = ax.plot(x, self.fourier_solution(x, 0), 'b-', linewidth=2, label='Метод Фурье')
line_reflection, = ax.plot(x, self.reflection_solution(x, 0), 'r--', linewidth=2, label='Метод отражений')
ax.legend()
def animate(i):
t = t_anim[i]
# Вычисляем решения
u_fourier = self.fourier_solution(x, t)
u_reflection = self.reflection_solution(x, t)
# Обновляем графики
line_fourier.set_ydata(u_fourier)
line_reflection.set_ydata(u_reflection)
ax.set_title(f'Сравнение методов, t = {t:.2f}')
return line_fourier, line_reflection
anim = animation.FuncAnimation(fig, animate, frames=len(t_anim),
interval=250, blit=True) # Увеличили интервал
# Сохраняем анимацию
animation_path = "results/images/methods_animation.gif"
anim.save(animation_path, writer='pillow', fps=4) # Уменьшили FPS
plt.close()
visualization_paths['methods_animation'] = animation_path
print("Анимация сравнения методов создана!")
except Exception as e:
print(f"Ошибка при создании анимации сравнения: {e}")
def calculate_displacement_table(self) -> List[List[Any]]:
"""Вычисление таблицы смещений в характерных точках"""
table_data = []
# Характерные точки и моменты времени
x_points = [0.5, 1.0, 2.0, 3.0, 3.5]
t_points = [0, 0.1, 0.2, 0.3, 0.5, 1.0]
for x in x_points:
for t in t_points:
displacement_fourier = self.fourier_solution(np.array([x]), t)[0]
displacement_reflection = self.reflection_solution(np.array([x]), t)[0]
difference = abs(displacement_fourier - displacement_reflection)
table_data.append([
f"{x:.1f}", f"{t:.1f}",
f"{displacement_fourier:.6f}",
f"{displacement_reflection:.6f}",
f"{difference:.6f}"
])
return table_data
def analyze(self):
"""Выполнение полного анализа волнового уравнения"""
print("Решение волнового уравнения методом Фурье и методом отражений...")
print("Создание визуализаций...")
visualizations = self.create_visualizations()
print("Вычисление таблицы смещений...")
displacement_table = self.calculate_displacement_table()
# Математический анализ методов
mathematical_analysis = self.mathematical_analysis()
# Сохранение результатов
self.results = {
'visualizations': visualizations,
'displacement_table': displacement_table,
'mathematical_analysis': mathematical_analysis,
'problem_definition': {
'equation': self.equation,
'initial_displacement': self.initial_displacement,
'initial_velocity': self.initial_velocity,
'boundary_conditions': self.boundary_conditions,
'domain': self.domain
},
'analytical_solution': {
'fourier_formula': "u(x,t) = sin(12πt) sin(3πx) - (1/4) sin(4πt) sin(πx)",
'reflection_interpretation': "Метод отражений использует нечетное продолжение начальной скорости и формулу Даламбера",
'comparison': "Оба метода дают идентичные результаты для данной задачи"
}
}
return self.results
def mathematical_analysis(self) -> Dict[str, Any]:
"""Математический анализ методов решения"""
return {
'fourier_method': {
'basis': 'sin(nπx/L)',
'eigenvalues': '(nπc/L)²',
'solution_form': 'Σ B_n sin(nπct/L) sin(nπx/L)',
'convergence': 'Точное решение (конечное число гармоник)'
},
'reflection_method': {
'basis': 'Формула Даламбера',
'extension': 'Нечетное продолжение начальных условий',
'periodicity': 'Период 2L',
'boundary_conditions': 'Автоматическое выполнение'
},
'comparison': {
'accuracy': 'Методы дают идентичные результаты',
'efficiency': 'Метод Фурье более эффективен для анализа спектра',
'applicability': 'Оба метода применимы для задач с фиксированными концами'
}
}
def get_analysis_description(self):
"""Возвращает описание анализа и методики"""
description = {
'title': "Решение уравнений гиперболического типа методом Фурье и методом отражений",
'discipline': "СПЕЦИАЛЬНЫЕ ГЛАВЫ МАТЕМАТИКИ",
'topic': "Решение волнового уравнения струны",
'variant': "Вариант 6",
'purpose': "Изучение методов решения начально-краевых задач для уравнений гиперболического типа на примере волнового уравнения струны",
'tasks': [
"Решение волнового уравнения методом Фурье (разделение переменных)",
"Решение методом отражений с использованием формулы Даламбера",
"Сравнение решений, полученных разными методами",
"Визуализация колебаний струны"
],
'methodology': {
'fourier_method': "Метод разделения переменных и разложение по собственным функциям",
'reflection_method': "Метод отражений с нечетным продолжением начальных условий",
'dalambert': "Использование формулы Даламбера для бесконечной струны"
},
'problem': {
'equation': self.equation,
'initial_displacement': self.initial_displacement,
'initial_velocity': self.initial_velocity,
'boundary_conditions': self.boundary_conditions,
'domain': self.domain
}
}
return description
class WaveEquationMarkdownReport:
"""Класс для генерации полного отчета в Markdown"""
def __init__(self, analyzer: WaveEquationAnalyzer, analysis_results: Dict[str, Any]):
self.analyzer = analyzer
self.results = analysis_results
def add_header(self, title: str, level: int = 1) -> str:
return f"{'#' * level} {title}\n\n"
def add_table(self, data: List[List[Any]], headers: List[str], title: str = "") -> str:
table_str = ""
if title:
table_str += f"**{title}**\n\n"
table_str += "| " + " | ".join(headers) + " |\n"
table_str += "|" + "|".join(["---"] * len(headers)) + "|\n"
for row in data:
table_str += "| " + " | ".join(map(str, row)) + " |\n"
table_str += "\n"
return table_str
def add_image(self, image_path: str, caption: str = "") -> str:
if os.path.exists(image_path):
return (f'<div align="center">\n\n'
f'<img src="{image_path}" alt="{caption}" style="max-width: 80%; height: auto; border: 1px solid #ddd; padding: 5px; background: #f8f9fa;">\n\n'
f'**Рисунок:** {caption}\n\n'
f'</div>\n\n')
else:
return f"*Изображение не найдено: {caption}*\n\n"
def add_code_section(self) -> str:
success, src = read_own_source()
section = self.add_header("ПРИЛОЖЕНИЕ А", 2)
section += "**Исходный код программы**\n\n"
if success:
section += "```python\n" + src
if not src.endswith("\n"):
section += "\n"
section += "```\n"
else:
section += f"*Не удалось прочитать исходный код: {src}*\n"
return section
def generate_report(self) -> str:
report = ""
# Титульная страница
report += '<div style="page-break-after: always;">\n\n'
report += "# Федеральное государственное бюджетное образовательное учреждение высшего профессионального образования\n\n"
report += "### «Казанский национальный исследовательский технологический университет»\n\n"
report += "#### Институт: Институт управления, автоматизации и информационных технологий\n"
report += "#### Кафедра Информатики и прикладной математики\n\n"
report += "---\n\n"
report += "# ЛАБОРАТОРНАЯ РАБОТА\n\n"
description = self.analyzer.get_analysis_description()
report += f"### по дисциплине: «{description['discipline']}»\n\n"
report += f"### на тему: «{description['topic']}»\n\n"
report += f"### {description['variant']}\n\n"
report += "---\n\n"
report += "**Выполнил:** \n"
report += "студент группы 851-М81 \n"
report += "Меркулов А. Д. \n"
report += '</div>\n\n'
# Содержание
report += self.add_header("СОДЕРЖАНИЕ", 1)
toc = [
"1. ВВЕДЕНИЕ",
"2. ПОСТАНОВКА ЗАДАЧИ",
"3. ТЕОРЕТИЧЕСКИЕ ОСНОВЫ",
" 3.1. Метод Фурье для волнового уравнения",
" 3.2. Метод отражений и формула Даламбера",
"4. АНАЛИТИЧЕСКОЕ РЕШЕНИЕ",
" 4.1. Решение методом Фурье",
" 4.2. Решение методом отражений",
"5. СРАВНЕНИЕ МЕТОДОВ",
"6. ВИЗУАЛИЗАЦИЯ РЕЗУЛЬТАТОВ",
"7. ВЫВОДЫ",
"ПРИЛОЖЕНИЕ А"
]
for item in toc:
report += f"{item}\n\n"
report += "---\n\n"
# 1) Введение
report += self.add_header("1. ВВЕДЕНИЕ", 1)
report += f"**Цель работы:** {description['purpose']}\n\n"
report += "**Задачи исследования:**\n"
for i, task in enumerate(description['tasks'], 1):
report += f"{i}. {task}\n"
report += "\n"
# 2) Постановка задачи
report += self.add_header("2. ПОСТАНОВКА ЗАДАЧИ", 1)
problem_def = description['problem']
report += f"**Волновое уравнение:** {problem_def['equation']}\n\n"
report += f"**Начальное смещение:** {problem_def['initial_displacement']}\n\n"
report += f"**Начальная скорость:** {problem_def['initial_velocity']}\n\n"
report += f"**Граничные условия:** {problem_def['boundary_conditions']}\n\n"
report += f"**Область определения:** {problem_def['domain']}\n\n"
# 3) Теоретические основы
report += self.add_header("3. ТЕОРЕТИЧЕСКИЕ ОСНОВЫ", 1)
report += self.add_header("3.1. Метод Фурье для волнового уравнения", 2)
methodology = description['methodology']
report += f"**{list(methodology.keys())[0]}:** {methodology['fourier_method']}\n\n"
report += "Для волнового уравнения с закрепленными концами:\n"
report += "u_tt = c² u_xx, u(0,t) = u(L,t) = 0\n\n"
report += "Решение ищется в виде ряда Фурье:\n"
report += "u(x,t) = Σₙ [Aₙ cos(ωₙt) + Bₙ sin(ωₙt)] sin(kₙx)\n"
report += "где kₙ = nπ/L, ωₙ = c kₙ = nπc/L\n\n"
report += self.add_header("3.2. Метод отражений и формула Даламбера", 2)
report += f"**{list(methodology.keys())[1]}:** {methodology['reflection_method']}\n\n"
report += "Для бесконечной струны решение задается формулой Даламбера:\n"
report += "u(x,t) = ½[f(x+ct) + f(x-ct)] + 1/(2c) ∫[g(s) ds] от x-ct до x+ct\n\n"
report += "Для струны с закрепленными концами используется нечетное продолжение начальных условий.\n\n"
# 4) Аналитическое решение
report += self.add_header("4. АНАЛИТИЧЕСКОЕ РЕШЕНИЕ", 1)
analytical = self.results['analytical_solution']
report += self.add_header("4.1. Решение методом Фурье", 2)
report += "1. Собственные функции и значения:\n"
report += " Xₙ(x) = sin(nπx/L), λₙ = (nπc/L)²\n"
report += " ωₙ = nπc/L = 4nπ/4 = nπ\n\n"
report += "2. Начальные условия:\n"
report += " u(x,0) = 0 ⇒ Aₙ = 0 для всех n\n"
report += " u_t(x,0) = 12π sin(3πx) - π sin(πx)\n\n"
report += "3. Коэффициенты Bₙ:\n"
report += " Bₙ = (2/(ωₙL)) ∫₀ᴸ u_t(x,0) sin(nπx/L) dx\n"
report += " B₁ = -1/4, B₃ = 1, Bₙ = 0 для n≠1,3\n\n"
report += f"**Окончательное решение:** {analytical['fourier_formula']}\n\n"
report += self.add_header("4.2. Решение методом отражений", 2)
report += "1. Нечетное продолжение начальной скорости относительно x=0 и x=L\n"
report += "2. Применение формулы Даламбера для продолженной функции\n"
report += "3. Учет граничных условий методом отражений\n\n"
report += f"**Интерпретация:** {analytical['reflection_interpretation']}\n\n"
# 5) Сравнение методов
report += self.add_header("5. СРАВНЕНИЕ МЕТОДОВ", 1)
mathematical = self.results['mathematical_analysis']
comparison = mathematical['comparison']
report += "**Сравнительная таблица методов:**\n\n"
comp_data = [
["Критерий", "Метод Фурье", "Метод отражений"],
["Точность", "Точное аналитическое решение", "Точное аналитическое решение"],
["Сложность", "Простота анализа спектра", "Простота физической интерпретации"],
["Применимость", "Линейные задачи с постоянными коэффициентами", "Задачи с фиксированными границами"]
]
for row in comp_data:
report += "| " + " | ".join(row) + " |\n"
report += "\n"
report += f"**Вывод:** {comparison['accuracy']}. {comparison['efficiency']}. {comparison['applicability']}.\n\n"
# 6) Визуализация результатов
report += self.add_header("6. ВИЗУАЛИЗАЦИЯ РЕЗУЛЬТАТОВ", 1)
visualizations = self.results['visualizations']
# Таблица смещений
table_data = self.results['displacement_table'][:6] # Только 6 строк
report += self.add_table(table_data,
["x", "t", "Метод Фурье", "Метод отражений", "Разность"],
"Сравнение смещений в характерных точках")
if 'initial_conditions' in visualizations:
report += self.add_image(visualizations['initial_conditions'],
"Начальные условия")
if 'methods_comparison' in visualizations:
report += self.add_image(visualizations['methods_comparison'],
"Сравнение методов Фурье и отражений в разные моменты времени")
if 'string_animation' in visualizations:
report += self.add_image(visualizations['string_animation'],
"Анимация колебаний струны методом Фурье")
if 'methods_animation' in visualizations:
report += self.add_image(visualizations['methods_animation'],
"Анимация сравнения методов Фурье и отражений")
report += "**Примечание:** Анимация демонстрирует идентичность решений, полученных разными методами.\n\n"
# 7) Выводы
report += self.add_header("7. ВЫВОДЫ", 1)
conclusions = [
"Успешно решена начально-краевая задача для волнового уравнения струны",
"Получено аналитическое решение методом Фурье: суперпозиция 1-й и 3-й гармоник",
"Решение методом отражений подтвердило корректность метода Фурье",
"Оба метода дают идентичные результаты, что подтверждает корректность решения",
"Визуализации наглядно демонстрируют колебания струны во времени"
]
for i, conclusion in enumerate(conclusions, 1):
report += f"{i}. {conclusion}\n"
report += "\n"
# Приложение
report += self.add_code_section()
return report
class WaveEquationDocxReport:
"""Класс для генерации отчета в формате DOCX"""
def __init__(self, analyzer: WaveEquationAnalyzer, analysis_results: Dict[str, Any]):
self.analyzer = analyzer
self.results = analysis_results
def setup_document_styles(self, doc):
"""Настройка стилей документа"""
try:
doc.styles['Normal'].font.name = 'Times New Roman'
doc.styles['Normal']._element.rPr.rFonts.set(qn('w:eastAsia'), 'Times New Roman')
doc.styles['Normal'].font.size = Pt(12)
except:
pass
def add_table_to_docx(self, doc, data: List[List[Any]], headers: List[str], title: str = ""):
"""Добавляет таблицу в DOCX документ"""
if title:
p = doc.add_paragraph()
p.add_run(title).bold = True
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
doc.add_paragraph()
table = doc.add_table(rows=len(data)+1, cols=len(headers))
table.style = 'Table Grid'
# Заголовки
hdr_cells = table.rows[0].cells
for i, header in enumerate(headers):
hdr_cells[i].text = str(header)
hdr_cells[i].paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
for paragraph in hdr_cells[i].paragraphs:
for run in paragraph.runs:
run.bold = True
# Данные
for i, row in enumerate(data, 1):
row_cells = table.rows[i].cells
for j, cell in enumerate(row):
row_cells[j].text = str(cell)
if j > 0:
row_cells[j].paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
doc.add_paragraph()
def add_image_to_docx(self, doc, image_path: str, caption: str = ""):
"""Добавляет изображение в DOCX документ"""
if os.path.exists(image_path):
try:
paragraph = doc.add_paragraph()
paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = paragraph.add_run()
run.add_picture(image_path, width=Inches(5.0))
if caption:
caption_paragraph = doc.add_paragraph()
caption_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
caption_run = caption_paragraph.add_run(f"Рисунок: {caption}")
caption_run.italic = True
doc.add_paragraph()
except Exception as e:
doc.add_paragraph(f"Ошибка при добавлении изображения: {e}")
else:
doc.add_paragraph(f"Изображение не найдено: {caption}")
def generate_docx_report(self, filename: str = "wave_equation_report.docx") -> str:
"""
Генерация отчёта в формате DOCX для волнового уравнения
"""
out_path = os.path.join("results", filename)
if not PYDOCX_AVAILABLE:
print("python-docx не установлен — DOCX отчёт не будет создан")
return out_path
try:
doc = Document()
self.setup_document_styles(doc)
# Титульная страница
title_paragraph = doc.add_paragraph()
title_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
title_run = title_paragraph.add_run("Федеральное государственное бюджетное образовательное учреждение высшего профессионального образования\n«Казанский национальный исследовательский технологический университет»")
title_run.bold = True
title_run.font.size = Pt(14)
doc.add_paragraph()
university_paragraph = doc.add_paragraph()
university_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
university_paragraph.add_run("Институт: Институт управления, автоматизации и информационных технологий\nКафедра Информатики и прикладной математики").bold = True
doc.add_paragraph("\n" * 3)
# Название работы
work_paragraph = doc.add_paragraph()
work_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
work_run = work_paragraph.add_run("ЛАБОРАТОРНАЯ РАБОТA")
work_run.bold = True
work_run.font.size = Pt(16)
description = self.analyzer.get_analysis_description()
doc.add_paragraph()
discipline_paragraph = doc.add_paragraph()
discipline_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
discipline_paragraph.add_run(f"по дисциплине: «{description['discipline']}»").bold = True
doc.add_paragraph()
topic_paragraph = doc.add_paragraph()
topic_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
topic_paragraph.add_run(f"на тему: «{description['topic']}»").bold = True
doc.add_paragraph()
variant_paragraph = doc.add_paragraph()
variant_paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
variant_paragraph.add_run(f"{description['variant']}").bold = True
doc.add_paragraph("\n" * 4)
# Информация о студенте
info_table = doc.add_table(rows=2, cols=2)
info_table.style = 'Table Grid'
info_table.cell(0, 0).text = "Выполнил:"
info_table.cell(0, 1).text = "студент группы 851-М81\nМеркулов А. Д."
for row in info_table.rows:
for cell in row.cells:
cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.LEFT
doc.add_page_break()
# Содержание
title = doc.add_paragraph()
title.alignment = WD_ALIGN_PARAGRAPH.CENTER
title.add_run("СОДЕРЖАНИЕ").bold = True
title.runs[0].font.size = Pt(14)
doc.add_paragraph()
toc_items = [
"1. ВВЕДЕНИЕ",
"2. ПОСТАНОВКА ЗАДАЧИ",
"3. ТЕОРЕТИЧЕСКИЕ ОСНОВЫ",
"4. АНАЛИТИЧЕСКОЕ РЕШЕНИЕ",
"5. СРАВНЕНИЕ МЕТОДОВ",
"6. ВИЗУАЛИЗАЦИЯ РЕЗУЛЬТАТОВ",
"7. ВЫВОДЫ",
"ПРИЛОЖЕНИЕ А"
]
for item in toc_items:
p = doc.add_paragraph()
p.add_run(item)
doc.add_page_break()
# Основное содержание
visualizations = self.results['visualizations']
analytical = self.results['analytical_solution']
# 1) Введение
heading = doc.add_paragraph()
heading.alignment = WD_ALIGN_PARAGRAPH.CENTER
heading.add_run("1. ВВЕДЕНИЕ").bold = True
heading.runs[0].font.size = Pt(14)
doc.add_paragraph()
doc.add_paragraph(f"Цель работы: {description['purpose']}")
doc.add_paragraph("Задачи исследования:")
for task in description['tasks']:
p = doc.add_paragraph(task, style='List Bullet')
# 2) Постановка задачи
doc.add_page_break()
heading = doc.add_paragraph()
heading.alignment = WD_ALIGN_PARAGRAPH.CENTER
heading.add_run("2. ПОСТАНОВКА ЗАДАЧИ").bold = True
heading.runs[0].font.size = Pt(14)
doc.add_paragraph()
problem_def = description['problem']
doc.add_paragraph(f"Волновое уравнение: {problem_def['equation']}")
doc.add_paragraph(f"Начальное смещение: {problem_def['initial_displacement']}")
doc.add_paragraph(f"Начальная скорость: {problem_def['initial_velocity']}")
doc.add_paragraph(f"Граничные условия: {problem_def['boundary_conditions']}")
doc.add_paragraph(f"Область определения: {problem_def['domain']}")
# 3) Теоретические основы
doc.add_page_break()
heading = doc.add_paragraph()
heading.alignment = WD_ALIGN_PARAGRAPH.CENTER
heading.add_run("3. ТЕОРЕТИЧЕСКИЕ ОСНОВЫ").bold = True
heading.runs[0].font.size = Pt(14)
doc.add_paragraph()
methodology = description['methodology']
doc.add_paragraph(f"Метод Фурье: {methodology['fourier_method']}")
doc.add_paragraph(f"Метод отражений: {methodology['reflection_method']}")
# 4) Аналитическое решение
doc.add_page_break()
heading = doc.add_paragraph()
heading.alignment = WD_ALIGN_PARAGRAPH.CENTER
heading.add_run("4. АНАЛИТИЧЕСКОЕ РЕШЕНИЕ").bold = True
heading.runs[0].font.size = Pt(14)
doc.add_paragraph()
doc.add_paragraph(f"Решение методом Фурье: {analytical['fourier_formula']}")
doc.add_paragraph(f"Метод отражений: {analytical['reflection_interpretation']}")
# 5) Сравнение методов
doc.add_page_break()
heading = doc.add_paragraph()
heading.alignment = WD_ALIGN_PARAGRAPH.CENTER
heading.add_run("5. СРАВНЕНИЕ МЕТОДОВ").bold = True
heading.runs[0].font.size = Pt(14)
doc.add_paragraph()
comparison_data = [
["x", "t", "Метод Фурье", "Метод отражений", "Разность"]
]
# Добавляем первые 5 строк таблицы
table_data = self.results['displacement_table'][:5]
for row in table_data:
comparison_data.append(row)
self.add_table_to_docx(doc, comparison_data[1:], comparison_data[0],
"Сравнение методов в характерных точках")
# 6) Визуализация
doc.add_page_break()
heading = doc.add_paragraph()
heading.alignment = WD_ALIGN_PARAGRAPH.CENTER
heading.add_run("6. ВИЗУАЛИЗАЦИЯ РЕЗУЛЬТАТОВ").bold = True
heading.runs[0].font.size = Pt(14)
doc.add_paragraph()
if 'initial_conditions' in visualizations:
self.add_image_to_docx(doc, visualizations['initial_conditions'],
"Начальные условия")
if 'methods_comparison' in visualizations:
self.add_image_to_docx(doc, visualizations['methods_comparison'],
"Сравнение методов")
if 'string_animation' in visualizations:
self.add_image_to_docx(doc, visualizations['string_animation'],
"Анимация колебаний струны")
if 'methods_animation' in visualizations:
self.add_image_to_docx(doc, visualizations['methods_animation'],
"Анимация сравнения методов")
# 7) Выводы
doc.add_page_break()
heading = doc.add_paragraph()
heading.alignment = WD_ALIGN_PARAGRAPH.CENTER
heading.add_run("7. ВЫВОДЫ").bold = True
heading.runs[0].font.size = Pt(14)
doc.add_paragraph()
conclusions = [
"Получено аналитическое решение волнового уравнения методом Фурье",
"Решение методом отражений подтвердило корректность метода Фурье",
"Анимации наглядно демонстрируют колебания струны",
"Оба метода дают идентичные результаты"
]
for i, conclusion in enumerate(conclusions, 1):
doc.add_paragraph(f"{i}. {conclusion}", style='List Number')
# Приложение
doc.add_page_break()
heading = doc.add_paragraph()
heading.alignment = WD_ALIGN_PARAGRAPH.CENTER
heading.add_run("ПРИЛОЖЕНИЕ А").bold = True
heading.runs[0].font.size = Pt(14)
doc.add_paragraph()
subheading = doc.add_paragraph()
subheading.add_run("Исходный код программы").bold = True
success_src, src_text = read_own_source()
if success_src:
for line in src_text.splitlines():
p = doc.add_paragraph()
run = p.add_run(line)
try:
run.font.name = 'Courier New'
run.font.size = Pt(9)
except Exception:
pass
else:
doc.add_paragraph("Не удалось прочитать исходный код скрипта: " + src_text)
# Сохранение файла
doc.save(out_path)
print(f"DOCX-отчёт сохранён: {out_path}")
except Exception as e:
print(f"Ошибка при генерации DOCX-отчёта: {e}")
raise e
return out_path
def main():
"""Основная функция для выполнения лабораторной работы по волновому уравнению"""
# Создаем анализатор
analyzer = WaveEquationAnalyzer()
# Выполняем анализ
print("Решение волнового уравнения методом Фурье и методом отражений...")
results = analyzer.analyze()
# Создаем папку для результатов
os.makedirs("results", exist_ok=True)
os.makedirs("results/images", exist_ok=True)
# Генерируем Markdown отчет
md_reporter = WaveEquationMarkdownReport(analyzer, results)
md_report = md_reporter.generate_report()
# Сохраняем Markdown отчет
with open("README.md", "w", encoding="utf-8") as f:
f.write(md_report)
print("Markdown отчет сохранен: README.md")
# Генерируем DOCX отчет
docx_reporter = WaveEquationDocxReport(analyzer, results)
docx_path = docx_reporter.generate_docx_report()
# Выводим основные результаты в консоль
print("\n" + "="*80)
print("РЕЗУЛЬТАТЫ РЕШЕНИЯ ВОЛНОВОГО УРАВНЕНИЯ")
print("="*80)
analytical = results['analytical_solution']
print(f"\nАНАЛИТИЧЕСКОЕ РЕШЕНИЕ МЕТОДОМ ФУРЬЕ:")
print("-" * 50)
print(analytical['fourier_formula'])
print(f"\nВИЗУАЛИЗАЦИИ СОЗДАНЫ:")
print("-" * 50)
for name, path in results['visualizations'].items():
if os.path.exists(path):
print(f"✓ {name}: {path}")
print(f"\nОТЧЕТЫ СОХРАНЕНЫ:")
print("-" * 50)
print(f"✓ Markdown отчет: README.md")
if PYDOCX_AVAILABLE:
print(f"✓ DOCX отчет: {docx_path}")
return results
if __name__ == "__main__":
main()