Tutorial

Mesh generation

A mesh is mainly composed by cells and nodes. in Amaru, a mesh is also composed by surface cells and surface edges and are computed at the time of mesh generation.

Nodes

A node is represented by an instance of the Node type. It contains the node coordinates, a identification number and a tag string that can be used to label a group of nodes.

using Amaru
node = Node(1.0, 2.0, 0.0, id=1, tag="vertex")
Node
    id: 1
    coord: [1.0, 2.0, 0.0]
    tag: "vertex"
    dofs: 0-element Vector{Dof}
    dofdict: OrderedDict{Symbol, Dof} with 0 entries

Cells

A mesh cell is represented by an instace of the Cell type. It is defined by a given shape object, a list of nodes, a identification number an a tag string. The shape object contains information related with the geometry of a finite element. For example, a TRI3 shape represents the shape of a linear triangular finite element.

using Amaru
node1 = Node(0.0, 0.0, id=1)
node2 = Node(1.0, 0.0, id=2)
node3 = Node(1.0, 1.0, id=3)
nodes = [ node1, node2, node3 ]

Cell(TRI3, nodes, id=1, tag="triangle")
Cell
    id: 1
    shape: CellShape  name="TRI3"
    nodes: 3-element Vector{Node}:
        1: Node  id=1
        2: Node  id=2
        3: Node  id=3
    tag: "triangle"
    active: true
    quality: 0.0
    embedded: false
    crossed: false
    owner: nothing
    linked_elems: 0-element Vector{Amaru.AbstractCell}
    env: Amaru.MeshEnv
        ndim: 0

There are many shapes defined in Amaru, for instance: LIN2, LIN3, TRI3, TRI6, QUAD4, QUAD8, QUAD9 TET4, TET10, PYR5, WED6, WED15, HEX8, HEX20, HEX27, etc.

Blocks

Blocks are entities used to aid the generation of structured meshes in 1D, 2D and 3D. A Block type represents a geometric segment, area (4 or 8 point quadrilateral) or volume (8 or 20 point hexahedron). An instance of the block type is generated from a coordinates matrix and predefined number of divisions divisions (nx, ny, nz) in the $x$, $y$ and $z$ directions of a local system. The block below represents a 2D Block with nx=7 and ny=5. The mplot function saves the block image to a file in svg format. Other formats as pdf and png can be used.

using Amaru
block = Block([0 0; 4 0; 3 2; 1 2], nx=7, ny=5)
mplot(block, "block.svg", markers=true)

Mesh plotting

  Optional arguments:

    axis, lw, markers, nodelabels, celllabels, opacity, field,
     fieldmult, fieldlims, vectorfield, arrowscale, colormap, colorbarscale,
     colorbarlabel, colorbarlocation, colorbarorientation, colorbarpad,
     warpscale, hicells, elev, azim, dist, outline, outlineangle,
     figsize, leaveopen, quiet

  Available node fields:

    node-id

  Available element fields:

    quality, elem-id, cell-type, tag

  file block.svg saved

Blocks can be combined to define a complex geometry. To manipulate blocks, Amaru provides several operators as copy, move!, scale!, rotate, mirror, array, polar, extrude, etc.

For example, let's generate a square mesh with a central hole. We start with a quadratic quadrilateral block. Note that the vertex coordinates follow the same numbering as conventional finite elements. Thus, the corner nodes are listed first, and then the middle nodes. All the nodes are listed couterclockwise.

using Amaru

block1 = Block(
    [
        0.0      0.0
        0.7643   0.7643
        0.6667   1.0
        0.0      1.0
        0.38215  0.38215
        0.692    0.8724
        0.3333   1.0
        0.0      0.5
    ],
    cellshape=QUAD8, nx=5, ny=5
)
mplot(block1, "block1.svg", markers=true)

Mesh plotting

  Optional arguments:

    axis, lw, markers, nodelabels, celllabels, opacity, field,
     fieldmult, fieldlims, vectorfield, arrowscale, colormap, colorbarscale,
     colorbarlabel, colorbarlocation, colorbarorientation, colorbarpad,
     warpscale, hicells, elev, azim, dist, outline, outlineangle,
     figsize, leaveopen, quiet

  Available node fields:

    node-id

  Available element fields:

    quality, elem-id, cell-type, tag

  file block1.svg saved

A mirror operation can be applyed to this block to get a second one. Then, both blocks can be grouped in an array for further manipulation.

