C-Shaped Magnet

Lesson 2: Creating a new problem

Here we will continue with C-Shaped magnet described in the Lesson 1. To go further, now we consider all the geometric properties as variable. Our model becomes fully parametric. You may download Lesson's 2 files

The discussion is divided into six steps:

   Step 1: Problem formulation

You can download the Visual Basic version of this sample Lesson2.zip. Even if you don't have Visual Basic 6.0 installed you can run the compiled example Lesson2.exe for better understanding the following text.

There is also VBA (MS Excel) version. You can download it here.

Opening this Excel Workbook please choose Enable Macros when asked. You can view the code by choosing Tools->Macro->Visual Basic Editor command or simply press Atl+F11.

Step2: Program structure

In the following chapters will discuss the Visual Basic (VB) version of our small project. It contains a dialog form as a user interface. Almost all issues discussed here are still valid if you use Visual Basic for Application (VBA) as a development platform. The only difference are user interface issues, but they are not in the focus of our discussion.

The VB project consists of two modules: the form module MainDialog a the standard module called Simulation. The last one contains almost all procedures except the user interface tasks. It begins with the declaration of some common variables:

' Set of geometric dimensions
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
' Magnet coercive force
Public Hc As Double

' Stored QF pictures to show in the MainDialog
Public geomPicture As StdPicture
Public resultPicture As StdPicture
Public sketchPicture As StdPicture

Private Const PI As Double = 3.1415926

' The most common QuickField objects: Application and Problem
Private QF As QuickField.Application
Private prb As QuickField.Problem

The Calculate button on the main application form calls the DoCalculation procedure:

Public Sub DoCalculation()
    Dim force As Double

    Set QF = CreateObject("QuickField.Application")
    QF.DefaultFilePath = VB.App.Path & "\Magn1"
    CreateProblem    ' Create a new problem: See Step1
    BuildGeometry    ' Building a geometry model: See Step2
    SetData          ' Defining physical properties : See step 3
    Solve            ' Solve the problem

    ViewResults      ' View and save the field picture
    force = CalculateForce()        ' Calculate mechanical force
    MainDialog.DisplayForce force   '     and display it

    QF.Quit           ' Finish the QuickField
    Set QF = Nothing
End Sub

Concerning the internal structure of our small application, we will learn following things:

Step 3: Creating a new QuickField problem.

 Creating of the Problem is very straightforward task: first we add a new empty problem by the Add method of the Problems collection, and than set desired properties for it. The last thing we have to do with new problem is saving it to the disk file. Newly created problem does not have a file name, so we should use SaveAs method.

Sub CreateProblem()
    Set prb = QF.Problems.Add   
    With prb                    
        .ProblemType = qfMagnetostatics
        .Class = qfPlaneParallel
        .LengthUnits = qfCentimeters
        .Coordinates = qfCartesian
        .ReferencedFile(qfModelFile) = "Magn1.mod"
        .ReferencedFile(qfDataFile) = "Magn1.dms"
        .SaveAs "Magn1.pbm" 'Save the new problem
    End With
End Sub


' Creates the new problem
' and set its prOperties:
'     Problem type
'     Coordinate system class
'     Lenght units
'     Coordinate system
'     Files referenced by problem:
'   
' Save the problem document


Step 4: Building a geometric model.

First let us declare some variables that we use working with the geometric model:

Sub BuildGeometry()
    Dim mdl As QuickField.Model        ' The model document
    Dim shp As QuickField.ShapeRange   '
Temporary collection of geometric objects
    Dim yBase As Double               
' Some geometric values
    Dim xBase As Double

End Sub

Than we create a new empty model document and just save it. For saved document we use the filename  that the problem refers to. So, just after saving QuickField establishes the link between problem and model documents. The benefit of establishing the link early is that the problem setting for coordinates and length unit will be applied to the model automatically.

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

Now we are ready to start building the edges constituting our model. The AddEgde method we will use belongs to the Shapes collection, so it seems convenient to place all these commands to the With..End With brackets:

