Введение в ActiveField
Обзор объектов
Диаграмма иерархии
С чего начать
Работа с задачами
Работа с моделью
Работа с данными
Анализ результатов

Подковообразный магнит

Урок 2: Создание новой задачи

Продолжаем работу с задачей "Подковообразный магнит", начатую в 1-м уроке. Рассмотрим теперь все геометрические свойства, как параметры. Таким образом, наша модель становится полностью параметрической.

Весь урок можно разделить на несколько шагов:

Шаг 1: Постановка задачи

Пример, написанный на Visual Basic, находится в папке ../ActiveField/Tutorial/Lesson2/VB_Code/... Даже если у Вас нет Visual Basic 6.0, можно запустить работающий файл Lesson2.exe для лучшего понимания текста.

Есть также пример на VBA (MS Excel), который находится в папке ../ActiveField/Tutorial/Lesson2/VBA_Code/Magn1_L2.xls

При открытии документа, программа спросит, разрешить ли выполнение макросов в документе или нет. Пожалуйста, разрешите выполнение макросов, чтобы запустить встроенную в документ программу. Текст программы можно посмотреть, войдя в редактор макросов (комманда Tools->Macro->Visual Basic Editor).

Шаг2: Структура программы

Всё о чем рассказывается на этом шаге, относится как к Visual Basic (VB) так и к Visual Basic for Applications (VBA). Единственная разница состоит в интерфейсах пользователя.

Проект на VB состоит из двух модулей: формы MainDialog и стандартного модуля Simulation. Стандартный модуль содержит почти все процедуры, кроме тех, которые относятся к интерфейсу пользователя. Он начинается с описания некоторых общих переменных:

' Набор геометрических размеров
Public yokeWidth As Double
Public yokeHeight As Double
Public maghetWidth As Double
Public magnetHeight As Double
Public keeperWidth As Double
Public keeperHeight As Double
Public airGap As Double
' Коэрцитивная сила магнита
Public Hc As Double

' Сохраненная картинка из ELCUT для показа в MainDialog
Public geomPicture As StdPicture
Public resultPicture As StdPicture
Public sketchPicture As StdPicture

Private Const PI As Double = 3.1415926

' Самые общие объекты ELCUT: Application и Problem
Private ELC As ELCUT.Application
Private prb As ELCUT.Problem

Нажатие на кнопке Calculate в окне приложения вызывает процедуру DoCalculation:

Public Sub DoCalculation()
    Dim force As Double

    Set ELC = CreateObject("ELCUT.Application")
    ELC.DefaultFilePath = VB.App.Path & "\Magn1"
    CreateProblem    ' Создание новой задачи: см. шаг 1
    BuildGeometry    ' Построение геометрической модели: см. шаг 2
    SetData          ' Задание физических свойств: см. шаг 3
    Solve            ' Решение задачи

    ViewResults      ' Просмотр и сохранение картины поля
    force = CalculateForce()        ' Вычисление силы
    MainDialog.DisplayForce force   '     и показ её

    ELC.Quit           ' Завершение работы с ELCUT
    Set ELC = Nothing
End Sub

Теперь займемся программированием внутренней структуры нашего приложения:

Шаг 3: Создание новой задачи.

Создать задачу очень просто: сначала добавляется новая пустая задача, используя метод Add коллекции Problems, потом устанавливаются свойства новой задачи. После этого задача сохраняется на диск. У только что созданной задачи нет имени, поэтому при сохранении надо использовать метод SaveAs.

Sub CreateProblem()
    Set prb = ELC.Problems.Add    ' Создание новой задачи
    With prb                    ' и настройка её свойств:
        .ProblemType = qfMagnetostatics ' тип задачи
        .Class = qfPlaneParallel ' класс модели
        .LengthUnits = qfCentimeters ' единицы длины
        .Coordinates = qfCartesian ' система координат
        .ReferencedFile(qfModelFile) = "Magn1.mod" ' Файлы, используемые в задачи: модель
        .ReferencedFile(qfDataFile) = "Magn1.dms" ' Файлы, используемые в задачи: свойства метериалов
        .SaveAs "Magn1.pbm" ' Сохранение файла задачи
    End With
End Sub

Шаг 4: Построение геометрической модели.

Опишем переменные, используемые при работе с геометрической моделью:

Sub BuildGeometry()
    Dim mdl As ELCUT.Model        ' Документ геометрической модели
    Dim shp As ELCUT.ShapeRange   '