block2 = mirror(block1, base=[0, 0], axis=[0.7643, -0.7643])
blocks = [block1, block2]
mplot(blocks, "blocks.svg", markers=true)
nothing # hide

The center of the circle that contains the arc is located at the coordinates (1,1). To place the center at the origin (0,0) we perform a move! operation.

move!(blocks, dx=-1.0, dy=-1.0)
mplot(blocks, "moved.svg", markers=true, axis=true)
nothing # hide

Next, we can apply a polar operation to the current geometry to obtain the square region with a central hole. Note that the original geometry was replicated four times (n=4) with different rotation angles. For this purpose a rotation axis and a base point were employed.

hole = polar(blocks, base=[0, 0], axis=[0, 0, 1], angle=360, n=4)
mplot(hole, "hole.svg", markers=true)
nothing # hide

Then, an extrude operation is used to obtain a volume following a defined axis and length. The argument n=4 represents the number of divisions intended along the new dimension.

solid = extrude(hole, axis=[0, 0, 1], length=1, n=4)
mplot(solid, "solid.svg", markers=true, dist=13)
nothing # hide

Note that, the solid variable is an array with four blocks, as shown in the figure. This array is used to generate the structured mesh.

mesh = Mesh(solid)
mplot(mesh, "mesh.svg", dist=13)
nothing # hide

Finally, a smooth operation can be applied to improve the cells quality.

smooth_mesh = smooth!(mesh)
mplot(smooth_mesh, "smooth_mesh.svg", dist=13)
nothing # hide

Mesh generation examples

Below are presented some examples of structured mesh generation using Amaru.

Simple 2D mesh

using Amaru

block = Block([0 0 ; 2 2], nx=8, ny=6)
mesh  = Mesh(block)
mplot(mesh, "mesh-quad4.svg", markers=true)
Mesh generation:
      1 blocks
  spliting block 1...    
  finding facets...   
     2d mesh
     63 nodes
     48 cells
     28 faces
     28 surface edges

Mesh plotting

  Optional arguments:

    axis, lw, markers, nodelabels, celllabels, opacity, field,
     fieldmult, fieldlims, vectorfield, arrowscale, colormap, colorbarscale,
     colorbarlabel, colorbarlocation, colorbarorientation, colorbarpad,
     warpscale, hicells, elev, azim, dist, outline, outlineangle,
     figsize, leaveopen, quiet

  Available node fields:

    node-id

  Available element fields:

    quality, elem-id, cell-type, tag

  file mesh-quad4.svg saved

Mesh with quadratic cells

using Amaru

block = Block([0 0 ; 2 2], nx=8, ny=6, cellshape=QUAD8)
mesh  = Mesh(block)
mplot(mesh, "mesh-quad8.svg", markers=true)
Mesh generation:
      1 blocks
  spliting block 1...    
  finding facets...   
     2d mesh
    173 nodes
     48 cells
     28 faces
     28 surface edges

Mesh plotting

  Optional arguments:

    axis, lw, markers, nodelabels, celllabels, opacity, field,
     fieldmult, fieldlims, vectorfield, arrowscale, colormap, colorbarscale,
     colorbarlabel, colorbarlocation, colorbarorientation, colorbarpad,
     warpscale, hicells, elev, azim, dist, outline, outlineangle,
     figsize, leaveopen, quiet

  Available node fields:

    node-id

  Available element fields:

    quality, elem-id, cell-type, tag

  file mesh-quad8.svg saved

Simple 3D mesh

using Amaru
block = Block([0 0 0; 2 4 3], nx=3, ny=6, nz=6)
mesh = Mesh(block)
mplot(mesh, "mesh-hex8.svg", markers=true)
Mesh generation:
      1 blocks
  spliting block 1...    
  finding facets...   
  finding edges...   
     3d mesh
    196 nodes
    108 cells
    144 faces
    288 surface edges

Mesh plotting

  Optional arguments:

    axis, lw, markers, nodelabels, celllabels, opacity, field,
     fieldmult, fieldlims, vectorfield, arrowscale, colormap, colorbarscale,
     colorbarlabel, colorbarlocation, colorbarorientation, colorbarpad,
     warpscale, hicells, elev, azim, dist, outline, outlineangle,
     figsize, leaveopen, quiet

  Available node fields:

    node-id

  Available element fields:

    quality, elem-id, cell-type, tag

  file mesh-hex8.svg saved

2D mesh constructed from two quadratic blocks

