К основному контенту

Пчела на соте

 

После того, как сформирована модель пчелы и сота (рамки), следующим шагом представляется модель улочки. Что такое улочка - это пространство между двумя сотами (или сотом и стенкой улья, колоды), в котором на одном и другом соте (стороне улочки) могут находиться пчелы, а между ними - воздушный зазор, ширина которого зависит от ширины улочки. Каждая пчела вырабатывает тепло (модель термогенеза у нас уже есть) и обменивается им с ячейками сот, с другими пчелами и с воздухом в улочке. Но математической модели этого обмена - пока нет, а без него, очевидно, модели улочки не построить.
Поэтому модель улочки начинаем разрабатывать с модели пчелы на соте.

Поскольку пчела будет нагревать сот, а статус ячеек под пчелой может быть любым, в том числе там может находиться расплод или пчела, постольку нужна тепловая модель сота (она у нас также есть), и модель пчелы на соте явится по сути ее развитием и продолжением.

Напомню, в качестве геометрической модели пчелы были приняты три соосных цилиндра диаметром 4 мм и высотой, соответственно, 1.5 (голова), 4.5 (торакс) и 6 мм (брюшко) с небольшими зазорами, зависящими от температуры окружающего воздуха. При расположении на соте пчела может характеризоваться своим положением и ориентацией. Ограничим множество положений пчелы множеством совпадений центра торакса с центром ячейки, а возможных ориентаций оставим 6 - по направлениям, перпендикулярным боковым стенкам ячеек. Полагаю, что для тепловой модели этого множества будет достаточно. Таким образом, если торакс находится в центре ячейки, то площадь его проекции (18 мм²) будет перекрывать 75% площади входа в ячейку (24 мм²), над соседней ячейкой голова будет перекрывать 25%, а над другой соседней ячейкой брюшко будет перекрывать практически 100% площади входа. (Пишу "практически", т.к. диаметр описанной окружности ячейки немного больше диаметра пчелы, но брюшко пчелы "выглядывает" за пределы ячейки и перекрывает небольшую часть четвертой ячейки, поэтому интегральный эффект экранирования будет примерно, как при перекрытии одной ячейки на 100%, т.к. площадь проекции брюшка равна площади входа в ячейку - 24 мм²). Эти проценты будут учтены при расчете теплообмена.

Рассмотрим небольшой кластер (7×7 ячеек) с пчелой в центральной ячейке (Y, 0, 0), как это уже делали ранее, но теперь разместим на стороне Y еще одну пчелу в строке №1 на соте. Адрес ее торакса будет (Y, 1, 0), головы - (Y, 1, -1), брюшка - (Y, 1, 1). Нижняя (вентральная) поверхность пчелы обменивается теплом с ячейками, а верхняя (дорсальная, спина) - с улочкой. Тогда модель теплообмена торакса можно представить так:

C_th · dT_th/dt = P_met(T_sense)                        ← генерация
                − (T_th − T_hd)/R_th_hd                 ← теплопроводность → голова
                − (T_th − T_ab)/R_th_ab                 ← теплопроводность → брюшко
                − (T_th − T_amb)/R_th_amb_surf          ← дорсальная конвекция
                − (T_th − T_air)/R_th_cell_surf         ← вентральная конвекция
                + P_rad_th_dorsal(T_th, T_amb)          ← излучение в улочку
                + P_rad_th_ventral(T_th, T_wall)        ← излучение в ячейки

Модели для головы и брюшка устроены аналогично, но без P_met и с добавлением испарения q_evap = ke0·T_th.
Мощность метаболизма зависит от температуры, которую пчела измеряет усиками, т.е. от температуры в районе головы. Для пчелы в ячейке это была температура воздуха в этой ячейке, а для пчелы на соте, очевидно, это будет температура в ячейке под головой, в нашем случае (Y, 1, -1).
Полный текст Python-скрипта - в спойлере внизу заметки. Если его запустить, то получим такие результаты:

Как видим на верхнем графике, картина температур в центральной ячейке с пчелой практически не изменилась - там она определяется прежде всего именно пчелой в ячейке, а пчела на соте слабо на нее влияет. На нижнем графике показано, как меняются температуры пчелы на соте. Температуры пчелы в ячейке и пчелы на соте существенно различаются: пчела в ячейке холоднее, т.к. больше отдает тепла сотам, стенки ячейки работают как радиатор охлаждения.
При этом, т.к. температуры в ячейке с пчелой и в ячейке под головой пчелы на соте разные, то и мощность термогенеза у них разная:

у пчелы в ячейке она составляет 19.28 мВт, а на соте - 19.86 мВт.
На следующем рисунке показано, как эта мощность распределяется между нагревом сота и нагревом воздуха в улочке:

Видно, что в улочку отдается примерно вдвое больше, чем соту - 11.36 мВт против 6.71 мВт.
Воздух в ячейках под пчелой на соте заметно прогревается, это видно на тепловой карте кластера:


если в соседних с центральной ячейкой без пчелы на соте температура составляет около 12.45°С, то тоже в соседней с центральной ячейкой, но находящейся под тораксом пчелы на соте она достигает 12.71°С.

Вывод: модель пчелы на соте есть, можем двигаться дальше - строить модель улочки.

🐝 Python-скрипт

  """
=============================================================================
Программа к заметке "Пчела на соте: теплообмен на границе кластера"
=============================================================================
Модель теплообмена пчелы, сидящей НА ПОВЕРХНОСТИ сота (в составе кластера).
Развитие модели пчелы В ЯЧЕЙКЕ (fast0.py).

Идея: пчела на поверхности — граница между тёплым нутром кластера и холодной
средой. Её тело (3 соосных цилиндра: голова, торакс, брюшко) лежит над тремя
соседними ячейками и обменивается теплом с РАЗНОЙ средой сверху и снизу:

         улочка (T_ambient)
   ═══════════════════════════════════════════
      ┌────┐  ┌──────────┐  ┌────────────┐
      │Голова│  │  Торакс  │  │   Брюшко   │   ← пчела (вид сверху)
      │1.5×4│  │  4.5×4   │  │   6×4 мм   │
      └──┬─┘  └────┬─────┘  └─────┬──────┘
   ┌─────▼───┐ ┌────▼─────┐ ┌─────▼──────┐
   │(Y,1,−1) │ │ (Y,1,0)  │ │ (Y,1,+1)   │   ← ячейки сота
   └─────────┘ └──────────┘ └────────────┘

Ключевые особенности модели:
  * Дорсально-вентральная асимметрия теплообмена:
      - ДОРСАЛЬНАЯ (спинная, верхняя) сторона → улочка (T_ambient);
      - ВЕНТРАЛЬНАЯ (брюшная, нижняя) сторона → ячейки сота (T_air, T_wall).
  * T_sense (температура, измеряемая усиками) = T воздуха в ячейке под ГОЛОВОЙ,
    т.е. в (Y,1,−1). Усики направлены вперёд-вниз, поэтому «термометр» пчелы
    отделён от источника тепла (торакса) — разнесённая обратная связь.
  * Доли перекрытия входа ячейки выводятся из геометрии проекции цилиндра:
      cover = (d_bee · h_segment) / A_entrance
      голова ≈ 25%, торакс ≈ 75%, брюшко ≈ 100%.
  * Вентральный теплообмен идёт ЧЕРЕЗ ПЕРЕКРЫТУЮ ЧАСТЬ ВХОДА (площадь
    cover·A_entrance), а НЕ через кольцевой зазор вокруг тела.

Баланс мощности поверхностной пчелы (первый закон термодинамики):
    P_met = Q_улочка + Q_соты + Q_испарение + dU/dt
где dU/dt — скорость накопления тепла телом (в стационаре → 0).

Архитектура: содержимое ячейки (States) и перекрытие входа (Cover) НЕЗАВИСИМЫ.
Ячейка под пчелой может содержать что угодно (пустая/расплод/мёд/перга/пчела) —
поверхностная пчела лишь добавляет перекрытие входа поверх содержимого.
=============================================================================
"""
import time
import math
import numpy as np
from numba import njit
from scipy.optimize import fsolve
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches

