FEMM 5

Электрическое поле тока · Плоскопараллельная

Проводимость анизотропной плёнки

Квадрат 20 × 20 мм, толщина 0.1 мм; короткие электроды у противоположных углов. Главные оси проводимости совпадают с осями модели.

Цель работы — связать полевое распределение с измеряемой величиной, получить проверяемый численный результат и объяснить границы выбранной модели. Все параметры заданы непосредственно в программе. Собственная постановка содержит физический ориентир и критерии приёмки, поэтому результат можно исследовать без внешней библиотеки готовых решений.

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

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

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

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

Теория и контрольная модель

В ориентированной плёнке отношение плотности тока к напряжённости зависит от направления. Закон Ома записывается тензором; при совпадении главных осей с координатами достаточно двух проводимостей σx и σy. Короткие электроды создают обе компоненты поля, поэтому заменить материал одним средним числом нельзя.

Независимая проверка использует преобразование координат x′=x√(σy/σx). Одновременно берётся изотропная проводимость √(σxσy). Такое преобразование сохраняет потенциалы электродов и суммарный ток при неизменной толщине. Программа отдельно строит и решает обе задачи на трёх уровнях сетки, сравнивая ток на выводе.

\mathbf J=\operatorname{diag}(\sigma_x,\sigma_y)\mathbf E,\quad x^{\prime}=x\sqrt{\sigma_y/\sigma_x}

Как читать расчёт и проверки

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

Для электрических моделей отдельно проверяются баланс токов на выводах и равенство суммарных джоулевых потерь и активной мощности на выводах. Для тепловых задач используются аналитический профиль, эквивалентное координатное преобразование или интегральный баланс источников и теплоотдачи. Эти проверки отвечают на разные вопросы: совпадение одной температуры или одного тока само по себе не подтверждает правильность всей постановки.

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

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

Проверьте предел σx=σy. Координатное преобразование должно стать тождественным, а обе контрольные модели — описывать одну задачу. Затем измените отношение проводимостей и сравните сходимость интегрального сопротивления с локальной плотностью тока.

Увеличьте длину обоих электродов до полного торца. При расположении электродов слева и справа решение становится одномерным и зависит только от σx. Этот предельный опыт проверяет назначение главных осей лучше, чем просмотр цветной карты.

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

Квадрат 20 × 20 мм, толщина 0.1 мм; короткие электроды у противоположных углов. Главные оси проводимости совпадают с осями модели.

Величины, единицы и происхождение параметров
ПараметрЗначениеЕдиницаПроисхождение
σx100000См/мПринято в учебной постановке
σy10000См/мПринято в учебной постановке
Толщина0,1ммПринято в учебной постановке
Напряжение0,01ВПринято в учебной постановке

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

  1. Скопируйте reproduce.py в отдельную рабочую папку и откройте его во встроенном редакторе Python FEMM 5. Запускайте файл целиком. Программа сама создаёт новый каталог calculated-XX рядом с файлом, поэтому повторная серия сохраняет предыдущие модели и результаты.
  2. До расчёта прочитайте параметры и единицы. После расчёта откройте последнюю входную модель и соответствующее решение. Сопоставьте назначение русских имён с физическим смыслом, определите путь тока или теплового потока и найдите величины, используемые в контрольной формуле.
  3. Сравните все три записи records и убедитесь, что каждый пункт checks имеет passed=true. Уточните различие между ошибкой аналитического сравнения, изменением по сетке, невязкой баланса и итерационным остатком. Для модели с координатным преобразованием откройте также отдельную контрольную геометрию.
  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", []):
        globals()[prefix + "_addnode"](x1, y1)
        globals()[prefix + "_addnode"](x2, y2)
        globals()[prefix + "_addsegment"](x1, y1, x2, y2)
    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, group)
        globals()[prefix + "_clearselected"]()