Временная коллекция геометрически объектов
    Dim yBase As Double               
' Некоторые геометрические величины
    Dim xBase As Double

End Sub

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

Sub BuildGeometry()
    .........
    Set mdl = ELC.Models.Add
    mdl.SaveAs prb.ReferencedFile(qfModelFile)
    ........
End Sub

Теперь можно начать строить модель. Метод AddEgde применяется к коллекции (Shapes), поэтому удобно поместть код в блок With..:

Sub BuildGeometry()
    ........
    Set mdl = ELC.Models.Add
    mdl.SaveAs prb.ReferencedFile(qfModelFile)
    With mdl.Shapes
        ' ярмо
            ........
        ' правый магнит
            ........
        ' левый магнит
            ........
        ' стальной якорь
            ........
        ' Окружающий воздух
            ........
        ' Настройка шагов сетки
            ........
        ' Построение сетки
           
.BuildMesh
    End With
    ' Сохранение картинки для показа в MainDialog
        ..........

    ' Сохранение модели
   mdl.Save
End Sub

Закончив с построением геометрической модели, мы получим картинку модели. Покажем картинку в нашей форме. Потом сохраним модель опять, но уже используя метод Save.

Теперь рассмотрим построение стального ярма. Здесь используются переменные, определенные на глобальном уровне в форме MainDialog (смотрите раздел Declarations)

    With mdl.Shapes
        ' ярмо
        Set shp = .AddEdge(ELC.PointXY(-yokeWidth / 2, 0), _
            ELC.PointXY(yokeWidth / 2, 0))
        .AddEdge ELC.PointXY(yokeWidth / 2, 0), _
            ELC.PointXY(yokeWidth / 2, yokeHeight)
        .AddEdge ELC.PointXY(yokeWidth / 2, yokeHeight), _
            ELC.PointXY(-yokeWidth / 2, yokeHeight)
        .AddEdge ELC.PointXY(-yokeWidth / 2, yokeHeight), _
            ELC.PointXY(-yokeWidth / 2, 0)
        shp.Left.Label = "Steel"
        .........
.

Набор из четырех команд AddEgde рисует прямоугольный блок. Чтобы присвоить метку этому блоку, запомним ребро (вообще говоря, группу ребер), нарисованное первой командой, в переменной shp. Когда блок полностью нарисован, можно сослаться на него как на блок, расположенный слева от ребра shp: shp.Left. Свойство Label используется для присвоения метки блоку.

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

       ' ярмо
        ..............


        ' правый магнит
       
Set shp = .AddEdge(ELC.PointXY(yokeWidth / 2, yokeHeight), _
            ELC.PointXY(yokeWidth / 2, yokeHeight + magnetHeight))
        .AddEdge ELC.PointXY(yokeWidth / 2, yokeHeight + magnetHeight), _
            ELC.PointXY(yokeWidth / 2 - maghetWidth, yokeHeight + magnetHeight)
        .AddEdge ELC.PointXY(yokeWidth / 2 - maghetWidth, yokeHeight + magnetHeight), _
            ELC.PointXY(yokeWidth / 2 - maghetWidth, yokeHeight)
        shp.Left.Label = "ALNICO down"

        ' левый магнит
       