# ==========================================================
# 1. ФИЗИЧЕСКИЕ ПАРАМЕТРЫ И ГЕОМЕТРИЯ
# ==========================================================
# --- Сетка и условия ---
# cols, rows = 11, 11          # Размер сетки (для расчёта кластера: 76, 57)
cols, rows = 7, 7
variant = 2                # 1: Тонкий сот, 2: Коричневый сот, 3: Толстый сот
T_ambient = 12.0           # Температура окружающего воздуха / улочки (°C)

# --- Пчела: три соосных цилиндра ---
d_bee = 0.004              # Диаметр тела (м)
s_r = math.pi * (d_bee / 2) ** 2          # Площадь поперечного сечения (м²)
h_head, h_thorax, h_ab = 0.0015, 0.0045, 0.006   # Длины сегментов (м)
h_total = h_head + h_thorax + h_ab
A_hd = math.pi * d_bee * h_head + s_r     # Площадь поверхности головы
A_th = math.pi * d_bee * h_thorax         # Площадь поверхности торакса
A_ab = math.pi * d_bee * h_ab + s_r       # Площадь поверхности брюшка
V_bee = h_total * s_r                     # Объём тела (м³)

# --- Теплофизика пчелы ---
ke0 = 3e-5               # Коэффициент испарения (Вт/°C)
Pmin = 0.2e-3            # Минимальная мощность термогенеза (Вт)
k_hd, k_ab = 0.065, 0.042   # Теплопроводность перешейков (Вт/м·К)
m_head, m_thorax, m_ab = 10.25e-6, 32.5e-6, 57e-6   # Массы сегментов (кг)
c = 3500                  # Удельная теплоёмкость тела (Дж/кг·К)
C_hd, C_th, C_ab = m_head * c, m_thorax * c, m_ab * c   # Теплоёмкости (Дж/К)

# --- Воздух ---
rho_air, k_air, c_air = 1.205, 0.025, 1005

# --- Воск и ячейки (зависят от варианта сота) ---
L_w = [0.0001, 0.00014, 0.0002]      # Толщина стенки (м)
L_b = [0.00013, 0.00055, 0.0012]     # Толщина дна (м)
c_w = [3300., 3000., 2700.]          # Теплоёмкость воска (Дж/кг·К)
lam_w = [0.235, 0.1608, 0.055]       # Теплопроводность воска (Вт/м·К)
eps_w = [0.22, 0.5, 0.9]             # Степень черноты воска
L_wall = L_w[variant - 1]
L_bottom = L_b[variant - 1]
c_wax = c_w[variant - 1]
lam_wax = lam_w[variant - 1]
rho_wax = 960                         # Плотность воска (кг/м³)

# --- Геометрия гексагональной ячейки ---
thread = 5.4e-3                       # Шаг ячеек (м)
d_cell = thread - L_wall              # Диаметр ячейки (м)
h_cell = 12e-3                        # Глубина ячейки (м)
dh = 1e-3
a = d_cell / 2. / np.cos(np.radians(30))   # Описанный радиус гексагона (м)
A_lateral = a * (h_cell - dh / 2.)    # Площадь боковой грани (м²)
d1, d2 = d_cell, np.sqrt(a ** 2 + dh ** 2)
A_bottom = d1 * d2 / 2.               # Площадь дна (м²)
C_wax_lateral = rho_wax * A_lateral * L_wall * c_wax
C_wax_bottom = rho_wax * A_bottom * L_bottom * c_wax
A_wall_total = 6 * A_lateral + 3 * A_bottom
area_hex = (3 * math.sqrt(3) / 2) * a ** 2
A_entrance = area_hex                 # Площадь входа ячейки (м²)
V_cell = area_hex * h_total
C_air_cell = rho_air * V_cell * c_air              # Теплоёмкость воздуха (пустая ячейка)
C_air_bee = rho_air * (V_cell - V_bee) * c_air     # Теплоёмкость воздуха (ячейка с пчелой)

# --- Излучение и тепловые сопротивления ---
sigma = 5.67e-8                       # Постоянная Стефана-Больцмана
t0 = -273.15                          # Перевод °C → К
eps = 0.22                            # Степень черноты тела пчелы
eps_wall = eps_w[variant - 1]
se = sigma * eps_wall
se_th, se_hd, se_ab_ = se * A_th, se * (A_hd - s_r), se * (A_ab - s_r)

# Коэффициент конвекции в зазоре пчела-стенка (для пчелы В ЯЧЕЙКЕ)
h_in = k_air / ((d_cell - d_bee) / 2)
# Коэффициент конвекции на входе ячейки (обмен с улочкой)
h_entrance = 11.0

# Сопротивления для пчелы В ЯЧЕЙКЕ (К/Вт)
R_th_ai = 1 / (h_in * A_th)
R_hd_ai = 1 / (h_in * A_hd)
R_ab_ai = 1 / (h_in * (A_ab - s_r))
R_ab_ = 1 / (h_entrance * s_r)        # Брюшко → улочка (через торец)
hA_ = h_entrance * (A_entrance - s_r)
hA = h_entrance * A_entrance
R_head_air_open = 1 / (h_entrance * A_hd)
R_thorax_air_open = 1 / (h_entrance * A_th)
R_ab_air_open = 1 / (h_entrance * A_ab)

# Режим "печки" (0.0 — обычный; 45e-3 — нагрев центральной пчелы)
P_warm_up = 0.0

# ==========================================================
# 1b. ПЧЕЛА НА ПОВЕРХНОСТИ: АСИММЕТРИЯ ТЕПЛООБМЕНА
# ==========================================================
# Тело пчелы на поверхности мысленно делится по оси на две половины:
#   ДОРСАЛЬНАЯ (верхняя) — открыта в улочку;
#   ВЕНТРАЛЬНАЯ (нижняя) — обращена ко входу ячейки.
# Сверху и снизу среда разная, поэтому и теплообмен разный.

# --- ДОРСАЛЬНАЯ сторона (вверх, в улочку) ---
# Свободная конвекция над горизонтальной поверхностью (Вт/м²·К)
h_amb_surface = 8.0
# Дорсальные площади = верхняя половина боковой поверхности + торец(ы)
A_dorsal_th = 0.5 * A_th + s_r
A_dorsal_hd = 0.5 * (A_hd - s_r) + s_r
A_dorsal_ab = 0.5 * (A_ab - s_r) + s_r
# Сопротивления дорсальной конвекции (К/Вт)
R_th_amb_surf = 1.0 / (h_amb_surface * A_dorsal_th)
R_hd_amb_surf = 1.0 / (h_amb_surface * A_dorsal_hd)
R_ab_amb_surf = 1.0 / (h_amb_surface * A_dorsal_ab)

# --- Доли перекрытия входа ячейки (из геометрии проекции цилиндра) ---
# Проекция сегмента на вход = d_bee · h_segment; делим на площадь входа.
cover_head = min((d_bee * h_head) / A_entrance, 1.0)      # ≈ 0.25
cover_thorax = min((d_bee * h_thorax) / A_entrance, 1.0)  # ≈ 0.75
cover_ab = min((d_bee * h_ab) / A_entrance, 1.0)          # ≈ 1.00
print(f"Доли перекрытия входа: голова={cover_head*100:.1f}%, "
      f"торакс={cover_thorax*100:.1f}%, брюшко={cover_ab*100:.1f}%")

