Загрузка данных
from datetime import datetime
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
from scipy.integrate import quad
import config
METHODS = (
("ЛПР", "Метод левых прямоугольников", "left_rectangles"),
("ППР", "Метод правых прямоугольников", "right_rectangles"),
("СПР", "Метод средних прямоугольников", "midpoint_rectangles"),
("ТР", "Метод трапеций", "trapezoids"),
("ММК", "Метод Монте-Карло для интеграла", "monte_carlo"),
("СИМП", "Метод Симпсона", "simpson"),
)
def f(x):
"""Вычисляем значение функции f(x) = sqrt((4-25*x**2)**3)."""
x_values = np.asarray(x)
return np.power(np.maximum(4 - 25 * x_values**2, 0), 1.5)
def true_area():
"""Возвращаем аналитическое (точное численное) значение площади."""
val, _ = quad(f, config.A, config.B)
return float(val)
def validate_config():
"""Проверяем корректность входных параметров."""
if config.B <= config.A:
raise ValueError("Параметр B должен быть больше A.")
if not config.N_VALUES:
raise ValueError("Параметр N_VALUES не должен быть пустым.")
for n in config.N_VALUES:
if int(n) <= 0:
raise ValueError("Все значения N_VALUES должны быть больше нуля.")
if int(config.PLOT_N) <= 0:
raise ValueError("Параметр PLOT_N должен быть больше нуля.")
def make_rng(n):
"""Создаём генератор для метода Монте-Карло с устойчивым seed."""
if getattr(config, "RANDOM_SEED", None) is None:
return np.random.default_rng()
return np.random.default_rng(int(config.RANDOM_SEED) + int(n))
def left_rectangles(n):
"""Вычисляем площадь методом левых прямоугольников."""
dx = (config.B - config.A) / n
x_left = config.A + np.arange(n) * dx
return float(dx * np.sum(f(x_left)))
def right_rectangles(n):
"""Вычисляем площадь методом правых прямоугольников."""
dx = (config.B - config.A) / n
x_right = config.A + np.arange(1, n + 1) * dx
return float(dx * np.sum(f(x_right)))
def midpoint_rectangles(n):
"""Вычисляем площадь методом средних прямоугольников."""
dx = (config.B - config.A) / n
x_mid = config.A + (np.arange(n) + 0.5) * dx
return float(dx * np.sum(f(x_mid)))
def trapezoids(n):
"""Вычисляем площадь методом трапеций."""
dx = (config.B - config.A) / n
x_points = np.linspace(config.A, config.B, n + 1)
y_points = f(x_points)
return float(dx * (0.5 * y_points[0] + np.sum(y_points[1:-1]) + 0.5 * y_points[-1]))
def monte_carlo(n):
"""Вычисляем площадь методом Монте-Карло для интеграла."""
rng = make_rng(n)
x_random = rng.uniform(config.A, config.B, n)
return float((config.B - config.A) * np.mean(f(x_random)))
def simpson(n):
"""Вычисляем площадь методом Симпсона для n параболических фигур."""
subintervals = 2 * n
dx = (config.B - config.A) / subintervals
x_points = np.linspace(config.A, config.B, subintervals + 1)
y_points = f(x_points)
return float(
dx
/ 3.0
* (
y_points[0]
+ y_points[-1]
+ 4.0 * np.sum(y_points[1:-1:2])
+ 2.0 * np.sum(y_points[2:-1:2])
)
)
METHOD_FUNCTIONS = {
"left_rectangles": left_rectangles,
"right_rectangles": right_rectangles,
"midpoint_rectangles": midpoint_rectangles,
"trapezoids": trapezoids,
"monte_carlo": monte_carlo,
"simpson": simpson,
}
def calculate_errors(estimate, exact_area):
absolute_error = abs(estimate - exact_area)
relative_error = absolute_error / exact_area * 100.0
return absolute_error, relative_error
def build_results():
exact_area = true_area()
results = []
for short_name, full_name, function_name in METHODS:
row = {
"short_name": short_name,
"full_name": full_name,
"values": {},
}
method = METHOD_FUNCTIONS[function_name]
for n in config.N_VALUES:
estimate = method(int(n))
absolute_error, relative_error = calculate_errors(estimate, exact_area)
row["values"][int(n)] = {
"estimate": estimate,
"absolute_error": absolute_error,
"relative_error": relative_error,
}
results.append(row)
return results
def format_number(value, digits=8, scientific_digits=10):
"""
Форматирует число:
- обычный fixed-point для стандартных значений,
- scientific notation для очень маленьких чисел.
"""
if value == 0:
return f"{0:.{digits}f}"
if abs(value) < 10 ** (-digits):
return f"{value:.{scientific_digits}e}"
return f"{value:.{digits}f}"
def format_result_cell(short_name, n, value):
estimate_str = format_number(value["estimate"], digits=8, scientific_digits=10)
if short_name in ("СПР", "ТР") and int(n) == 1000:
absolute_str = format_number(value["absolute_error"], digits=16, scientific_digits=12)
relative_str = format_number(value["relative_error"], digits=12, scientific_digits=12)
else:
absolute_str = format_number(value["absolute_error"], digits=8, scientific_digits=10)
relative_str = format_number(value["relative_error"], digits=4, scientific_digits=10)
return f"Ŝ={estimate_str}; Δ={absolute_str}; δ={relative_str}%"
def format_results_table(results):
headers = ["Методы"] + [f"n={int(n)}" for n in config.N_VALUES]
rows = []
for row in results:
rows.append(
[row["short_name"]]
+ [format_result_cell(row["short_name"], int(n), row["values"][int(n)]) for n in config.N_VALUES]
)
widths = [
max(len(headers[col]), *(len(row[col]) for row in rows))
for col in range(len(headers))
]
border = "+-" + "-+-".join("-" * width for width in widths) + "-+"
lines = [border]
lines.append(
"| "
+ " | ".join(headers[col].ljust(widths[col]) for col in range(len(headers)))
+ " |"
)
lines.append(border)
for row in rows:
lines.append(
"| "
+ " | ".join(row[col].ljust(widths[col]) for col in range(len(row)))
+ " |"
)
lines.append(border)
return "\n".join(lines)
def find_best_results(results):
best_by_n = {}
overall_best = None
for n in config.N_VALUES:
n = int(n)
best_row = min(
results,
key=lambda row: row["values"][n]["absolute_error"],
)
best_by_n[n] = {
"short_name": best_row["short_name"],
"full_name": best_row["full_name"],
**best_row["values"][n],
}
for row in results:
for n, value in row["values"].items():
candidate = {
"n": n,
"short_name": row["short_name"],
"full_name": row["full_name"],
**value,
}
if (
overall_best is None
or candidate["absolute_error"] < overall_best["absolute_error"]
):
overall_best = candidate
return best_by_n, overall_best
def format_best_result(value):
return (
f"{value['short_name']} ({value['full_name']}): "
f"Ŝ={format_number(value['estimate'], digits=10, scientific_digits=12)}; "
f"Δ={format_number(value['absolute_error'], digits=16, scientific_digits=12)}; "
f"δ={format_number(value['relative_error'], digits=12, scientific_digits=12)}%"
)
def build_output_basename(output_dir):
base = getattr(config, "PLOT_BASENAME", "plot")
if getattr(config, "SAVE_UNIQUE_NAMES", False):
timestamp = datetime.now().strftime(
getattr(config, "FILENAME_TIMESTAMP_FORMAT", "%Y%m%d_%H%M%S")
)
base = f"{base}_{timestamp}"
candidate = base
index = 1
formats = getattr(config, "SAVE_FORMATS", ["png"])
while any((output_dir / f"{candidate}.{ext}").exists() for ext in formats):
candidate = f"{base}_{index}"
index += 1
return candidate
def plot_function(ax):
samples = getattr(config, "CURVE_SAMPLES", 400)
x_line = np.linspace(config.A, config.B, samples)
y_line = f(x_line)
ax.plot(
x_line,
y_line,
color=getattr(config, "FUNCTION_COLOR", "b"),
linewidth=2.2,
label=getattr(config, "FUNCTION_LABEL", r"$f(x) = \sqrt{(4-25x^2)^3}$"),
zorder=3,
)
def setup_method_axis(ax, title):
ax.set_title(title, fontsize=10)
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_xlim(config.A, config.B)
ax.set_ylim(0, float(np.max(f(np.linspace(config.A, config.B, 200)))) * 1.12)
ax.grid(True, alpha=getattr(config, "GRID_ALPHA", 0.5))
def plot_left_rectangles(ax, n):
dx = (config.B - config.A) / n
x_left = config.A + np.arange(n) * dx
ax.bar(
x_left,
f(x_left),
width=dx,
align="edge",
color=getattr(config, "FIGURE_COLOR", "orange"),
edgecolor=getattr(config, "FIGURE_EDGE_COLOR", "black"),
alpha=0.35,
linewidth=1.0,
label="Левые прямоугольники",
)
def plot_right_rectangles(ax, n):
dx = (config.B - config.A) / n
x_right = config.A + np.arange(1, n + 1) * dx
ax.bar(
x_right - dx,
f(x_right),
width=dx,
align="edge",
color=getattr(config, "FIGURE_COLOR", "green"),
edgecolor=getattr(config, "FIGURE_EDGE_COLOR", "black"),
alpha=0.35,
linewidth=1.0,
label="Правые прямоугольники",
)
def plot_midpoint_rectangles(ax, n):
dx = (config.B - config.A) / n
x_left = config.A + np.arange(n) * dx
x_mid = x_left + 0.5 * dx
ax.bar(
x_left,
f(x_mid),
width=dx,
align="edge",
color=getattr(config, "FIGURE_COLOR", "red"),
edgecolor=getattr(config, "FIGURE_EDGE_COLOR", "black"),
alpha=0.35,
linewidth=1.0,
label="Средние прямоугольники",
)
ax.scatter(
x_mid,
f(x_mid),
s=18,
color=getattr(config, "FIGURE_EDGE_COLOR", "black"),
zorder=4,
)
def plot_trapezoids(ax, n):
x_points = np.linspace(config.A, config.B, n + 1)
y_points = f(x_points)
for i in range(n):
polygon_x = [x_points[i], x_points[i], x_points[i + 1], x_points[i + 1]]
polygon_y = [0, y_points[i], y_points[i + 1], 0]
ax.fill(
polygon_x,
polygon_y,
color=getattr(config, "FIGURE_COLOR", "purple"),
edgecolor=getattr(config, "FIGURE_EDGE_COLOR", "black"),
alpha=0.32,
linewidth=1.0,
)
ax.plot(
x_points,
y_points,
color=getattr(config, "FIGURE_EDGE_COLOR", "black"),
linewidth=1.2,
label="Трапеции",
)
def plot_monte_carlo(ax, n):
rng = make_rng(n)
x_random = np.sort(rng.uniform(config.A, config.B, n))
y_random = f(x_random)
width = (config.B - config.A) / n
ax.bar(
x_random,
y_random,
width=width * 0.85,
align="center",
color=getattr(config, "MC_COLOR", "green"),
edgecolor=getattr(config, "FIGURE_EDGE_COLOR", "black"),
alpha=0.32,
linewidth=0.9,
label="Случайные прямоугольники",
)
ax.scatter(x_random, y_random, s=20, color=getattr(config, "MC_COLOR", "green"), zorder=4)
def plot_simpson(ax, n):
subintervals = 2 * n
x_points = np.linspace(config.A, config.B, subintervals + 1)
y_points = f(x_points)
for figure_index in range(n):
i = 2 * figure_index
local_x = x_points[i: i + 3]
local_y = y_points[i: i + 3]
coefficients = np.polyfit(local_x, local_y, deg=2)
x_dense = np.linspace(local_x[0], local_x[-1], 80)
y_dense = np.polyval(coefficients, x_dense)
ax.fill_between(
x_dense,
0,
y_dense,
color=getattr(config, "SIMPSON_COLOR", "cyan"),
alpha=0.28,
edgecolor=getattr(config, "FIGURE_EDGE_COLOR", "black"),
linewidth=0.8,
)
ax.plot(
x_dense,
y_dense,
color=getattr(config, "SIMPSON_COLOR", "cyan"),
linewidth=1.2,
)
ax.vlines(
[local_x[0], local_x[-1]],
0,
[local_y[0], local_y[-1]],
color=getattr(config, "FIGURE_EDGE_COLOR", "black"),
alpha=0.55,
linewidth=0.8,
)
ax.scatter(
x_points,
y_points,
s=16,
color=getattr(config, "FIGURE_EDGE_COLOR", "black"),
zorder=4,
label="Узлы",
)
PLOTTERS = {
"left_rectangles": plot_left_rectangles,
"right_rectangles": plot_right_rectangles,
"midpoint_rectangles": plot_midpoint_rectangles,
"trapezoids": plot_trapezoids,
"monte_carlo": plot_monte_carlo,
"simpson": plot_simpson,
}
def plot_results():
output_dir = Path(__file__).resolve().parent / getattr(config, "PLOT_DIR", "plots")
output_dir.mkdir(parents=True, exist_ok=True)
n = int(config.PLOT_N)
fig, axes = plt.subplots(3, 2, figsize=getattr(config, "FIGURE_SIZE", (12, 10)))
axes_flat = axes.ravel()
exact_area = true_area()
fig.suptitle(
f"{getattr(config, 'PLOT_TITLE', 'Численное интегрирование')}\n"
f"{getattr(config, 'FUNCTION_LABEL', 'f(x)')}, Sист={format_number(exact_area, digits=10, scientific_digits=12)}, n={n}",
fontsize=13,
)
for ax, (short_name, full_name, function_name) in zip(axes_flat, METHODS):
PLOTTERS[function_name](ax, n)
plot_function(ax)
estimate = METHOD_FUNCTIONS[function_name](n)
absolute_error, relative_error = calculate_errors(estimate, exact_area)
title_estimate = format_number(estimate, digits=8, scientific_digits=10)
if short_name in ("СПР", "ТР") and n == 1000:
title_abs = format_number(absolute_error, digits=16, scientific_digits=12)
title_rel = format_number(relative_error, digits=12, scientific_digits=12)
else:
title_abs = format_number(absolute_error, digits=6, scientific_digits=10)
title_rel = format_number(relative_error, digits=6, scientific_digits=10)
setup_method_axis(
ax,
f"{short_name}: {full_name}\n"
f"Ŝ={title_estimate}; Δ={title_abs}; δ={title_rel}%",
)
ax.legend(loc="best", fontsize=8)
plt.tight_layout(rect=(0, 0, 1, 0.94))
base_name = build_output_basename(output_dir)
saved_paths = []
formats = getattr(config, "SAVE_FORMATS", ["png"])
for extension in formats:
file_path = output_dir / f"{base_name}.{extension}"
fig.savefig(file_path, dpi=getattr(config, "PLOT_DPI", 100), bbox_inches="tight")
saved_paths.append(file_path)
backend = plt.get_backend().lower()
if getattr(config, "SHOW_PLOT", True) and "agg" not in backend:
plt.show()
else:
plt.close(fig)
return saved_paths
def print_report(results, saved_paths):
exact_value = true_area()
print("=" * 76)
print("ЛАБОРАТОРНАЯ РАБОТА №4")
print("Вычисление площади функции различными методами")
print("=" * 76)
print("f(x) = sqrt((4 - 25x^2)^3)")
print(f"Отрезок интегрирования: [{config.A}, {config.B}]")
print(f"Sист = {format_number(exact_value, digits=10, scientific_digits=12)}")
print("-" * 76)
print(format_results_table(results))
print("-" * 76)
best_by_n, overall_best = find_best_results(results)
print("ЛУЧШИЙ МЕТОД ПО МИНИМАЛЬНОЙ АБСОЛЮТНОЙ ПОГРЕШНОСТИ")
for n in config.N_VALUES:
n = int(n)
print(f"n={n}: {format_best_result(best_by_n[n])}")
print("Лучший результат по всей таблице:")
print(f"n={overall_best['n']}: {format_best_result(overall_best)}")
print("=" * 76)
print("Файлы графика:")
for path in saved_paths:
print(f"- {path}")
def main():
try:
validate_config()
except ValueError as exc:
raise SystemExit(f"Ошибка в параметрах config.py: {exc}") from exc
results = build_results()
saved_paths = plot_results()
print_report(results, saved_paths)
if __name__ == "__main__":
main()