Sub BuildGeometry()
    ........
    Set mdl = QF.Models.Add
    mdl.SaveAs prb.ReferencedFile(qfModelFile)
    With mdl.Shapes
        ' the Yoke
            ........
        ' Right Magnet
            ........
        ' Left Magnet
            ........
        ' Steel Keeper
            ........
        ' Surrounding Air
            ........
        ' Set Mesh Spacing
            ........
        ' Generate the Mesh
           
.BuildMesh
    End With
    ' Store the picture to show in the MainDialog
        ..........
    ' Save the complete model
   mdl.Save
End Sub

The comments (in green) put into the With block outline our next work with several parts of model geometry. When finish with building the model we will get the model picture to show it on the main screen (see discussion later) and save the model document again, this time by simple Save method.

Now consider building of the steel yoke block. We use here the geometric dimension variables, declared at the global level and set by user in the MainDialog form (see Declarations section)

With mdl.Shapes
   ' The Yoke

   Set shp = .AddEdge(QF.PointXY(-yokeWidth / 2, 0),	_
       QF.PointXY(yokeWidth / 2, 0))
   .AddEdge QF.PointXY(yokeWidth / 2, 0), _
       QF.PointXY(yokeWidth / 2, yokeHeight)
   .AddEdge QF.PointXY(yokeWidth / 2, yokeHeight), _
       QF.PointXY(-yokeWidth / 2, yokeHeight)
   .AddEdge QF.PointXY(-yokeWidth / 2, yokeHeight), _
       QF.PointXY(-yokeWidth / 2, 0)
   shp.Left.Label = "Steel"
   ..........

There is the sequence of four AddEgde command builds a rectangular block. Please note the way used to assign label to the block: we remember an edge (generally speaking, group of edges) built by the first command in a shp variable. When the block is completely built, we can refer to it as a block, located from the left side of the shp edge by the shp.Left property. The Label property is used to assigning label to the block.

All other rectangular blocks are built in a similar way. Please note that the left magnet block is build in such a way that it located from the right (not left) side of the edge.

   ' The Yoke
    ..............

    ' Right Magnet
    Set shp = .AddEdge(QF.PointXY(yokeWidth / 2, yokeHeight), _
       QF.PointXY(yokeWidth / 2, yokeHeight + magnetHeight))
    .AddEdge QF.PointXY(yokeWidth / 2, yokeHeight + magnetHeight), _
       QF.PointXY(yokeWidth / 2 - maghetWidth, yokeHeight + magnetHeight)
    .AddEdge QF.PointXY(yokeWidth / 2 - maghetWidth,	yokeHeight + magnetHeight), _
       QF.PointXY(yokeWidth / 2 - maghetWidth, yokeHeight)
    shp.Left.Label = "ALNICO down"

    ' Left Magnet
    Set shp = .AddEdge(QF.PointXY(-yokeWidth / 2, yokeHeight), _
       QF.PointXY(-yokeWidth / 2, yokeHeight + magnetHeight))
    .AddEdge QF.PointXY(-yokeWidth / 2, yokeHeight + magnetHeight), _
       QF.PointXY(-yokeWidth / 2 + maghetWidth, yokeHeight + magnetHeight)
    .AddEdge QF.PointXY(-yokeWidth / 2 + maghetWidth, yokeHeight + magnetHeight), _
       QF.PointXY(-yokeWidth / 2 + maghetWidth, yokeHeight)
    shp.Right.Label = "ALNICO up"

    ' Steel Keeper
    yBase = yokeHeight + magnetHeight + airGap
    Set shp = .AddEdge(QF.PointXY(-keeperWidth / 2, yBase), _
       QF.PointXY(keeperWidth / 2, yBase))
    .AddEdge QF.PointXY(keeperWidth / 2, yBase), _
       QF.PointXY(keeperWidth / 2, yBase + keeperHeight)
    .AddEdge QF.PointXY(keeperWidth / 2, yBase + keeperHeight), _
       QF.PointXY(-keeperWidth / 2,	yBase + keeperHeight)
    .AddEdge QF.PointXY(-keeperWidth / 2, yBase + keeperHeight), _
       QF.PointXY(-keeperWidth / 2,	yBase)
    shp.Left.Label = "Steel Keeper"

    ' Surrounding air
    yBase = yokeHeight +	magnetHeight + airGap + keeperHeight
    xBase = (yokeWidth + keeperWidth) * 1.5
    Set shp = .AddEdge(QF.PointXY(-xBase, -yBase), QF.PointXY(xBase, -yBase))
    .AddEdge QF.PointXY(xBase, -yBase), QF.PointXY(xBase, 2 * yBase)
    .AddEdge QF.PointXY(xBase, 2 * yBase), QF.PointXY(-xBase, 2 * yBase)
    .AddEdge QF.PointXY(-xBase, 2 * yBase), QF.PointXY(-xBase, -yBase)
    shp.Left.Label = "Air"
    .Boundary(qfOuterOnly).Label = "Zero"

