FEMM 5

Теплопроводность · Плоскопараллельная

Взаимный нагрев кабелей и расстояние между ними

Три кабеля радиусами жилы 5 мм и изоляции 10 мм находятся на глубине 1 м в грунте k=1 Вт/(м К). Каждый выделяет 20 Вт/м. Сравниваются расстояния между осями 150, 300 и 600 мм; поверхность грунта поддерживается при 20 °C.

Работа связывает полевое распределение с измеряемой электротехнической величиной. Все исходные данные заданы в программе; принятые идеализации сформулированы до расчёта. Основной результат проверяется независимой формулой или балансом, а чувствительность к геометрии исследуется отдельными вариантами.

Самостоятельная учебная модель FEMM 5: от физических соотношений к полевому решению и проверке результата.

Допущения и устройство учебной модели

Все размеры, свойства материалов и условия возбуждения заданы ниже. Численные результаты относятся к опубликованной версии модели FEMM 5.

Физическая и математическая постановка

Теория и физическая постановка

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

Тепловой поток в изоляции создаёт собственный логарифмический перепад, который почти не зависит от расстояния между кабелями. Взаимное влияние передаётся через грунт. При увеличении расстояния снижается именно этот вклад; оценивать его по одному внутреннему сопротивлению кабеля невозможно.

Численная область ограничена удалённым прямоугольником 40 × 21 м с заданной температурой 20 °C. Это приближение полупространства проверяется сравнением с аналитикой. Разность температур между тесной и разреженной прокладкой дополнительно сравнивается с разностью аналитических оценок, что уменьшает влияние общего удалённого граничного условия.

\Delta T_{mut}=\sum_j\frac{P_j^{\prime}}{2\pi k_s}\ln\frac{\sqrt{s_j^2+(2H)^2}}{s_j}

Как читать расчёт

Программа строит три сетки, сохраняет каждую модель, закрывает её и повторно открывает перед решением. Поэтому проверка относится к точному записанному файлу с русскими именами материалов, проводников и граничных условий. Результаты каждой серии находятся в новой папке calculated-XX рядом с программой; предыдущая серия не перезаписывается.

В result.json массив records содержит расчётные величины и параметры сетки, checks — фактические отклонения, допустимые пределы и логический результат. Проверяйте все критерии одновременно. Совпадение одной точки с ориентиром не заменяет проверки баланса и изменения результата по сетке. Для итерационной обратной связи отдельно контролируется сходимость итераций; она не является оценкой пространственной погрешности.

Практическое исследование

Увеличьте удалённую область и сравните изменение абсолютных температур с изменением их разности между двумя расстояниями. Отделите ошибку внешней границы от погрешности мелкой кабельной сетки.

Отключите потери крайних кабелей при сохранённой геометрии. Разность температур среднего кабеля прямо показывает взаимный вклад. Проверьте его линейное масштабирование с потерями соседей.

Измените глубину прокладки и сопоставьте собственный и взаимный вклады. Не переносите результат изотермической поверхности на реальную сезонную границу без дополнительной модели окружающей среды.

Исходные данные

Три кабеля радиусами жилы 5 мм и изоляции 10 мм находятся на глубине 1 м в грунте k=1 Вт/(м К). Каждый выделяет 20 Вт/м. Сравниваются расстояния между осями 150, 300 и 600 мм; поверхность грунта поддерживается при 20 °C.

Величины, единицы и происхождение параметров
ПараметрЗначениеЕдиницаПроисхождение
Глубина1000ммПринято в учебной постановке
Расстояния осей150; 300; 600ммПринято в учебной постановке
Потери каждого кабеля20Вт/мПринято в учебной постановке
k грунта1Вт/(м К)Принято в учебной постановке
Температура поверхности20°CПринято в учебной постановке