Set shp = .AddEdge(ELC.PointXY(-yokeWidth / 2, yokeHeight), _
            ELC.PointXY(-yokeWidth / 2, yokeHeight + magnetHeight))
        .AddEdge ELC.PointXY(-yokeWidth / 2, yokeHeight + magnetHeight), _
            ELC.PointXY(-yokeWidth / 2 + maghetWidth, yokeHeight + magnetHeight)
        .AddEdge ELC.PointXY(-yokeWidth / 2 + maghetWidth, yokeHeight + magnetHeight), _
            ELC.PointXY(-yokeWidth / 2 + maghetWidth, yokeHeight)
        shp.Right.Label = "ALNICO up"

        ' стальной якорь
        yBase = yokeHeight + magnetHeight + airGap
        Set shp = .AddEdge(ELC.PointXY(-keeperWidth / 2, yBase), _
            ELC.PointXY(keeperWidth / 2, yBase))
        .AddEdge ELC.PointXY(keeperWidth / 2, yBase), _
            ELC.PointXY(keeperWidth / 2, yBase + keeperHeight)
        .AddEdge ELC.PointXY(keeperWidth / 2, yBase + keeperHeight), _
            ELC.PointXY(-keeperWidth / 2, yBase + keeperHeight)
        .AddEdge ELC.PointXY(-keeperWidth / 2, yBase + keeperHeight), _
            ELC.PointXY(-keeperWidth / 2, yBase)
        shp.Left.Label = "Steel Keeper"

        ' окружающий воздух
        yBase = yokeHeight + magnetHeight + airGap + keeperHeight
        xBase = (yokeWidth + keeperWidth) * 1.5
        Set shp = .AddEdge(ELC.PointXY(-xBase, -yBase), ELC.PointXY(xBase, -yBase))
        .AddEdge ELC.PointXY(xBase, -yBase), ELC.PointXY(xBase, 2 * yBase)
        .AddEdge ELC.PointXY(xBase, 2 * yBase), ELC.PointXY(-xBase, 2 * yBase)
        .AddEdge ELC.PointXY(-xBase, 2 * yBase), ELC.PointXY(-xBase, -yBase)
        shp.Left.Label = "Air"
        .Boundary(QfOuterOnly).Label = "Zero"

Заметьте, что в последней строчке кода для окружающего воздуха мы ссылаемся на ребра, образующие внешнюю границу области, через свойство Boundary объекта Shapes. Этим ребрам присваиваем метку "Zero".

Наш следующий шаг - задать шаги дискретизации у некоторых вершин и построить сетку. Для простоты будем использовать прямой подход: присвоение маленького шага всем углам стальных частей и большой шаг (в четыре раза больше чем маленький) на внешней границе. Вспомогательная функция fMin возвращает минимум из своих аргументов.

После установки шагов дискретизации, сетка строится методом BuildMesh объекта Shapes.

    With mdl.Shapes
        ............
        ' Устновка шагов дискретизации
        Dim sp As Double
        sp = fMin(magnetHeight, airGap) / 5
        .LabeledAs(Block:="Steel Keeper").Spacing = sp
        .LabeledAs(Block:="Steel").Spacing = sp
        .LabeledAs(Block:="ALNICO up").Spacing = sp
        .LabeledAs(Edge:="ALNICO down").Spacing = sp
        .LabeledAs(Edge:="Zero").Spacing = sp * 4
       
        ' Построение сетки конечных элементов
        .BuildMesh
   
EndWith

Теперь осталось выполнить только вспомогательные задания: сохранить картинку геометрической модели, чтобы показать её позже в главной форме MainDialog. Для этого мы использовали объект ModelWindow (переменная win), представляющий MDI окно, показывающее модель. Потом преобразуем форму окна к квадратной, потому что в нашей форме окно под картинку имеет квадратную форму. Это довольно тяжело сделать точно, т.к. необходимо знать размер клиентской части окна, в то время как свойства Width и Height объекта ModelWindow возвращают размер окна целиком.

Метод Zoom объекта ModelWindow, вызванный без параметров, устанавливает размер изображения так крупно, как это возможно. Потом можно использовать метод GetPicture, который помещает картинку в буфер обмена Windows (аналог команды Правка->Копировать картинку). В следующей строчке кода картинка из буфера обмена копируется в объект geomPicture (описанный как StdPicture)

    ..........
    ' Сохранение картинки для показа в MainDialog
    Dim win As ELCUT.ModelWindow
    Set win = mdl.Windows(1)
    win.WindowState = qfNormal
    win.Height = win.Width + 10
    win.Zoom
    win.GetPicture
    Set geomPicture = Clipboard.GetData

    ' Сохранение всей модели
   mdl.Save
End Sub

Теперь геометрическая модель готова для дальнейшего использования.

Шаг 5: Работа с физическими свойствами и метками.

Наша текущая задача это установить надлежащие физические данные для всех меток, заданных в модели.