using Amaru
block1 = Block(
    [ 
        0.0      0.0    
        0.7643   0.7643 
        0.6667   1.0    
        0.0      1.0    
        0.38215  0.38215
        0.692    0.8724 
        0.3333   1.0    
        0.0      0.5 
    ],
    cellshape=QUAD8, nx=5, ny=5
)
block2 = mirror(block1, base=[0, 0], axis=[0.7643, -0.7643])
mesh = Mesh(block1, block2)
mplot(mesh, "mesh-2d.svg", markers=true)
nothing # hide

Mesh with cells growing in the $x$ direction

using Amaru

R = 5.0
r = 1.0

block = BlockGrid(
    [ 0.0, r, R ],
    [ 0.0, R ],
    nx=[ 4, 8 ],
    ny=[ 8 ],
    rx=[ 1, 1.2] # elements growing rate in the x direction
)

mesh = Mesh(block)
mplot(mesh, "mesh-rate.svg", markers=true)
Mesh generation:
      2 blocks
  spliting block 1...    
  spliting block 2...    
  finding facets...   
     2d mesh
    117 nodes
     96 cells
     40 faces
     40 surface edges

Mesh plotting

  Optional arguments:

    axis, lw, markers, nodelabels, celllabels, opacity, field,
     fieldmult, fieldlims, vectorfield, arrowscale, colormap, colorbarscale,
     colorbarlabel, colorbarlocation, colorbarorientation, colorbarpad,
     warpscale, hicells, elev, azim, dist, outline, outlineangle,
     figsize, leaveopen, quiet

  Available node fields:

    node-id

  Available element fields:

    quality, elem-id, cell-type, tag

  file mesh-rate.svg saved

3D mesh obtained revolving the last example

mesh = revolve(mesh, minangle=0, maxangle=90, base=[0,0,0], axis=[0,1,0])
changeaxes!(mesh, "xzy")
rotate!(mesh, axis=[0,0,1], angle=90)
mplot(mesh, "mesh-rev.svg", azim=-45)
nothing # hide

Wire mesh

using Amaru

block1 = Block([0 0 0; 1 0 1; 0.7 0 0.3 ], n=7) # curved wire (quadratic)
block2 = Block([1 0 1; 1 0 2], n=5) # straight wire
mesh = Mesh(block1, block2)

mplot(mesh, "mesh-arc.svg", azim=-90, elev=0, markers=true)
Mesh generation:
      2 blocks
  spliting block 1...    
  spliting block 2...    
  finding facets...   
  finding edges...   
     3d mesh
     13 nodes
     12 cells
      0 faces
      0 surface edges

Mesh plotting

  Optional arguments:

    axis, lw, markers, nodelabels, celllabels, opacity, field,
     fieldmult, fieldlims, vectorfield, arrowscale, colormap, colorbarscale,
     colorbarlabel, colorbarlocation, colorbarorientation, colorbarpad,
     warpscale, hicells, elev, azim, dist, outline, outlineangle,
     figsize, leaveopen, quiet

  Available node fields:

    node-id

  Available element fields:

    quality, elem-id, cell-type, tag

  file mesh-arc.svg saved

3D mesh obtained revolving the last example

mesh = revolve(mesh, n=24, axis=[0,0,1], base=[0,0,0]) # surface by revolution
mplot(mesh, "mesh-surf.svg", elev=15)

Mesh plotting

  Optional arguments:

    axis, lw, markers, nodelabels, celllabels, opacity, field,
     fieldmult, fieldlims, vectorfield, arrowscale, colormap, colorbarscale,
     colorbarlabel, colorbarlocation, colorbarorientation, colorbarpad,
     warpscale, hicells, elev, azim, dist, outline, outlineangle,
     figsize, leaveopen, quiet

  Available node fields:

    node-id

  Available element fields:

    quality, elem-id, cell-type, tag

  file mesh-surf.svg saved

Finite element model

To performe a finite element analysis, an instance of the Model type is required. This represents the domain model and contains nodes and finite elements. The element instances are different from the cells in a Mesh object since the elements are constructed according to the analysis problem. To instantiate a Model object a mesh and a list of material models objects are needed. The Model type is used for all analysis problems, e.g. mechanical, thermal, seepage, etc. Thus, for a particular problem, a consistent list of materials should be used. For example, in a mechanical analysis, all elements should be associated to mechanical material types. Based on the choosen materials, the Model object will setup the corresponding finite elements and degrees of freedom.