Порядок воспроизведения

  1. Скопируйте reproduce.py в отдельную рабочую папку и откройте встроенным редактором Python FEMM 5. Проверьте заданные параметры и единицы, затем выполните программу целиком. Внешний модуль pyFEMM для этой программы не используется.
  2. Откройте модель и решение окончательной сетки. Сопоставьте материалы, проводники и границы с теорией. Для осесимметричной задачи найдите ось вращения и объясните, какие поверхности образуются при вращении каждой линии поперечного рисунка.
  3. Составьте таблицу трёх сеток и приведите независимый расчёт по формуле. Для серии из нескольких геометрий сравнивайте только результаты одной и той же сетки, затем отдельно исследуйте её уточнение. Укажите абсолютные и относительные ошибки с единицами.
  4. Выполните предложенные варианты по одному. До запуска предскажите знак и примерный масштаб эффекта. В отчёте объясните, какие величины действительно определяет модель, какие параметры остаются предположениями и что пришлось бы уточнить для реального изделия.

Скачайте нужные файлы по отдельным ссылкам ниже в одну рабочую папку, затем откройте reproduce.py во встроенном Python FEMM 5 и выполните его. Сохраните имена файлов и структуру подпапок. Условия конкретного запуска и путь новых результатов указаны в комментариях программы.

Программа встроенного Python FEMM 5
Python · reproduce.py
"""Самостоятельная лабораторная работа курса FEMM 5.
Запуск: встроенный MicroPython FEMM 5, команды FEMM доступны непосредственно.
Геометрические координаты и глубина — мм; результаты интегралов — единицы СИ.
Данные, допущения, аналитические ориентиры и ход исследования заданы в README.md.
Каждая серия записывается в новую calculated-XX; прежние результаты сохраняются.
Сохранённая модель закрывается и открывается до расчёта, включая русские имена.
"""

import os
import sys
import json
import math

script = globals().get("__file__", "") or (sys.argv[0] if sys.argv else "")
base = os.path.dirname(os.path.abspath(script)) if script else os.getcwd()
index = 1
while "calculated-%02d" % index in os.listdir(base):
    index += 1
directory = base + "/calculated-%02d" % index
os.mkdir(directory)
records = []
checks = []


def real(value):
    if isinstance(value, (tuple, list)):
        return real(value[0])
    return complex(value).real


def check(name, value, limit):
    checks.append(
        {
            "name": name,
            "value": value,
            "limit": limit,
            "passed": math.isfinite(value) and value <= limit,
        }
    )


def polygon(prefix, points):
    for x, y in points:
        globals()[prefix + "_addnode"](x, y)
    for i, (x, y) in enumerate(points):
        x2, y2 = points[(i + 1) % len(points)]
        globals()[prefix + "_addsegment"](x, y, x2, y2)


def geometry(prefix, job, mesh):
    if job.get("polygon"):
        polygon(prefix, job["polygon"])
    for x1, y1, x2, y2 in job.get("interfaces", []) + job.get("segments", []):
        globals()[prefix + "_addnode"](x1, y1)
        globals()[prefix + "_addnode"](x2, y2)
        globals()[prefix + "_addsegment"](x1, y1, x2, y2)
    for x1, y1, x2, y2, angle, name in job.get("arcs", []):
        globals()[prefix + "_addnode"](x1, y1)
        globals()[prefix + "_addnode"](x2, y2)
        globals()[prefix + "_addarc"](x1, y1, x2, y2, angle, 1)
        cx, cy = job.get("arc_center", (0, 0))
        mx = (x1 + x2) / 2 - cx
        my = (y1 + y2) / 2 - cy
        radius = math.sqrt((x1 - cx) ** 2 + (y1 - cy) ** 2)
        factor = radius / math.sqrt(mx * mx + my * my)
        globals()[prefix + "_selectarcsegment"](cx + mx * factor, cy + my * factor)
        if prefix == "ci":
            ci_setarcsegmentprop(1, "<None>", 0, 0, name or "<None>")
        else:
            hi_setarcsegmentprop(1, name or "<None>", 0, 0, "<None>")
        globals()[prefix + "_clearselected"]()
    for x, y, radius, name in job.get("circles", []):
        globals()[prefix + "_addnode"](x - radius, y)
        globals()[prefix + "_addnode"](x + radius, y)
        globals()[prefix + "_addarc"](x - radius, y, x + radius, y, 180, 1)
        globals()[prefix + "_addarc"](x + radius, y, x - radius, y, 180, 1)
        globals()[prefix + "_selectarcsegment"](x, y + radius)
        globals()[prefix + "_selectarcsegment"](x, y - radius)
        if prefix == "ci":
            ci_setarcsegmentprop(1, "<None>", 0, 0, name or "<None>")
        else:
            hi_setarcsegmentprop(1, name or "<None>", 0, 0, "<None>")
        globals()[prefix + "_clearselected"]()
    for x, y, name, group in job["blocks"]:
        globals()[prefix + "_addblocklabel"](x, y)
        globals()[prefix + "_selectlabel"](x, y)
        globals()[prefix + "_setblockprop"](
            name,
            0,
            mesh
            * job.get("block_mesh_multipliers", {}).get(
                str(group), job.get("mesh_multipliers", {}).get(name, 1)
            ),
            group,
        )
        globals()[prefix + "_clearselected"]()


