```
import tkinter as tk
from tkinter import messagebox
from tkinter import ttk
import numpy as np
import math
from scipy.stats import *
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk


# ==================================================== ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ ==============================================================

discrete_distributions = [
    "Биномиальное",
    "Отрицательное биномиальное",
    "Пуассона",
    "Геометрическое"
]

continuous_distributions = [
    "Нормальное",
    "Экспоненциальное",
    "Равномерное",
    "Стьюдента",
    "Хи-квадрат"
]

distribution_parameters = {
    "Биномиальное": ["n", "p"],
    "Отрицательное биномиальное": ["r", "p"],
    "Пуассона": ["lambda"],
    "Геометрическое": ["p"],
    "Нормальное": ["mu", "sigma"],
    "Экспоненциальное": ["lambda"],
    "Равномерное": ["a", "b"],
    "Стьюдента": ["df"],
    "Хи-квадрат": ["df"]
}

fig1 = None
ax1 = None
canvas1 = None

fig2 = None
ax2 = None
canvas2 = None

fig_sum = None
ax_sum = None
canvas_sum = None

param_entries1 = {}
param_entries2 = {}



# ==================================================== ФУНКЦИИ =========================================================

# ----------------------------------- Обновление для выбора распределений -------------------------------------------
def update_distribution_comboboxes(selected_type):
    if selected_type == "discrete":
        available_distributions = discrete_distributions
    elif selected_type == "continuous":
        available_distributions = continuous_distributions
    else:
        available_distributions = []

    dist1_combobox['values'] = available_distributions
    dist2_combobox['values'] = available_distributions

    dist1_combobox.set("Выберите...")
    dist2_combobox.set("Выберите...")

    update_parameter_fields()
    clear_plots()

# ----------------------------------- Обновление для параметров распределений -------------------------------------
def update_parameter_fields(event=None):
    global param_entries1, param_entries2

    dist1_name = dist1_combobox.get()
    dist2_name = dist2_combobox.get()

    param_entries1 = create_parameter_widgets(params1_frame, dist1_name)

    param_entries2 = create_parameter_widgets(params2_frame, dist2_name)

    clear_plots()

# ----------------------------------- Создание виджетов для параметров -------------------------------------------
def create_parameter_widgets(frame, dist_name):
    for widget in frame.winfo_children():
        widget.destroy()

    parameters = distribution_parameters.get(dist_name)

    if parameters:
        ttk.Label(frame, text=f"Параметры для {dist_name}:").grid(row=0, column=0, columnspan=2, sticky="w", pady=5)
        entry_widgets = {}

        for i, param_name in enumerate(parameters):
            ttk.Label(frame, text=f"{param_name}:").grid(row=i+1, column=0, sticky="w", padx=5, pady=2)
            entry = ttk.Entry(frame)
            entry.grid(row=i+1, column=1, sticky="ew", padx=5, pady=2)
            entry_widgets[param_name] = entry

        frame.columnconfigure(1, weight=1)

        return entry_widgets
    else:
        ttk.Label(frame, text="Выберите распределение").grid(row=0, column=0, sticky="w")
        return None

# ----------------------------------- Очистка графиков -------------------------------------------
def clear_plots():
    global ax1, ax2, ax_sum
    global canvas1, canvas2, canvas_sum

    if ax1:
        ax1.clear()
        ax1.set_title("График Распределения 1")
        ax1.set_xlabel("Значение")
        ax1.set_ylabel("Вероятность/Плотность")
        if canvas1:
            canvas1.draw_idle()

    if ax2:
        ax2.clear()
        ax2.set_title("График Распределения 2")
        ax2.set_xlabel("Значение")
        ax2.set_ylabel("Вероятность/Плотность")
        if canvas2:
            canvas2.draw_idle()

    if ax_sum:
        ax_sum.clear()
        ax_sum.set_title("Распределение сумм")
        ax_sum.set_xlabel("Значение суммы")
        ax_sum.set_ylabel("Частота")
        if canvas_sum:
            canvas_sum.draw_idle()

    dist1_description_label.config(text="Распределение 1: Не выбрано")
    dist2_description_label.config(text="Распределение 2: Не выбрано")

# ----------------------------------- Постройка графиков -------------------------------------------
def plot_distributions():
    clear_plots()

    dist1_name = dist1_combobox.get()
    dist2_name = dist2_combobox.get()
    # print("got names")

    if dist1_name == "Выберите..." or dist2_name == "Выберите...":
        print("Пожалуйста, выберите оба распределения.")
        # TODO: Добавить вывод ошибки в GUI
        return

    params1_values = {name: entry.get() for name, entry in param_entries1.items()}
    params2_values = {name: entry.get() for name, entry in param_entries2.items()}

    dist1_description_label.config(text=f"Распределение 1: {dist1_name} (Параметры: {params1_values})")
    dist2_description_label.config(text=f"Распределение 2: {dist2_name} (Параметры: {params2_values})")
    # print("got params")

    global fig1, fig2
    global ax1, ax2
    global canvas1, canvas2

    if fig1 is None:
        fig1, ax1 = plt.subplots(figsize=(6, 4))
        canvas1 = FigureCanvasTkAgg(fig1, master=plot1_frame)
        canvas1.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)

    if fig2 is None:
        fig2, ax2 = plt.subplots(figsize=(6, 4))
        canvas2 = FigureCanvasTkAgg(fig2, master=plot2_frame)
        canvas2.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)

    # --- Создание и отображение графика для Распределения 1 ---

    values1, probabilities1 = get_graph(dist1_name, params1_values)
    if values1 is None:
        return
    # print("start viz1")
    if dist1_name in discrete_distributions:
        if len(values1) < 50:
            ax1.bar(values1, probabilities1, width=0.7, color='blue', edgecolor='black')
        else:
            ax1.bar(values1, probabilities1, width=1, color='blue')
    elif dist1_name in continuous_distributions:
        ax1.plot(values1, probabilities1, color='blue')

    ax1.set_title(f"{dist1_name}")
    ax1.set_xlabel("Значение")
    ax1.set_ylabel(f"{'Вероятность' if dist2_name in discrete_distributions else 'Плотность'}")
    # print("end viz1")

    canvas1.draw_idle()

    # --- Создание и отображение графика для Распределения 2 ---
    values2, probabilities2 = get_graph(dist2_name, params2_values)
    if values2 is None:
        return
    # print("start viz2")
    if dist2_name in discrete_distributions:
        if len(values2) < 50:
            ax2.bar(values2, probabilities2, width=0.7, color='red', edgecolor='black')
        else:
            ax2.bar(values2, probabilities2, width=1, color='red')
    elif dist2_name in continuous_distributions:
        ax2.plot(values2, probabilities2, color='red')

    ax2.set_title(f"{dist2_name}")
    ax2.set_xlabel("Значение")
    ax2.set_ylabel(f"{'Вероятность' if dist2_name in discrete_distributions else 'Плотность'}")
    # print("end viz2")

    canvas2.draw_idle()

    plt.close(fig1)
    plt.close(fig2)

    main_notebook.select(results_frame)

# ----------------------------------- Создание объекта распределения ----------------------------------------------

def get_dist(dist_name, params_values):
    if dist_name in discrete_distributions:
        dist = None
        x = None
        limit = None

        if dist_name == "Биномиальное":
            try:
                n = int(params_values['n'])
                if n <= 0:
                    messagebox.showerror("Ошибка ввода", f"Параметр 'n' должен быть натуральным числом. Вы ввели: {params_values['n']}")
                    return None, None, None
            except ValueError:
                messagebox.showerror("Ошибка ввода", f"Параметр 'n' должен быть натуральным числом. Вы ввели: {params_values['n']}")
                return None, None, None
            try:
                p = float(params_values['p'])
                if not 0 < p < 1:
                    messagebox.showerror("Ошибка ввода", f"Параметр 'p' должен быть числом из диапазона (0; 1). Вы ввели: {params_values['p']}")
                    return None, None, None

            except ValueError:
                messagebox.showerror("Ошибка ввода", f"Параметр 'p' должен быть числом из диапазона (0; 1). Вы ввели: {params_values['p']}")
                return None, None, None

            dist = binom(n=n, p=p)
            x = 0
            limit = n

        elif dist_name == "Отрицательное биномиальное":
            try:
                r = int(params_values['r'])
                if r <= 0:
                    messagebox.showerror("Ошибка ввода", f"Параметр 'r' должен быть натуральным числом. Вы ввели: {params_values['r']}")
                    return None, None, None
            except ValueError:
                messagebox.showerror("Ошибка ввода", f"Параметр 'r' должен быть натуральным числом. Вы ввели: {params_values['r']}")
                return None, None, None
            try:
                p = float(params_values['p'])
                if not 0 < p < 1:
                    messagebox.showerror("Ошибка ввода", f"Параметр 'p' должен быть числом из диапазона (0; 1). Вы ввели: {params_values['p']}")
                    return None, None, None

            except ValueError:
                messagebox.showerror("Ошибка ввода", f"Параметр 'p' должен быть числом из диапазона (0; 1). Вы ввели: {params_values['p']}")
                return None, None, None

            dist = nbinom(n=r, p=p)
            x = r

        elif dist_name == "Пуассона":
            try:
                lmd = float(params_values['lambda'])
                if lmd <= 0:
                    messagebox.showerror("Ошибка ввода", f"Параметр 'lambda' должен быть положительным числом. Вы ввели: {params_values['lambda']}")
                    return None, None, None
            except ValueError:
                messagebox.showerror("Ошибка ввода", f"Параметр 'lambda' должен быть положительным числом. Вы ввели: {params_values['lambda']}")
                return None, None, None

            dist = poisson(mu=lmd)
            x = 0

        elif dist_name == "Геометрическое":
            try:
                p = float(params_values['p'])
                if not 0 < p < 1:
                    messagebox.showerror("Ошибка ввода", f"Параметр 'p' должен быть числом из диапазона (0; 1). Вы ввели: {params_values['p']}")
                    return None, None, None
            except ValueError:
                messagebox.showerror("Ошибка ввода", f"Параметр 'p' должен быть числом из диапазона (0; 1). Вы ввели: {params_values['p']}")
                return None, None, None
            dist = geom(p=p)
            x = 1
        else:
            print("Пожалуйста, выберите распределение")
            # TODO: сделать ошибку в GUI
            return None, None, None
        return dist, x, limit

    elif dist_name in continuous_distributions:
        dist = None
        x = None
        limit = None

        if dist_name == "Нормальное":
            try:
                mu = float(params_values['mu'])
            except ValueError:
                messagebox.showerror("Ошибка ввода", f"Параметр 'mu' должен быть числом. Вы ввели: {params_values['mu']}")
                return None, None, None
            try:
                sigma = float(params_values['sigma'])
            except ValueError:
                messagebox.showerror("Ошибка ввода", f"Параметр 'sigma' должен быть числом. Вы ввели: {params_values['sigma']}")
                return None, None, None
            dist = norm(loc=mu, scale=sigma)

        elif dist_name == "Экспоненциальное":
            try:
                lmd = float(params_values['lambda'])
                if lmd <= 0:
                    messagebox.showerror("Ошибка ввода", f"Параметр 'lambda' должен быть положительным числом. Вы ввели: {params_values['lambda']}")
                    return None, None, None
            except ValueError:
                messagebox.showerror("Ошибка ввода", f"Параметр 'lambda' должен быть положительным числом. Вы ввели: {params_values['lambda']}")
                return None, None, None
            dist = expon(loc=0, scale=1/lmd)
            x = 0

        elif dist_name == "Равномерное":
            try:
                a = float(params_values['a'])
            except ValueError:
                messagebox.showerror("Ошибка ввода", f"Параметр 'a' должен быть числом. Вы ввели: {params_values['a']}")
                return None, None, None
            try:
                b = float(params_values['b'])
            except ValueError:
                messagebox.showerror("Ошибка ввода", f"Параметр 'b' должен быть числом. Вы ввели: {params_values['b']}")
                return None, None, None

            dist = uniform(loc=a, scale=b-a)
            x = a
            limit = b

        elif dist_name == "Стьюдента":
            try:
                df = int(params_values['df'])
                if df < 0:
                    messagebox.showerror("Ошибка ввода", f"Параметр 'df' должен быть натуральным числом. Вы ввели: {params_values['df']}")
                    return None, None, None
            except ValueError:
                messagebox.showerror("Ошибка ввода", f"Параметр 'df' должен быть натуральным числом. Вы ввели: {params_values['df']}")
                return None, None, None
            dist = t(df, loc=0, scale=1)

        elif dist_name == "Хи-квадрат":
            try:
                df = int(params_values['df'])
                if df < 0:
                    messagebox.showerror("Ошибка ввода", f"Параметр 'df' должен быть натуральным числом. Вы ввели: {params_values['df']}")
                    return None, None, None
            except ValueError:
                messagebox.showerror("Ошибка ввода", f"Параметр 'df' должен быть натуральным числом. Вы ввели: {params_values['df']}")
                return None, None, None

            dist = chi2(df, loc=0, scale=1)

        else:
            print("Пожалуйста, выберите распределение")
            # TODO: сделать ошибку в GUI
            return None, None, None

        return dist, x, limit

    else:
        print("Пожалуйста, выберите распределение")
        # TODO: сделать ошибку в GUI
        return None, None, None

# ----------------------------------- Получение данных для графика ----------------------------------------------

def get_graph(dist_name, params_values):
    if dist_name in discrete_distributions:
        epsilon = 5e-4

        def build(dist_, x_, limit_=None):
            mean = dist_.mean()

            values_ = []
            probabilities_ = []
            while True and (limit_ is None or x_ <= limit_):
                px_ = dist_.pmf(x_)
                if px_ < epsilon:
                    if x_ < mean:
                        x_ += 1
                        continue
                    break

                values_.append(x_)
                probabilities_.append(px_)
                x_ += 1
            values_ = np.array(values_)
            probabilities_ = np.array(probabilities_)

            return values_, probabilities_

        dist, x, limit = get_dist(dist_name, params_values)
        if dist is None:
            return None, None
        values, probabilities = build(dist, x, limit)

    elif dist_name in continuous_distributions:
        coverage = 0.99
        if dist_name == "Равномерное":
            coverage = 0.9999
        alpha_ = 1 - coverage
        num_points = 1000

        def build(dist_):
            low_b = dist_.ppf(alpha_ / 2)
            up_b = dist_.ppf(1 - alpha_ / 2)

            values_ = np.linspace(low_b, up_b, num_points)
            probabilities_ = np.array([dist_.pdf(k) for k in values_])

            return values_, probabilities_

        dist, _, _ = get_dist(dist_name, params_values)
        if dist is None:
            return None, None
        values, probabilities = build(dist)

    else:
        print("Пожалуйста, выберите распределение")
        # TODO: сделать ошибку в GUI
        return None, None

    return values, probabilities

# ----------------------------------- Получение числа с графика ----------------------------------------------

def get_dist_rnd(dist_name, params_values, sample_size):
    if dist_name in discrete_distributions:
        dist, _, _ = get_dist(dist_name, params_values)
        if dist is None:
            return None

    elif dist_name in continuous_distributions:
        dist, _, _ = get_dist(dist_name, params_values)
        if dist is None:
            return None

    else:
        print("Пожалуйста, выберите распределение")
        # TODO: сделать ошибку в GUI
        return None

    return dist.rvs(size=sample_size)

# ----------------------------------- Построение гистограммы сум ----------------------------------------------

def create_sum_result_hist():
    dist1_name = dist1_combobox.get()
    dist2_name = dist2_combobox.get()

    if dist1_name == "Выберите..." or dist2_name == "Выберите...":
        print("Пожалуйста, выберите оба распределения.")
        # TODO: Добавить вывод ошибки в GUI
        return

    params1_values = {name: entry.get() for name, entry in param_entries1.items()}
    params2_values = {name: entry.get() for name, entry in param_entries2.items()}

    try:
        sample_size = int(sample_size_entry.get())
        if sample_size <= 0:
            messagebox.showerror("Ошибка ввода", f"Введите корректный размер выборки - натуральное число. Вы ввели {sample_size_entry.get()}")
            return
    except ValueError:
        messagebox.showerror("Ошибка ввода", f"Введите корректный размер выборки - натуральное число. Вы ввели {sample_size_entry.get()}")
        return

    sample1 = get_dist_rnd(dist1_name, params1_values, sample_size)
    sample2 = get_dist_rnd(dist2_name, params2_values, sample_size)

    if sample1 is None or sample2 is None:
        return

    samples_sum = sample1 + sample2
    max_el = max(np.quantile(sample1, 0.99), np.quantile(sample2, 0.99), np.quantile(samples_sum, 0.99))
    min_el = min(np.quantile(sample1, 0.01), np.quantile(sample2, 0.01), np.quantile(samples_sum, 0.01))

    bins_num = math.floor(max_el - min_el + 1)
    bins = None

    if dist1_name in continuous_distributions:
        bins_num = max(bins_num, 20)
        bins_num = math.ceil(2 * (sample_size * bins_num ** 0.5) ** (1/3))
        bins = np.linspace(min_el, max_el, bins_num)

    else:
        bins = np.arange(min_el, max_el + 2).astype(float)
        bins = bins - 0.5

    global fig_sum
    global ax_sum
    global canvas_sum

    if fig_sum is None:
        fig_sum, ax_sum = plt.subplots(figsize=(7, 5))
        canvas_sum = FigureCanvasTkAgg(fig_sum, master=plot_sum_frame)
        canvas_sum.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)

    if ax_sum:
        ax_sum.clear()
        ax_sum.set_title("Распределение сумм")
        ax_sum.set_xlabel("Значение суммы")
        ax_sum.set_ylabel("Частота")
        if canvas_sum:
            canvas_sum.draw_idle()

    ax_sum.hist(samples_sum, bins=bins, density=True, rwidth=0.7, color="black", histtype='stepfilled')

    ax_sum.hist(sample1, bins=bins, density=True, rwidth=0.7, color="blue", histtype='step', linewidth=3, alpha=0.5)
    ax_sum.hist(sample2, bins=bins, density=True, rwidth=0.7, color="red", histtype='step', linewidth=3, alpha=0.5)

    # ax.grid(True)

    canvas_sum.draw_idle()

    plt.close(fig_sum)

# ==================================================== СОЗДАНИЕ ОКНА ==================================================

# ------------------------------------------ Создание вкладок  -------------------------------------

root = tk.Tk()
root.title("Project-KD")
# root.geometry("800x700")

main_notebook = ttk.Notebook(root)
main_notebook.pack(pady=10, padx=10, fill="both", expand=True)

setup_frame = ttk.Frame(main_notebook, padding="10")
results_frame = ttk.Frame(main_notebook, padding="10")

main_notebook.add(setup_frame, text='Настройка')
main_notebook.add(results_frame, text='Результаты')


# ------------------------------------------ Фрейм для выбора типа -------------------------------------

type_selection_frame = ttk.LabelFrame(setup_frame, text="Выберите тип распределений", padding="10")
type_selection_frame.pack(pady=10, padx=10, fill="x")


selected_distribution_type = tk.StringVar()
selected_distribution_type.set("discrete")


discrete_radio = ttk.Radiobutton(
    type_selection_frame,
    text="Дискретные",
    variable=selected_distribution_type,
    value="discrete",
    command=lambda: update_distribution_comboboxes(selected_distribution_type.get())
)
discrete_radio.pack(side="left", padx=10)


continuous_radio = ttk.Radiobutton(
    type_selection_frame,
    text="Непрерывные",
    variable=selected_distribution_type,
    value="continuous",
    command=lambda: update_distribution_comboboxes(selected_distribution_type.get())
)
continuous_radio.pack(side="left", padx=10)


# ------------------------------------------ Фрейм для выбора распределений -------------------------------------

distribution_selection_frame = ttk.LabelFrame(setup_frame, text="Выберите два распределения", padding="10")
distribution_selection_frame.pack(pady=5, padx=10, fill="x")


ttk.Label(distribution_selection_frame, text="Распределение 1:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
dist1_combobox = ttk.Combobox(distribution_selection_frame, state="readonly")
dist1_combobox.grid(row=0, column=1, sticky="ew", padx=5, pady=5)


ttk.Label(distribution_selection_frame, text="Распределение 2:").grid(row=1, column=0, sticky="w", padx=5, pady=5)
dist2_combobox = ttk.Combobox(distribution_selection_frame, state="readonly")
dist2_combobox.grid(row=1, column=1, sticky="ew", padx=5, pady=5)


distribution_selection_frame.columnconfigure(1, weight=1)

dist1_combobox.bind("<<ComboboxSelected>>", update_parameter_fields)
dist2_combobox.bind("<<ComboboxSelected>>", update_parameter_fields)

# ------------------------------------------ Фрейм для параметров -------------------------------------

params1_frame = ttk.LabelFrame(setup_frame, text="Параметры Распределения 1", padding="10")
params1_frame.pack(pady=5, padx=10, fill="x")

params2_frame = ttk.LabelFrame(setup_frame, text="Параметры Распределения 2", padding="10")
params2_frame.pack(pady=5, padx=10, fill="x")

# ------------------------------------------ Кнопка постройки графиков -------------------------------------

plots_button = ttk.Button(setup_frame, text="Построить графики", command=plot_distributions)
plots_button.pack(pady=10)

# ------------------------------------------ Фрейм для графиков -------------------------------------

plots_frame = ttk.LabelFrame(results_frame, text="Распределения", padding="10")
plots_frame.pack(pady=5, padx=10, fill="x")

plot1_frame = ttk.LabelFrame(plots_frame, text="График Распределения 1", padding="10")
plot1_frame.grid(row=1, column=0, sticky="nsew", padx=10, pady=5)

plot2_frame = ttk.LabelFrame(plots_frame, text="График Распределения 2", padding="10")
plot2_frame.grid(row=1, column=1, sticky="nsew", padx=10, pady=5)

plots_frame.columnconfigure(0, weight=1)
plots_frame.columnconfigure(1, weight=1)

plots_frame.rowconfigure(1, weight=1)

dist1_description_label = ttk.Label(plots_frame, text="Распределение 1: Не выбрано")
dist1_description_label.grid(row=0, column=0, sticky="nw", padx=10, pady=5)

dist2_description_label = ttk.Label(plots_frame, text="Распределение 2: Не выбрано")
dist2_description_label.grid(row=0, column=1, sticky="nw", padx=10, pady=5)

# --------------------------------- Фрейм для размера выборки и кнопки запуска симуляции -------------------------------

simulation_frame = ttk.LabelFrame(results_frame, text="Настройка симуляции", padding="10")
simulation_frame.pack(pady=5, padx=10, fill="x")

ttk.Label(simulation_frame, text=f"Размер выборки: ").grid(row=0, column=0, sticky="w", pady=5)
sample_size_entry = ttk.Entry(simulation_frame)
sample_size_entry.insert(0, "100000")
sample_size_entry.grid(row=0, column=1, sticky="e", padx=5, pady=2)

simulation_button = ttk.Button(simulation_frame, text="Начать симуляцию", command=create_sum_result_hist)
simulation_button.grid(row=0, column=2, sticky="e", padx=5, pady=2)

simulation_frame.columnconfigure(2, weight=1)
simulation_frame.rowconfigure(0, weight=1)

# --------------------------------- Фрейм для гистограммы сумм -------------------------------

plot_sum_frame = ttk.LabelFrame(results_frame, text="Гистограмма сумм", padding="10")
plot_sum_frame.pack(pady=5, padx=10, fill="x")

# ==================================================== ЗАПУСК =========================================================

update_distribution_comboboxes(selected_distribution_type.get())
update_parameter_fields()

root.mainloop()
```
are there any bugs?