Material definitions

There are several material models implemented in Amaru and are associated to a corresponding type, e.g. LinearElastic, DruckerPrager, VonMises, ElasticRod, etc. A particular material instance is defined by calling the construcor with set of corresponding parameters.

using Amaru

steel = LinearElastic(E=2e8, nu=0.2) # E in kPa
rock = DruckerPrager(E=2e8, nu=0.2, alpha=0.4, kappa=2500, H=1e4)
rebar = ElasticRod(E=2e8, A=1e-4)

nothing # hide

Model generation

The following example shows the walk through of creating a Model object. Note that the list of materials contains a pair relating a domain region to material instance.

using Amaru

# Mesh generation
block = Block([0 0 0; 1 0.1 0.1], nx=20, ny=2, nz=2, cellshape=HEX20)
mesh = Mesh(block)

# Analysis model
steel = LinearElastic(E=2e8, nu=0.2) # E in kPa

materials = [
    :(x>=0) => steel,
]
model = Model(mesh, materials)

nothing # hide

Boundary conditions

The boundary conditions are given by objects that define the quantities that should be applied to nodes, faces, edges or even elements (NodeBC, SurfaceBC, EdgeBC and ElementBC).

The example below shows a nodal boundary condition where all displacements are set to zero.

using Amaru

fixed_end = NodeBC(ux=0, uy=0, uz=0)
NodeBC
    conds: Base.Pairs(:ux => 0, :uy => 0, :uz => 0)
    filter: Expr ()
    nodes: 0-element Vector{Node}

This other example shows a face boudary condition where a traction is applied in the negative $z$ direction.

using Amaru

load = SurfaceBC(tz=-10)
SurfaceBC
    conds: Base.Pairs(:tz => -10)
    filter: Expr ()
    faces: 0-element Vector{Cell}

The keys (e.g. ux, tz, etc.) to be set in a boundary condition are related to the analysis problem, and ultimately to the material types in use. For example, in a mechanical analysis, the keys for nodal boundary conditions are ux, uy, uz, fx, fy and fz where ux represents displacement and fx concentrated force, both in the $x$ direction. Similarly, the keys for a face boudary condition are ux, uy, uz, tx, ty and tz, where tx is a traction (force per area) in the $x$ direction. Besides, the tn key can be used to apply traction normal to the face.

Analysis example

This example shows a mechanical analysis in a steel beam. The left end was clamped, thus all displacements at x==0 are set to zero. Also, a vertical traction is applied at the right end. The solve! function performs the calculations of a Model instance subjected to a set of boundary conditions. The function also updates the state variables at the elements' integration points.

For post-processing in a visualization software (e.g. Paraview), the model can save the current per node and per element values (e.g. displacements, stresses, etc.) in "vtk" and "vtu" formats. To save the full state of a domain model, the output in "xml" format is also supported; thus, the model can be reused in future analyses.

using Amaru

# Mesh generation
block = Block([0 0 0; 1 0.1 0.1], nx=20, ny=2, nz=2, cellshape=HEX8)
mesh = Mesh(block)

# Analysis model
steel = LinearElastic(E=200e6, nu=0.2) # E in kPa

materials = [
    :(x>=0) => steel,
]
model = Model(mesh, materials)

bcs = [
    :(x==0) => NodeBC(ux=0, uy=0, uz=0)
    :(x==1) => SurfaceBC(tz=-1000)
]

addstage!(model, bcs)

solve!(model, quiet=true)
save(model, "beam.vtu")

nothing # hide

The mplot function allow to save a plot of the domain in several formats (e.g. "pdf", "svg", "png", etc.). In this case, the deformed state is plot using a mangnification scale of 100. Also the $u_y$ field is displayed and the corresponding label is placed next to the colorbar. To adjunst the point of view, the azim, elev and dist parameters can be set. They represent, respectively, the azimut, elevation angle and distance to the rendered domain.

mplot(model, "beam.svg", warpscale=100, field=:uz, colorbarlabel=raw"$u_z$", colorbarscale=0.4, azim=-90, elev=30, dist=7)
nothing # hide

Nonlinear analysis

The use of material models with nonlinear behavior lead to a nonlinear analyses. For example, the VonMises material type represents a nonlinear material model. For the analysis, the number of increments can be adjusted using the nincs parameter. Also, the autoinc parameter can be set to true to le the solve! function to automatically compute and recompute the increment sizes. The tol paramter specifies the maximum force discrepancy between internal and external forces.