Please note the last line of code for the surrounding air block. It refers to edges constitute the outer border of the area by Boundary property of the Shapes object. We need to assign them a Zero label.

Our next step is to set the mesh spacing values to some vertices and that build the mesh. For simplicity sake we employ a straightforward strategy: assign a small spacing value to all corners of the steel parts and a big spacing (four times bigger than small one) to the outer corners. The auxiliary function fMin returns the smaller of its argument.

When the mesh spacing is set the mesh itself is generated by a single command: BuildMesh method of the Shapes object.

    With mdl.Shapes
        ............
        ' Set Mesh Spacing
       
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
       
        ' Generate the mesh
       
.BuildMesh
   
EndWith

Now we have to do only the auxiliary task: store a picture of the geometric model to show it later in the picture box on the MainDialog form. To do it we get a ModelWindow object (win variable) represents the MDI window shows the geometric model. Then we try to make it form close to a square, because our target picture box control is of square form. It is rather hard to do that exactly because we need to know the size of window client area, whereas Width and Height properties of the ModelWindow object returns the size of the whole window.

The Zoom method of the ModelWindow with no parameters sets the scale to view the whole model. Than we can use the GetPicture method that puts the picture to the Windows clipboard like the Edit->Copy command does. The next line of code gets a picture from the clipboard and stores it in the geomPicture object (declared as StdPicture)

    ..........
    ' Store the picture to show in the MainDialog
   
Dim win As QuickField.ModelWindow
    Set win = mdl.Windows(1)
    win.WindowState = qfNormal
    win.Height = win.Width + 10
    win.Zoom
    win.GetPicture
    Set geomPicture = Clipboard.GetData

    ' Save the complete model
   mdl.Save
End Sub

Now the geometric model is ready for further using.

Step 5: Working with data labels.

Our current task is to set appropriate physical values to all the labels we have defined in the model. First have a look at the whole procedure below, and than we discuss some important points.

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

    ' First set properties for block labels
    Dim cntBlock As QuickField.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 QuickField.Spline
            Set spl = cntBlock.CreateBHCurve
            spl.Add QF.PointXY(0.73, 400)
            spl.Add QF.PointXY(0.92, 600)
            spl.Add QF.PointXY(1.05, 800)
            spl.Add QF.PointXY(1.15, 1000)
            spl.Add QF.PointXY(1.28, 1400)
            spl.Add QF.PointXY(1.42, 2000)
            spl.Add QF.PointXY(1.52, 3000)
            spl.Add QF.PointXY(1.58, 4000)
            spl.Add QF.PointXY(1.6, 6000)
            cntBlock.Spline = spl

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

    ' The only edge label
    Dim cntEdge As QuickField.LabelEdgeMS
    Set cntEdge = prb.Labels(qfEdge).Item("Zero").Content
    cntEdge.Dirichlet = 0
    prb.Labels(qfEdge).Item("Zero").Content = cntEdge

    ' Saving data document
    prb.DataDoc.Save
End Sub

To look through the block labels defining in the model we employ the Visual Basic For Each loop. There are several way to get the label collection: from the Problem object or from the DataDoc object, represents the QuickField data document (as you remember, with a problem can be associated up to two data documents). In our case, the data document is now empty, and the only way to get the collection of labels is the Labels property of the Problem object. It is used with parameter of QfShapes type denoted which of three collection we want to iterate.