def current(job, mesh, stem):
    newdocument(3)
    ci_probdef("millimeters", job.get("type", "planar"), 0, 1e-10, job["depth"], 30)
    for name, sx, sy in job["materials"]:
        ci_addmaterial(name, sx, sy, 1, 1, 0, 0)
    for name, voltage, amperes, kind in job["terminals"]:
        ci_addconductorprop(name, voltage, amperes, kind)
    geometry("ci", job, mesh)
    for x, y, name in job.get("electrode_segments", []):
        ci_selectsegment(x, y)
        ci_setsegmentprop("<None>", mesh, 0, 0, 0, name)
        ci_clearselected()
    path = directory + "/" + stem + ".fec"
    ci_saveas(path)
    ci_close()
    opendocument(path)
    ci_analyze(1)
    ci_loadsolution()
    co_smooth("off")
    terminals = {}
    terminal_power = 0
    balance = 0
    current_scale = 0
    for name, voltage, amperes, kind in job["terminals"]:
        u, i = co_getconductorproperties(name)
        terminals[name] = {"U_V": real(u), "I_A": real(i)}
        terminal_power += real(complex(u) * complex(i).conjugate())
        balance += complex(i)
        current_scale = max(current_scale, abs(complex(i)))
    powers = {}
    power = 0
    for x, y, name, group in job["blocks"]:
        if name == "<No Mesh>":
            continue
        co_groupselectblock(group)
        powers[str(group)] = real(co_blockintegral(0))
        power += powers[str(group)]
        co_clearblock()
    samples = []
    for x, y in job.get("samples", []):
        values = co_getpointvalues(x, y)
        samples.append(
            {
                "x_mm": x,
                "y_mm": y,
                "U_V": real(values[0]),
                "J_A_m2": math.sqrt(
                    abs(complex(values[1])) ** 2 + abs(complex(values[2])) ** 2
                ),
            }
        )
    row = {
        "model": stem + ".fec",
        "mesh_mm": mesh,
        "nodes": co_numnodes(),
        "elements": co_numelements(),
        "terminals": terminals,
        "P_W": power,
        "group_power_W": powers,
        "samples": samples,
        "current_balance_relative": abs(balance) / current_scale,
        "power_balance_relative": abs(power - terminal_power) / max(power, 1e-30),
    }
    check(stem + ": баланс токов", row["current_balance_relative"], 1e-6)
    check(stem + ": баланс мощности", row["power_balance_relative"], 1e-6)
    co_close()
    ci_close()
    return row


