FEMM 5

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

Ток кабеля при ограничении температуры и обратной связи сопротивления

Медная жила радиуса 5 мм, изоляция до радиуса 8 мм, длина 1 м. Охлаждение воздухом 20 °C с h=10 Вт/(м² К). Учебное ограничение температуры жилы 90 °C; сопротивление зависит от температуры.

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

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

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

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

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

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

Продольный ток в круглой жиле считается равномерным, поэтому J=I/A. Его тепловое действие передаётся поперечной модели как qv=J²/σ. Это аналитический электрический подрасчёт, а не поле осевого тока, ошибочно решённое в поперечной плоскости. При постоянных тепловых коэффициентах радиальное тепловое сопротивление имеет точную формулу.

Удельное электрическое сопротивление растёт по закону 1+αΔT. Для заданного тока это положительная обратная связь: больший нагрев увеличивает сопротивление и джоулевы потери. В учебной модели проводимость всей жилы определяется её центральной температурой. При выбранной высокой теплопроводности меди внутренний перепад мал, но принятое приближение явно сохраняется в интерпретации.

Сначала нативный тепловой расчёт с единичной погонной мощностью определяет коэффициент температура/мощность. Из него вычисляется ток, достигающий 90 °C с учётом изменения сопротивления. Дополнительный расчёт проверяет эту температуру. Затем для 80 процентов найденного тока выполняются явные итерации J²-нагрева с релаксацией и сравнением с независимым нелинейным аналитическим решением.

I_{\lim}=\sqrt{\frac{\Delta T_{\lim}}{R_{20}^{\prime}R_{th}^{\prime}(1+\alpha\Delta T_{\lim})}},\quad q_v=J^2/\sigma(T)

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

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

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

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

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

Измените толщину изоляции и коэффициент теплоотдачи отдельно. Определите, какой тепловой участок ограничивает результат. Сравните чувствительность тока и перегрева: зависимость через квадрат тока не является линейной.

Исследуйте приближение к условию I²R20′Rth′α=1 в аналитической модели. Объясните, почему потеря стационарного решения этой упрощённой модели не является готовым прогнозом разрушения реального кабеля.

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

Медная жила радиуса 5 мм, изоляция до радиуса 8 мм, длина 1 м. Охлаждение воздухом 20 °C с h=10 Вт/(м² К). Учебное ограничение температуры жилы 90 °C; сопротивление зависит от температуры.

Величины, единицы и происхождение параметров
ПараметрЗначениеЕдиницаПроисхождение
Радиус жилы5ммПринято в учебной постановке
Радиус изоляции8ммПринято в учебной постановке
α меди0,003931/КПринято в учебной постановке
k изоляции0,2Вт/(м К)Принято в учебной постановке
Температурное ограничение90°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 = {
    "depth": 1000,
    "materials": [],
    "blocks": [[0, 0, "Медная жила", 1], [7, 0, "Изоляция", 2]],
    "circles": [[0, 0, 5, ""], [0, 0, 8, "Конвекция"]],
    "boundaries": [["Конвекция", 2, 0, 293.15, 10]],
    "samples": [[0, 0]],
}
area = math.pi * 0.005**2
resistance20 = 1 / (5.8e7 * area)
alpha = 0.00393
analytic_rth = (
    1 / (4 * math.pi * 380)
    + math.log(8 / 5) / (2 * math.pi * 0.2)
    + 1 / (2 * math.pi * 0.008 * 10)
)
limit_temperature = 363.15
reference_current = math.sqrt(70 / (resistance20 * analytic_rth * (1 + alpha * 70)))
for level, mesh in enumerate((0.4, 0.2, 0.1)):
    job = json.loads(json.dumps(JOB))
    job["materials"] = [["Медная жила", 380, 380, 1 / area], ["Изоляция", 0.2, 0.2, 0]]
    unit = heat(job, mesh, "mesh-%d-unit" % (level + 1))
    rth = unit["samples"][0]["T_K"] - 293.15
    maximum_current = math.sqrt(70 / (resistance20 * rth * (1 + alpha * 70)))
    operating_current = 0.8 * maximum_current
    temperature = 293.15
    history = []
    for iteration in range(1, 61):
        conductivity = 5.8e7 / (1 + alpha * (temperature - 293.15))
        q = (operating_current / area) ** 2 / conductivity
        job["materials"] = [["Медная жила", 380, 380, q], ["Изоляция", 0.2, 0.2, 0]]
        result = heat(job, mesh, "mesh-%d-feedback-%02d" % (level + 1, iteration))
        new_temperature = result["samples"][0]["T_K"]
        residual = abs(new_temperature - temperature)
        history.append(
            {"iteration": iteration, "T_K": new_temperature, "residual_K": residual}
        )
        if residual < 0.001:
            break
        temperature = (temperature + new_temperature) / 2
    check("Итерационная сходимость, сетка %d" % (level + 1), residual, 0.001)
    check(
        "Ток температурного ограничения против аналитики",
        abs(maximum_current / reference_current - 1),
        0.002,
    )
    analytic_delta = (
        operating_current**2
        * resistance20
        * analytic_rth
        / (1 - operating_current**2 * resistance20 * analytic_rth * alpha)
    )
    check(
        "Итерационная температура против аналитики, К",
        abs(new_temperature - 293.15 - analytic_delta),
        0.1,
    )
    q = (maximum_current / area) ** 2 / (5.8e7 / (1 + alpha * 70))
    job["materials"] = [["Медная жила", 380, 380, q], ["Изоляция", 0.2, 0.2, 0]]
    limit_result = heat(job, mesh, "mesh-%d-limit" % (level + 1))
    check(
        "Достижение заданной температуры, К",
        abs(limit_result["samples"][0]["T_K"] - limit_temperature),
        0.01,
    )
    records.append(
        {
            "mesh_mm": mesh,
            "model": limit_result["model"],
            "I_limit_A": maximum_current,
            "I_operating_A": operating_current,
            "operating_model": result["model"],
            "unit_model": unit["model"],
            "T_operating_K": new_temperature,
            "iterations": iteration,
            "history": history,
        }
    )
