Input
The file src/Input.jl and files contained in src/input/ are collectively referred to as the input module. Here, all calculations prior to the finite element analysis are carried out—including
- specification of the problem scenario, choice of mesh motion, and other parameters
- generation of the finite element mesh
- calculation of the basis functions and their derivatives
- initialization of the surface position and degrees of freedom
- application of boundary conditions
All files below are in the src/input/ directory.
Enums.jl
The following enums are defined to simplify problem input.
MembraneAleFem.Scenario — TypeScenarioScenarios to simulate: instances(Scenario)
F_CAVI = 1–> lid-driven cavityF_COUE = 2–> Couette flowF_POIS = 3–> Hagen–Poiseuille flowF_PULL = 4–> tether pulling from a flat patchF_BEND = 5–> bending of a flat patch
The first letter indicates mesh Topology: F for a flat patch and C for a cylinder. At present, no cylindrical scenarios are implemented.
To create a new scenario:
- add and export a new entry in the
Scenarioenum - edit
get_scenario_bc_infoinsrc/input/Bc.jl - write the corresponding
apply_<SCENARIO>_bcsfunction insrc/input/Mesh.jl - add the new
scenarioto theimplementedlist insrc/input/Params.jl
MembraneAleFem.Topology — TypeTopologyMesh topologies: instances(Topology)
FLAT = 1CYLINDER = 2
MembraneAleFem.Motion — TypeMotionTypes of mesh motion: instances(Motion)
STATIC = 1–> mesh does not moveEUL = 2–> Eulerian mesh motionLAG = 3–> Lagrangian mesh motionALEV = 4–> ALE-viscous mesh motionALEVB = 5–> ALE-viscous-bending mesh motion
The choice of motion is stored in Params.motion variable in Params.jl, and is relevant both to the specification of boundary conditions in Bc.jl and the finite element analysis.
MembraneAleFem.Boundary — TypeBoundaryEnumerate four mesh boundaries: instances(Boundary), never to be used as an index.
BOTTOM = 1RIGHT = 2TOP = 3LEFT = 4
MembraneAleFem.Corner — TypeCornerEnumerate four mesh corners: instances(Corner), never to be used as an index.
BOTTOM_LEFT = 1BOTTOM_RIGHT = 2TOP_LEFT = 3TOP_RIGHT = 4
MembraneAleFem.Neumann — TypeNeumannEnumerate types of Neumann boundary conditions: instances(Neumann)
SHEAR = 1–> apply in-plane force/length in the τ directionSTRETCH = 2–> apply in-plane force/length in the ν directionMOMENT = 3–> apply boundary moment
Associate finite element calculations are found in calc_bdry_element_residual
MembraneAleFem.Curve — TypeCurveEnumerate types of curves: instances(Curve)
CLAMPEDCLOSED
These curves are used to generate B-spline basis functions—see Spline.jl
Params.jl
The requisite parameters for each simulation are split between the Params struct and the keyword arguments args. The data contained in Params is meant to be immutable, even across restarted (or continued) simulations. For this reason, temporal data (the initial time t0, initial time ID t0_id, and time steps Δts) is treated as a keyword argument, despite it being required for every simulation. Additional details about required keywords are contained in check_params.
MembraneAleFem.Params — TypeParamsParameters that must be specified for any simulation.
For Scenario-specific data that must be included via keyword arguments, see check_params. The following information is contained:
motion::Motion→ type of motionscenario::Scenario→ type of scenarionum1el::Int64→ number of elements in $ζ^1$num2el::Int64→ number of elements in $ζ^2$output::Bool→ write output (yes by default)length::Float64→ length, in real spacekb::Float64→ mean bending moduluskg::Float64→ Gaussian bending modulusζv::Float64→ membrane viscositypn::Float64→ normal pressure (body force)poly::Int64→ polynomial order of basis functionsgp1d::Int64→ number of Gauss points, in 1-Dnders::Int64→ number of 1-D B-spline derivativesnen::Int64→ number of element nodesαdb::Float64→ Dohrmann–Bochev parameterαm::Float64→ mesh parameterεk::Float64→ tolerance numerical calculation of matrix Kεnr::Float64→ Netwon–Raphson tolerance
MembraneAleFem.check_params — Functioncheck_params(p::Params; args...)Check relevant parameters, including scenario-specific keyword arguments.
For any simulation, the fields t0, t0_id, and Δts are required. If data is output (as is the default, for p.output), the output path out_path and file name out_file are necessary. Finally, the information needed to solve the various Scenarios is checked.
Dof.jl
Within our ALE formalism, the membrane position $\boldsymbol{x}$ is not a fundamental unknown. The mesh velocity $\boldsymbol{v}^{\text{m}}$, on the other hand, is a fundamental unknown, and over a time step $\Delta t$ the membrane position is updated according to
\[\boldsymbol{x} (\zeta^\alpha, t + \Delta t) \, = \, \boldsymbol{x} (\zeta^\alpha, t) \, + \, \Delta t \, \boldsymbol{v}^{\text{m}} (\zeta^\alpha, t + \Delta t) ~,\]
where $\Delta t$ is one of the entries of the array ΔTS specified in Params.jl. The membrane position is thus treated differently from the fundamental unknowns. For this reason, we define all possible fundamental unknowns and the three Cartesian components of the position in the Dof module:
MembraneAleFem.Dof.Unknown — TypeDof.UnknownPossible degrees of freedom (or fundamental unknowns) at each node.
For different mesh motions, different fundamental unknowns are required. A Lagrangian simulation requires only vx, vy, vz, and λ, while an ALE simulation requires all eight fundamental unknowns. Currently, the mapping from the choice of Motion to the corresponding fundamental unknowns is specified in the function get_dofs and stored in Mesh.dofs.
This enum is encapsulated in a module; access elements with e.g. Dof.vx.
Types of fundamental unknowns:
vx–> $x$-velocity, $v_x$vy–> $y$-velocity, $v_y$vz–> $z$-velocity, $v_z$vmx–> $x$-mesh velocity, $v^{\text{m}}_x$vmy–> $y$-mesh velocity, $v^{\text{m}}_y$vmz–> $z$-mesh velocity, $v^{\text{m}}_z$λ–> surface tension, $λ$pm–> mesh pressure, $p^{\text{m}}$
MembraneAleFem.Dof.Position — TypeDof.PositionCartesian components of the mesh position at each node.
This enum is encapsulated in a module; access elements with e.g. Dof.xm.
Options:
xm–> $x$-positionym–> $y$-positionzm–> $z$-position
Spline.jl
All B-spline calculations independent from finite element analysis, based entirely on The NURBS Book (Piegl and Tiller, 1997). See in particular Chapter Two for a description of B-spline functions, and Chapter Three for an explanation of how the basis functions are used to generate 1D curves and 2D surfaces in $\mathbb{R}^3$. We employ the same terminology in our code.
MembraneAleFem.KnotVector — TypeKnotVector(ζs::Vector{Float64}, nel::Int64, poly::Int64, curve::Curve)Vector of knots, with which all 1-D B-spline functions are calculated.
The knot vector struct contains four fields:
ζs–> list of all knots, including repeated onesnel–> number of 1-D elements, or knot spanspoly–> polynomial ordercurve–> type of Curve, which is eitherCLAMPEDorCLOSED
All of these data are not required to generate a knot vector. Two constructors are available—see knot_vector
MembraneAleFem.knot_vector — Functionknot_vector(ζs::Vector{Float64}, poly::Int64, curve::Curve)External constructor of the KnotVector struct, where knots need not be uniform.
The list of knots ζs, polynomial order poly, and Curve type curve are passed in. If they represent a valid knot vector, then the struct is created.
knot_vector(nel::Int64, poly::Int64, curve::Curve)External constructor of the KnotVector struct with uniform knots.
The number of elements nel, polynomial order poly, and Curve type curve are passed in. If the curve is CLOSED, then no knots are repeated. If the curve is CLAMPED, then the first and last (poly+1) knots are repeated. In both cases, the generated list of knots ζs ranges from 0 to 1.
MembraneAleFem.get_knot_span_index — Functionget_knot_span_index(kv::KnotVector, ζ::Float64)::Int64Return knot span index of ζ in CLAMPED or CLOSED KnotVector kv.
Algorithm A2.1 in Chap. 2, §5 of Piegl and Tiller (1997) ensures kv.ζs[idx] ≤ ζ < kv.ζs[idx+1], where idx is the returned value. The algorithm, valid only for CLAMPED knots, is here extended to CLOSED knots as well.
MembraneAleFem.get_bspline_vals — Functionget_bspline_vals(kv::KnotVector, ζ::Float64)::Vector{Float64}Return kv.poly+1 nonzero B-spline functions at ζ for the given KnotVector kv.
Algorithm A2.2 in Chap. 2, §5 of Piegl and Tiller (1997), valid for CLAMPED knot vectors with (kv.poly+1) repeated knots at the start and end, is also used for CLOSED knot vectors. The local-to-global mapping returned by get_bspline_indices is necessary to determine which global basis functions are nonzero at the provided ζ.
MembraneAleFem.get_bspline_ders — Functionget_bspline_ders(kv::KnotVector, ζ::Float64, num_ders::Int64)::Matrix{Float64}Return B-spline functions and num_ders derivatives at ζ for the given KnotVector kv.
Algorithm A2.3 in Chap. 2, §5 of Piegl and Tiller (1997), valid for CLAMPED knot vectors with (kv.poly+1) repeated knots at the start and end, is also used for CLOSED knot vectors. Here num_ders is required to not be greater than kv.poly, and also greater than or equal to zero. The local-to-global mapping returned by get_bspline_indices is necessary to determine which global basis functions are nonzero at the provided ζ.
The returned matrix has kv.poly+1 rows and num_ders+1 columns. The first column contains the functions themselves, and is thus identical to the output of get_bspline_vals. The second column contains the first derivatives, and so on.
MembraneAleFem.get_bspline_indices — Functionget_bspline_indices(kv::KnotVector, ζ::Float64)::Vector{Int64}Return global indices of nonzero B-splines at the value ζ in the KnotVector kv.
By definition, there are only kv.poly+1 nonzero B-spline functions at any ζ. The functions get_bspline_vals and get_bspline_ders calculate these nonzero quantities, and the mapping returned here places them within the global ordering. The global indices are the same for all ζ within a single knot span, and thus we begin by calling get_knot_span_index.
Both CLAMPED knot vectors, with (kv.poly+1) repeats at the start and end, as well as CLOSED knot vectors, are handled.
get_bspline_indices(kv::KnotVector, ks_id::Int64)::Vector{Int64}Return global indices of nonzero B-splines at ks_id$^{\textrm{th}}$ knot span in the KnotVector kv.
By definition, there are only kv.poly+1 nonzero B-spline functions over any knot span. The mapping returned here places these functions within the global ordering. Both CLAMPED knot vectors, with (kv.poly+1) repeats at the start and end, as well as CLOSED knot vectors, are handled. Note that ks_id is not the element ID eid; rather, ks_id = eid + kv.poly
MembraneAleFem.get_1d_bspline_cps — Functionget_1d_bspline_cps(kv::KnotVector, x::Function)::Vector{Float64}Return the global control points $\{ x^{}_K \}$ for the function $x(ζ)$.
The global control points $\{ x^{}_K \}$ are calculated for the provided KnotVector kv such that
\[x(ζ) \, \approx \, \sum_{K = 1}^{\texttt{nn}} N^{}_K (ζ) \, x^{}_K ~,\]
where $x(ζ)$ is the provided x::Function and here $\texttt{nn}$ is the number of global 1-D nodes. To determine the unknown $\{ x^{}_K \}$, a set of $\texttt{nn}$ collocation points $\{ ζ^{}_J \}$ is chosen via collocate_ζ. A matrix equation is then obtained as
\[[x (ζ^{}_J)] \, = \, [N^{}_K (ζ^{}_J)] \, [x^{}_K] ~,\]
where the $N^{}_K (ζ^{}_J)$ are determined with get_bspline_indices and get_bspline_vals. The $x^{}_K$ are then calculated and returned as a vector.
MembraneAleFem.get_2d_bspline_cps — Functionget_2d_bspline_cps(kv1::KnotVector, kv2::KnotVector, x::Function)::Matrix{Float64}Return the global control points $\{ x^{}_K \}$ for the scalar-valued function $z(ζ^1, ζ^2)$.
The procedure of get_1d_bspline_cps is repeated, except in this case collocation points are chosen across the 2-D parametric domain $(ζ^1, ζ^2)$.
MembraneAleFem.collocate_ζ — Functioncollocate_ζ(kv::KnotVector)::Vector{Float64}Take a KnotVector kv and return a list of points ζ used for collocation.
The length of the returned list of ζ values is equal to the number of global basis functions associated with the provided knot vector. Both CLAMPED and CLOSED knot vectors are accounted for.
MembraneAleFem.get_unique_1d_elements — Functionget_unique_1d_elements(kv::KnotVector)Get all unique elements for a given knot vector kv.
This function returns several quantities, in the following order:
uel_num::Int64-> number of unique elementsnum_el::Int64-> number of elementsuel_ids::Vector{Int64}-> mapping from element id to unique element iduel_list::Vector{Tuple{Float64, Float64}}-> start and endζfor each unique element
GaussPoint.jl
As is standard in finite element analysis, Gauss–Legendre quadrature is used to approximate integrals over the parametric domain. Here, the primary objective is to calculate the 1-D integral
\[I_1 \, = \, \int_{ζ_{\texttt{lo}}}^{ζ_{\texttt{hi}}} \!\!\! f(ζ) \, \text{d} ζ ~.\]
The domain of integration is mapped from $[ ζ_{\texttt{lo}}, ζ_{\texttt{hi}} ]$ to $[-1, +1]$ via the change of variables
\[ζ \, = \, \dfrac{1}{2} \, ξ \, \big( ζ_{\texttt{hi}} - ζ_{\texttt{lo}} \big) \, + \, \dfrac{1}{2} \, \big( ζ_{\texttt{hi}} + ζ_{\texttt{lo}} \big) ~,\]
for which the integral $I_1$ is equivalently given by
\[I_1 \, = \, \dfrac{1}{2} \, \big( ζ_{\texttt{hi}} - ζ_{\texttt{lo}} \big) \int_{-1}^{+1} \!\! f \bigg( \dfrac{1}{2} \Big[ ξ \big( ζ_{\texttt{hi}} - ζ_{\texttt{lo}} \big) + \big( ζ_{\texttt{hi}} + ζ_{\texttt{lo}} \big) \Big] \bigg) \, \text{d} ξ \, = \, \dfrac{1}{2} \, \big( ζ_{\texttt{hi}} - ζ_{\texttt{lo}} \big) \int_{-1}^{+1} \!\! \hat{f} (ξ) \, \text{d} ξ ~.\]
At this point, the integral is approximated by applying the Gaussian quadrature formula
\[I_1 \, ≈ \, \dfrac{1}{2} \, \big( ζ_{\texttt{hi}} - ζ_{\texttt{lo}} \big) \, \sum_{k = 1}^{\texttt{ngp}} \hat{w}_k \, \hat{f} (ξ_k) ~,\]
where $\texttt{ngp}$ is the number of 1D Gauss points, $\hat{w}_k$ are the corresponding weights, and $ξ_k$ are the associated Gauss–Legendre points. In our code, GaussPointsξ contains $\texttt{ngp}$, $\hat{w}_k$, and $ξ_k$. Note that this struct is the same for every line element, because $ξ ∈ [-1, +1]$ always.
MembraneAleFem.GaussPointsξ — TypeGaussPointsξ(ngp::Int64)Weights $\hat{w}_k$ and points $ξ_k$ on the interval $[-1, +1]$ with which a 1-D integral is approximated.
The struct has three fields:
ngp–> number of Gaussian quadrature pointsξs–> set of points $ξ_k$ws–> set of weights $\hat{w}_k$
Calculations from Wikipedia: Gaussian quadrature.
In our finite element implementation, it is often convenient to evaluate integrals over the original variable ζ rather than the transformed variable ξ. To this end, we recognize that
\[I_1 \, ≈ \, \sum_{k = 1}^{\texttt{ngp}} w_k \, f (ζ_k) ~,\]
where
\[w_k \, := \,\dfrac{1}{2} \, \hat{w}_k \, \big( ζ_{\texttt{hi}} - ζ_{\texttt{lo}} \big) \qquad \text{and} \qquad ζ_k \, := \, \dfrac{1}{2} \Big[ ξ_k \big( ζ_{\texttt{hi}} - ζ_{\texttt{lo}} \big) + \big( ζ_{\texttt{hi}} + ζ_{\texttt{lo}} \big) \Big] ~.\]
In our code, GaussPointsζ contains ngp, $w_k$, and $ζ_k$.
MembraneAleFem.GaussPointsζ — TypeGaussPointsζ(gpsξ::GaussPointsξ, ζlo::Float64, ζhi::Float64)Weights $w_k$ and points $ζ_k$ with which a 1-D integral is approximated over ζlo ≤ ζ ≤ ζhi.
Calculated via the transform from ξ to ζ via the relations
ζ$_k$ = ξ$_k$ (ζhi - ζlo) / 2 + (ζhi + ζlo) / 2
$w_k$ = $\hat{w}_k$ (ζhi - ζlo) / 2
The struct has three fields:
ngp–> number of Gaussian quadrature pointsζs–> set of points $ζ_k$ws–> set of weights $w_k$
GpBasisFn.jl
Calculation of nonzero B-spline basis functions at every Gaussian quadrature point. Since the basis functions are determined upon discretizing the parametric domain, they are calculated once and then stored in the structs listed below—which systematically build up in complexity.
MembraneAleFem.GpBasisFnsζ — TypeGpBasisFnsζ(gpwζ::Float64, ζ::Float64, kv::KnotVector)1-D basis functions $N(ζ)$ at the provided quadrature point ζ with weight gpwζ.
The B-spline values and derivatives are determined with get_bspline_ders and stored here. The struct contains four fields:
w–> Gauss point weight, passed in asgpwN–> (poly+1)×1 vector of nonzero basis functions $N(ζ)$dN–> (poly+1)×1 vector of nonzero first derivatives $N'(ζ)$ddN–> (poly+1)×1 vector of nonzero second derivatives $N''(ζ)$
MembraneAleFem.GpBasisFnsζα — TypeGpBasisFnsζα(fns1::GpBasisFnsζ, fns2::GpBasisFnsζ)2-D basis functions $N(ζ^α)$ at a single Gauss point $ζ^α$, as set by 1-D GpBasisFnsζ fns1 and fns2.
Here $ζ^1$ and $ζ^2$ respectively correspond to the ζ values passed to fns1 and fns2, even though this information is not stored. The 2-D basis functions are calculated as the tensor product of 1-D functions. The struct contains four fields:
w–> Gauss point weight, given byfns1.w × fns2.wN–>NEN×1 vector of nonzero basis functions $N(ζ^α)$, ordered by the tensor product structure∂Nα–>NEN×2 matrix of first derivatives: rows respectively contain $N_{, 1}$ and $N_{, 2}$∂∂Nαβ–>NEN×3 matrix of second derivatives: rows respectively contain $N_{, 1 1}$, $N_{, 2 2}$, and $N_{, 1 2}$
MembraneAleFem.LineGpBasisFns — TypeLineGpBasisFns(kv::KnotVector, ngp::Int64)1-D B-spline basis functions and derivatives at all quadrature points on the domain of the KnotVector kv.
Basis functions are determined at each of the ngp Gauss points, over each element. When using B-splines, scenarios often arise where the basis functions are identical across many elements. We accordingly store only the 1-D basis functions over unique elements, as well as a mapping from elements to unique elements. The basis functions at the edges of the 1-D parametric domain are stored separately, as they are required subsequently for application of boundary conditions. The struct has five fields:
nel–> number of elements in theKnotVectorkvuel_ids–> mapping fromelem_idtounique_elem_idufns–>num_unique_elems×ngpmatrix of unique 1-D basis functions, of typeGpBasisFnsζζmin_fns–> 1-D basis functions at the min value of the parametric domainζmax_fns–> 1-D basis functions at the max value of the parametric domain
Note that this struct contains no information about how basis functions vary in the orthogonal parametric direction, and thus cannot be directly used to apply boundary conditions. See BdryGpBasisFns.
MembraneAleFem.BdryGpBasisFns — TypeBdryGpBasisFns(line_gp_fns::LineGpBasisFns, perp_edge_fns::GpBasisFnsζ, bdry::Boundary)2-D B-spline basis functions and derivatives at all quadrature points on a boundary.
The LineGpBasisFns struct line_gp_fns contains all 1-D basis functions and derivatives along the boundary, with ngp Gauss points for each element. Here perp_edge_fns are the basis functions and derivatives at the boundary in the orthogonal parametric direction, as contained in a GpBasisFnsζ struct. For example, on the RIGHT boundary, line_gp_fns captures 1-D derivatives in the $ζ^2$ direction, while perp_edge_fns contains 1-D derivatives in the $ζ^1$ direction on the right edge. With knowledge of the underlying tensor product structure (Piegl and Tiller, 1997), the 2-D basis functions along the specified Boundary bdry are generated. The struct has four fields:
nel–> number of elements associated withLineGpBasisFnsline_gp_fnsuel_ids–> mapping fromelem_idtounique_elem_idufns–>num_unique_elems×ngpmatrix of unique 2-D basis functions, of typeGpBasisFnsζαbdry–>Boundaryof the parametric domain
MembraneAleFem.AreaGpBasisFns — TypeAreaGpBasisFns(line_gp_fns1::LineGpBasisFns, line_gp_fns2::LineGpBasisFns)2-D B-spline basis functions and derivatives at all quadrature points in the mesh area.
The two LineGpBasisFns structs passed in, line_gp_fns1 and line_gp_fns2, respectively contain 1-D derivatives in the $ζ^1$ and $ζ^2$ directions. With the tensor product structure of B-splines over a surface (Piegl and Tiller, 1997), it is straightforward to calculate the 2-D basis functions and their derivatives. As is the case for LineGpBasisFns, a mapping from elements to unique elements is generated; only unique basis function calculations are stored. This struct has three fields:
nel–> number of area elementsuel_ids–> mapping fromelem_idtounique_elem_idufns–>num_unique_elems×ngpmatrix of unique 2-D basis functions, of typeGpBasisFnsζα
Mesh.jl and Bc.jl
The mesh is one of the main constructions required for finite element analysis. In this codebase, the functions associated with mesh generation are divided into two files. Mesh.jl deals with aspects of mesh generation that are independent of the scenario under consideration, while Bc.jl handles the boundary conditions and their effect on mesh organization—which is scenario-dependent.
Mesh.jl
All relevant information is contained in the Mesh struct, and will not change over the course of a simulation.
MembraneAleFem.Mesh — TypeMesh(p::Params; args...)The generated mesh, which includes the degree-of-freedom numbering and all basis functions for the given Scenario.
This struct contains the following scenario-independent fields:
num1el–> number of elements in $ζ^1$ directionnum2el–> number of elements in $ζ^2$ directionnumel–> number of area elementsnum1np–> number of nodal points in $ζ^1$ directionnum2np–> number of nodal points in $ζ^2$ directionnumnp–> number of nodal points on the 2-d meshIX–> matrix containing nonzero node numbers for each area elementbdry_elems–> mapping fromBoundaryto element numberscrnr_elems–> mapping fromCornerto element numberbdry_nodes–> mapping fromBoundaryto node numbersbdry_inner_nodes–> mapping fromBoundaryto inner node numberscrnr_nodes–> mapping fromCornerto node numbercrnr_inner_nodes–> mapping fromCornerto inner node numberkv1–>KnotVectoralong $ζ^1$ directionkv2–>KnotVectoralong $ζ^2$ directionarea_gp_fns–>AreaGpBasisFns: 2-D area basis functionsbdry_gp_fns–> mapping fromBoundarytoBdryGpBasisFnscrnr_gp_fns–> mapping fromCornerto 2-D basis functionsGpBasisFnsζα
The generated mesh depends on the problem being solved. The function generate_scenario uses the scenario-specific results from Bc.jl to set the following scenario-dependent fields:
topology–> surfaceTopologydofs–> list of activeUnknowns with global orderingndf–> number of activeUnknownsID–> matrix mapping node number to indices of unknowns at that nodeID_inv–> inverse ofID: mapunknown_idto(node_number, dof_number)nmdf–> total number of mesh degrees of freedomLM–> map from area element to indices of all unknowns of that elementinh_dir_bcs–> list of inhomogeneous Dirichlet boundary conditionsinh_neu_bcs–> list of inhomogeneous Neumann boundary conditions
MembraneAleFem.generate_scenario — Functiongenerate_scenario(numel, numnp, IX, bdry_nodes, ..., p::Params; args...)Apply scenario-specific boundary conditions towards mesh generation.
Checks to see whether a helper function is available in Bc.jl for the given Scenario, and then constructs the ID matrix that maps from nodes to global degrees of freedom. The inverse ID_inv is also generated, as is the LM matrix—which provides the local degrees of freedom for each element.
The following helper functions allow the user to easily obtain basis functions, their derivatives, and Gauss point weights both in the mesh interior and on the mesh boundary.
MembraneAleFem.get_basis_fns — Functionget_basis_fns(el_id::Int64, gp_id::Int64, mesh::Mesh)::GpBasisFnsζαReturn GpBasisFnsζα of the gp_id$^{\text{th}}$ 2-D Gauss point of the el_id$^{\text{th}}$ area element.
get_basis_fns(bdry::Boundary, el_id::Int64, gp_id::Int64, mesh::Mesh)::GpBasisFnsζαReturn GpBasisFnsζα of the gp_id$^{\text{th}}$ 1-D Gauss point of the el_id$^{\text{th}}$ element on the Boundary bdry.
MembraneAleFem.get_gpw — Functionget_gpw(el_id::Int64, gp_id::Int64, mesh::Mesh)::Float64Return the Gauss point weight of the call to get_basis_fns.
get_gpw(bdry::Boundary, el_id::Int64, gp_id::Int64, mesh::Mesh)::Vector{Float64}Return the Gauss point weight of the call to get_basis_fns.
MembraneAleFem.get_N — Functionget_N(el_id::Int64, gp_id::Int64, mesh::Mesh)::SVector{NEN,Float64}Return local basis functions $[𝐍^e]$ of the call to get_basis_fns.
get_N(bdry::Boundary, el_id::Int64, gp_id::Int64, mesh::Mesh)Return local basis functions $[𝐍^e]$ of the call to get_basis_fns.
MembraneAleFem.get_∂Nα — Functionget_∂Nα(el_id::Int64, gp_id::Int64, mesh::Mesh)::SMatrix{NEN,ζDIM,Float64}Return local basis function derivatives $[𝐍^e]_{, α}$ of the call to get_basis_fns.
get_∂Nα(bdry::Boundary, el_id::Int64, gp_id::Int64, mesh::Mesh)Return local basis function derivatives $[𝐍^e]_{, α}$ of the call to get_basis_fns.
MembraneAleFem.get_∂∂Nαβ — Functionget_∂∂Nαβ(el_id::Int64, gp_id::Int64, mesh::Mesh)::SMatrix{NEN,VOIGT,Float64}Return local basis function derivatives $[𝐍^e]_{, α β}$ of the call to get_basis_fns.
get_∂∂Nαβ(bdry::Boundary, el_id::Int64, gp_id::Int64, mesh::Mesh)Return local basis function derivatives $[𝐍^e]_{, α β}$ of the call to get_basis_fns.
Bc.jl
When constructing the Mesh, generate_scenario (in Mesh.jl) calls get_scenario_bc_info—which is a helper function that then calls the appropriate Scenario-specific function. Each such function returns the following:
dofs–> global ordering of activeUnknownsndf–> number of activeUnknownsID–> matrix mapping node number to indices of unknowns at that nodeinh_dir_bcs–> list of inhomogeneous Dirichlet boundary conditionsinh_neu_bcs–> list of inhomogeneous Neumann boundary conditions
MembraneAleFem.get_scenario_bc_info — Functionget_scenario_bc_info(numnp, IX, bdry_nodes, ..., p::Params; args...)Return Scenario-specific information for the provided Params.
Each of the functions called returns (dofs, ndf, ID, inh_dir_bcs, inh_neu_bcs), in that order.
MembraneAleFem.get_f_cavi_bc_info — Functionget_f_cavi_bc_info(numnp, bdry_nodes, crnr_nodes)Generate Mesh data for the flat cavity Scenario F_CAVI.
In this scenario, a STATIC mesh Motion is required; relevant Unknowns are vx, vy, and λ. The following boundary conditions are prescribed:
- $v_x = 1.0$ along the top edge (corners excluded)
- $v_x = 0.0$ along all other edges (corners included)
- $v_y = 0.0$ along all edges
- $λ = 0.0$ at (or near) the center of the domain
Note that $λ$ is pinned to remove the global indeterminacy of the surface tension, up to a constant.
MembraneAleFem.get_f_coue_bc_info — Functionget_f_coue_bc_info(numnp, bdry_nodes)Generate Mesh data for the flat Couette Scenario F_COUE.
In this scenario, a STATIC mesh Motion is required; relevant Unknowns are vx, vy, and λ. The following boundary conditions are prescribed:
- $v_x = 0.0$ along the bottom edge (corners included)
- $v_x = 3.0$ along the top edge (corners included)
- $v_y = 0.0$ along all edges
- $\bm{\bar{F}} = 4\bm{\nu}$ on the left and right edges
MembraneAleFem.get_f_pois_bc_info — Functionget_f_pois_bc_info(numnp, bdry_nodes)Generate Mesh data for the flat Hagen–Poiseuille Scenario F_POIS.
In this scenario, a STATIC mesh Motion is required; relevant Unknowns are vx, vy, and λ. The following boundary conditions are prescribed:
- $v_x = 0.0$ along the top and bottom edges (corners included)
- $v_y = 0.0$ on all edges
- $\bm{\bar{F}} = 4 \bm{\nu}$ on the left edge
- $\bm{\bar{F}} = 8 \bm{\nu}$ on the right edge
MembraneAleFem.get_f_pull_bc_info — Functionget_f_pull_bc_info(numnp, IX, bdry_nodes, bdry_inner_nodes, p::Params; args...)Generate Mesh data for the flat tube-pulling Scenario F_PULL.
Relevant unknowns are determined via get_dofs, depending on the choice of mesh Motion. The following boundary conditions are prescribed for all mesh motions:
- $v_z = 0$ on all boundaries, and all inner boundaries
- $\bm{\bar{F}} = (k_{\text{b}} / 4)\bm{\nu}$ on all boundaries
- $v_x = 0$, $v_y = 0$ at center of each edge
- $v_x = 0$, $v_y = 0$, $v_z = \,$
args[:pull_speed]$\bm{e}_z$ on nodes of the central element
The following are prescribed when the Motion is not Lagrangian:
- $v^{\text{m}}_x = 0$, $v^{\text{m}}_y = 0$ at center of each edge
- $v^{\text{m}}_x = 0$, $v^{\text{m}}_y = 0$, $v^{\text{m}}_z = \,$
args[:pull_speed}$\bm{e}_z$ on nodes of the central element
The following are prescribed when the Motion is either ALE-viscous or ALE-viscous-bending:
- $\bm{v}^{\text{m}} = \bm{0}$ on all boundaries
The following are prescribed when the Motion is ALE-viscous-bending:
- $v^{\text{m}}_z = 0$ on all inner boundaries
MembraneAleFem.get_f_bend_bc_info — Functionget_f_bend_bc_info(numnp, bdry_nodes, p::Params; args...)Generate Mesh data for the flat bending Scenario F_BEND.
Relevant unknowns are determined via get_dofs, depending on the choice of mesh Motion. The following boundary conditions are prescribed for all mesh motions:
LEFTedge:- $\bm{v} = \bm{0}$
- $M = \,$
args[:bend_mf](seeParams.jl) - if ALE mesh motion: $\bm{v}^{\text{m}} = \bm{0}$
RIGHTedge:- $f_x = 0$ and $f_y = 0$
- $v_z = 0$
- $M = \,$
args[:bend_mf](seeParams.jl) - if ALE mesh motion: $v^{\text{m}}_z = 0$
BOTTOMedge:- $f_x = 0$ and $f_z = 0$
- $v_y = 0$
- $M = 0$
- if ALE mesh motion: $v^{\text{m}}_y = 0$
TOPedge:- $f_x = 0$ and $f_z = 0$
- $v_y = 0$
- $M = 0$
- if ALE mesh motion: $v^{\text{m}}_y = 0$
MembraneAleFem.get_dofs — Functionget_dofs(motion::Motion)::Dict{Dof.Unknown, Int64}Return global ordering of Unknown degrees of freedom, depending on the mesh Motion.
The following is the mapping from motion to unknowns:
- Lagrangian –> $v_x$, $v_y$, $v_z$, $λ$
- Eulerian –> $v_x$, $v_y$, $v_z$, $v^{\text{m}}_x$, $v^{\text{m}}_y$, $v^{\text{m}}_z$, $λ$
- ALE –> $v_x$, $v_y$, $v_z$, $v^{\text{m}}_x$, $v^{\text{m}}_y$, $v^{\text{m}}_z$, $λ$, $p^{\text{m}}$
Note that the flat, 2-D Scenarios (F_CAVI, F_COUE, F_POIS) have a reduced number of degrees of freedom, because there are no unknowns in the $z$-direction. Thus, this function is not called in those scenarios.
Input.jl
MembraneAleFem.prepare_input — Functionprepare_input(p::Params; args...)Generate and return the mesh, surface position xms, and Unknown control points cps.
The mesh is generated by instantiating a Mesh with the provided parameters and arguments, which include the Scenario and number of elements. The surface positions and initial unknowns are set with calls to get_1d_bspline_cps and get_2d_bspline_cps.