Please not that VB requires the loop variable elem in the For Each construction being of Variant type. So, after receiving the next element from the collection we cast it to the Label type by assigning it to the lab variable. All operations with the content of an individual label we do with an object, accessible by the Content property of the Label object. Exact type of that object depends upon problem and label types as described in the DataDoc topic. When finished with the Content, we must put it back to the parent Label by assigning it to the Label's Content property.

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

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

Now consider the parameter setting for each individual labels. The most simple looks code for linear media, like Air label:

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

Here we have to assign the relative magnetic permeability value to both components of the permeability tensor. An optional parameter of the Kxx (and Kyy) property allows to deal also with absolute values of permeability.

Working with a label describing the saturated media, we have to create and define a Spline object.

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

The label is empty now, so we have to first create a new spline by the CreateBHCurve method. Than we add each spline node individually by the Add method and finally assign the spline to the label's content.

With a non-linear permanent magnet we should also set the coercive force value. On the code fragment below we first set the coercive force value and then add the spline nodes. In this case we must subtract the coercive force from the magnetic field value to put the curve into the second quadrant.

It is possible first to define the curve, located in the first quadrant, and then set the coercive force.

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

It is possible first to define the curve, located in the first quadrant, and then set the coercive force.

With edge label we do not need to look through the edge labels collection because we have only one edge label. The following code puts zero Dirichlet condition to the "Zero" label:

' The only edge label
Dim cntEdge As QuickField.LabelEdgeMS
Set cntEdge = prb.Labels(qfEdge).Item("Zero").Content
cntEdge.Dirichlet = 0
prb.Labels(qfEdge).Item("Zero").Content = cntEdge

The Dirichlet property set and returns Dirichlet boundary condition that do not depends upon coordinates. If you nee to set Dirichlet condition as a linear function of coordinates, please you the DirichletLinear property.

The last line of the SetData procedure saves the modified data document. It has its file name assigned when the problem was created, so we only need a Save method.

Step 6: Analyzing the solution

When the model and data are ready, we are able to solve a problem and start to analyze result:

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

The AnalyzeResults method loads the solution for analyzing and creates the first FieldWindow object, represents a field picture window. After that we can get the Result object, which gives access to analyzing capabilities.

In our program we have to do two tasks with the problem solution: get the field picture and calculate the force acting to the steel keeper. We get the field picture in a very similar way that we have used for the geometric model picture:

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

    Dim win As QuickField.FieldWindow
    ' Get the field picture window
    Set win = res.Windows(1)
    ' Set the window form close to a square
    win.WindowState = qfNormal
    win.Height = win.Width + 10
    ' Set view zoom to see the whole model
    win.Zoom
    ' Put the picture into the clipboard
    win.GetPicture
    ' Store the copied picture
    Set resultPicture = Clipboard.GetData
End Sub

To calculate the mechanical force we have to create a contour surrounding the keeper's body. Each FieldWindow object always possesses only one the Contour, even an empty one. We get the Contour object from our FieldWindow object called win and use its AddLineTo method to add lines to the contour. The last AddLineTo call with no parameters builds a line, connecting the last contour point with its starting point.

When the closed contour oriented counter clockwise is ready, we can use the GetIntegral method of the Result object to calculate desired integral value. The force value is really a vector quantities, so being interested only in its absolute value we use its Abs property.

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

    Dim win As QuickField.FieldWindow
    Set win = res.Windows(1)
    With win.Contour
        .AddLineTo QF.PointXY(-keeperWidth / 2 - maghetWidth, _
            yokeHeight + magnetHeight + airGap / 2)
        .AddLineTo QF.PointXY(keeperWidth / 2 + maghetWidth, _
            yokeHeight + magnetHeight + airGap / 2)
        .AddLineTo QF.PointXY(keeperWidth / 2 + maghetWidth, _
            yokeHeight + magnetHeight + airGap + keeperHeight * 1.5)
        .AddLineTo QF.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

What's More

This very simple application can be considered as an prototype for more developed custom project intended to automate analyzing of some parameterized model. To do it more realistic we obviously have to develop some code for validation user's input. Probably it will be useful to allow the customer to build his or her model from some parameterized "building blocks", implement some methods for optimization an much more.