Электрическое поле тока · Осесимметричная
Растекание тока в осесимметричном ступенчатом токоподводе
Два соосных медных цилиндра длиной по 20 мм соединены ступенью: радиусы 5 и 1 мм. Ток идёт вдоль оси, боковые поверхности изолированы, напряжение между торцами 10 мВ.
Работа связывает полевое распределение с измеряемой электротехнической величиной. Все исходные данные заданы в программе; принятые идеализации сформулированы до расчёта. Основной результат проверяется независимой формулой или балансом, а чувствительность к геометрии исследуется отдельными вариантами.
Самостоятельная учебная модель FEMM 5: от физических соотношений к полевому решению и проверке результата.
Допущения и устройство учебной модели
Все размеры, свойства материалов и условия возбуждения заданы ниже. Численные результаты относятся к опубликованной версии модели FEMM 5.
Физическая и математическая постановка
Теория и физическая постановка
В широком цилиндре ток должен собраться к узкому соединению. Вдали от перехода плотность тока почти равномерна по сечению, а около ступени имеет радиальную составляющую. Обычная сумма двух цилиндрических сопротивлений не учитывает дополнительную энергию растекания.
Нижняя оценка получается при искусственно равномерном токе в каждом поперечном сечении. Верхняя оценка соответствует удалению всей внешней части широкого цилиндра, то есть оставлению узкого цилиндра по всей длине. Удаление проводящего материала не может уменьшить сопротивление при тех же торцевых выводах.
Программа вычисляет ток на выводе, джоулеву мощность и сопротивление на трёх сетках, проверяет оба энергетических ограничения. В идеальном входящем углу плотность тока локально чувствительна к сетке; интегральное сопротивление существенно устойчивее и служит основной мерой.
Как читать расчёт
Программа строит три сетки, сохраняет каждую модель, закрывает её и повторно открывает перед решением. Поэтому проверка относится к точному записанному файлу с русскими именами материалов, проводников и граничных условий. Результаты каждой серии находятся в новой папке calculated-XX рядом с программой; предыдущая серия не перезаписывается.
В result.json массив records содержит расчётные величины и параметры сетки, checks — фактические отклонения, допустимые пределы и логический результат. Проверяйте все критерии одновременно. Совпадение одной точки с ориентиром не заменяет проверки баланса и изменения результата по сетке. Для итерационной обратной связи отдельно контролируется сходимость итераций; она не является оценкой пространственной погрешности.
Практическое исследование
Увеличьте радиус узкого цилиндра и проверьте переход к однородному цилиндру. В этом пределе нижняя и верхняя оценки должны совпасть с численным сопротивлением.
Добавьте физическое скругление ступени и сравните изменение полного сопротивления с изменением максимальной плотности тока. Объясните, почему два показателя могут реагировать очень по-разному.
Измените длины цилиндров, сохранив ступень. Отделите сопротивление длинных однородных участков от дополнительного сопротивления зоны растекания. Проверьте устойчивость выделенного вклада к удалению торцевых границ.
Исходные данные
Два соосных медных цилиндра длиной по 20 мм соединены ступенью: радиусы 5 и 1 мм. Ток идёт вдоль оси, боковые поверхности изолированы, напряжение между торцами 10 мВ.
| Параметр | Значение | Единица | Происхождение |
|---|---|---|---|
| Большой радиус | 5 | мм | Принято в учебной постановке |
| Малый радиус | 1 | мм | Принято в учебной постановке |
| Длина каждого цилиндра | 20 | мм | Принято в учебной постановке |
| Проводимость | 58000000 | См/м | Принято в учебной постановке |
| Напряжение | 0,01 | В | Принято в учебной постановке |
Порядок воспроизведения
- Скопируйте reproduce.py в отдельную рабочую папку и откройте встроенным редактором Python FEMM 5. Проверьте заданные параметры и единицы, затем выполните программу целиком. Внешний модуль pyFEMM для этой программы не используется.
- Откройте модель и решение окончательной сетки. Сопоставьте материалы, проводники и границы с теорией. Для осесимметричной задачи найдите ось вращения и объясните, какие поверхности образуются при вращении каждой линии поперечного рисунка.
- Составьте таблицу трёх сеток и приведите независимый расчёт по формуле. Для серии из нескольких геометрий сравнивайте только результаты одной и той же сетки, затем отдельно исследуйте её уточнение. Укажите абсолютные и относительные ошибки с единицами.
- Выполните предложенные варианты по одному. До запуска предскажите знак и примерный масштаб эффекта. В отчёте объясните, какие величины действительно определяет модель, какие параметры остаются предположениями и что пришлось бы уточнить для реального изделия.
Скачайте нужные файлы по отдельным ссылкам ниже в одну рабочую папку, затем откройте reproduce.py во встроенном Python FEMM 5 и выполните его. Сохраните имена файлов и структуру подпапок. Условия конкретного запуска и путь новых результатов указаны в комментариях программы.
Программа встроенного Python FEMM 5
"""Самостоятельная лабораторная работа курса 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"]
Картина рассчитанного поля
Окна FEMM 5
Снимки интерфейса FEMM показывают геометрию, настройки и работу с результатами в FEMM 5. Откройте изображение, чтобы рассмотреть подписи и значения в полном размере.
Результаты и численные проверки
Сохранено решений FEMM: 3. Выполнено проверок: 9. Полная точность чисел и условия сравнения доступны в таблицах результатов JSON.
| Сопротивление, Ом | Масштаб сетки, мм | Мощность области, Вт | Элементов | Контрольные точки | Электроды | Относительная невязка тока | Относительная невязка мощности | Мощность, Вт | Узлов | Модель |
|---|---|---|---|---|---|---|---|---|---|---|
| 117,31861 × 10⁻⁶ | 0,5 | 1 полей; полные данные JSON | 1522 | 2 точек; полные данные JSON | 2 полей; полные данные JSON | 17,220495 × 10⁻⁹ | 17,166485 × 10⁻⁹ | 0,85237971 | 879 | model-01.fec |
| 117,33471 × 10⁻⁶ | 0,25 | 1 полей; полные данные JSON | 4631 | 2 точек; полные данные JSON | 2 полей; полные данные JSON | 5,6539644 × 10⁻⁹ | 2,8602484 × 10⁻⁹ | 0,85226275 | 2505 | model-02.fec |
| 117,38092 × 10⁻⁶ | 0,125 | 1 полей; полные данные JSON | 18190 | 2 точек; полные данные JSON | 2 полей; полные данные JSON | 28,481428 × 10⁻⁹ | 41,893844 × 10⁻⁹ | 0,85192726 | 9460 | model-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 | Выполнен |
| Нижняя оценка последовательных цилиндров | 0 | 1e-06 | Выполнен |
| Верхняя оценка узкого цилиндра | 0 | 1e-06 | Выполнен |
| Сходимость сопротивления растекания | 393,8 × 10⁻⁶ | 0,005 | Выполнен |
Допуски относятся к указанной учебной задаче и способу сравнения. Число показанных цифр не означает такой же физической точности.
Модели, решения и программа
Для нового расчёта сохраните программу, модели и необходимые данные в одну рабочую папку, сохраняя структуру подпапок.
- Файл результатов: README.md8155 байт
- Решение электрического тока: model-01.anc73164 байт
- Модель электрического тока: model-01.fec1168 байт
- Решение электрического тока: model-02.anc231155 байт
- Модель электрического тока: model-02.fec1171 байт
- Решение электрического тока: model-03.anc923834 байт
- Модель электрического тока: model-03.fec1174 байт
- Программа встроенного Python: reproduce.py10629 байт
- Файл результатов: verification.json912 байт
Границы применимости
- Идеальное соединение одного материала без оксидного слоя, микроконтактов, давления и нагрева. Полученное сопротивление относится к геометрическому растеканию, а не к полному сопротивлению реального контакта.
- Проверка относится к данной учебной постановке и решателю FEMM 5.0 06Oct2026. Снимки интерфейса выполняются отдельным согласованным проходом.