# --- ВЕНТРАЛЬНАЯ сторона (вниз, в ячейку) ---
# Пчела сидит сверху как «крышка». Тепло идёт ЧЕРЕЗ ПЕРЕКРЫТУЮ ЧАСТЬ ВХОДА
# (площадь cover·A_entrance), а не через кольцевой зазор вокруг тела.
# Площадь вентрального теплообмена = проекция сегмента:
A_cov_hd = cover_head * A_entrance
A_cov_th = cover_thorax * A_entrance
A_cov_ab = cover_ab * A_entrance
# Вентральная конвекция: тот же коэффициент h_entrance, что и для открытой
# части входа (вход — два параллельных пути: открытый и закрытый пчелой).
G_cell_hd = h_entrance * A_cov_hd     # проводимость (Вт/К)
G_cell_th = h_entrance * A_cov_th
G_cell_ab = h_entrance * A_cov_ab
# Вентральное излучение: тело → стенки ячейки, через перекрытую площадь
se_cell_hd = sigma * eps_wall * A_cov_hd
se_cell_th = sigma * eps_wall * A_cov_th
se_cell_ab = sigma * eps_wall * A_cov_ab

# ==========================================================
# 2. ГЕНЕРАЦИЯ ТОПОЛОГИИ И СЕТКИ
# ==========================================================
# Типы СОДЕРЖИМОГО ячейки (не зависят от перекрытия входом!)
CELL_EMPTY = 0
CELL_BEE = 5
# Индексы в массиве состояний [13 элементов]:
#   [0]=T_hd, [1]=T_th, [2]=T_ab, [3]=T_air, [4..12]=T_wall (6 бок. + 3 дна)
I_HD, I_TH, I_AB, I_AI = 0, 1, 2, 3

# Массивы сопротивлений/теплоёмкостей для Numba
R_ai_wall_arr = np.array([L_wall / (k_air * A) for A in [A_lateral] * 6 + [A_bottom] * 3], dtype=np.float64)
R_wall_self = np.array([L_wall / (lam_wax * A_lateral)] * 6 + [L_bottom / (lam_wax * A_bottom)] * 3, dtype=np.float64)
C_wall_base = np.array([C_wax_lateral] * 6 + [C_wax_bottom] * 3, dtype=np.float64)

# --- Адреса ячеек (две стороны сота: Y и ^) ---
cells = []
for side in ["Y", "^"]:
    for col in range(-int(cols / 2), int(cols / 2) + 1):
        for row in range(-int(rows / 2), int(rows / 2) + 1):
            cells.append((0, side, col, row))
cell_to_index = {cell: i for i, cell in enumerate(cells)}
N_CELLS = len(cells)

# Смещения для поиска соседей в гексагональной сетке
EVEN_ROW_DELTAS = [(-1, 0), (-1, -1), (0, -1), (1, 0), (0, 1), (-1, 1)]
ODD_ROW_DELTAS = [(1, 0), (1, -1), (0, -1), (-1, 0), (0, 1), (1, 1)]

def get_neighbor_for_face(side, col, row, face_id):
    """Адрес соседа для грани face_id (0-5: боковые, 6-8: дно)."""
    if face_id < 6:
        deltas = ODD_ROW_DELTAS if (row % 2 == 1) else EVEN_ROW_DELTAS
        if side == "^":
            deltas = EVEN_ROW_DELTAS if (row % 2 == 1) else ODD_ROW_DELTAS
        dc, dr = deltas[face_id]
        return (0, side, int(col + dc), int(row + dr))
    else:
        k = face_id - 6
        if side == "Y":
            bottom_list = [(col, row - 1), (col, row), (col - 1, row)] if row % 2 == 0 else \
                          [(col, row - 1), (col, row), (col + 1, row)]
            n_side = "^"
        else:
            bottom_list = [(col, row), (col, row + 1), (col + 1, row)] if row % 2 == 0 else \
                          [(col, row), (col, row + 1), (col - 1, row)]
            n_side = "Y"
        n_col, n_row = bottom_list[k]
        return (0, n_side, n_col, n_row)

# --- Матрицы топологии ---
Topology = np.full((N_CELLS, 9), -1, dtype=np.int32)
Neighbor_Faces = np.full((N_CELLS, 9), -1, dtype=np.int32)
all_cells_set = set(cells)
for i, cell in enumerate(cells):
    _, side, col, row = cell
    for face_id in range(9):
        n_addr = get_neighbor_for_face(side, col, row, face_id)
        if n_addr in all_cells_set:
            Topology[i, face_id] = cell_to_index[n_addr]
            _, n_side, n_col, n_row = n_addr
            for rev_f in range(9):
                if get_neighbor_for_face(n_side, n_col, n_row, rev_f) == (0, side, col, row):
                    Neighbor_Faces[i, face_id] = rev_f
                    break

# --- Содержимое ячеек (States). Одна пчела в ячейке (центральная). ---
States = np.full(N_CELLS, CELL_EMPTY, dtype=np.int32)
bee_cells = [(0, 'Y', 0, 0)]
# bee_cells = [(0, 'Y', 0, 0), (0, 'Y', 0, 1), (0, 'Y', 0, 2), (0, 'Y', 2, -1)]
bee_idx = [cell_to_index[cell] for cell in bee_cells]
for idx in bee_idx:
    States[idx] = CELL_BEE
# print(f">>> НАША: cols={cols}, rows={rows}, пчёл в ячейках={len(bee_cells)}")
# --- Пчела на поверхности: какие ячейки перекрывает ---
# Торакс над (Y,1,0), голова над (Y,1,−1), брюшко над (Y,1,+1)
surf_bee_thorax_cell = (0, 'Y', 0, 1)
surf_bee_head_cell = (0, 'Y', -1, 1)
surf_bee_ab_cell = (0, 'Y', 1, 1)
surf_bee_thorax_idx = cell_to_index[surf_bee_thorax_cell]
surf_bee_head_idx = cell_to_index[surf_bee_head_cell]
surf_bee_ab_idx = cell_to_index[surf_bee_ab_cell]

# --- Перекрытие входа (Cover) — НЕЗАВИСИМО от содержимого ячейки ---
# В будущем ячейка может содержать расплод/мёд/пергу/пчелу — Cover просто
# добавляет перекрытие входа поверх любого содержимого.
Cover = np.zeros(N_CELLS, dtype=np.float64)
Cover[surf_bee_head_idx] = cover_head
Cover[surf_bee_thorax_idx] = cover_thorax
Cover[surf_bee_ab_idx] = cover_ab
print(f"Ячейки под поверхностной пчелой (содержимое не меняется, добавлено перекрытие):")
print(f"  Голова:  {surf_bee_head_cell}   cover={Cover[surf_bee_head_idx]:.2f}")
print(f"  Торакс:  {surf_bee_thorax_cell}    cover={Cover[surf_bee_thorax_idx]:.2f}")
print(f"  Брюшко:  {surf_bee_ab_cell}    cover={Cover[surf_bee_ab_idx]:.2f}")
# Cover = np.zeros(N_CELLS, dtype=np.float64)
# ==========================================================
# 3. РАСЧЁТ НАЧАЛЬНЫХ ТЕМПЕРАТУР
# ==========================================================
def metabolic_power(T):
    """Мощность термогенеза пчелы (Вт) в зависимости от T_sense."""
    return max((29.133 - 0.739 * T) * 1e-3, Pmin)