def current(job, mesh, stem):
    newdocument(3)
    ci_probdef("millimeters", "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", "planar", 1e-10, job["depth"], 30)
    for name, kx, ky, q in job["materials"]:
        hi_addmaterial(name, kx, ky, q, 0)
    for name, kind, temp, ambient, h in job["boundaries"]:
        hi_addboundprop(name, kind, temp, 0, ambient, h, 0)
    geometry("hi", job, mesh)
    for x, y, name in job.get("boundary_segments", []):
        hi_selectsegment(x, y)
        hi_setsegmentprop(name, 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"):
        check(
            stem + ": баланс конвективного теплоотвода",
            abs(convection - generated) / max(generated, 1e-30),
            0.005,
        )
    ho_close()
    hi_close()
    return row


JOB = json.loads(
    '{"polygon": [[0, 0], [20, 0], [20, 15], [20, 20], [0, 20], [0, 5]], "depth": 0.1, "materials": [["Ортотропная плёнка", 100000.0, 10000.0]], "blocks": [[10, 10, "Ортотропная плёнка", 1]], "terminals": [["Питание", 0.01, 0, 1], ["Возврат", 0, 0, 1]], "electrode_segments": [[0, 2.5, "Питание"], [20, 17.5, "Возврат"]], "samples": [[10, 10]], "interfaces": []}'
)
for level, mesh in enumerate((0.5, 0.25, 0.125)):
    original = current(JOB, mesh, "anisotropic-%02d" % (level + 1))
    alpha = math.sqrt(1e4 / 1e5)
    equivalent = json.loads(json.dumps(JOB))
    equivalent["polygon"] = [[x * alpha, y] for x, y in JOB["polygon"]]
    equivalent["blocks"] = [
        [x * alpha, y, name, group] for x, y, name, group in JOB["blocks"]
    ]
    equivalent["electrode_segments"] = [
        [x * alpha, y, name] for x, y, name in JOB["electrode_segments"]
    ]
    equivalent["samples"] = [[x * alpha, y] for x, y in JOB["samples"]]
    equivalent["materials"] = [["Ортотропная плёнка", math.sqrt(1e9), math.sqrt(1e9)]]
    reference = current(equivalent, mesh * alpha, "isotropic-%02d" % (level + 1))
    i = abs(original["terminals"]["Питание"]["I_A"])
    original["R_ohm"] = 0.01 / i
    original["transformed_relative_error"] = abs(
        i / abs(reference["terminals"]["Питание"]["I_A"]) - 1
    )
    records.append(original)
check(
    "Эквивалентное преобразование анизотропной области",
    records[-1]["transformed_relative_error"],
    0.005,
)
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"]

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

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

Окна FEMM 5

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

Рабочая область модели «Проводимость анизотропной плёнки». Нативный препроцессор FEMM 5.
Рабочая область модели «Проводимость анизотропной плёнки». Нативный препроцессор FEMM 5. Открыть в полном размере
Сохранённое решение «Проводимость анизотропной плёнки». Модуль потенциала |V|, В, и эквипотенциали ортотропной проводящей плёнки при постоянном токе.
Сохранённое решение «Проводимость анизотропной плёнки». Модуль потенциала |V|, В, и эквипотенциали ортотропной проводящей плёнки при постоянном токе. Открыть в полном размере

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

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

Результаты расчётов и производные итоги
Сопротивление, ОмМасштаб сетки, ммМощность области, ВтОтносительная ошибка преобразованияЭлементовКонтрольные точкиЭлектродыОтносительная невязка токаОтносительная невязка мощностиМощность, ВтУзловМодель
0,808255120,51 полей; полные данные JSON0,003963190692591 точек; полные данные JSON2 полей; полные данные JSON640,79393 × 10⁻¹²87,212975 × 10⁻¹²123,72331 × 10⁻⁶4774anisotropic-01.fec
0,808359130,251 полей; полные данные JSON0,0042314306149531 точек; полные данные JSON2 полей; полные данные JSON1,3452951 × 10⁻⁹280,65902 × 10⁻¹²123,70739 × 10⁻⁶7657anisotropic-02.fec
0,809983440,1251 полей; полные данные JSON0,0042692028594941 точек; полные данные JSON2 полей; полные данные JSON5,3130941 × 10⁻⁹214,81083 × 10⁻¹²123,45931 × 10⁻⁶30094anisotropic-03.fec
Проверки опубликованного расчёта
КритерийПолученоДопуск или ожидаемое значениеИтог
anisotropic-01: баланс токов640,8 × 10⁻¹²1e-06Выполнен
anisotropic-01: баланс мощности87,21 × 10⁻¹²1e-06Выполнен
isotropic-01: баланс токов677,4 × 10⁻¹²1e-06Выполнен
isotropic-01: баланс мощности14,83 × 10⁻¹²1e-06Выполнен
anisotropic-02: баланс токов1,345 × 10⁻⁹1e-06Выполнен
anisotropic-02: баланс мощности280,7 × 10⁻¹²1e-06Выполнен
isotropic-02: баланс токов502,1 × 10⁻¹²1e-06Выполнен
isotropic-02: баланс мощности3,555 × 10⁻⁹1e-06Выполнен
anisotropic-03: баланс токов5,313 × 10⁻⁹1e-06Выполнен
anisotropic-03: баланс мощности214,8 × 10⁻¹²1e-06Выполнен
isotropic-03: баланс токов542,5 × 10⁻¹²1e-06Выполнен
isotropic-03: баланс мощности2,897 × 10⁻⁹1e-06Выполнен
Эквивалентное преобразование анизотропной области0,0042690,005Выполнен
Сходимость сопротивления0,0020090,005Выполнен

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

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

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

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

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