def heat(job, mesh, stem):
    newdocument(2)
    hi_probdef("millimeters", job.get("type", "planar"), 1e-10, job["depth"], 30)
    for name, kx, ky, q in job["materials"]:
        hi_addmaterial(name, kx, ky, q, 0)
    for boundary in job["boundaries"]:
        name, kind, temp, ambient, h = boundary[:5]
        beta = boundary[5] if len(boundary) > 5 else 0
        hi_addboundprop(name, kind, temp, 0, ambient, h, beta)
    geometry("hi", job, mesh)
    for x, y, name in job.get("boundary_segments", []):
        hi_selectsegment(x, y)
        hi_setsegmentprop(name, job.get("boundary_mesh", mesh), 0, 0, 0, "<None>")
        hi_clearselected()
    path = directory + "/" + stem + ".feh"
    hi_saveas(path)
    hi_close()
    opendocument(path)
    hi_analyze(1)
    hi_loadsolution()
    ho_smooth("off")
    averages = {}
    generated = 0
    for x, y, name, group in job["blocks"]:
        ho_groupselectblock(group)
        averages[str(group)] = real(ho_blockintegral(0))
        volume = real(ho_blockintegral(2))
        q = [m[3] for m in job["materials"] if m[0] == name][0]
        generated += q * volume
        ho_clearblock()
    samples = []
    for x, y in job.get("samples", []):
        samples.append({"x_mm": x, "y_mm": y, "T_K": real(ho_getpointvalues(x, y)[0])})
    convection = 0
    for x1, y1, x2, y2, ambient, h in job.get("convection_edges", []):
        count = 200
        total = 0
        for n in range(count + 1):
            t = real(
                ho_getpointvalues(
                    x1 + (x2 - x1) * n / count, y1 + (y2 - y1) * n / count
                )[0]
            )
            total += (0.5 if n in (0, count) else 1) * (t - ambient)
        length = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) * 0.001
        convection += total / count * length * job["depth"] * 0.001 * h
    row = {
        "model": stem + ".feh",
        "mesh_mm": mesh,
        "nodes": ho_numnodes(),
        "elements": ho_numelements(),
        "samples": samples,
        "group_average_K": averages,
        "generated_W": generated,
        "convection_W": convection,
    }
    if job.get("convection_edges") and not job.get("skip_convection_balance"):
        check(
            stem + ": баланс конвективного теплоотвода",
            abs(convection - generated) / max(generated, 1e-30),
            0.005,
        )
    ho_close()
    hi_close()
    return row


JOB = {
    "polygon": [[-20000, -20000], [20000, -20000], [20000, 1000], [-20000, 1000]],
    "depth": 1000,
    "boundaries": [["Удалённая среда", 0, 293.15, 0, 0]],
    "boundary_segments": [
        [-20000, -1000, "Удалённая среда"],
        [20000, -1000, "Удалённая среда"],
        [0, -20000, "Удалённая среда"],
        [0, 1000, "Удалённая среда"],
    ],
    "boundary_mesh": 500,
    "mesh_multipliers": {"Грунт": 2000},
    "samples": [[0, 0]],
}
for level, mesh in enumerate((0.8, 0.4, 0.2)):
    readings = []
    for pitch in (150, 300, 600):
        job = json.loads(json.dumps(JOB))
        job["materials"] = [
            ["Медь", 380, 380, 20 / (math.pi * 0.005**2)],
            ["Изоляция", 0.2, 0.2, 0],
            ["Грунт", 1, 1, 0],
        ]
        job["circles"] = []
        job["interfaces"] = [
            [-1000, -1000, 1000, -1000],
            [-1000, -1000, -1000, 1000],
            [1000, -1000, 1000, 1000],
        ]
        job["boundary_segments"] += [
            [-10000, 1000, "Удалённая среда"],
            [10000, 1000, "Удалённая среда"],
        ]
        job["blocks"] = [[0, -500, "Грунт", 99], [5000, -5000, "Грунт", 98]]
        job["block_mesh_multipliers"] = {"99": 80, "98": 2000}
        for cable, x in enumerate((-pitch, 0, pitch)):
            job["circles"] += [
                [x, 0, 5, ""],
                [x, 0, 10, ""],
                [x, 0, 30, ""],
                [x, 0, 60, ""],
            ]
            job["blocks"] += [
                [x, 0, "Медь", 10 * cable + 1],
                [x + 7, 0, "Изоляция", 10 * cable + 2],
                [x + 20, 0, "Грунт", 10 * cable + 3],
                [x + 45, 0, "Грунт", 10 * cable + 4],
            ]
            job["block_mesh_multipliers"][str(10 * cable + 3)] = 5
            job["block_mesh_multipliers"][str(10 * cable + 4)] = 15
        result = heat(job, mesh, "mesh-%d-pitch-%d" % (level + 1, pitch))
        p = pitch * 0.001
        self_heat = (
            20 / (2 * math.pi) * math.log(2 / 0.01)
            + 20 / (2 * math.pi * 0.2) * math.log(10 / 5)
            + 20 / (4 * math.pi * 380)
        )
        neighbor = 2 * 20 / (2 * math.pi) * math.log(math.sqrt(p * p + 4) / p)
        result["pitch_mm"] = pitch
        result["T_center_K"] = result["samples"][0]["T_K"]
        result["analytic_halfspace_K"] = 293.15 + self_heat + neighbor
        check(
            "Изображения линейных источников, шаг %d мм" % pitch,
            abs(result["T_center_K"] - result["analytic_halfspace_K"]),
            1,
        )
        check("Полная мощность трёх кабелей", abs(result["generated_W"] - 60), 0.02)
        readings.append(result)
    records.append(
        {"mesh_mm": mesh, "readings": readings, "model": readings[-1]["model"]}
    )