def calculate_initial_temperatures(T_air_val):
    """Стационарные температуры пчелы В ЯЧЕЙКЕ при заданной T воздуха."""
    l_val = (1.3493 * np.exp(-0.058 * T_air_val)) * 1e-3   # длина перешейка
    R_th_hd_val = l_val / (k_hd * s_r)
    R_th_ab_val = l_val / (k_ab * s_r)
    def equations(vars_val):
        Thd_v, Tth_v, Tab_v = vars_val
        q_evap = ke0 * Tth_v
        P_rest = metabolic_power(T_air_val)
        p_s_hd = -eps * sigma * ((Thd_v - t0) ** 4 - (T_air_val - t0) ** 4) * A_hd
        p_s_th = -eps * sigma * ((Tth_v - t0) ** 4 - (T_air_val - t0) ** 4) * A_th
        p_s_ab = -eps * sigma * ((Tab_v - t0) ** 4 - (T_air_val - t0) ** 4) * A_ab
        eq1 = p_s_hd + (Tth_v - Thd_v) / R_th_hd_val - (Thd_v - T_air_val) / R_head_air_open - q_evap
        eq2 = P_rest + p_s_th - (Tth_v - Thd_v) / R_th_hd_val - (Tth_v - Tab_v) / R_th_ab_val - \
              (Tth_v - T_air_val) / R_thorax_air_open
        eq3 = p_s_ab + (Tth_v - Tab_v) / R_th_ab_val - (Tab_v - T_air_val) / R_ab_air_open - q_evap
        return [eq1, eq2, eq3]
    return fsolve(equations, [T_air_val + 10, T_air_val + 15, T_air_val + 10], xtol=1e-8)

Thd_init, Tth_init, Tab_init = calculate_initial_temperatures(T_ambient)
T_bee_avg_init = (m_head * Thd_init + m_thorax * Tth_init + m_ab * Tab_init) / (m_head + m_thorax + m_ab)

# Начальные температуры поверхностной пчелы = те же, что у пчелы в ячейке:
# при T_sense = T_ambient = 12°C метаболизм одинаков. Разница в теплопотерях
# (дорсальная конвекция в улочку) проявится уже в динамике.
Thd_surf_init, Tth_surf_init, Tab_surf_init = Thd_init, Tth_init, Tab_init
print(f"Начальные T (обе пчелы, T_sense={T_ambient}°C): "
      f"Hd={Thd_init:.2f}, Th={Tth_init:.2f}, Ab={Tab_init:.2f}")

# ==========================================================
# 4. ПОДГОТОВКА МАТРИЦ ДЛЯ NUMBA
# ==========================================================
C_grid = np.zeros((N_CELLS, 13), dtype=np.float64)
for i in range(N_CELLS):
    C_grid[i, 4:13] = C_wall_base
    if States[i] == CELL_BEE:
        C_grid[i, I_HD] = C_hd
        C_grid[i, I_TH] = C_th
        C_grid[i, I_AB] = C_ab
        C_grid[i, I_AI] = C_air_bee
    else:
        C_grid[i, I_AI] = C_air_cell

# Двойной буфер температур (обмен ссылок, не копирование)
T_grid_A = np.full((N_CELLS, 13), T_ambient, dtype=np.float64)
T_grid_B = np.full((N_CELLS, 13), T_ambient, dtype=np.float64)
for t_idx_b in bee_idx:
    T_grid_A[t_idx_b, I_HD] = Thd_init
    T_grid_A[t_idx_b, I_TH] = Tth_init
    T_grid_A[t_idx_b, I_AB] = Tab_init
    T_grid_B[t_idx_b, I_HD] = Thd_init
    T_grid_B[t_idx_b, I_TH] = Tth_init
    T_grid_B[t_idx_b, I_AB] = Tab_init

# Состояние поверхностной пчелы: [T_hd, T_th, T_ab]
T_surf_bee = np.array([Thd_surf_init, Tth_surf_init, Tab_surf_init], dtype=np.float64)

# ==========================================================
# 5. ЯДРО СИМУЛЯЦИИ (NUMBA JIT)
# ==========================================================
@njit(fastmath=True, cache=True)
def do_cluster_step_surf(T_cur, T_next, Topology, Neighbor_Faces, States, Cover, C_grid,
                         dt, T_ambient_val, heater_idx,
                         surf_thorax_idx, surf_head_idx, surf_ab_idx,
                         T_surf_hd, T_surf_th, T_surf_ab):
    """
    Один шаг тепловой сети кластера.

    Вход ячейки может быть перекрыт поверхностной пчелой (Cover[i] > 0)
    НЕЗАВИСИМО от содержимого ячейки (States[i]).

    Модель входа для перекрытой ячейки (содержимое — не пчела):
      открытая часть  (1−cover)·A_entrance : конвекция воздух ↔ T_ambient (h_entrance)
      закрытая часть  cover·A_entrance     : конвекция воздух ↔ T_тела     (h_entrance)

    Возвращает: (Q_entrance_Y, Q_entrance_X) — теплопотери через входы (Вт).
    """
    Q_entrance_Y = 0.0
    Q_entrance_X = 0.0
    half_cells = len(T_cur) // 2

    for i in range(len(T_cur)):
        state = States[i]
        cover = Cover[i]
        Thd = T_cur[i, I_HD]
        Tth = T_cur[i, I_TH]
        Tab = T_cur[i, I_AB]

        # --- Температура воздуха в ячейке (Tai) ---
        if state == CELL_BEE:
            # Пчела внутри ячейки: воздух греется от частей тела,
            # охлаждается через торец брюшка (R_ab_)
            num = Tth / R_th_ai + Thd / R_hd_ai + Tab / R_ab_ai + T_ambient_val / R_ab_
            den = 1.0 / R_th_ai + 1.0 / R_hd_ai + 1.0 / R_ab_ai + 1.0 / R_ab_
        elif cover > 0.0:
            # Вход частично перекрыт поверхностной пчелой
            if i == surf_thorax_idx:
                T_part = T_surf_th
            elif i == surf_head_idx:
                T_part = T_surf_hd
            else:
                T_part = T_surf_ab
            open_area = (1.0 - cover) * A_entrance
            covered_area = cover * A_entrance
            # Два параллельных пути через вход
            num = T_ambient_val * h_entrance * open_area + T_part * h_entrance * covered_area
            den = h_entrance * A_entrance
        else:
            # Пустая ячейка, вход полностью открыт
            num = T_ambient_val * hA
            den = hA

        # Вклад стенок (9 граней)
        for f in range(9):
            num += T_cur[i, 4 + f] / R_ai_wall_arr[f]
            den += 1.0 / R_ai_wall_arr[f]
        Tai = num / den
        T_next[i, I_AI] = Tai

        is_side_Y = (i < half_cells)

        # --- Теплопотери через вход ---
        if state == CELL_BEE:
            Q_cell = hA_ * (Tai - T_ambient_val)
            Q_ab_loss = (Tab - T_ambient_val) / R_ab_
            if is_side_Y:
                Q_entrance_Y += (Q_cell + Q_ab_loss)
            else:
                Q_entrance_X += (Q_cell + Q_ab_loss)
        elif cover > 0.0:
            # В улочку теряет только ОТКРЫТАЯ часть входа
            # (закрытая обменивается с пчелой — учтено в Q_cells поверхностной пчелы)
            Q_cell = h_entrance * (1.0 - cover) * A_entrance * (Tai - T_ambient_val)
            if is_side_Y:
                Q_entrance_Y += Q_cell
            else:
                Q_entrance_X += Q_cell
        else:
            Q_cell = hA * (Tai - T_ambient_val)
            if is_side_Y:
                Q_entrance_Y += Q_cell
            else:
                Q_entrance_X += Q_cell

        # --- Температуры стенок (9 граней) ---
        T_wall_avg = 0.0
        for f in range(9):
            T_wall_avg += T_cur[i, 4 + f]
        T_wall_avg /= 9.0
        # Излучение пчела(в ячейке) → стенки
        p_s_th = -se_th * ((Tth - t0) ** 4 - (T_wall_avg - t0) ** 4)
        p_s_hd = -se_hd * ((Thd - t0) ** 4 - (T_wall_avg - t0) ** 4)
        p_s_ab = -se_ab_ * ((Tab - t0) ** 4 - (T_wall_avg - t0) ** 4)
        p_s_total = p_s_th + p_s_hd + p_s_ab

        # +++ Излучение ПОВЕРХНОСТНОЙ пчелы к стенкам покрытой ячейки
        if cover > 0.0 and state != CELL_BEE:
            if i == surf_thorax_idx:
                T_part_rad = T_surf_th
            elif i == surf_head_idx:
                T_part_rad = T_surf_hd
            else:
                T_part_rad = T_surf_ab
            p_surf_to_wall = sigma * eps_wall * (cover * A_entrance) * \
                             ((T_part_rad - t0) ** 4 - (T_wall_avg - t0) ** 4)
        else:
            p_surf_to_wall = 0.0

        # Что греет стенки: пчела внутри (p_s_total) ИЛИ поверхностная пчела (p_surf_to_wall)
        p_rad_to_walls = p_s_total if state == CELL_BEE else p_surf_to_wall

        for f in range(9):
            Q_from_air = (Tai - T_cur[i, 4 + f]) / R_ai_wall_arr[f]
            neighbor_idx = Topology[i, f]
            if neighbor_idx != -1:
                n_face = Neighbor_Faces[i, f]
                Q_from_neighbor = (T_cur[neighbor_idx, 4 + n_face] - T_cur[i, 4 + f]) / R_wall_self[f]
            else:
                Q_from_neighbor = (T_ambient_val - T_cur[i, 4 + f]) / R_wall_self[f]
            p_s_to_wall_k = ((A_lateral if f < 6 else A_bottom) / A_wall_total) * p_rad_to_walls
            T_next[i, 4 + f] = T_cur[i, 4 + f] + (dt / C_grid[i, 4 + f]) * (
                    Q_from_air + Q_from_neighbor + p_s_to_wall_k)

        # --- Температуры пчелы В ЯЧЕЙКЕ ---
        if state == CELL_BEE:
            # Метаболизм зависит от T воздуха в ячейке (= T_sense для ячеечной пчелы)
            P_rest = (29.133 - 0.739 * Tai) * 1e-3
            if P_rest < Pmin:
                P_rest = Pmin
            P_local = P_warm_up if i == heater_idx else 0.0
            l_dyn = (1.3493 * np.exp(-0.058 * Tai)) * 1e-3
            R_th_hd_dyn = l_dyn / (k_hd * s_r)
            R_th_ab_dyn = l_dyn / (k_ab * s_r)
            T_next[i, I_TH] = Tth + (dt / C_grid[i, I_TH]) * (
                P_rest + P_local + p_s_th
                - (Tth - Thd) / R_th_hd_dyn - (Tth - Tab) / R_th_ab_dyn - (Tth - Tai) / R_th_ai)
            T_next[i, I_HD] = Thd + (dt / C_grid[i, I_HD]) * (
                (Tth - Thd) / R_th_hd_dyn - (Thd - Tai) / R_hd_ai - ke0 * Tth)
            T_next[i, I_AB] = Tab + (dt / C_grid[i, I_AB]) * (
                p_s_ab + (Tth - Tab) / R_th_ab_dyn - (Tab - Tai) / R_ab_ai
                - (Tab - T_ambient_val) / R_ab_ - ke0 * Tth)

    return Q_entrance_Y, Q_entrance_X