Sub SetData()
    Dim lab As ELCUT.Label
    Dim elem As Variant

    ' Установка свойств для меток блоков
    Dim cntBlock As ELCUT.LabelBlockMS
    For Each elem In prb.Labels(qfBlock)
        Set lab = elem
        Set cntBlock = lab.Content
        Select Case lab.Name
        Case "Air"
            cntBlock.Kxx = 1
            cntBlock.Kyy = 1

        Case "Steel", "Steel Keeper"
            Dim spl As ELCUT.Spline
            Set spl = cntBlock.CreateBHCurve
            spl.Add ELC.PointXY(0.73, 400)
            spl.Add ELC.PointXY(0.92, 600)
            spl.Add ELC.PointXY(1.05, 800)
            spl.Add ELC.PointXY(1.15, 1000)
            spl.Add ELC.PointXY(1.28, 1400)
            spl.Add ELC.PointXY(1.42, 2000)
            spl.Add ELC.PointXY(1.52, 3000)
            spl.Add ELC.PointXY(1.58, 4000)
            spl.Add ELC.PointXY(1.6, 6000)
            cntBlock.Spline = spl

        Case "ALNICO up", "ALNICO down"
            If StrComp(lab.Name, "ALNICO up") = 0 Then
                cntBlock.Coercive = ELC.PointRA(Hc, PI / 2)
            Else
                cntBlock.Coercive = ELC.PointRA(Hc, -PI / 2)
            End If
            Set spl = cntBlock.CreateBHCurve
            spl.Add ELC.PointXY(0.24, 27818 - Hc)
            spl.Add ELC.PointXY(0.4, 47748 - Hc)
            spl.Add ELC.PointXY(0.5, 67641 - Hc)
            spl.Add ELC.PointXY(0.6, 93504 - Hc)
            spl.Add ELC.PointXY(0.71, 127324 - Hc)
            spl.Add ELC.PointXY(0.77, 147218 - Hc)
            cntBlock.Spline = spl
        End Select
        lab.Content = cntBlock
    Next

    ' Единственная метка ребра
    Dim cntEdge As ELCUT.LabelEdgeMS
    Set cntEdge = prb.Labels(qfEdge).Item("Zero").Content
    cntEdge.Dirichlet = 0
    prb.Labels(qfEdge).Item("Zero").Content = cntEdge

    ' Сохранение файла описания свойств
    prb.DataDoc.Save
End Sub

Для перебора всех меток блоков мы использовали конструкцию For Each. Есть несколько способов получить коллекцию меток: из объекта Problem или DataDoc, представляющего документ описания физических свойств ELCUT. С задачей может быть связано два документа описания свойств. Документ описаня описания сначала пустой, и единственный способ получить коллекцию меток это использовать свойство Labels объекта Problem. Необходимо также указать параметр qfShapes, определяющий тип коллекции.

Заметьте, что в VB необходимо, чтобы переменная цикла elem в конструкции For Each была типа Variant. Поэтому, после получения элемента коллекции, мы преобразуем её к типу Label путем присвоения переменной lab. Все операции с содержимым отдельной метки производятся через специальный объект, получаемый через свойство Content объекта Label. Точный тип этого объекта зависит от типа метки и типа задачи, как это описано в теме DataDoc. Закончив редактировать содержимое метки (объект Content), необходимо вернуть его обратно объекту Label.

    Dim lab As ELCUT.Label
    Dim elem As Variant
    Dim cntBlock As ELCUT.LabelBlockMS

    For Each elem In prb.Labels(qfBlock)
        Set lab = elem
        Set cntBlock = lab.Content
            ...........
        lab.Content = cntBlock
    Next

Рассмотрим настройки параметров для каждой отдельной метки. Самым простым выглядит код для линейной среды, такой как воздух (метка "Air"):

Case "Air"
    cntBlock.Kxx = 1
    cntBlock.Kyy = 1

Для метки воздуха надо присвоить относительную магнитную проницаемость обеим компонентам тензора проницаемости. Необязательный параметр свойства Kxx (и Kyy) позволяет задавать как абсолютную, так и относительную проницаемости.

Работая с меткой, описывающей нелинейную среду, надо использовать объект Spline.

Case "Steel", "Steel Keeper"
    Dim spl As ELCUT.Spline
    Set spl = cntBlock.CreateBHCurve
    spl.Add ELC.PointXY(0.73, 400)
    spl.Add ELC.PointXY(0.92, 600)
    spl.Add ELC.PointXY(1.05, 800)
    spl.Add ELC.PointXY(1.15, 1000)
    spl.Add ELC.PointXY(1.28, 1400)
    spl.Add ELC.PointXY(1.42, 2000)
    spl.Add ELC.PointXY(1.52, 3000)
    spl.Add ELC.PointXY(1.58, 4000)
    spl.Add ELC.PointXY(1.6, 6000)
    cntBlock.Spline = spl

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

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

