FEMM 5

Электрическое поле тока · Осесимметричная

Растекание тока в осесимметричном ступенчатом токоподводе

Два соосных медных цилиндра длиной по 20 мм соединены ступенью: радиусы 5 и 1 мм. Ток идёт вдоль оси, боковые поверхности изолированы, напряжение между торцами 10 мВ.

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

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

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

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

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

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

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

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

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

\frac1\sigma\sum_i\frac{l_i}{\pi a_i^2}\le R\le\frac{l_1+l_2}{\sigma\pi a_{\min}^2}

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

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

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

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

Увеличьте радиус узкого цилиндра и проверьте переход к однородному цилиндру. В этом пределе нижняя и верхняя оценки должны совпасть с численным сопротивлением.

Добавьте физическое скругление ступени и сравните изменение полного сопротивления с изменением максимальной плотности тока. Объясните, почему два показателя могут реагировать очень по-разному.

Измените длины цилиндров, сохранив ступень. Отделите сопротивление длинных однородных участков от дополнительного сопротивления зоны растекания. Проверьте устойчивость выделенного вклада к удалению торцевых границ.

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

Два соосных медных цилиндра длиной по 20 мм соединены ступенью: радиусы 5 и 1 мм. Ток идёт вдоль оси, боковые поверхности изолированы, напряжение между торцами 10 мВ.

Величины, единицы и происхождение параметров
ПараметрЗначениеЕдиницаПроисхождение
Большой радиус5ммПринято в учебной постановке
Малый радиус1ммПринято в учебной постановке
Длина каждого цилиндра20ммПринято в учебной постановке
Проводимость58000000См/мПринято в учебной постановке
Напряжение0,01ВПринято в учебной постановке

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

  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 = {
    "type": "axi",
    "polygon": [[0, 0], [5, 0], [5, 20], [1, 20], [1, 40], [0, 40]],
    "depth": 1,
    "materials": [["Медный токоподвод", 58000000.0, 58000000.0]],
    "blocks": [[0.5, 30, "Медный токоподвод", 1]],
    "terminals": [["Широкий торец", 0.01, 0, 1], ["Узкий торец", 0, 0, 1]],
    "electrode_segments": [[2.5, 0, "Широкий торец"], [0.5, 40, "Узкий торец"]],
    "samples": [[0.5, 30], [2.5, 10]],
}
for level, mesh in enumerate((0.5, 0.25, 0.125)):
    row = current(JOB, mesh, "model-%02d" % (level + 1))
    row["R_ohm"] = 0.01 / abs(row["terminals"]["Широкий торец"]["I_A"])
    records.append(row)
lower = (0.02 / (math.pi * 0.005**2) + 0.02 / (math.pi * 0.001**2)) / 5.8e7
upper = 0.04 / (5.8e7 * math.pi * 0.001**2)
check(
    "Нижняя оценка последовательных цилиндров",
    max(0, lower / records[-1]["R_ohm"] - 1),
    1e-6,
)
check("Верхняя оценка узкого цилиндра", max(0, records[-1]["R_ohm"] / upper - 1), 1e-6)
check(
    "Сходимость сопротивления растекания",
    abs(records[-1]["R_ohm"] / records[-2]["R_ohm"] - 1),
    0.005,
)

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"]

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

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

Окна FEMM 5

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

Рабочая область модели «Растекание тока в осесимметричном ступенчатом токоподводе». Нативный препроцессор FEMM 5.
Рабочая область модели «Растекание тока в осесимметричном ступенчатом токоподводе». Нативный препроцессор FEMM 5. Открыть в полном размере
Сохранённое решение «Растекание тока в осесимметричном ступенчатом токоподводе». Модуль плотности тока |J|, А/м², в увеличенном переходе от тонкого стержня к широкому токоподводу. Шкала отображения 0–5·10⁷ А/м²; эквипотенциали скрыты. Торцы модели за кадром.
Сохранённое решение «Растекание тока в осесимметричном ступенчатом токоподводе». Модуль плотности тока |J|, А/м², в увеличенном переходе от тонкого стержня к широкому токоподводу. Шкала отображения 0–5·10⁷ А/м²; эквипотенциали скрыты. Торцы модели за кадром. Открыть в полном размере

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

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

Результаты расчётов и производные итоги
Сопротивление, ОмМасштаб сетки, ммМощность области, ВтЭлементовКонтрольные точкиЭлектродыОтносительная невязка токаОтносительная невязка мощностиМощность, ВтУзловМодель
117,31861 × 10⁻⁶0,51 полей; полные данные JSON15222 точек; полные данные JSON2 полей; полные данные JSON17,220495 × 10⁻⁹17,166485 × 10⁻⁹0,85237971879model-01.fec
117,33471 × 10⁻⁶0,251 полей; полные данные JSON46312 точек; полные данные JSON2 полей; полные данные JSON5,6539644 × 10⁻⁹2,8602484 × 10⁻⁹0,852262752505model-02.fec
117,38092 × 10⁻⁶0,1251 полей; полные данные JSON181902 точек; полные данные JSON2 полей; полные данные JSON28,481428 × 10⁻⁹41,893844 × 10⁻⁹0,851927269460model-03.fec
Проверки опубликованного расчёта
КритерийПолученоДопуск или ожидаемое значениеИтог
model-01: баланс токов17,22 × 10⁻⁹1e-06Выполнен
model-01: баланс мощности17,17 × 10⁻⁹1e-06Выполнен
model-02: баланс токов5,654 × 10⁻⁹1e-06Выполнен
model-02: баланс мощности2,86 × 10⁻⁹1e-06Выполнен
model-03: баланс токов28,48 × 10⁻⁹1e-06Выполнен
model-03: баланс мощности41,89 × 10⁻⁹1e-06Выполнен
Нижняя оценка последовательных цилиндров01e-06Выполнен
Верхняя оценка узкого цилиндра01e-06Выполнен
Сходимость сопротивления растекания393,8 × 10⁻⁶0,005Выполнен

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

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

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

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

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