check(
    "Сходимость температуры средней прокладки, К",
    abs(
        records[-1]["readings"][1]["T_center_K"]
        - records[-2]["readings"][1]["T_center_K"]
    ),
    0.15,
)
actual = (
    records[-1]["readings"][0]["T_center_K"] - records[-1]["readings"][-1]["T_center_K"]
)
reference = (
    records[-1]["readings"][0]["analytic_halfspace_K"]
    - records[-1]["readings"][-1]["analytic_halfspace_K"]
)
check(
    "Влияние расстояния против аналитической разности, К", abs(actual - reference), 0.15
)

result = {
    "runtime": "FEMM 5; embedded MicroPython",
    "records": records,
    "checks": checks,
    "passed": all(item["passed"] for item in checks),
}
with open(directory + "/result.json", "w") as output:
    json.dump(result, output)
print(json.dumps(result))
assert result["passed"]

Картина рассчитанного поля

Температура, К: mesh-3-pitch-600.anh
Визуализация сохранённого решения FEMM 5 mesh-3-pitch-600.anh. Абсолютная температура по сохранённым узловым значениям. Показана расчётная область. Контуры деталей взяты из той же модели. Области вне сохранённых треугольников не заполняются. Это научный график сохранённых данных. Нажмите на карту, чтобы открыть её отдельно и увеличить.

Окна FEMM 5

Снимки интерфейса FEMM показывают геометрию, настройки и работу с результатами в FEMM 5. Откройте изображение, чтобы рассмотреть подписи и значения в полном размере.

Увеличенная область трёх подземных кабелей с шагом 600 мм. Крупные внешние окружности ограничивают области сгущения сетки в грунте. Поверхность и удалённые границы за кадром; подписи скрыты.
Увеличенная область трёх подземных кабелей с шагом 600 мм. Крупные внешние окружности ограничивают области сгущения сетки в грунте. Поверхность и удалённые границы за кадром; подписи скрыты. Открыть в полном размере
Сохранённое решение «Взаимный нагрев кабелей и расстояние между ними». Температура T, К, и изотермы взаимного нагрева трёх кабелей. Верхняя горизонталь — поверхность грунта; внутренний прямоугольник — область сгущения сетки; удалённые границы за кадром.
Сохранённое решение «Взаимный нагрев кабелей и расстояние между ними». Температура T, К, и изотермы взаимного нагрева трёх кабелей. Верхняя горизонталь — поверхность грунта; внутренний прямоугольник — область сгущения сетки; удалённые границы за кадром. Открыть в полном размере