@njit(fastmath=True, cache=True)
def do_surface_bee_step(T_surf, T_cur, dt, T_ambient_val,
                        surf_head_idx, surf_thorax_idx, surf_ab_idx):
    """
    Расчёт температур пчелы НА ПОВЕРХНОСТИ + все тепловые потоки НАПРЯМУЮ.

    Дорсально (в улочку): свободная конвекция (h_amb) + излучение → T_ambient,
                          площадь = дорсальная поверхность тела (A_dorsal).
    Вентрально (в соты):  конвекция (h_entrance) + излучение → T_air/T_wall,
                          площадь = перекрытая часть входа (A_cov = cover·A_entrance).

    Возвращает: (P_met, Q_street, Q_cells, Q_evap, dU_dt)
      P_met    — мощность термогенеза (Вт)
      Q_street — поток в улочку: дорсальная конвекция + излучение (Вт)
      Q_cells  — поток в соты: вентральная конвекция + излучение (Вт)
      Q_evap   — поток на испарение (Вт)
      dU_dt    — скорость накопления тепла телом (Вт)

    Проверка (первый закон термодинамики):
      P_met − Q_street − Q_cells − Q_evap − dU_dt == 0  (с машинной точностью)
    В стационаре dU_dt → 0, тогда P_met = Q_street + Q_cells + Q_evap.
    """
    T_hd = T_surf[0]
    T_th = T_surf[1]
    T_ab = T_surf[2]

    # T_sense — температура, измеряемая усиками (в области головы)
    T_sense = T_cur[surf_head_idx, I_AI]

    # Средние температуры стенок ячеек под частями тела
    T_wall_hd = 0.0
    for f in range(9):
        T_wall_hd += T_cur[surf_head_idx, 4 + f]
    T_wall_hd /= 9.0
    T_wall_th = 0.0
    for f in range(9):
        T_wall_th += T_cur[surf_thorax_idx, 4 + f]
    T_wall_th /= 9.0
    T_wall_ab = 0.0
    for f in range(9):
        T_wall_ab += T_cur[surf_ab_idx, 4 + f]
    T_wall_ab /= 9.0

    # Температуры воздуха в ячейках под частями тела
    T_air_hd = T_cur[surf_head_idx, I_AI]
    T_air_th = T_cur[surf_thorax_idx, I_AI]
    T_air_ab = T_cur[surf_ab_idx, I_AI]

    # Метаболизм (зависит от T_sense)
    P_met = (29.133 - 0.739 * T_sense) * 1e-3
    if P_met < Pmin:
        P_met = Pmin

    # Динамические сопротивления теплопроводности перешейков
    l_dyn = (1.3493 * np.exp(-0.058 * T_sense)) * 1e-3
    R_th_hd_dyn = l_dyn / (k_hd * s_r)
    R_th_ab_dyn = l_dyn / (k_ab * s_r)

    # --- Излучение дорсальное (→ T_ambient, площадь тела A_dorsal) ---
    p_rad_hd_d = -sigma * eps * ((T_hd - t0)**4 - (T_ambient_val - t0)**4) * A_dorsal_hd
    p_rad_th_d = -sigma * eps * ((T_th - t0)**4 - (T_ambient_val - t0)**4) * A_dorsal_th
    p_rad_ab_d = -sigma * eps * ((T_ab - t0)**4 - (T_ambient_val - t0)**4) * A_dorsal_ab
    # --- Излучение вентральное (→ T_wall, площадь перекрытого входа A_cov) ---
    p_rad_hd_v = -se_cell_hd * ((T_hd - t0)**4 - (T_wall_hd - t0)**4)
    p_rad_th_v = -se_cell_th * ((T_th - t0)**4 - (T_wall_th - t0)**4)
    p_rad_ab_v = -se_cell_ab * ((T_ab - t0)**4 - (T_wall_ab - t0)**4)

    # Испарение с одной поверхности (головы или брюшка)
    q_evap = ke0 * T_th

    # ==========================================================
    # ПРЯМЫЕ ТЕПЛОВЫЕ ПОТОКИ (положительные = от пчелы наружу)
    # ==========================================================
    # В УЛОЧКУ (дорсально): конвекция + излучение
    Q_street_conv = ((T_hd - T_ambient_val) / R_hd_amb_surf +
                     (T_th - T_ambient_val) / R_th_amb_surf +
                     (T_ab - T_ambient_val) / R_ab_amb_surf)
    Q_street_rad = -(p_rad_hd_d + p_rad_th_d + p_rad_ab_d)
    Q_street = Q_street_conv + Q_street_rad

    # В СОТЫ (вентрально): конвекция через перекрытый вход + излучение
    Q_cells_conv = (G_cell_hd * (T_hd - T_air_hd) +
                    G_cell_th * (T_th - T_air_th) +
                    G_cell_ab * (T_ab - T_air_ab))
    Q_cells_rad = -(p_rad_hd_v + p_rad_th_v + p_rad_ab_v)
    Q_cells = Q_cells_conv + Q_cells_rad

    # ИСПАРЕНИЕ (с головы и брюшка)
    Q_evap = 2.0 * q_evap

    # ==========================================================
    # УРАВНЕНИЯ БАЛАНСА (для интегрирования температур)
    # ==========================================================
    # Голова: теплопроводность от торакса = дорс.конв + дорс.изл + вентр.конв + вентр.изл + испарение
    dT_hd = ((T_th - T_hd) / R_th_hd_dyn
             - (T_hd - T_ambient_val) / R_hd_amb_surf
             - G_cell_hd * (T_hd - T_air_hd)
             + p_rad_hd_d + p_rad_hd_v - q_evap)
    # Торакс: метаболизм = теплопроводность + дорс.конв + дорс.изл + вентр.конв + вентр.изл
    dT_th = (P_met
             - (T_th - T_hd) / R_th_hd_dyn - (T_th - T_ab) / R_th_ab_dyn
             - (T_th - T_ambient_val) / R_th_amb_surf
             - G_cell_th * (T_th - T_air_th)
             + p_rad_th_d + p_rad_th_v)
    # Брюшко: теплопроводность от торакса = дорс.конв + дорс.изл + вентр.конв + вентр.изл + испарение
    dT_ab = ((T_th - T_ab) / R_th_ab_dyn
             - (T_ab - T_ambient_val) / R_ab_amb_surf
             - G_cell_ab * (T_ab - T_air_ab)
             + p_rad_ab_d + p_rad_ab_v - q_evap)

    # dU/dt — мощность, идущая на изменение внутренней энергии тела
    # (dT_hd, dT_th, dT_ab имеют размерность Вт; C·dT/dt = dT_здесь)
    dU_dt = dT_hd + dT_th + dT_ab

    # Интегрирование (явный Эйлер)
    T_surf[0] = T_hd + (dt / C_hd) * dT_hd
    T_surf[1] = T_th + (dt / C_th) * dT_th
    T_surf[2] = T_ab + (dt / C_ab) * dT_ab

    return P_met, Q_street, Q_cells, Q_evap, dU_dt