check(
    "Сходимость предельного тока",
    abs(records[-1]["I_limit_A"] / records[-2]["I_limit_A"] - 1),
    0.002,
)

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-limit.anh
Визуализация сохранённого решения FEMM 5 mesh-3-limit.anh. Абсолютная температура по сохранённым узловым значениям. Показана расчётная область. Контуры деталей взяты из той же модели. Области вне сохранённых треугольников не заполняются. Это научный график сохранённых данных. Нажмите на карту, чтобы открыть её отдельно и увеличить.

Окна FEMM 5

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

Рабочая область модели «Ток кабеля при ограничении температуры и обратной связи сопротивления». Нативный препроцессор FEMM 5.
Рабочая область модели «Ток кабеля при ограничении температуры и обратной связи сопротивления». Нативный препроцессор FEMM 5. Открыть в полном размере
Сохранённое решение «Ток кабеля при ограничении температуры и обратной связи сопротивления». Температура T, К, и изотермы сохранённого предельного режима кабеля; обратная связь потерь и температуры учитывается в учебной программе.
Сохранённое решение «Ток кабеля при ограничении температуры и обратной связи сопротивления». Температура T, К, и изотермы сохранённого предельного режима кабеля; обратная связь потерь и температуры учитывается в учебной программе. Открыть в полном размере

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

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

Результаты расчётов и производные итоги
Рабочий ток, АМодель рабочего режимаМодель единичного возбужденияМодельРабочая температура, КИтерацийИстория итерацийМасштаб сетки, ммПредельный ток по температуре, А
260,2mesh-1-feedback-20.fehmesh-1-unit.fehmesh-1-limit.feh333,92020 точек; полные данные JSON0,4325,3
260,2mesh-2-feedback-20.fehmesh-2-unit.fehmesh-2-limit.feh333,92020 точек; полные данные JSON0,2325,3
260,2mesh-3-feedback-20.fehmesh-3-unit.fehmesh-3-limit.feh333,92020 точек; полные данные JSON0,1325,3
Проверки опубликованного расчёта
КритерийПолученоДопуск или ожидаемое значениеИтог
Итерационная сходимость, сетка 1782,4 × 10⁻⁶0,001Выполнен
Ток температурного ограничения против аналитики44,26 × 10⁻⁶0,002Выполнен
Итерационная температура против аналитики, К0,0043130,1Выполнен
Достижение заданной температуры, К313,8 × 10⁻⁹0,01Выполнен
Итерационная сходимость, сетка 2782,4 × 10⁻⁶0,001Выполнен
Ток температурного ограничения против аналитики35,83 × 10⁻⁶0,002Выполнен
Итерационная температура против аналитики, К0,0035140,1Выполнен
Достижение заданной температуры, К240,3 × 10⁻⁹0,01Выполнен
Итерационная сходимость, сетка 3782,4 × 10⁻⁶0,001Выполнен
Ток температурного ограничения против аналитики24,41 × 10⁻⁶0,002Выполнен
Итерационная температура против аналитики, К0,0024350,1Выполнен
Достижение заданной температуры, К364,1 × 10⁻⁹0,01Выполнен
Сходимость предельного тока11,41 × 10⁻⁶0,002Выполнен

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

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

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

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

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