Результаты и численные проверки

Сохранено решений FEMM: 9. Выполнено проверок: 20. Полная точность чисел и условия сравнения доступны в таблицах результатов JSON.

Результаты расчётов и производные итоги
Масштаб сетки, ммМодельТепловыделение, ВтКонвективный тепловой поток, ВтРасстояние между осями кабелей, ммЭлементовКонтрольные точкиТемпература в центре, КТемпература аналитического полупространства, КСредняя температура области, КУзловКонтрольная точка: Температура, ККонтрольная точка: Координата x, ммКонтрольная точка: Координата y, мм
0,8mesh-1-pitch-150.feh600150576861 точек; полные данные JSON337,4337,614 полей; полные данные JSON29048337,400
0,8mesh-1-pitch-300.feh600300578141 точек; полные данные JSON333333,214 полей; полные данные JSON2911333300
0,8mesh-1-pitch-600.feh600600578221 точек; полные данные JSON328,832914 полей; полные данные JSON29123328,800
0,4mesh-2-pitch-150.feh600150727371 точек; полные данные JSON337,5337,614 полей; полные данные JSON36590337,500
0,4mesh-2-pitch-300.feh600300725941 точек; полные данные JSON333,1333,214 полей; полные данные JSON36518333,100
0,4mesh-2-pitch-600.feh600600726231 точек; полные данные JSON328,932914 полей; полные данные JSON36534328,900
0,2mesh-3-pitch-150.feh6001501519401 точек; полные данные JSON337,5337,614 полей; полные данные JSON76226337,500
0,2mesh-3-pitch-300.feh6003001525861 точек; полные данные JSON333,1333,214 полей; полные данные JSON76550333,100
0,2mesh-3-pitch-600.feh6006001520041 точек; полные данные JSON328,932914 полей; полные данные JSON76261328,900
Итоги исходных серий
Сводные записи; отдельные показания приведены выше
Контрольные показанияМасштаб сетки, ммМодель
3 точек; полные данные JSON0,8mesh-1-pitch-600.feh
3 точек; полные данные JSON0,4mesh-2-pitch-600.feh
3 точек; полные данные JSON0,2mesh-3-pitch-600.feh
Проверки опубликованного расчёта
КритерийПолученоДопуск или ожидаемое значениеИтог
Изображения линейных источников, шаг 150 мм0,18651Выполнен
Полная мощность трёх кабелей0,0030460,02Выполнен
Изображения линейных источников, шаг 300 мм0,1831Выполнен
Полная мощность трёх кабелей0,0030460,02Выполнен
Изображения линейных источников, шаг 600 мм0,21361Выполнен
Полная мощность трёх кабелей0,0030460,02Выполнен
Изображения линейных источников, шаг 150 мм0,10431Выполнен
Полная мощность трёх кабелей0,0030460,02Выполнен
Изображения линейных источников, шаг 300 мм0,12631Выполнен
Полная мощность трёх кабелей0,0030460,02Выполнен
Изображения линейных источников, шаг 600 мм0,13371Выполнен
Полная мощность трёх кабелей0,0030460,02Выполнен
Изображения линейных источников, шаг 150 мм0,070511Выполнен
Полная мощность трёх кабелей0,0030460,02Выполнен
Изображения линейных источников, шаг 300 мм0,088091Выполнен
Полная мощность трёх кабелей0,0030460,02Выполнен
Изображения линейных источников, шаг 600 мм0,093051Выполнен
Полная мощность трёх кабелей0,0030460,02Выполнен
Сходимость температуры средней прокладки, К0,038160,15Выполнен
Влияние расстояния против аналитической разности, К0,022540,15Выполнен

Допуски относятся к указанной учебной задаче и способу сравнения. Число показанных цифр не означает такой же физической точности.

Модели, решения и программа

Для нового расчёта сохраните программу, модели и необходимые данные в одну рабочую папку, сохраняя структуру подпапок.

Полные численные результаты

Границы применимости