# ==========================================================
# 6. ЦИКЛ СИМУЛЯЦИИ
# ==========================================================
dt_sim = 0.1               # Шаг интегрирования (с)
time_total = 600.0          # Длительность симуляции (с)
steps = int(time_total / dt_sim)

# --- Предаллокация логов ---
log_time = []
# Пчела в ячейке:
log_T_hd, log_T_th, log_T_ab = [], [], []
log_T_ai_bee, log_T_bee_avg = [], []
log_Power = []
# Пчела на поверхности:
log_T_hd_surf, log_T_th_surf, log_T_ab_surf = [], [], []
log_T_sense_surf = []
log_Power_surf = []
# Потоки поверхностной пчелы (все — прямые расчёты):
log_Q_street_surf, log_Q_cells_surf, log_Q_evap_surf = [], [], []
log_dU_dt_surf = []          # накопление тепла (в стационаре → 0)
log_balance_residual = []    # невязка баланса (для контроля, ~машинная точность)
# Теплопотери кластера:
log_Q_Y, log_Q_X = [], []

print(f"\nСетка {cols}x{rows}. Запуск...")
t_idx = bee_idx[0]

# Прогрев Numba (первый вызов — компиляция)
do_cluster_step_surf(T_grid_A, T_grid_B, Topology, Neighbor_Faces, States, Cover, C_grid,
                     dt_sim, T_ambient, bee_idx[0],
                     surf_bee_thorax_idx, surf_bee_head_idx, surf_bee_ab_idx,
                     T_surf_bee[0], T_surf_bee[1], T_surf_bee[2])
do_surface_bee_step(T_surf_bee, T_grid_A, dt_sim, T_ambient,
                    surf_bee_head_idx, surf_bee_thorax_idx, surf_bee_ab_idx)

# --- Основной цикл ---
start_time = time.time()
T_cur, T_next = T_grid_A, T_grid_B

for step in range(steps):
    # Шаг 1: тепловая сеть кластера
    Q_Y, Q_X = do_cluster_step_surf(
        T_cur, T_next, Topology, Neighbor_Faces, States, Cover, C_grid,
        dt_sim, T_ambient, bee_idx[0],
        surf_bee_thorax_idx, surf_bee_head_idx, surf_bee_ab_idx,
        T_surf_bee[0], T_surf_bee[1], T_surf_bee[2])
    # Swap буферов
    T_cur, T_next = T_next, T_cur

    # Шаг 2: пчела на поверхности (все потоки напрямую)
    P_surf, Q_street, Q_cells, Q_evap, dU_dt = do_surface_bee_step(
        T_surf_bee, T_cur, dt_sim, T_ambient,
        surf_bee_head_idx, surf_bee_thorax_idx, surf_bee_ab_idx)

    # Шаг 3: дорсальный поток поверхностной пчелы — в теплопотери стороны Y
    Q_Y += Q_street

    # --- Логирование (каждый 10-й шаг) ---
    if step % 10 == 0:
        t = step * dt_sim
        log_time.append(t)
        # Пчела в ячейке
        log_T_hd.append(T_cur[t_idx, I_HD])
        log_T_th.append(T_cur[t_idx, I_TH])
        log_T_ab.append(T_cur[t_idx, I_AB])
        log_T_ai_bee.append(T_cur[t_idx, I_AI])
        T_bee_avg = (m_head * T_cur[t_idx, I_HD] + m_thorax * T_cur[t_idx, I_TH] +
                     m_ab * T_cur[t_idx, I_AB]) / (m_head + m_thorax + m_ab)
        log_T_bee_avg.append(T_bee_avg)
        P_rest = max((29.133 - 0.739 * T_cur[t_idx, I_AI]) * 1e-3, Pmin)
        log_Power.append((P_rest + P_warm_up) * 1000)
        # Пчела на поверхности
        log_T_hd_surf.append(T_surf_bee[0])
        log_T_th_surf.append(T_surf_bee[1])
        log_T_ab_surf.append(T_surf_bee[2])
        log_T_sense_surf.append(T_cur[surf_bee_head_idx, I_AI])
        log_Power_surf.append(P_surf * 1000)
        # Потоки (мВт)
        log_Q_street_surf.append(Q_street * 1000)
        log_Q_cells_surf.append(Q_cells * 1000)
        log_Q_evap_surf.append(Q_evap * 1000)
        log_dU_dt_surf.append(dU_dt * 1000)
        # Невязка баланса (контроль корректности кода)
        residual = (P_surf - Q_street - Q_cells - Q_evap - dU_dt) * 1000
        log_balance_residual.append(residual)
        # Теплопотери кластера
        log_Q_Y.append(Q_Y * 1000)
        log_Q_X.append(Q_X * 1000)

time_calc = time.time() - start_time
print(f"Общее время: {time_calc:.3f} c ({steps / time_calc:.0f} шагов/сек)")