Case "ALNICO up", "ALNICO down"
    If StrComp(lab.Name, "ALNICO up") = 0 Then
        cntBlock.Coercive = ELC.PointRA(Hc, PI / 2)
    Else
        cntBlock.Coercive = ELC.PointRA(Hc, -PI / 2)
    End If
    Set spl = cntBlock.CreateBHCurve
    spl.Add ELC.PointXY(0.24, 27818 - Hc)
    spl.Add ELC.PointXY(0.4, 47748 - Hc)
    spl.Add ELC.PointXY(0.5, 67641 - Hc)
    spl.Add ELC.PointXY(0.6, 93504 - Hc)
    spl.Add ELC.PointXY(0.71, 127324 - Hc)
    spl.Add ELC.PointXY(0.77, 147218 - Hc)
    cntBlock.Spline = spl

Можно также сначала задать кривую, расположенную в первом квадранте, а потом коэрцитивную силу.

У нас есть только одна метка ребра, поэтому не надо просматривать коллекцию ребер. Следующий код задает нулевой условие Дирихле метке "Zero":

' Единственная метка ребра
Dim cntEdge As ELCUT.LabelEdgeMS
Set cntEdge = prb.Labels(qfEdge).Item("Zero").Content
cntEdge.Dirichlet = 0
prb.Labels(qfEdge).Item("Zero").Content = cntEdge

Свойство Dirichlet устанавливает и возвращает граничное условие Дирихле, которое не зависит от координат. Если надо задать зависимость от координат, используйте свойство DirichletLinear.

Последняя строчка в тексте процедуры SetData сохраняет измененный файл физических свойств. Файл получает своё имя из задачи, так что мы используем метод Save.

Шаг 6: Анализ результатов

Когда модель и данные готовы, можно решить задачу и начать анализ результатов:

Sub Solve()
    If prb.CanSolve Then prb.SolveProblem
    If prb.Solved Then prb.AnalyzeResults
End Sub

Метод AnalyzeResults загружает решение и создает первое окно картины поля (объект FieldWindow). После этого можно получить объект Result, который представляет собой результат.

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

Sub ViewResults()
    Dim res As ELCUT.Result
    Set res = prb.Result
    If res Is Nothing Then Exit Sub

    Dim win As ELCUT.FieldWindow
    ' Получение окна картины поля
    Set win = res.Windows(1)
    ' Установка формы окна близкой к квадрату
    win.WindowState = qfNormal
    win.Height = win.Width + 10
    ' Установка масштаба изоражения
    win.Zoom
    ' Копирование картинки в буфер обмена
    win.GetPicture
    ' Сохранение скопированной картинки
    Set resultPicture = Clipboard.GetData
End Sub

Чтобы посчитать силу, необходимо создать контур, окружающий тело якоря. Каждое окно картины поля (объект FieldWindow) может содержать только один контур (объект Contour), даже пустой. Получим объект Contour у объекта FieldWindow (переменная win) и используем его метод AddLineTo, чтобы добавить линию к контору. Последний вызов метода AddLineTo без параметров строит линию от конечной точки к начальной.

Когда замкнутый, ориентированный против часовой стрелки, контур готов, можно использовать метод GetIntegral объекта Result для вычисления интегральных величин. Величина силы является векторной величиной. Нас интересует только модуль силы, поэтому мы используем свойство Abs.

Function CalculateForce() As Double
    Dim res As ELCUT.Result
    Set res = prb.Result
    If res Is Nothing Then Exit Function

    Dim win As ELCUT.FieldWindow
    Set win = res.Windows(1)
    With win.Contour
        .AddLineTo ELC.PointXY(-keeperWidth / 2 - maghetWidth, _
            yokeHeight + magnetHeight + airGap / 2)
        .AddLineTo ELC.PointXY(keeperWidth / 2 + maghetWidth, _
            yokeHeight + magnetHeight + airGap / 2)
        .AddLineTo ELC.PointXY(keeperWidth / 2 + maghetWidth, _
            yokeHeight + magnetHeight + airGap + keeperHeight * 1.5)
        .AddLineTo ELC.PointXY(-keeperWidth / 2 - maghetWidth, _
            yokeHeight + magnetHeight + airGap + keeperHeight * 1.5)
        .AddLineTo
    End With
    CalculateForce = res.GetIntegral(qfInt_MaxwellForce).Abs
    win.Contour.Delete True
End Function

Что дальше

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