Продолжаем работу с задачей "Подковообразный магнит", начатую в 1-м уроке. Рассмотрим теперь все геометрические свойства, как параметры. Таким образом, наша модель становится полностью параметрической.
Весь урок можно разделить на несколько шагов:
Шаг 1: Постановка задачи
Изучение свойства задачи и эскиз модели.
Шаг2: Структура программы
Обсуждение структуры программы, интерфейса и общих переменных.
Шаг 3: Создание новой задачи
Создание новой задачи и описание её свойств.
Шаг 4: Построение геометрической модели.
Создание новой геометрической модели, присвоение меток блокам и ребрам, построение сетки конечных элементов, сохранение файла.
Шаг 5: Работа с физическими свойствами и метками.
Задание источников поля, граничных условий и свойств сред для каждой метки.
Шаг 6: Анализ результатов
Решение задачи и вычисление силы, действующей на стальной якорь.
Что дальше:
Как расширить возможности программы.

Пример, написанный на 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).
Всё о чем рассказывается на этом шаге, относится как к 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
Теперь займемся программированием внутренней структуры нашего приложения:
Создать задачу очень просто: сначала добавляется новая пустая задача, используя метод 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
Опишем переменные, используемые при работе с геометрической моделью:
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
Теперь геометрическая модель готова для дальнейшего использования.
Наша текущая задача это установить надлежащие физические данные для всех меток, заданных в модели.
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.
Когда модель и данные готовы, можно решить задачу и начать анализ результатов:
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
Наше простое приложение может быть рассмотрено как прототип для более развитого проекта, включающего автоматизированный анализ нескольких параметрических моделей. Потребуется разработать код для проверки параметров, введенных пользователем. Вероятно, будет полезно позволить пользователям построить собственную модель из нескольких параметризованных "конструктивных блоков", встроить некоторые методы оптимизации и многое другое.