# ==========================================================
# 7. ВИЗУАЛИЗАЦИЯ
# ==========================================================
# --- График 1: Динамика температур ---
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(6, 8), sharex=True)
# Пчела в ячейке
ax1.scatter(log_time, log_T_th, s=2, c='r', label='Торакс', zorder=5)
ax1.plot(log_time, log_T_hd, 'b-', label='Голова', linewidth=1.2)
ax1.plot(log_time, log_T_ab, 'g-', label='Брюшко', linewidth=1.2)
ax1.plot(log_time, log_T_ai_bee, 'c--', label='Воздух в ячейке', linewidth=1.2)
ax1.plot(log_time, log_T_bee_avg, 'k:', label='Средняя T', linewidth=1.5)
ax1.axhline(T_ambient, color='gray', linestyle=':', label=f'T_amb={T_ambient}°C')
ax1.set_ylabel('Температура (°C)')
ax1.set_title(f'Пчела В ЯЧЕЙКЕ (центральная). Вариант {variant}')
ax1.legend(fontsize=8); ax1.grid(True, linestyle=':')
# Пчела на поверхности
ax2.plot(log_time, log_T_th_surf, 'r-', label='Торакс', linewidth=1.5)
ax2.plot(log_time, log_T_hd_surf, 'b-', label='Голова', linewidth=1.2)
ax2.plot(log_time, log_T_ab_surf, 'g-', label='Брюшко', linewidth=1.2)
ax2.plot(log_time, log_T_sense_surf, 'c--', label='T_sense (воздух под головой)', linewidth=1.2)
ax2.axhline(T_ambient, color='gray', linestyle=':', label=f'T_amb={T_ambient}°C')
ax2.set_xlabel('Время (с)'); ax2.set_ylabel('Температура (°C)')
ax2.set_title('Пчела НА ПОВЕРХНОСТИ СОТА')
ax2.legend(fontsize=8); ax2.grid(True, linestyle=':')
plt.tight_layout(); plt.show()

# --- График 2: Мощность термогенеза (в ячейке vs на поверхности) ---
fig, ax = plt.subplots(figsize=(6, 5))
ax.plot(log_time, log_Power, 'orange', linewidth=2.2, label='Пчела В ЯЧЕЙКЕ')
ax.plot(log_time, log_Power_surf, 'darkred', linewidth=2.2, linestyle='--',
        label='Пчела НА ПОВЕРХНОСТИ')
ax.set_xlabel('Время (с)')
ax.set_ylabel('Мощность термогенеза P_met (мВт)')
ax.set_title('Мощность термогенеза:\n пчела в ячейке vs пчела на поверхности')
ax.legend(); ax.grid(True, linestyle=':')
plt.tight_layout(); plt.show()

# --- График 3: Баланс мощности поверхностной пчелы ---
# Все слагаемые первого закона термодинамики:
#   P_met = Q_улочка + Q_соты + Q_испарение + dU/dt
fig, ax = plt.subplots(figsize=(6, 6))
ax.plot(log_time, log_Power_surf, 'k-', linewidth=2.5, label='P_met (генерация)')
ax.plot(log_time, log_Q_street_surf, 'r-', linewidth=1.8, label='Q → улочка (дорсально)')
ax.plot(log_time, log_Q_cells_surf, 'b-', linewidth=1.8, label='Q → соты (вентрально)')
ax.plot(log_time, log_Q_evap_surf, 'g--', linewidth=1.5, label='Q испарение')
ax.plot(log_time, log_dU_dt_surf, color='purple', linewidth=1.5, label='dU/dt (накопление)')
# Сумма потоков — должна совпадать с P_met (проверка баланса)
sum_flows = (np.array(log_Q_street_surf) + np.array(log_Q_cells_surf) +
             np.array(log_Q_evap_surf) + np.array(log_dU_dt_surf))
ax.plot(log_time, sum_flows, color='gray', linestyle=':', linewidth=2.0,
        label='Σ потоков (= P_met)')
ax.axhline(0, color='k', linestyle='-', linewidth=0.5)
ax.set_xlabel('Время (с)')
ax.set_ylabel('Мощность (мВт)')
ax.set_title('Баланс мощности поверхностной пчелы:\n'
             'P_met = Q_улочка + Q_соты + Q_испарение + dU/dt')
ax.legend(fontsize=8); ax.grid(True, linestyle=':')
plt.tight_layout(); plt.show()

# --- График 4: 2D-карта температур сот + пчела (тепловизор) ---
temp_Y_clean, temp_X_clean = {}, {}
for i, cell in enumerate(cells):
    _, side, col, row = cell
    T_air_final = T_cur[i, I_AI]
    if side == 'Y':
        temp_Y_clean[(col, row)] = T_air_final
    else:
        temp_X_clean[(col, row)] = T_air_final

size = 1.0
width = np.sqrt(3) * size
dy_hex = 1.5 * size
def hex_corner(center, i):
    angle_rad = np.pi / 180 * (60 * i - 30)
    return (center[0] + size * np.cos(angle_rad), center[1] + size * np.sin(angle_rad))
def primal_to_pixel(col, row):
    return (width * (col + 0.5 * (row & 1)), -dy_hex * row)
def dual_to_pixel(col, row):
    return (width * (col + 0.5 * (1 - (row & 1))), -dy_hex * row - size / 2)

fig, ax = plt.subplots(figsize=(9, 7))
ax.set_aspect('equal')
# Диапазон шкалы: включаем температуры пчелы
all_temps = list(temp_Y_clean.values()) + list(temp_X_clean.values())
all_temps += [T_surf_bee[0], T_surf_bee[1], T_surf_bee[2]]
vmin, vmax = min(all_temps), max(all_temps)

# Гексагоны стороны Y
for col in range(-int(cols / 2), int(cols / 2) + 1):
    for row in range(-int(rows / 2), int(rows / 2) + 1):
        if (col, row) in temp_Y_clean:
            x, y = primal_to_pixel(col, row)
            corners = [hex_corner((x, y), i) for i in range(6)]
            hex_x, hex_y = zip(*corners)
            color = plt.cm.viridis((temp_Y_clean[(col, row)] - vmin) / max(vmax - vmin, 1e-5))
            ax.fill(hex_x, hex_y, color=color, edgecolor='steelblue', linewidth=1.2, alpha=0.8)
            if (col, row) == (0, 0):
                ax.fill(hex_x, hex_y, facecolor='none', edgecolor='black', linewidth=3)
            ax.text(x, y, f"{temp_Y_clean[(col, row)]:.2f}", ha='center', va='center',
                    fontsize=12, color='b')
# Гексагоны стороны ^
for col in range(-int(cols / 2), int(cols / 2) + 1):
    for row in range(-int(rows / 2), int(rows / 2) + 1):
        if (col, row) in temp_X_clean:
            x, y = dual_to_pixel(col, row)
            corners = [hex_corner((x, y), i) for i in range(6)]
            hex_x, hex_y = zip(*corners)
            ax.plot(hex_x + (hex_x[0],), hex_y + (hex_y[0],), color='crimson', linewidth=1.2)
            ax.text(x, y, f"{temp_X_clean[(col, row)]:.2f}", ha='center', va='center',
                    fontsize=12, color='crimson')

# Пчела на поверхности: три прямоугольника (вид сверху = проекции сегментов)
scale = size / a                       # масштаб: map units на метр
w_bee_map = d_bee * scale              # ширина тела (4 мм)
l_head_map = h_head * scale            # длина головы (1.5 мм)
l_thorax_map = h_thorax * scale        # длина торакса (4.5 мм)
l_ab_map = h_ab * scale                # длина брюшка (6 мм)
# Зазор между сегментами (длина перешейка, зависит от T_sense)
T_sense_final = T_cur[surf_bee_head_idx, I_AI]
l_gap_map = (1.3493 * np.exp(-0.058 * T_sense_final)) * 1e-3 * scale
# Привязка: центр торакса = центр ячейки (Y,1,0)
cx_th, cy_th = primal_to_pixel(surf_bee_thorax_cell[2], surf_bee_thorax_cell[3])
cx_head = cx_th - l_thorax_map / 2 - l_gap_map - l_head_map / 2
cx_ab = cx_th + l_thorax_map / 2 + l_gap_map + l_ab_map / 2
cy_bee = cy_th
# Прямоугольники (полупрозрачные, без подписей T — видны ячейки под пчелой)
for cx_seg, l_seg, T_part in [(cx_head, l_head_map, T_surf_bee[0]),
                               (cx_th, l_thorax_map, T_surf_bee[1]),
                               (cx_ab, l_ab_map, T_surf_bee[2])]:
    norm_T = (T_part - vmin) / max(vmax - vmin, 1e-5)
    rect = mpatches.Rectangle((cx_seg - l_seg / 2, cy_bee - w_bee_map / 2), l_seg, w_bee_map,
                              facecolor=plt.cm.viridis(norm_T), edgecolor='black',
                              linewidth=1.5, alpha=0.45, zorder=10)
    ax.add_patch(rect)