using Amaru

# Mesh generation
block = Block([0 0 0; 1 0.1 0.1], nx=20, ny=2, nz=4, cellshape=HEX8)
mesh = Mesh(block)

# Analysis model
steel1 = LinearElastic(E=2e8, nu=0.2) # E in kPa
steel2 = VonMises(E=2e8, nu=0.2, fy=500e3, H=1e4)

materials = [
    :(x>=0.5) => steel1,
    :(x<=0.5) => steel2
]
model = Model(mesh, materials)

bcs = [
    :(x==0) => NodeBC(ux=0, uy=0, uz=0)
    :(x==1) => SurfaceBC(uz=-0.04)
]

addstage!(model, bcs)

solve!(model, tol=0.01, autoinc=true, quiet=true)
nothing # hide
mplot(model, "beam-vm.svg", warpscale=10, field=:sxx, fieldmult=1e-3, colorbarlabel=raw"$\sigma_{xx}$", colorbarscale=0.4, azim=-90, dist=7)
nothing # hide

Loggers

Loggers are objects designed to track the information of nodes, integration points, faces, edges, points and segments. That information is updated at every increment and can be saved to a file.

using Amaru

nodelog = NodeLogger("nodelog.table")
iplog = IpLogger("nodelog.table")
facelog = FacesSumLogger("nodelog.table")
nodeslog = NodeGroupLogger("nodes.book")

The loggers need to be associated to the entity they track by using a filter expression, coordinates or a tag string. Finally, the loggers have to be linked to a Model object by using the setloggers! function.

using Amaru

# Mesh generation
block = Block([0 0 0; 1 0.1 0.1], nx=20, ny=2, nz=3, cellshape=HEX8)
mesh = Mesh(block)

# Analysis model
steel1 = LinearElastic(E=2e8, nu=0.2) # E in kPa
steel2 = VonMises(E=2e8, nu=0.2, fy=500e3, H=1e4)

materials = [
    :(x>=0.5) => steel1,
    :(x<=0.5) => steel2
]
model = Model(mesh, materials)

bcs = [
    :(x==0) => NodeBC(ux=0, uy=0, uz=0)
    :(x==1) => SurfaceBC(uz=-0.04)
]

loggers = [
    :(x==1) => FacesSumLogger("tip.table"),
    [0.05, 0.05, 0.1] => IpLogger("ip.table"),
    :(y==0.1 && z==0.1) => NodeGroupLogger("topgroup.book"),
    [0 0.05 0.1; 1 0.05 0.1] => SegmentLogger("top.book", n=40),
    [0 0.05 0.0; 1 0.05 0.0] => SegmentLogger("bottom.book", n=40),
]

setloggers!(model, loggers)
addstage!(model, bcs, nouts=2)

solve!(model, tol=0.01, autoinc=true, quiet=true)

nothing # hide

The loggers are classified single loggers and group loggers. Single loggers record a table of data. Group loggers record a book, which is a set of tables. Once the analysis is finished, the loggers data can be recovered in another script calling the constructors of the types DataTable and DataBook.

using Amaru

tip = DataTable("tip.table")
using Amaru

long = DataBook("topgroup.book").tables[end]

Plotting

Amaru provides a chart ploting function cplot to ease the visualization of the data stored in DataTable and DataBook objects.

using Amaru

tip = DataTable("tip.table")
cplot(
    (x=tip.uz, y=tip.fz, marker="o"),
    "uz_vs_fz.svg",
    xmult  = 1e3,
    xlabel = raw"$u_z$ [mm]",
    ylabel = raw"$f_z$ [kN]",
)

topgroup = DataBook("topgroup.book").tables[end]
cplot(
    (x=topgroup.x, y=topgroup.uz, marker="o"),
    "x_vs_uz.svg",
    ymult  = 1e3,
    xlabel = raw"$x$ [m]",
    ylabel = raw"$u_z$ [mm]",
)

top = DataBook("top.book").tables[end]
bottom = DataBook("bottom.book").tables[end]
cplot(
    (x=top.x, y=top.sxx, marker="o", label="top"),
    (x=bottom.x, y=bottom.sxx, marker="o", label="bottom"),
    "x_vs_sxx.svg",
    ymult  = 1e-3,
    xlabel = raw"$x$ [m]",
    ylabel = raw"$\sigma_{xx}$ [MPa]",
)

nothing # hide