# Осевая линия тела
ax.plot([cx_head - l_head_map/2, cx_ab + l_ab_map/2], [cy_bee, cy_bee],
        'k-', linewidth=0.8, alpha=0.4, zorder=9)
# ax.text(cx_th, cy_bee + w_bee_map / 2 + 0.2, 'Пчела на поверхности',
#         ha='center', va='bottom', fontsize=9, fontweight='bold',
#         bbox=dict(boxstyle='round,pad=0.2', facecolor='yellow', alpha=0.7))
ax.axis('off'); ax.autoscale_view()
sm = plt.cm.ScalarMappable(cmap=plt.cm.viridis, norm=plt.Normalize(vmin=vmin, vmax=vmax))
sm.set_array([])
plt.colorbar(sm, ax=ax, shrink=0.8).set_label('Температура (°C)', fontsize=10)
plt.title(f'Тепловизор: температурное поле сот + пчела на поверхности.\n'
          f'Вариант {variant}, t = {time_total:.0f} с. '
          f'(Синяя — сторона "Y", Красная — сторона "^")')
plt.tight_layout(); plt.show()

# ==========================================================
# 8. ФИНАЛЬНЫЙ ВЫВОД
# ==========================================================
print(f"\n{'='*60}")
print(f"  РЕЗУЛЬТАТЫ (t = {time_total:.0f} с)")
print(f"{'='*60}")
print(f"\n  Пчела В ЯЧЕЙКЕ (центральная):")
print(f"    Торакс:   {log_T_th[-1]:.2f} °C")
print(f"    Голова:   {log_T_hd[-1]:.2f} °C")
print(f"    Брюшко:   {log_T_ab[-1]:.2f} °C")
print(f"    Воздух:   {log_T_ai_bee[-1]:.2f} °C")
print(f"    Мощность: {log_Power[-1]:.2f} мВт")
print(f"\n  Пчела НА ПОВЕРХНОСТИ:")
print(f"    Торакс:   {log_T_th_surf[-1]:.2f} °C")
print(f"    Голова:   {log_T_hd_surf[-1]:.2f} °C")
print(f"    Брюшко:   {log_T_ab_surf[-1]:.2f} °C")
print(f"    T_sense:  {log_T_sense_surf[-1]:.2f} °C")
print(f"\n  БАЛАНС МОЩНОСТИ поверхностной пчелы (прямые расчёты):")
print(f"    P_met:        {log_Power_surf[-1]:.3f} мВт  (генерация)")
print(f"    Q → улочка:   {log_Q_street_surf[-1]:.3f} мВт  (дорсально)")
print(f"    Q → соты:     {log_Q_cells_surf[-1]:.3f} мВт  (вентрально)")
print(f"    Q испарение:  {log_Q_evap_surf[-1]:.3f} мВт")
print(f"    dU/dt:        {log_dU_dt_surf[-1]:.3f} мВт  (→ 0 в стационаре)")
print(f"\n  ВЕРИФИКАЦИЯ:")
print(f"    Макс. невязка баланса: {max(abs(r) for r in log_balance_residual):.2e} мВт")
print(f"    (должна быть ~1e-12 — машинная точность)")
print(f"    Q_cells > 0?  {'ДА ✓' if log_Q_cells_surf[-1] > 0 else 'НЕТ ✗'}")
print(f"    dU/dt → 0?    {'ДА ✓' if abs(log_dU_dt_surf[-1]) < 0.01 else 'ещё нет (сот прогревается)'}")
print(f"\n  ТЕПЛОПОТЕРИ КЛАСТЕРА:")
print(f"    Сторона Y:    {log_Q_Y[-1]:.2f} мВт  (включая дорсальный поток пчелы)")
print(f"    Сторона ^:    {log_Q_X[-1]:.2f} мВт")
print(f"{'='*60}")

Комментарии

Популярные сообщения из этого блога

Температура и мощность термогенеза пчелы

Введение Помню, в школе на уроках биологии рассказывали про теплокровных и холоднокровных животных; насекомых, а, значит, и пчелу относили к холоднокровным. Поэтому когда первый раз прочитал, что пчела - пойкилотермное животное, слегка насторожился. Оказалось - зря, это тоже, что холоднокровное, но "по-научному", ещё встречается "эктотермное". А теплокровные - гомойтермные или эндотермные; они способны сохранять постоянную температуру тела, независимо от температуры окружающей среды - это птицы и млекопитающие, остальные - холоднокровные. Итак, согласимся: пчела - пойкилотермное животное. Однако, "всё не так однозначно". Например голый землекоп - холоднокровное млекопитающее. А в мае 2015 года нашли  "полностью теплокровную рыбу"  . Оказалось, правда, на мой взгляд, не полностью - она способна держать температуру всего на 5°С выше окружающей. Выделяют отдельную группу гетеротермных животных, куда относят как некоторых холоднокровных, так и н...

Режим печки

Пчеле для полёта необходима температура торакса не ниже 27°C. Однако, если мы посмотрим на график температуры пчелы в покое в заметке Температура и мощность термогенеза пчелы , то увидим, что это условие обеспечивается в пасмурную погоду только при температуре воздуха выше 17°C, а на солнце - выше 10°С. Но первые очистительные облёты пчёлы делают и в пасмурную погоду уже при температуре 10-12°C. Для того, чтобы взлететь пчела разогревает торакс до рабочей температуры путем изометрического сокращения летательных мышц. При этом махания крыльями не происходит. Такой режим можно назвать режимом печки. Работа этих мышц осуществляется с КПД 4.4%, остальное идёт на нагрев, т.е. КПД такой печки составляет 95.6%!  В заметке про термогенез  была сделана попытка оценить какая дополнительная мощность нужна пчеле, чтобы поднимать свою температуру со скоростью 2°C в минуту, получилось - нужно 6.3 мВт. Сделано это было ещё до создания модели (по крайней мере без её применения). Но теперь-то...

Выступил на конференции АЕП-2025

  22.11.2025 состоялась ежегодная конференция Ассоциации естественного пчеловодства.  Она заняла весь день. Было много интересных докладов. Иван Пигарёв подвел итоги работы ассоциации за год и планах на будущий - в центре внимания новый проект в Окском государственном заповеднике по сохранению и восстановлению естественного ареала обитания (реинтродукции) темной лесной пчелы.  Александр Новик рассказал, как он занимается бортничеством в США - титаническая работа по сохранению гнезд в дуплах деревьев. Яна Тыжнова поведала о некоторых подробностях перевода очередной книги Томаса Сили "Пчелы. Апиология и жизнь. 20 раскрытых загадок поведения медоносных пчёл" - глубочайший анализ смыслов слов на разных языках. Здесь она превзошла самою себя, переводя уже, кажется,  практически с пчелиного языка. Андрей Богданов рассказал и показал, как он содержит пасеку из нескольких точков в лесах Псковской области на протяжении многих лет безо всякого лечения. Были и другие интересные...