Skip to content

Script API

This is the API definition of quanscient python module.

Functions

abs

def abs(input: expression) -> expression

This returns an expression that is the absolute value of the input expression.

Example
>>> expr = abs(-2.5)
>>> expr.print();
Expression size is 1x1
@ row 0, col 0 :
2.5

acos

def acos(input: expression) -> expression

This returns an expression that is the arccosarccos or cos1cos^{-1} of input. The output expression is in radians.

Example
>>> expr = acos(0)
>>> expr.print(); # in radians
Expression size is 1x1
@ row 0, col 0 :
1.5708
>>>
>>> (expr * 180/getpi()).print(); # in degrees
Expression size is 1x1
@ row 0, col 0 :
90
See Also

sin(), cos(), tan(), asin(), atan()

adapt

def adapt(verbosity: int = 0) -> bool

This function is used to perform a h/p/hp adaptation according to the defined h/p/hp adaptivity settings. To define the h-adaptivity use function mesh.setadaptivity(). To define a p-adaptivity for a field use function field.setorder().

The function returns True if the mesh or any field order was changed by the adaption and returns False if no changes were made.

Example
>>> all = 1
>>> q = shape("quadrangle", all, [0,0,0, 1,0,0, 1.2,1,0, 0,1,0], [5,5,5,5])
>>> mymesh = mesh([q])
>>> x = field("x"); y = field("y")
>>> criterion = 1 + sin(10*x)*sin(10*y)
>>>
>>> mymesh.setadaptivity(criterion, 0, 5)
>>>
>>> for i in range(5):
... criterion.write(all, f"criterion_{100+i}.vtk", 1)
... adapt(1)
See Also

mesh.setadaptivity(), field.setorder(), alladapt()

alladapt

def alladapt(verbosity: int = 0) -> bool

This is a collective MPI operation and hence must be called by all ranks. It replaces the adapt() function in the DDM framework. This function is used to perform a h/p/hp adaptation according to the defined h/p/hp adaptivity settings. To define the h-adaptivity use function mesh.setadaptivity(). To define a p-adaptivity for a field use function field.setorder().

Example
>>> ...
>>> alladapt()
See Also

adapt()

alleigenport

def alleigenport(
portphysreg: int,
numeigenvalues: int,
targetbeta: float,
E: field,
mu: expression,
eps: expression,
portnormal: list[float],
ddmrelrestol: float,
ddmmaxnumit: int,
drivingfrequency: float = -1.0,
eigentol: float = 1e-06,
eigenmaxnumits: int = 1000,
integrationorder: int = 5,
verbosity: int = 1
) -> list[list[expression]]
def alleigenport(
portphysreg: int,
numeigenvalues: int,
targetreal: float,
targetimag: float,
E: field,
mu_real: expression,
mu_imag: expression,
eps_real: expression,
eps_imag: expression,
sigma_real: expression,
sigma_imag: expression,
portnormal: list[float],
ddmrelrestol: float,
ddmmaxnumit: int,
drivingfrequency: float = -1.0,
eigentol: float = 1e-06,
eigenmaxnumits: int = 1000,
integrationorder: int = 5,
verbosity: int = 1
) -> list[list[expression]]

This function computes the eigenmodes of a waveguide whose cross section SS is the given physical region portphysreg. The material parameters μ\mu, ϵ\epsilon, and σ\sigma are required as input arguments; these can be anisotropic, and both real and imaginary parts should be given. The surface normal portnormal is assumed to point in the direction of propagation. The boundary condition (PEC) and the approximation order is extracted from the input field E.

For each found mode, the function returns the real and imaginary parts of the transverse and longitudal components of the fields EE and HH and the propagation constant γ=α+jβ\gamma=\alpha+j\beta as a list of expressions. The returned modes are scaled such that the power into the waveguide,

P=12SRe(E×H)dS,P = \frac12\int_{S}\textup{Re}(E\times H^{*})\cdot \textup{d}S,

equals one watt; integrationorder determines the order of the integration rule used to compute this integral.

Example
>>> # Load a mesh with physical regions:
>>> wg1_crosssection = 9; wg2_crossection = 10;
>>> portphysreg = 11; full_waveguide = 12;
>>> portboundary = 13; skin = 14;
>>> mymesh = qs.mesh()
>>> mymesh.selectskin(portboundary, portphysreg)
>>> mymesh.selectskin(skin)
>>> mymesh.partition()
>>> mymesh.load("gmsh:waveguide.msh", skin, 1, 1)
>>>
>>> # Set input parameter values:
>>> E = qs.field("hcurl")
>>> E.setorder(full_waveguide, 2)
>>> E.setconstraint(portboundary)
>>> epsr = qs.parameter(); mur = qs.parameter();
>>> epsr.setvalue(wg1_crosssection, 2.25)
>>> epsr.setvalue(wg2_crossection, 1.0)
>>> mur.setvalue(portphysreg, 1.0);
>>>
>>> # Attempt to find 4 modes, targetting the eigenvalue 3e6 * i:
>>> modes = qs.alleigenport(portphysreg, 4, 0, 3e6, E, mur * qs.getmu0(), 0.0, epsr * qs.getepsilon0(), 0.0, 0.0, 0.0, [0.0, 0.0, 1.0], 1e-6, 100, 1.4314e14, 1e-8)
>>>
>>> for i in range(len(modes)):
>>> # The fields are returned in the following order:
>>> modes[i][0].write(portphysreg, 'Etreal_rank' + str(qs.getrank()) + '_mode' + str(i) + '.vtu', 2)
>>> modes[i][1].write(portphysreg, 'Etimag_rank' + str(qs.getrank()) + '_mode' + str(i) + '.vtu', 2)
>>> modes[i][2].write(portphysreg, 'Elreal_rank' + str(qs.getrank()) + '_mode' + str(i) + '.vtu', 2)
>>> modes[i][3].write(portphysreg, 'Elimag_rank' + str(qs.getrank()) + '_mode' + str(i) + '.vtu', 2)
>>> modes[i][4].write(portphysreg, 'Htreal_rank' + str(qs.getrank()) + '_mode' + str(i) + '.vtu', 2)
>>> modes[i][5].write(portphysreg, 'Htimag_rank' + str(qs.getrank()) + '_mode' + str(i) + '.vtu', 2)
>>> modes[i][6].write(portphysreg, 'Hlreal_rank' + str(qs.getrank()) + '_mode' + str(i) + '.vtu', 2)
>>> modes[i][7].write(portphysreg, 'Hlimag_rank' + str(qs.getrank()) + '_mode' + str(i) + '.vtu', 2)
>>> # Evaluate the eigenvalue expression to access the value:
>>> if (qs.getrank() == 0):
>>> alpha = modes[i][8].evaluate()
>>> beta = modes[i][9].evaluate()
>>> print('Mode ' + str(i) + ' has eigenvalue ' + str(alpha) + ' + ' + str(beta) + ' * i.')

allintegrate

def allintegrate(
physreg: int,
expr: expression,
integrationorder: int
) -> float

This integrates the expression expr over the physical region physreg. The integration is exact up to the order of polynomials specified in the argument integrationorder.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1"); x=field("x")
>>> v.setorder(vol, 1)
>>> v.setvalue(vol, 12*x)
>>>
>>> integratedvalue = allintegrate(vol, v, 4)
See Also

allinterpolate(), allprobe()

allinterpolate

def allinterpolate(
physreg: int,
expr: expression,
xyzcoord: list[float]
) -> float

This interpolates the expression expr value at a single point whose [x,y,z] coordinate is provided as an argument. The flattened interpolated field values are returned if the point was found in the elements of the physical region physreg.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1"); x=field("x")
>>> v.setorder(vol, 1)
>>> v.setvalue(vol, 12*x)
>>> xyzcoord = [0.5,0.6,0.05]
>>>
>>> interpolated = allinterpolate(vol, v, xyzcoord)
>>> interpolated
[5.954696754335098]
See Also

allintegrate(), allprobe()

alllaplace

def alllaplace(
physreg: int,
regionv1: int,
regionv0: int,
coef: expression,
vorder: int
) -> field

allmax

def allmax(
physreg: int,
expr: expression,
refinement: int
) -> float

This returns the maximum value of the expression expr obtained over the geometric region physreg by splitting all elements refinement times in each direction. Increasing the refinement will thus lead to a more accurate maximum value, but at an increased computational cost. The maximum value is exact when the refinement nodes added to the elements correspond to the position of maximum. For a first-order nodal shape function interpolation, on a mesh that is not curved, the maximum is always exact to machine precision.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1"); x = field("x")
>>> v.setorder(vol, 1)
>>> v.setvalue(vol, 12*x)
>>> maxdata = allmax(vol, v, 1)
>>> maxdata
12.0
See Also

allmin()

allmeasuredistance

def allmeasuredistance(
a: vec,
b: vec,
c: vec
) -> float

This returns a relative L2 norm according to the below formula:

abc\frac{|\boldsymbol{a} - \boldsymbol{b}|}{|\boldsymbol{c}|}
Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>>
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v), 0, 2)
>>> projection += integral(vol, dt(v)*tf(v) - 2*tf(v))
>>> projection += integral(vol, dtdt(dof(v))*tf(v))
>>>
>>> v_prev = vec(projection) # previous iteration solution
>>> v_curr = vec(projection) # current iteration solution
>>>
>>> relativeerror = 1.0
>>> while(relativeerror < 1e-5):
>>> projection.allsolve(1e-6, 500)
>>> v_curr.setdata()
>>> relativeerror = allmeasuredistance(v_curr, v_prev, v_curr)
>>> v_prev = v_curr.copy()

allmin

def allmin(
physreg: int,
expr: expression,
refinement: int
) -> float

This returns the minimum value of the expression expr obtained over the geometric region physreg by splitting all elements refinement times in each direction. Increasing the refinement will thus lead to a more accurate minimum value, but at an increased computational cost. The minimum value is exact when the refinement nodes added to the elements correspond to the position of minimum. For a first-order nodal shape function interpolation, on a mesh that is not curved, the minimum is always exact to machine precision.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1"); x = field("x")
>>> v.setorder(vol, 1)
>>> v.setvalue(vol, 12*x)
>>> mindata = allmin(vol, v, 1)
>>> mindata
-2.0
See Also

allmax()

allpartition

def allpartition(meshfile: str, skinphysreg: int = -1) -> str

This is a collective MPI operation and hence must be called by all ranks. This function partitions the requested mesh into a number of parts equal to the number of ranks. All parts are saved to disk and the part file name for each rank is returned. The GMSH API must be available to partition into more than one part. A physical region containing the global skin can be created with the last argument.

This function will be deprecated. Use mesh.partition() instead.

Example
>>> initialize()
>>> partfilename = allpartition("disk.msh")
>>> finalize()

allprobe

def allprobe(physreg: int, expr: expression) -> float

This functions returns the value of a scalar expression expr at the point region physreg.

Example
>>> vol=1; anynode=12
>>> mymesh = mesh()
>>> mymesh.selectanynode(anynode)
>>> mymesh.load("disk.msh")
>>>
>>> x = field("x")
>>> y = field("y")
>>> probedvalue = allprobe(anynode, 2*x+y)

allsolve

def allsolve(
relrestol: float,
maxnumit: int,
nltol: float,
maxnumnlit: int,
relaxvalue: float,
formuls: list[formulation],
verbosity: int = 1
) -> int

This is a collective MPI operation and hence must be called by all ranks. It solves across all the ranks a nonlinear problem with a fixed-point iteration.

Example
>>> ...
>>> allsolve(1e-8, 500, 1e-4, 1.0, [electrostatics])
See Also

solve(), formulation.solve(), formulation.allsolve()

andpositive

def andpositive(exprs: list[expression]) -> expression

This returns an expression whose value is 1 for all evaluation points where the value of all the input expressions is larger or equal to zero. Otherwise, its value is -1.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> x=field("x"); y=field("y"); z=field("z")
>>> expr = andpositive([x,y]) # At points, where x>=0 and y>=0, value is 1, otherwise -1.
>>> expr.write(vol, "andpositive.vtk", 1)
See Also

ifpositive(), orpositive()

array1x1

def array1x1(arg0: expression) -> expression

This defines a vector or matrix operation of size 1×11\times1. The array is populated in a row-major way.

[arg0]\begin{bmatrix} arg0 \end{bmatrix}

array1x2

def array1x2(arg0: expression, arg1: expression) -> expression

This defines a vector or matrix operation of size 1×21\times2. The array is populated in a row-major way.

[arg0arg1]\begin{bmatrix} arg0 & arg1 \end{bmatrix}
Example
>>> myarray = array1x2(1,2)

array1x3

def array1x3(
arg0: expression,
arg1: expression,
arg2: expression
) -> expression

This defines a vector or matrix operation of size 1×31\times3. The array is populated in a row-major way.

[arg0arg1arg2]\begin{bmatrix} arg0 & arg1 & arg2 \end{bmatrix}
Example
>>> myarray = array1x3(1,2,3)

array2x1

def array2x1(arg0: expression, arg1: expression) -> expression

This defines a vector or matrix operation of size 2×12\times1. The array is populated in a row-major way.

[arg0arg1]\begin{bmatrix} arg0 \cr arg1 \end{bmatrix}
Example
>>> myarray = array2x1(1, 2)

array2x2

def array2x2(
arg0: expression,
arg1: expression,
arg2: expression,
arg3: expression
) -> expression

This defines a vector or matrix operation of size 2×22\times2. The array is populated in a row-major way.

[arg0arg1arg2arg3]\begin{bmatrix} arg0 & arg1 \cr arg2 & arg3 \end{bmatrix}
Example
>>> myarray = array2x2(1,2, 3,4)

array2x3

def array2x3(
arg0: expression,
arg1: expression,
arg2: expression,
arg3: expression,
arg4: expression,
arg5: expression
) -> expression

This defines a vector or matrix operation of size 2×32\times3. The array is populated in a row-major way.

[arg0arg1arg2arg3arg4arg5]\begin{bmatrix} arg0 & arg1 & arg2 \cr arg3 & arg4 & arg5 \end{bmatrix}
Example
>>> myarray = array2x3(1,2,3, 4,5,6)

array3x1

def array3x1(
arg0: expression,
arg1: expression,
arg2: expression
) -> expression

This defines a vector or matrix operation of size 3×13\times1. The array is populated in a row-major way.

[arg0arg1arg2]\begin{bmatrix} arg0 \cr arg1 \cr arg2 \end{bmatrix}
Example
>>> myarray = array3x1(1, 2, 3)

array3x2

def array3x2(
arg0: expression,
arg1: expression,
arg2: expression,
arg3: expression,
arg4: expression,
arg5: expression
) -> expression

This defines a vector or matrix operation of size 3×23\times2. The array is populated in a row-major way.

[arg0arg1arg2arg3arg4arg5]\begin{bmatrix} arg0 & arg1 \cr arg2 & arg3 \cr arg4 & arg5 \end{bmatrix}
Example
>>> myarray = array3x2(1,2, 3,4, 5,6)

array3x3

def array3x3(
arg0: expression,
arg1: expression,
arg2: expression,
arg3: expression,
arg4: expression,
arg5: expression,
arg6: expression,
arg7: expression,
arg8: expression
) -> expression

This defines a vector or matrix operation of size 3×33\times3. The array is populated in a row-major way.

[arg0arg1arg2arg3arg4arg5arg6arg7arg8]\begin{bmatrix} arg0 & arg1 & arg2 \cr arg3 & arg4 & arg5 \cr arg6 & arg7 & arg8 \end{bmatrix}
Example
>>> myarray = array3x3(1,2,3, 4,5,6, 7,8,9)

asin

def asin(input: expression) -> expression

This returns an expression that is the arcsinarcsin or sin1sin^{-1} of input. The output expression is in radians.

Example
>>> expr = asin(sqrt(0.5))
>>> expr.print(); # in radians
Expression size is 1x1
@ row 0, col 0 :
0.785398
>>>
>>> (expr * 180/getpi()).print(); # in degrees
Expression size is 1x1
@ row 0, col 0 :
45
See Also

sin(), cos(), tan(), acos(), atan()

atan

def atan(input: expression) -> expression

This returns an expression that is the arctanarctan or tan1tan^{-1} of input. The output expression is in radians.

Example
>>> expr = atan(0.57735)
>>> expr.print(); # in radians
Expression size is 1x1
@ row 0, col 0 :
0.523599
>>>
>>> (expr * 180/getpi()).print(); # in degrees
Expression size is 1x1
@ row 0, col 0 :
30
See Also

sin(), cos(), tan(), asin(), acos()

bode

def bode(reals: list[float], imags: list[float]) -> list[list[float]]

cn

def cn(n: float) -> expression

This function takes as an argument the fundamental frequency multiplier. It is a shortform for cos(n2πft)cos(n * 2\pi f t).

Example
>>> f = 15.5 * 1e+9 # fundamnetal frequency
>>> setfundamentalfrequency(f)
>>> drivingsignal = cn(2) # same as cos(n* 2*getpi*f*t())
See Also

sn()

comp

def comp(selectedcomp: int, input: expression) -> expression

This returns the selected component of a column vector expression. For a column vector expression, selectedcomp is 0 for the first, 1 for the second component and 2 for the third component respectively. For a matrix expression, the whole corresponding row is returned in the form of an expression. Thus, if selectedcomp, for example is 5, then an expression containing the entries of the fifth row of the matrix is returned.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> u.setvalue(vol, array3x1(10,20,30))
>>> vecexpr = 2*(u+u)
>>> comp(0, vecexpr).write(vol, "comp.vtk", 1)
See Also

compx(), compy(), compz()

complexdivision

def complexdivision(a: list[expression], b: list[expression]) -> list[expression]

complexinverse

def complexinverse(a: list[expression]) -> list[expression]

complexproduct

def complexproduct(a: list[expression], b: list[expression]) -> list[expression]

computeradiationpattern

def computeradiationpattern(
skinregion: int,
region: int,
E: field,
mu: float,
epsilon: float,
numpts: int
) -> iodata

compx

def compx(input: expression) -> expression

This returns the first or x component of a column vector expression. For a matrix expression, an expression containing the entries of the first row of the matrix is returned. This is equivalent to setting selectedcomp=0 in comp().

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> u.setvalue(vol, array3x1(10,20,30))
>>> vecexpr = 2*(u+u)
>>> compx(vecexpr).write(vol, "compx.vtk", 1)
See Also

comp(), compy(), compz()

compy

def compy(input: expression) -> expression

This returns the second or y component of a column vector expression. For a matrix expression, an expression containing the entries of the second row of the matrix is returned. This is equivalent to setting selectedcomp=1 in comp().

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> u.setvalue(vol, array3x1(10,20,30))
>>> vecexpr = 2*(u+u)
>>> compx(vecexpr).write(vol, "compx.vtk", 1)
See Also

comp(), compx(), compz()

compz

def compz(input: expression) -> expression

This returns the third or z component of a column vector expression. For a matrix expression, an expression containing the entries of the third row of the matrix is returned. This is equivalent to setting selectedcomp=2 in comp().

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> u.setvalue(vol, array3x1(10,20,30))
>>> vecexpr = 2*(u+u)
>>> compx(vecexpr).write(vol, "compx.vtk", 1)
See Also

comp(), compx(), compy()

continuitycondition

def continuitycondition(
gamma1: int,
gamma2: int,
u1: field,
u2: field,
errorifnotfound: bool = True,
lagmultorder: int = 0
) -> list[integration]
def continuitycondition(
gamma1: int,
gamma2: int,
u1: field,
u2: field,
rotcent: list[float],
rotangz: float,
angzmod: float,
factor: float,
lagmultorder: int = 0
) -> list[integration]

This returns the formulation terms required to enforce field continuity.

Examples

Example 1: continuitycondition(gamma1:int, gamma2:int. u1:field, u2:field, errorifnotfound:bool=True, lagmultorder:int=0)

>>> ...
>>> u1 = field("h1xyz")
>>> u2 = field("h1xyz")
>>>
>>> elasticity = formulation()
>>> ...
>>> elasticity += continuitycondition(gamma1, gamma2, u1, u2)

This returns the formulation terms required to enforce u1=u2u_1 = u_2 between boundary region Γ1\Gamma_1 and Γ2\Gamma_2 (with Γ1Γ2\Gamma_1 \subseteq \Gamma_2, meshes can be non-matching). In case Γ2\Gamma_2 is larger than Γ1\Gamma_1 (Γ1Γ2\Gamma_1 \subset \Gamma_2) the boolean flag errorifnotfound must be set to false.


Example 2: continuitycondition(gamma:int, gamma2:int, u1:field, u2:field, rotcent:List[double], rotangz:double, angzmod:double, factor:double, lagmultorder:int=0)

>>> ...
>>> # Rotor-stator interface
>>> rotorside=11; statorside=12
>>> ...
>>> # Rotor rotation around z axis
>>> alpha = 30.0
>>> ...
>>> az = field("h1")
>>> ...
>>> magentostatics = formulation()
>>> ...
>>> magnetostatics += continuitycondition(statorside, rotorside, az, az, [0,0,0], alpha, 45.0, -1.0)

This returns the formulation terms required to enforce field continuity across an angzmodangzmod degrees slice of a rotor-stator interface where the rotor geometry is rotated by rotangzrotangz around the zz axis with rotation center at rotcentrotcent. This situation arises for example in electric motor simulations when (anti)periodicity can be considered and thus only a slice of the entire 360 degrees needs to be simulated. Use a factor of -11 for antiperiodicity. Boundary Γ1\Gamma_1 is the rotor-stator interface on the (non-moving) stator side while the boundary Γ2\Gamma_2 is the interface on the rotor side. In the unrotated position the bottom boundary of the stator and rotor slice must be aligned with the xx axis.


The condition is based on a Lagrange multiplier of the same type and the same harmonic content as the field u1u_1 and u2u_2. The mortar finite element method is used to link the unknown dofdof field on Γ1\Gamma_1 and Γ2\Gamma_2 so that there is no restriction on the mesh used for both regions.

See Also

periodicitycondition(), symmetrycondition()

cos

def cos(input: expression) -> expression

This returns an expression that is the coscos of input. The input expression is in radians.

Example
>>> expr = cos(getpi())
>>> expr.print()
Expression size is 1x1
@ row 0, col 0 :
-1
>>>
>>> expr = cos(1)
>>> expr.print()
Expression size is 1x1
@ row 0, col 0 :
0.540302
See Also

sin(), tan(), asin(), acos(), atan()

crossproduct

def crossproduct(a: expression, b: expression) -> expression

This computes the cross-product of two vector expressions. The returned expression is a vector.

c=a×b\boldsymbol{c} = \boldsymbol{a} \times \boldsymbol{b}
Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; top=3
>>> E = field("hcurl")
>>> n = normal(vol)
>>> cp = crossproduct(E, n)

curl

def curl(input: expression) -> expression

This computes the curl of a vector expression. The returned expression is a vector.

w=×u\boldsymbol{w} = \nabla \times \boldsymbol{u}
Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> u = field("h1xyz")
>>> u.setorder(vol, 1);
>>> curl(u).write(vol, "curlu.vtk", 1)
See Also

grad(), div()

d

def d(
expr: expression,
var: expression,
flds: list[field],
deltas: list[expression]
) -> expression

This functions approximates the derivative of an expression when its variable is moved by a delta.

Example

dbtoneper

def dbtoneper(toconvert: expression) -> expression

This converts the expression toconvert from a dBdB units to NepersNepers.

Example
>>> neperattenuation = dbtoneper(100.0)

determinant

def determinant(input: expression) -> expression

This returns the determinant of a square matrix.

Example
>>> matexpr = expression(3,3, [1,2,3, 6,5,4, 8,9,7])
>>> detmat = determinant(matexpr)
>>> detmat.print()
Expression size is 1x1
@ row 0, col 0 :
21
See Also

inverse()

detjac

def detjac() -> expression

This returns the determinant of the Jacobian matrix.

Example
>>> detJ = detjac()
See Also

jac(), invjac()

div

def div(input: expression) -> expression

This computes the divergence of a vector expression. The returned expression is a scalar.

s=us = \nabla \cdot \boldsymbol{u}
Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> u = field("h1xyz")
>>> u.setorder(vol, 1);
>>> div(u).write(vol, "divu.vtk", 1)
See Also

grad(), curl()

dof

def dof(input: expression) -> expression
def dof(input: expression, physreg: int) -> expression

This declares an unknown field (dof \ for degree of freedom). The dofs are defined only on the region physreg which when not provided is set to the element integration region.

Examples

Example 1: dof(input:expression)

>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v) - 2*tf(v))

Example 2: dof(input:expression, physreg:int)

>>> ...
>>> projection += integral(vol, dof(v, vol)*tf(v) - 2*tf(v))
See Also

tf()

doubledotproduct

def doubledotproduct(a: expression, b: expression) -> expression

This computes the double-dot product of two matrix expressions. The returned expression is a scalar.

A:B=i,jAijBij\boldsymbol{A:B} = \sum_{i,j} A_{ij} B_{ij}
Example
>>> a = array2x2(1,2, 3,4)
>>> b = array2x2(11,12, 13, 14)
>>> addotb = doubledotproduct(a, b)
>>> addotb.print()
Expression size is 1x1
@ row 0, col 0 :
130

dt

def dt(input: expression) -> expression
def dt(
input: expression,
initdt: float,
initdtdt: float
) -> expression

This returns the first-order time derivative expression.

Examples

Example 1: dt(input:expression)

>>> ...
>>> setfundamentalfrequency(50)
>>> vmh = field("h1", [2,3])
>>> vmh.setorder(vol, 1)
>>> dt(vmh).write(vol, "dtv.vtk", 1)

Example 2: dt(input:expression, initdt:double, initdtdt)

This gives the transient approximation of the first-order time derivative of a space-independent expression. The initial values must be provided when using generalized alpha (genalpha) and are ignored otherwise.

>>> ...
>>> dtapprox = dt(t()*t(), 0, 2)
See Also

dtdt(), dtdtdt(), dtdtdtdt()

dtdt

def dtdt(input: expression) -> expression
def dtdt(
input: expression,
initdt: float,
initdtdt: float
) -> expression

This returns the second-order time derivative expression.

Examples

Example 1: dtdt(input:expression)

>>> ...
>>> setfundamentalfrequency(50)
>>> vmh = field("h1", [2,3])
>>> vmh.setorder(vol, 1)
>>> dtdt(vmh).write(vol, "dtdtv.vtk", 1)

Example 2: dtdt(input:expression, initdt:double, initdtdt:double)

This gives the transient approximation of the second-order time derivative of a space-independent expression. The initial values must be provided when using generalized alpha (genalpha) and are ignored otherwise.

>>> ...
>>> dtapprox = dtdt(t()*t(), 0, 2)
See Also

dt(), dtdtdt(), dtdtdtdt()

dtdtdt

def dtdtdt(input: expression) -> expression

This returns the third-order time derivative expression.

Example
>>> ...
>>> setfundamentalfrequency(50)
>>> vmh = field("h1", [2,3])
>>> vmh.setorder(vol, 1)
>>> dtdtdt(vmh).write(vol, "dtdtdtv.vtk", 1)
See Also

dt(), dtdt(), dtdtdtdt()

dtdtdtdt

def dtdtdtdt(input: expression) -> expression

This returns the fourth-order time derivative expression.

Example
>>> ...
>>> setfundamentalfrequency(50)
>>> vmh = field("h1", [2,3])
>>> vmh.setorder(vol, 1)
>>> dtdtdtdt(vmh).write(vol, "dtdtdtdtv.vtk", 1)
See Also

dt(), dtdt(), dtdtdt()

dx

def dx(input: expression) -> expression

This returns the xx space derivative expression.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> dx(v).write(vol, "dxv.vtk", 1)
See Also

dy(), dz()

dy

def dy(input: expression) -> expression

This returns the yy space derivative expression.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> dy(v).write(vol, "dyv.vtk", 1)
See Also

dx(), dz()

dz

def dz(input: expression) -> expression

This returns the zz space derivative expression.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> dz(v).write(vol, "dzv.vtk", 1)
See Also

dx(), dy()

elasticwavespeed

def elasticwavespeed(
rho: expression,
E: expression,
nu: expression
) -> list[expression]
def elasticwavespeed(rho: expression, H: expression) -> list[expression]

elementwiseproduct

def elementwiseproduct(a: expression, b: expression) -> expression

This computes the element-wise product of two matrix expressions a and b. The returned expression has the same size as the two input expressions.

Cij=AijBijC_{ij} = A_{ij} B_{ij}
Example
>>> a = array2x2(1,2, 3,4)
>>> b = array2x2(11,12, 13, 14)
>>> aelwb = elementwiseproduct(a, b)
>>> aelwb.print()
Expression size is 2x2
@ row 0, col 0 :
11
@ row 0, col 1 :
24
@ row 1, col 0 :
39
@ row 1, col 1 :
56

emwavespeed

def emwavespeed(mur: expression, epsr: expression) -> expression

entry

def entry(
row: int,
col: int,
input: expression
) -> expression

This gets the (row, col) entry in the input vector or matrix expression.

Example
>>> u = array3x2(1,2, 3,4, 5,6)
>>> arrayentry_row2col0 = entry(2, 0, u) # entry from third row (index=2), first column (index=0)
>>> arrayentry_row2col0.print()
Expression size is 1x1
@ row 0, col 0 :
5

exp

def exp(input: expression) -> expression

This returns an exponential function of base ee:

einpute^{input}
Example
>>> expr = exp(2)
>>> expr.print()
Expression size is 1x1
@ row 0, col 0 :
7.38906
See Also

pow()

eye

def eye(size: int) -> expression

This returns a size x size identity matrix.

Example
>>> II = eye(2)
>>> II.print()
Expression size is 2x2
@ row 0, col 0 :
1
@ row 0, col 1 :
0
@ row 1, col 0 :
0
@ row 1, col 1 :
1

fieldorder

def fieldorder(
input: field,
alpha: float = -1.0,
absthres: float = 0.0
) -> expression

This returns an expression whose value is the interpolation order on each element for the provided input field. The value is a constant on each element. When the argument alpha is set, the value returned is the lowest order required to include alpha percentage of the total shape function coefficient weight. An additional optional argument absthres can be set to provide a minimum total weight below which the lowest possible field order is returned.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1; sur = 2; top = 3
>>> v = field("h1")
>>> v.setorder(vol, 2)
>>> fo = fieldorder(v)
>>> fo.write(vol, "fieldorder.vtk", 1)

getc0

def getc0() -> float

getcirculationports

def getcirculationports(harmonicnumbers: list[int] = []) -> tuple[list[port], list[expression]]

getcirculationsources

def getcirculationsources(circulations: list[expression], inittimederivatives: list[list[float]] = []) -> list[expression]

getdimension

def getdimension(physreg: int) -> int

This returns the x, y and z mesh dimensions.

Example
>>> mymesh = mesh("disk.msh")
>>> dims = mymesh.getdimensions()

getepsilon0

def getepsilon0() -> float

This returns the value of vacuum permittivity ϵ0=8.854187812813e12Fm1\epsilon_0 = 8.854187812813e-12 Fm^{-1}.

Example
>>> eps0 = getepsilon0()
8.854187812813e-12

getextrusiondata

def getextrusiondata() -> list[expression]

This gives the relative depth δ\delta in the extruded layer, the extrusion normal n\boldsymbol{n} and tangents t1\boldsymbol{t_1} and t2\boldsymbol{t_2}. This is useful when creating Perfectly Matched Layers (PMLs).

Example

We use the disk.msh for the example here.

>>> vol=1; sur=2; top=3; circle=4 # physical regions defined in disk.msh
>>> mymesh = mesh()
>>>
>>> # predefine extrusion
>>> volextruded=5; bndextruded=6; # physical regions that will be utilized in extrusion
>>> mymesh.extrude(volextruded, bndextruded, sur, [0.1,0.05])
>>> mymesh.load("disk.msh") # extrusion is performed when the mesh is loaded.
>>> mymesh.write("diskpml.msh")
>>> pmldata = getextrusiondata()
See Also

mesh.extrude()

getharmonic

def getharmonic(
harmnum: int,
input: expression,
numfftharms: int = -1
) -> expression

This returns a single harmonic from a multi-harmonic expression. Set a positive last argument to use an FFT to compute the harmonic. The returned expression is on harmonic 1.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3
>>> v = field("h1", [2])
>>> v.setorder(vol, 1)
>>> v.harmonic(2).setvalue(vol, 1)
>>> constcomp = getharmonic(1, abs(v), 10)
>>> constcomp.write(vol, "constcomp.pos", 1)

getmu0

def getmu0() -> float

This returns the value of vacuum permeability μ0=1.2566370621219e06NA2\mu_0 = 1.2566370621219e-06 NA^{-2}.

Example
>>> mu0 = getmu0()
1.2566370621219e-06

getpi

def getpi() -> float

This returns value of π\pi.

Example
>>> pi = getpi()
3.141592653589793

getrandom

def getrandom() -> float

This returns a random value uniformly distributed between 0.0 and 1.0.

Example
>>> rnd = getrandom()

getrank

def getrank() -> int

This returns the rank of the current process.

Example
>>> import quanscient as qs
>>> rank = qs.getrank()
See Also

count

getsstkomegamodelconstants

def getsstkomegamodelconstants() -> list[float]

getsubversion

def getsubversion() -> int

gettime

def gettime() -> float

This gets the value of the time variable t.

Example
>>> settime(1e-3)
>>> gettime()
0.001
See Also

settime(), t()

gettotalforce

def gettotalforce(
physreg: int,
EorH: expression,
epsilonormu: expression,
extraintegrationorder: int = 0
) -> list[float]
def gettotalforce(
physreg: int,
meshdeform: expression,
EorH: expression,
epsilonormu: expression,
extraintegrationorder: int = 0
) -> list[float]

This returns the components of the total magnetostatic/electrostatic force acting on a given region. In the axisymmetric case zero xx and zz components are returned and the yy component includes a 2π2 \pi factor to provide the force acting on the corresponding 3D shape. Units are ”NN per unit depth” in 2D and NN in 3D and 2D axisymmetry.

Examples

Example 1: gettotalforce(physreg:int, EorH:expression, epsilonormu:expression, extraintegrationorder:int=0)

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> phi = field("h1")
>>> phi.setorder(vol, 2)
>>>
>>> mu0 = 4 * getpi() * 1e-7
>>> mu = parameter()
>>> mu.setvalue(vol, mu0)
>>> totalforce = gettotalforce(vol, -grad(phi), mu)

Example 2: gettotalforce(physreg:int, meshdeform:expression, EorH:expression, epsilonormu:expression, extraintegrationorder:int=0)

This is similar to the above function but the total force is computed on the mesh deformed by the field u.

>>> ...
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> totalforce = gettotalforce(vol, u, -grad(phi), mu)
See Also

printtotalforce()

getturbulentpropertiessstkomegamodel

def getturbulentpropertiessstkomegamodel(
v: expression,
rho: expression,
viscosity: expression,
walldistance: expression,
kp: expression,
omegap: expression,
logomega: expression
) -> list[expression]

getversion

def getversion() -> int

getversionname

def getversionname() -> str

getx

def getx() -> field

This returns the xx coordinate.

Example

An expression for distance can be calculated as follows:

>>> x = getx()
>>> y = gety()
>>> z = getz()
>>> d = sqrt(x*x+y*y+z*z)
See Also

gety(), getz()

gety

def gety() -> field

This returns the yy coordinate.

Example

In CFD applications, a parabolic inlet velocity profile can be prescribed as follows:

>>> y = gety() # y coordinate
>>> U = 0.3 # maximum velocity
>>> h = 0.41 # height of the domain
>>> u = 4*U*y(h-y)/(h*h) # parabolic inlet velocity profile expression
See Also

getx(), getz()

getz

def getz() -> field

This returns the zz coordinate.

Example

An expression for distance can be calculated as follows:

>>> x = getx()
>>> y = gety()
>>> z = getz()
>>> d = sqrt(x*x+y*y+z*z)
See Also

getx(), gety()

grad

def grad(input: expression) -> expression

For a scalar input expression, this is mathematically treated as the gradient of a scalar (v\nabla{v}) and the output is a column vector with one entry per space derivative. For a vector input expression, this is mathematically treated as the gradient of a vector (u\nabla{\boldsymbol{u}}) and the output has one row per component of the input and one column per space derivative.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1);
>>> grad(v).write(vol, "gradv.vtk", 1)
See Also

div(), curl()

greenlagrangestrain

def greenlagrangestrain(input: expression) -> expression

This defines the (nonlinear) Green-Lagrange strains in Voigt form (ϵxx,ϵyy,ϵzz,γyz,γxz,γxy)(\epsilon_{xx},\epsilon_{yy},\epsilon_{zz},\gamma_{yz}, \gamma_{xz},\gamma_{xy}). The input can either be the displacement field or its gradient.

ϵij=12[uiXj+ujXi+uiXiuiXj+ujXiujXj+ukXiukXj] \epsilon_{ij} = \frac{1}{2} \left[ \frac{\partial u_i}{\partial X_j} + \frac{\partial u_j}{\partial X_i} + \frac{\partial u_i}{\partial X_i} \frac{\partial u_i}{\partial X_j} + \frac{\partial u_j}{\partial X_i} \frac{\partial u_j}{\partial X_j} + \frac{\partial u_k}{\partial X_i} \frac{\partial u_k}{\partial X_j} \right]
Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> u = field("h1xyz")
>>> glstrain = greenlagrangestrain(u)
>>> glstrain.print()
See Also

strain()

grouptimesteps

def grouptimesteps(
filename: str,
filestogroup: list[str],
timevals: list[float]
) -> None
def grouptimesteps(
filename: str,
fileprefix: str,
firstint: int,
timevals: list[float]
) -> None

This writes a .pvd ParaView file to group a set of .vtu files that are time solutions at the time values provided in timevals.

Examples

Example 1: grouptimesteps(filename::str, filestogroup:List[str], timevals:List[double])

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> v.write(vol, "v1.vtu", 1)
>>> v.write(vol, "v2.vtu", 1)
>>> grouptimesteps("v.pvd", ["v1.vtu", "v2.vtu"], [0.0, 10.0])

Example 2: grouptimesteps(filename::str, fielprefix:str, firstint:int, timevals:List[double])

This is similar to the previous function except that the full list of file names to group does not have to be provided. The file names are constructed from the file prefix with an appended integer starting from ‘firstint’ by steps of 1. The filenames are ended with .vtu.

>>> ...
>>> grouptimesteps("v.pvd", "v", 1, [0.0, 10.0])

harm

def harm(
harmnum: int,
input: expression,
numfftharms: int = -1
) -> expression

ifpositive

def ifpositive(
condexpr: expression,
trueexpr: expression,
falseexpr: expression
) -> expression

This returns a conditional expression. The argument condexpr specifies the conditional argument. The expression value is trueexpr for all evaluation points where condexpr is larger or equal to zero. Otherwise, its value is falseexpr.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1
>>> x=field("x"); y=field("y"); z=field("z")
>>> expr = ifpositive(x+y, 1, -1) # At points, where x+y>=0, value is 1, otherwise -1.
>>> expr.write(vol, "ifpositive.vtk", 1)
See Also

andpositive(), orpositive()

insertrank

def insertrank(name: str) -> str

integral

def integral(
physreg: int,
tointegrate: expression,
integrationorderdelta: int = 0,
blocknumber: int = 0
) -> integration
def integral(
physreg: int,
meshdeform: expression,
tointegrate: expression,
integrationorderdelta: int = 0,
blocknumber: int = 0
) -> integration
def integral(
physreg: int,
numcoefharms: int,
tointegrate: expression,
integrationorderdelta: int = 0,
blocknumber: int = 0
) -> integration
def integral(
physreg: int,
numcoefharms: int,
meshdeform: expression,
tointegrate: expression,
integrationorderdelta: int = 0,
blocknumber: int = 0
) -> integration
def integral(
physreg: int,
tointegrate: tuple[expression, preconditioner],
integrationorderdelta: int = 0,
blocknumber: int = 0
) -> list[tuple[integration, preconditioner]]
def integral(
physreg: int,
meshdeform: expression,
tointegrate: tuple[expression, preconditioner],
integrationorderdelta: int = 0,
blocknumber: int = 0
) -> list[tuple[integration, preconditioner]]
def integral(
physreg: int,
numcoefharms: int,
tointegrate: tuple[expression, preconditioner],
integrationorderdelta: int = 0,
blocknumber: int = 0
) -> list[tuple[integration, preconditioner]]
def integral(
physreg: int,
numcoefharms: int,
meshdeform: expression,
tointegrate: tuple[expression, preconditioner],
integrationorderdelta: int = 0,
blocknumber: int = 0
) -> list[tuple[integration, preconditioner]]
def integral(
physreg: int,
tointegrate: list[tuple[expression, int, preconditioner]],
integrationorderdelta: int = 0,
blocknumber: int = 0
) -> list[tuple[integration, preconditioner]]
def integral(
physreg: int,
meshdeform: expression,
tointegrate: list[tuple[expression, int, preconditioner]],
integrationorderdelta: int = 0,
blocknumber: int = 0
) -> list[tuple[integration, preconditioner]]
def integral(
physreg: int,
numcoefharms: int,
tointegrate: list[tuple[expression, int, preconditioner]],
integrationorderdelta: int = 0,
blocknumber: int = 0
) -> list[tuple[integration, preconditioner]]
def integral(
physreg: int,
numcoefharms: int,
meshdeform: expression,
tointegrate: list[tuple[expression, int, preconditioner]],
integrationorderdelta: int = 0,
blocknumber: int = 0
) -> list[tuple[integration, preconditioner]]

inverse

def inverse(input: expression) -> expression

This returns the inverse of a square matrix.

Example
>>> matexpr = array2x2(1,2, 3,4)
>>> invmat = inverse(matexpr)
>>> invmat.print()
Expression size is 2x2
@ row 0, col 0 :
-2
@ row 0, col 1 :
1
@ row 1, col 0 :
1.5
@ row 1, col 1 :
-0.5
>>>
>>> matexpr = expression(3,3, [1,2,3, 6,5,4, 8,9,7])
>>> invmat = inverse(matexpr)
See Also

determinant()

invjac

def invjac(row: int, col: int) -> expression
def invjac() -> expression

Example 1: invjac(row:int, col:int)

This returns the inverse of the Jacobian matrix at the entry (row, col).

>>> Ji = invjac(1,2)

Example 2: invjac()

This returns the whole 3×33 \times 3 Jacobian matrix.

>>> Ji = invjac()
See Also

jac(), detjac()

isdefined

def isdefined(physreg: int) -> bool

This checks if a physical region physreg is defined.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3; circle=4;
>>> isdefined(sur) # returns True as the physical region sur=2 is defined
True
>>>
>>> isdefined(5) # returns False as the mesh loaded has no physical region 5
>>> False
See Also

isempty(), isinside(), istouching()

isempty

def isempty(physreg: int) -> bool

This checks if a physical region physreg is empty.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3; circle=4;
>>> isempty(sur)
False
See Also

isdefined(), isinside(), istouching()

isinside

def isinside(physregtocheck: int, physreg: int) -> bool

This checks if a physical region is fully included in another region. The physreg is the physical region with which physregtocheck is checked.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3; circle=4;
>>> isinside(sur, vol)
True
>>>
>>> isinside(vol, sur)
False
See Also

isdefined(), isempty(), istouching()

istouching

def istouching(physregtocheck: int, physreg: int) -> bool

This checks if a region is touching another region. The physreg is the physical region with which physregtocheck is checked.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3; circle=4;
>>> istouching(sur, top)
True
See Also

isdefined(), isempty(), isinside()

jac

def jac(row: int, col: int) -> expression
def jac() -> expression

Example 1: jac(row:int, col:int)

This returns the inverse of the Jacobian matrix at the entry (row, col).

>>> J = jac(1,2)

Example 2: jac()

This returns the whole 3×33 \times 3 Jacobian matrix.

>>> J = jac()
See Also

invjac(), detjac()

linspace

def linspace(
a: float,
b: float,
num: int
) -> list[float]

This gives a vector of num equally spaced values from a to b. The space between each values is calculated as:

banum1\frac{b - a}{num -1}
Example
>>> vals = linspace(0.5, 2.5, 5)
>>> printvector(vals)
0.5 1 1.5 2 2.5
See Also

logspace()

loadshape

def loadshape(meshfile: str) -> list[list[shape]]

This function loads a mesh file to shapes. The output holds a shape for every physical region of dimension d (0D, 1D, 2D, 3D) defined in the mesh file. The loaded shapes can be edited (extruded, deformed, …) and grouped with other shapes to create a new mesh. Note that the usage of loaded shapes might be more limited than other shapes.

Example
>>> diskshapes = loadshape("disk.msh")
>>>
>>> # Add a thin slice on top of the disk (diskshapes[2][1] is the top face of the disk)
>>> thinslice = diskshapes[2][1].extrude(5, 0.02, 2)
>>>
>>> mymesh = mesh([diskshapes[2][0], diskshapes[2][1], diskshapes[3][0], thinslice])
>>> mymesh.write("editeddisk.msh")

loadvector

def loadvector(
filename: str,
delimiter: str = ',',
sizeincluded: bool = False
) -> list[float]

This loads a list from the file filename. The delimiter specfies the character separating each entry in the file. The sizeincluded must be set to True if the first number in the file is the size of the list.

Example
>>> v = [2.4, 3.14, -0.1]
>>> writevector("vecvals.txt", v, '\n', True)
>>> vloaded = loadvector("vecvals.txt", \n', True)
>>> vloaded
[2.4, 3.14, -0.1]
See Also

printvector(), writevector()

log

def log(input: expression) -> expression

This returns an expression that is the natural logarithm of the input expression.

Example
>>> expr = log(2);
>>> expr.print()
Expression size is 1x1
@ row 0, col 0 :
0.693147

logspace

def logspace(
a: float,
b: float,
num: int,
basis: float = 10.0
) -> list[float]

This returns the basis to the power of each of the num values in the linspace.

Example
>>> vals = logspace(1, 3, 3) # resulting linspace: 1, 2, 3
>>> printvector(vals) # 10^1, 10^2, 10^3
10 100 1000
See Also

linspace()

makeharmonic

def makeharmonic(harms: list[int], exprs: list[expression]) -> expression
def makeharmonic(
harms: list[int],
expr: expression,
numfftharms: int
) -> expression

This returns a multi-harmonic expression whose harmonic numbers and expressions are provided as arguments. The argument expressions must be on harmonic 1.

Example
>>> ...
>>> harmexpr = makeharmonic([1,2,4], [11, v.harmonic(2), 14])
>>> harmexpr.write(vol, "harmexpr.pos", 1)

max

def max(a: expression, b: expression) -> expression
def max(a: field, b: field) -> expression
def max(a: parameter, b: parameter) -> expression

This returns an expression whose value is the maximum of the two input arguments a and b.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> x=field("x"); y=field("y"); z=field("z")
>>> max(x,y).write(vol, "max.pos", 1)
See Also

min()

meshsize

def meshsize(integrationorder: int) -> expression

This returns an expression whose value is the length/area/volume for each 1D/2D/3D mesh element respectively. The value is constant on each mesh element. The integrationorder determines the accuracy of mesh size calculated. Higher the number, the better the accuracy. Integration order cannot be negative and if the integration order < 00 a RuntimeError is raised.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1; sur = 2; top = 3
>>> h = meshsize(0)
>>> h.write(top, "meshsize.vtk", 1)

min

def min(a: expression, b: expression) -> expression
def min(a: field, b: field) -> expression
def min(a: parameter, b: parameter) -> expression

This returns an expression whose value is the minimum of the two input arguments a and b.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> x=field("x"); y=field("y"); z=field("z")
>>> min(x,y).write(vol, "min.pos", 1)
See Also

max()

mod

def mod(input: expression, modval: float) -> expression

This is a modulo function. This returns an expression equal to the remainder resulting from the division of input by modval.

Example
>>> expr = mod(10, 9)
>>> expr.print()
Expression size is 1x1
@ row 0, col 0 :
1
>>> expr = mod(99, 100)
>>> expr.print()
Expression size is 1x1
@ row 0, col 0 :
99
>>> expr = mod(2.55, 0.6)
>>> expr.print()
Expression size is 1x1
@ row 0, col 0 :
0.15

moveharmonic

def moveharmonic(
origharms: list[int],
destharms: list[int],
input: expression,
numfftharms: int = -1
) -> expression

This returns an expression equal to the input expression with a selected and moved harmonic content. Set a positive last argument to use an FFT to compute the harmonics of the input expression.

Example
>>> ...
>>> movedharm = moveharmonic([1,2], [5,3], 11+v)
>>> movedharm.write("vol", "movedharm.pos", 1)

norm

def norm(arg0: expression) -> expression

This gives the L2L2 norm of an expression input.

Example
>>> myvector = array3x1(1,2,3)
>>> normL2 = norm(myvector)
>>> normL2.print()
Expression size is 1x1
@ row 0, col 0 :
3.74166

normal

def normal() -> expression
def normal(pointoutofphysreg: int) -> expression

This defines a normal vector with unit norm. If a physical region is provided as an argument then the normal points out of it. if no physical region is provided then the normal can be flipped depending on the element orientation in the mesh.

Examples
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3 # physical regions
>>> normal(vol).write(sur, "normal.vtk", 1)

on

def on(
physreg: int,
expression: expression,
errorifnotfound: bool = True
) -> expression
def on(
physreg: int,
coordshift: expression,
expression: expression,
errorifnotfound: bool = True
) -> expression

This function allows to use fields, unknown dof \ fields or general expressions across physical regions with possibly non- matching meshes by evaluating the expression argument using a (x, y, z) coordinate interpolation. It makes it straightforward to setup the mortar finite element method to enforce general relations, such as field equality u1=u2u_1 = u_2, at the interface Γ\Gamma of non-matching meshes. This can for example be achieved with a Lagrange multiplier λ\lambda such that

Γ( λ(u1u2)+λ(u1u2) )dΓ=0\int_{\Gamma} \left( \ \lambda (u_1^{\prime} - u_2^{\prime}) + {\lambda}^{\prime} (u_1 - u_2) \ \right) d \Gamma = 0

holds for any appropriate field u1u_1^{\prime}, u2u_2^{\prime} and λ{\lambda}^{\prime}. The example below illustrates the formulation terms needed to implement the Lagrange multiplier between interfaces Γ1\Gamma_1 and Γ2\Gamma_2.

Examples

Example 1: on(physreg:int, expr:expression, errorifnotfound:bool=True)

physreg is the physical region across which the expression expr is evaluated. The expr argument can be fields or dof \ fields or any general expressions.

>>> ...
>>> u1=field("h1xyz"); u2=field("h1xyz"); lambda=field("h1xyz")
>>> ...
>>> elasticity = formulation()
>>> ...
>>> elasticity += integral(gamma1, dof(lambda)*tf(u1) )
>>> elasticity += integral(gamma2, -on(gamma1, dof(lambda)) * tf(u2) )
>>> elasticity += integral(gamma1, (dof(u1) - on(gamma2, dof(u2)))*tf(lambda) )

When setting the flag errorifnotfound=False, any point in Γ1\Gamma_1 without a relative in Γ2\Gamma_2 (and vice versa) does not contribute to the assembled matrix. The default value is True for which an error is raised if a point in Γ1\Gamma_1 is without a relative in Γ2\Gamma_2 (and vice versa).

The case where there is no unknown dof \ term in the expression argument is described below:

>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3
>>> x=field("x"); y=field("y"); z=field("z"); v=field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>> projection += integral(top, dof(v)*tf(v) - on(vol, dz(z))*tf(v))
>>> projection.solve()
>>> v.write(top, "dzz.pos", 1)

With the on function, the (x, y, z) coordinates corresponding to each Gauss point of the integral are first calculated then the dz(z) \ expression is evaluated through interpolation at these (x, y, z) coordinates on region vol. With on here dz(z) \ is correctly evaluated as 11 because the z-derivative calculation is performed on the volume region vol. Without the on operator the z-derivative would be wrongly calculated on the top face of the disk (a plane perpendicular to the z-axis).

If a requested interpolation point cannot be found (because it is outside of physreg or because the interpolation algorithm fails to converge, as can happen on curved 3D elements) then an error occurs unless errorifnotfound is set to False. In the latter case, the value returned at any non-found coordinate is zero, without raising an error.

Example 2: on(physreg:int, expr:coordshift, expr:expression, errorifnotfound:bool=True)

This is similar to the previous example but here the (x, y, z) coordinates at which to interpolate the expression are shifted by (x+compx(coordshift), y+compy(coordshift), z+compz(coordshift)).

>>> ...
>>> projection += integral(top, dof(v)*tf(v) - on(vol, array3x1(2*x,2*y,2*z), dz(z))*tf(v))

orpositive

def orpositive(exprs: list[expression]) -> expression

This returns an expression whose value is 1 for all evaluation points where at least one input expression has a value larger or equal to zero. Otherwise, its value is -1.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> x=field("x"); y=field("y"); z=field("z")
>>> expr = orpositive([x,y]) # At points, where x>=0 or y>=0, value is 1, otherwise -1.
>>> expr.write(vol, "orpositive.vtk", 1)
See Also

ifpositive(), andpositive()

periodicitycondition

def periodicitycondition(
gamma1: int,
gamma2: int,
u: field,
dat1: list[float],
dat2: list[float],
factor: float,
lagmultorder: int = 0
) -> list[integration]

This returns the formulation terms required to enforce on field uu a rotation or translation periodic condition between boundary region Γ1\Gamma_1 and Γ2\Gamma_2 (meshes can be non-conforming). A factor different than 11 can be provided to scale the field on Γ2\Gamma_2 (use -11 for antiperiodicity).

Example
>>> ...
>>> u = field("h1xyz")
>>> ...
>>> elasticity = formulation()
>>> ...
>>> # In case gamma2 is gamma1 rotated by (ax, ay, az) degrees around first the x, then y and then z-axis.
>>> # Rotation center is (cx, cy, cz)
>>> cx=0; cy=0; cz=0
>>> ax=0; ay=0; az=60
>>>
>>> elasticity += periodicitycondition(gamma1, gamma2, u, [cx,cy,cz], [ax,ay,az], 1.0)
>>>
>>> # In case gamma2 is gamma1 translated by a distance d in direction (nx, ny, nz)
>>> d=0.8
>>> nx=1; ny=0; nz=0
>>> elasticity += peridocitycondition(gamma1, gamma2, u, [nx,ny,nz], [d], 1.0)

The condition is based on a Lagrange multiplier of the same type and the same harmonic content as the field u1u_1 and u2u_2. The mortar finite element method is used to link the unknown dofdof field on Γ1\Gamma_1 and Γ2\Gamma_2 so that there is no restriction on the mesh used for both regions.

More advanced periodic conditions can be implemented easily using on() function.

See Also

continuitycondition(), symmetrycondition()

pow

def pow(base: expression, exponent: expression) -> expression

This is a power function. This returns an expression equal to base to the power exponent:

baseexponent{base}^{exponent}
Example
>>> expr = pow(2, 5)
>>> expr.print()
Expression size is 1x1
@ row 0, col 0 :
32
See Also

exp()

predefinedacousticradiation

def predefinedacousticradiation(
dofp: expression,
tfp: expression,
soundspeed: expression,
neperattenuation: expression
) -> expression

This function defines the equation for the Sommerfeld acoustic radiation condition

np+1cpt=0\partial_{\boldsymbol{n}} p + \frac{1}{c} \frac{\partial p}{\partial t} = 0

which forces the outgoing pressure waves at infinity: a pressure field of the form

p(r,t)=P cos(ωtkr)p(r, t) = P \ cos(\omega t - kr)

propagating in the direction er\boldsymbol{e_r} perpendicular to the truncation boundary indeed satisfies the Sommerfeld radiation condition since

np=erp=k P sin(ωtkr)=wc P sin(ωtkr)=1c(P cos(ωtkr))t \partial_{\boldsymbol{n}} p = \partial_{\boldsymbol{e}_r} p = k \ P \ sin(\omega t - kr) = \frac{w}{c} \ P \ sin(\omega t - kr) = - \frac{1}{c} \frac{\partial (P \ cos(\omega t -kr))}{\partial t}

Zero artificial wave reflection at the truncation boundary happens only if it is perpendicular to the outgoing waves. In practical applications however the truncation boundary is not at an infinite distance from the acoustic source and the wave amplitude is not constant and thus, some level of artificial reflection cannot be avoided. To minimize this effect the truncation boundary should be placed as far as possible from the acoustic source (at least a few wavelengths away).

An acoustic attenuation value can be provided (in Neper/mNeper/m) in case of harmonic problems. For convenience use the function dbtoneper() to convert dB/mdB/m attenuation values to Np/mNp/m.

Example
>>> ...
>>> acoustics += integral(sur, predefinedacousticradiation(dof(p), tf(p), 340, dbtoner(500)))

predefinedacousticstructureinteraction

def predefinedacousticstructureinteraction(
dofp: expression,
tfp: expression,
dofu: expression,
tfu: expression,
soundspeed: expression,
fluiddensity: expression,
normal: expression,
neperattenuation: expression,
scaling: float = 1.0
) -> expression

This function defines the bi-directional coupling for acoustic-structure interaction at the medium interface. Field pp is the acoustic pressure and field u\boldsymbol{u} is the mechanical displacement. Calling n\boldsymbol{n} the normal to the interface pointing out of the solid region, the bi-directional coupling is obtained by adding the fluid pressure loading to the structure

fpressure=pn\boldsymbol{f}_{pressure} = -p \boldsymbol{n}

as well as linking the structure acceleration to the fluid pressure normal derivative using Newton’s law:

np=ρfluid2ut2n\partial_{\boldsymbol{n}} p = - \rho_{fluid} \frac{\partial^2 \boldsymbol{u}}{\partial t^2} \cdot \boldsymbol{n}

To have a good matrix conditioning a scaling factor ss (e.g s=1e10s = 1e10) can be provided. In this case, the pressure source is divided by ss and, to compensate, the pressure force is multiplied by ss. This leads to the correct membrane deflection but the pressure field is divided by the scaling factor.

An acoustic attenuation value can be provided (in Neper/mNeper/m) in case of harmonic problems. For convenience use the function dbtoneper() to convert dB/mdB/m attenuation values to Np/mNp/m.

Example
>>> ...
>>> u = field("h1xy", [2,3])
>>> u.setorder(sur, 2)
>>> acoustics += integral(left, predefinedacousticstructureinteraction(dof(p), tf(p), dof(u), tf(u), 340, 1.2, array2x1(1,0), dbtoneper(500), 1e10)

predefinedacousticwave

def predefinedacousticwave(
dofp: expression,
tfp: expression,
soundspeed: expression,
neperattenuation: expression,
precondtype: str = ''
) -> list[tuple[expression, int, preconditioner]]
def predefinedacousticwave(
dofp: expression,
tfp: expression,
soundspeed: expression,
neperattenuation: expression,
pmlterms: list[expression],
precondtype: str = ''
) -> list[tuple[expression, int, preconditioner]]

This function defines the equation for (linear) acoustic wave propagation:

2p1c22pt2=0\nabla^2 p - \frac{1}{c²} \frac{\partial^2 p}{\partial t^2} = 0

An acoustic attenuation value can be provided (in Neper/mNeper/m) in case of harmonic problems. For convenience use the function dbtoneper() to convert attenuation values from dB/mdB/m to Np/mNp/m.

The arguments have the following meaning:

  • dofpis the dof of the acoustic pressure field.
  • tfp is the test function of the acoustic pressure field.
  • soundspeed is the speed of sound in m/sm/s.
  • neperattenuation is the attenuation in Np/mNp/m.
  • pmlterms is the list of pml terms.
  • precondtype is the type of precondition.
Examples

Example 1: predefinedacousticwave(dofp:expression, tfp:expression, soundspeed:expression, neperattenuation:expression, precondtype:str="")

In the illustrative example below, a highly-attenuated acoustic wave propogation in a rectangular 2D box is simulated.

>>> sur=1; left=2; wall=3
>>> h=10e-3; l=50e-3
>>> q = shape("quadrangle", sur, [0,0,0, l,0,0, l,h,0, 0,h,0], [250,50,250,50])
>>> ll = q.getsons()[3]
>>> ll.setphysicalregion(left)
>>> lwall = shape("union", wall, [q.getsons()[0], q.getsons()[1], q.getsons()[2]])
>>>
>>> mymesh = mesh([q, ll, lwall])
>>>
>>> setfundamentalfrequency(40e3)
>>>
>>> # Wave propogation requires both the in-phase (2) and quadrature (3) harmonics:
>>> p=field("h1", [2,3]); y=field("y")
>>> p.setorder(sur, 2)
>>> # In-phase only pressure source
>>> p.harmonic(2).setconstraint(left, y*(h-y)/(h*h/4))
>>> p.harmonic(3).setconstraint(left, 0)
>>> p.setconstraint(wall)
>>>
>>> acoustics = formulation()
>>> acoustics += integral(sur, predefinedacousticwave(dof(p), tf(p), 340, dbtoneper(500)))
>>> acoustics.solve()
>>> p.write(sur, "p.vtu", 2)
>>>
>>> # Write 50 timesteps for a time visualization
>>> # p.write(sur, "p.vtu", 2, 50)

Example 2: predefinedacousticwave(dofp:expression, tfp:expression, soundspeed:expression, neperattenuation:expression, pmlterms:List[expression], precondtype:str="")

This is the same as the previous example but with PML boundary conditions.

>>> ...
>>> pmlterms = [detDr, detDi, Dr, Di, invDr, invDi]
>>> acoustics += integral(sur, predefinedacousticwave(dof(p), tf(p), 340, 0, pmlterms))

predefinedadmittancebcincompressible

def predefinedadmittancebcincompressible(
physreg: int,
dofv: expression,
tfv: expression,
v: field,
dofp: expression,
yharm: expression
) -> expression

This function returns the weak formulation of admittance boundary condition for incompressible flows:

p(\omega) = \frac{1.0}{Y(\omega)} (\mathbf{V} \cdot {\mathbf{n})

where pp is the pressure, V\bf{V} the velocity of the flow, n\mathbf{n} the normal vector to the boundary (pointing outward), and Y(ω)Y(\omega) the frequency-dependent admittance in harmonic form. It is assumed that the tabulated values of Y(ω)Y(\omega) are provided as function of frequency by the user.

Example
>>> # Fundamental frequency
>>> f = 100.0
>>> setfundamentalfrequency(f)
>>>
>>> # physical regions
>>> fluid=1, inlet=2, admittanceoutlet=3
>>>
>>> # Pressure field
>>> p = field("h1", [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
>>> p.setorder(all, 1)
>>>
>>> # Velocity field
>>> V = field("h1xyz", [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
>>> V.setorder(all, 2)
>>>
>>> p.harmonic(1).setconstraint(admittanceoutlet)
>>>
>>> form = formulation()
>>>
>>> omega = 2*getpi()*f
>>>
>>> yharm = makeharmonic([1,2,3,4,5,6,7,8,9,10,11], [yreal(0), yreal(f), yimag(f), yreal(2*f), yimag(2*f), yreal(3*f), yimag(3*f), yreal(4*f), yimag(4*f), yreal(5*f), yimag(5*f)])
>>>
>>> form += integral(admittanceoutlet, 5, predefinedimpedancebcincompressible(fluid, V, dof(V), tf(V), dof(p), yharm))
See Also

predefinedimpedancebcincompressible()

predefinedadvectiondiffusion

def predefinedadvectiondiffusion(
doff: expression,
tff: expression,
v: expression,
alpha: expression,
beta: expression,
gamma: expression,
isdivvzero: bool = True
) -> expression

This defines the weak formulation for the generalized advection-diffusion equation:

βct(αc)+γ(cv)=0\beta \frac{\partial c}{\partial t} - \nabla \cdot \left( \boldsymbol{\alpha} \nabla c\right) + \gamma \nabla \cdot \left( c \boldsymbol{v} \right)= 0

where cc is the scalar quantity of interest and v\boldsymbol{v} is the velocity that the quantity is moving with. With β\beta and γ\gamma set to unit, the classical advection-diffusion equation with diffusivity tensor α\boldsymbol{\alpha} is obtained. Set isdivvzero to True if v\nabla \cdot \boldsymbol{v} is zero (for incompressible flows).

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> T = field("h1")
>>> v = field("h1xyz")
>>> T.setorder(vol, 1)
>>> v.setorder(vol, 1)
>>> advdiff = formulation()
>>> advdiff += integral(vol, predefinedadvectiondiffusion(dof(T), tf(T), v, 1e-4, 1.0, 1.0, True))
See Also

predefineddiffusion()

predefinedaml

def predefinedaml(c: expression, shifted: bool = False) -> list[expression]

predefinedboxpml

def predefinedboxpml(
pmlreg: int,
innerreg: int,
c: expression,
shifted: bool = False
) -> list[expression]

This is a collective MPI operation and hence must be called by all the ranks. This function returns

[(D)(D)(D)(D)(D1)(D1)] \Big[ \quad \Re (|\boldsymbol{D}|) \quad \Im (|\boldsymbol{D}|) \quad \Re (\boldsymbol{D}) \quad \Im (\boldsymbol{D}) \quad \Re (\boldsymbol{D}^{-1}) \quad \Im (\boldsymbol{D}^{-1}) \quad \Big]

where D\boldsymbol{D} is the PML transformation matrix for a square box in a square box. A hyperbolic or shifted hyperbolic PML can be selected with the last argument.

Example
>>> ...
>>> k = 2*getpi()*freq/c # wave number
>>> Dterms = predefinedboxpml(pmlreg, innerreg, k)

predefineddiffusion

def predefineddiffusion(
doff: expression,
tff: expression,
alpha: expression,
beta: expression
) -> expression

This defines the weak formulation for the generalized diffusion equation:

βct(αc)=0\beta \frac{\partial c}{\partial t} - \nabla \cdot \left( \boldsymbol{\alpha} \nabla c\right) = 0

where cc is the scalar quantity of interest. With β\beta set to unit, the classical diffusion equation with diffusivity tensor α\boldsymbol{\alpha} is obtained.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; top=3
>>>
>>> # Temperature field in [K]:
>>> T = field("h1")
>>> T.setorder(vol, 1)
>>> T.setconstraint(top, 298)
>>>
>>> # Material properties:
>>> k = 237.0 # thermal conductivity of aluminium [W/mK]
>>> cp = 897.0 # heat capacity of aluminium [J/kgK]
>>> rho = 2700 # density of aluminium [Kg/m^3]
>>>
>>> heatequation = formulation()
>>> heatequation += integral(vol, predefineddiffusion(dof(T), tf(T), k, rho*cp))
See Also

predefinedadvectiondiffusion()

predefinedelasticity

def predefinedelasticity(
dofu: expression,
tfu: expression,
Eyoung: expression,
nupoisson: expression,
myoption: str = ''
) -> expression
def predefinedelasticity(
dofu: expression,
tfu: expression,
elasticitymatrix: expression,
myoption: str = ''
) -> expression
def predefinedelasticity(
dofu: expression,
tfu: expression,
u: field,
Eyoung: expression,
nupoisson: expression,
prestress: expression,
myoption: str = ''
) -> expression
def predefinedelasticity(
dofu: expression,
tfu: expression,
u: field,
elasticitymatrix: expression,
prestress: expression,
myoption: str = ''
) -> expression

This defines a classical linear elasticity formulation.

Examples

Example 1: predefinedelasticity(dofu:expression, tfu:expression, Eyoung:expression, nupoisson:Expression, myoption:str="")

This defines a classical linear isotropic elasticity formulation whose strong form is:

{σρu¨+F=0σ=H ⁣: ⁣ϵϵ=12[u+(u)T]\begin{cases} \nabla \cdot \boldsymbol{\sigma} - \rho \boldsymbol{\ddot{u}} + \boldsymbol{F} = 0 \\[5pt] \boldsymbol{\sigma} = \boldsymbol{H} \! : \! \boldsymbol{\epsilon} \\[5pt] \boldsymbol{\epsilon} = \frac{1}{2} \Bigl[\nabla \boldsymbol{u} + \bigl(\nabla \boldsymbol{u}\bigr)^T \Bigr] \end{cases}

where,

  • u\boldsymbol{u} is the displacement field vector
  • σ\boldsymbol{\sigma} is the Cauchy stress tensor in Voigt notation
  • ϵ\boldsymbol{\epsilon} is the strain tensor in Voigt notation
  • H\boldsymbol{H} is the 4th4^{th} order elasticity/stiffness tensor
  • F\boldsymbol{F} is the volumetric body force vector
  • ρ\rho is the mass density

This is used when the material is isotropic. u is the mechanical displacement, Eyoung is the Young’s modulus [Pa] and nupoisson is the Poisson’s ratio. In 2D the option string must be either set to “planestrain” or “planestress” for a plane strain or plane stress assumption respectively.

>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3
>>>
>>> u = field("h1xyz")
>>> u.setorder(vol, 2)
>>> u.setconstraint(sur)
>>>
>>> elasticity = formulation()
>>> elasticity += integral(vol, predefinedelasticity(dof(u), tf(u), 150e9, 0.3))
>>>
>>> # elasticity += integral(vol, -2300*dtdt(dof(u)) * tf(u)); # 2300 is mass density
>>>
>>> # Atmospheric pressure load (volumetric force) on top face deformed by field u (might require a nonlinear iteration)
>>> elasticity += integral(top, u, -normal(vol)*1e5 * tf(u))
>>>
>>> elasticity.solve()
>>> u.write(top, "u.vtk", 2)

Example 2: predefinedelasticity(dofu:expression, tfu:expression, elasticitymatrix:expression, myoption:str="")

This extends the previous definition (Example 1) to general anisotropic materials. The elasticity matrix [Pa] must be provided such that it relates the stress and strain in Voigt notation.

>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3
>>>
>>> u = field("h1xyz")
>>> u.setorder(vol, 2)
>>> u.setconstraint(sur)
>>>
>>> # It is enough to only provide the lower triangular part of the elasticity matrix as it is symmetric
>>> H = expression(6,6, [195e9, 36e9,195e9, 64e9,64e9,166e9, 0,0,0,80e9, 0,0,0,0,80e9, 0,0,0,0,0,51e9])
>>>
>>> elasticity = formulation()
>>> elasticity += integral(vol, predefinedelasticity(dof(u), tf(u), H))
>>>
>>> # elasticity += integral(vol, -2300*dtdt(dof(u)) * tf(u)); # 2300 is mass density
>>>
>>> # Atmospheric pressure load (volumetric force) on top face deformed by field u (might require a nonlinear iteration)
>>> elasticity += integral(top, u, -normal(vol)*1e5 * tf(u))
>>>
>>> elasticity.solve()
>>> u.write(top, "u.vtk", 2)

Example 3: predefinedelasticity(dofu:expression, tfu:expression, u:field, Eyoung:expression, nupoisson:expression, prestress:expression, myoption:str="")

This defines an isotropic linear elasticity formulation with geometric nonlinearity taken into account (full-Lagrangian formulation using the Green-Lagrange strain tensor). Problems with large displacements and rotations can be simulated with this equation but strains must always remain small. Buckling, snap-through and the likes or eigenvalues of prestressed structures can be simulated with the above equation in combination with a nonlinear iteration loop.

The prestress vector [Pa] must be provided in Voigt notation (σxx,σyy,σzz,σyz,σxz,σxy)(\sigma_{xx},\sigma_{yy},\sigma_{zz},\sigma_{yz},\sigma_{xz},\sigma_{xy}). Set the prestress expression to 0.00.0 for no prestress.

>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3
>>>
>>> u = field("h1xyz")
>>> u.setorder(vol, 2)
>>>
>>> ... # boundary conditions
>>>
>>> prestress = expression(6,1, [10e6,0,0,0,0,0])
>>>
>>> elasticity = formulation()
>>> elasticity += integral(vol, predefinedelasticity(dof(u), tf(u), u, 150e9, 0.3, prestress))
>>> elasticity += integral(vol, -2300*dtdt(dof(u)) * tf(u)); # 2300 is mass density
>>>
>>> ... # nonlinear iteration loop

Example 4: predefinedelasticity(dofu:expression, tfu:expression, u:field, elasticitymatrix:expression, prestress:expression, myoption:str="")

This extends the previous definition (Example 3) to general anisotropic materials. The elasticity matrix [Pa] must be provided such that it relates the stress and strain in Voigt notation. Similarly, the prestress vector [Pa] must be also provided in Voigt notation (σxx,σyy,σzz,σyz,σxz,σxy)(\sigma_{xx},\sigma_{yy},\sigma_{zz},\sigma_{yz},\sigma_{xz},\sigma_{xy}).

>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3
>>>
>>> u = field("h1xyz")
>>> u.setorder(vol, 2)
>>>
>>> ... # boundary conditions
>>>
>>> # It is enough to only provide the lower triangular part of the elasticity matrix as it is symmetric
>>> H = expression(6,6, [195e9, 36e9,195e9, 64e9,64e9,166e9, 0,0,0,80e9, 0,0,0,0,80e9, 0,0,0,0,0,51e9])
>>>
>>> prestress = expression(6,1, [10e6,0,0,0,0,0])
>>>
>>> elasticity = formulation()
>>> elasticity += integral(vol, predefinedelasticity(dof(u), tf(u), u, H, prestress))
>>> elasticity += integral(vol, -2300*dtdt(dof(u)) * tf(u)); # 2300 is mass density
>>>
>>> ... # nonlinear iteration loop

predefinedelasticradiation

def predefinedelasticradiation(
dofu: expression,
tfu: expression,
rho: expression,
H: expression
) -> expression

predefinedelectrostaticforce

def predefinedelectrostaticforce(
input: expression,
E: expression,
epsilon: expression
) -> expression

This function defines the weak formulation term for electrostatic forces. The first argument is the mechanical displacement test function or its gradient, the second is the electric field expression and the third argument is the electric permittivity ( must be a scalar).

Let us call T\boldsymbol{T} [N/m2N/m^2] the electrostatic Maxwell stress tensor:

T=ϵ EE12ϵ(EE) I\boldsymbol{T} = \epsilon \ \boldsymbol{E} \otimes \boldsymbol{E} - \frac{1}{2} \epsilon \left( \boldsymbol{E} \cdot \boldsymbol{E} \right) \ \boldsymbol{I}

where ϵ\epsilon is the electric permittivity, E\boldsymbol{E} is the electric field and I\boldsymbol{I} is the identity matrix. The electrostatic force density is T[\nabla \cdot \boldsymbol{T} [N/m^3]] so that the loading for a mechanical problem can be obtained by adding the following term:

Ω(T)udΩ\int_{\Omega} \left( \nabla \cdot \boldsymbol{T} \right) \cdot \boldsymbol{u}^{\prime} d\Omega

where u\boldsymbol{u} is the mechanical displacement. The term can be rewritten in the form that is provided by this function:

ΩT ϵdΩ-\int_{\Omega} \boldsymbol{T} \ \boldsymbol{\epsilon}^{\prime} d\Omega

where ϵ\boldsymbol{\epsilon} is the infinitesimal strain tensor. This is identical to what is obtained using the virtual work principle. For details refer to ‘Domain decomposition techniques for the nonlinear, steady state, finite element simulation of MEMS ultrasonic transducer arrays’, page 40.

In this function, a region should be provided to the test function argument to compute the force only for the degrees of freedom associated to that specific region (in the example below with tf(u, top) the force only acts on the surface region ‘top’. In any case, a correct force calculation requires including in the integration domain all elements in the region where the force acts and in the element layer around it (in the example below ‘vol’ includes all volume elements touching surface ‘top’).

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; top=3
>>> v=field("h1"); u=field("h1xyz")
>>> v.setorder(vol,1)
>>> u.setorder(vol,2)
>>>
>>> elasticity = formulation()
>>> elasticity += integral(vol, predefinedelasticity(dof(u), tf(u), 150e9, 0.3))
>>> elasticity += integral(vol, predefinedelectrostaticforce(tf(u,top), -grad(v), 8.854e-12))
See Also

predefinedmagnetostaticforce()

predefinedemwave

def predefinedemwave(
dofE: expression,
tfE: expression,
mur: expression,
mui: expression,
epsr: expression,
epsi: expression,
sigr: expression,
sigi: expression,
precondtype: str = ''
) -> tuple[expression, preconditioner]
def predefinedemwave(
dofE: expression,
tfE: expression,
mur: expression,
mui: expression,
epsr: expression,
epsi: expression,
sigr: expression,
sigi: expression,
pmlterms: list[expression],
precondtype: str = ''
) -> tuple[expression, preconditioner]

This defines the equation for (linear) electromagnetic wave propagation:

×(1μ×E)+σEt+ϵ2Et2=0 \nabla \times \left( \frac{1}{\mu} \nabla \times \boldsymbol{E} \right) + \sigma \frac{\partial \boldsymbol{E}}{\partial t} + \epsilon \frac{\partial^2 \boldsymbol{E}}{\partial t^2} = 0

where E\boldsymbol{E} is the electric field, μ\mu is the magnetic permeability, ϵ\epsilon is the electric permiitivity and σ\sigma is the electric conductivity. The real and imaginary parts of each material property can be provided.

The argument have the following meaning:

  • dofE is the dof of the electric field.
  • tfE is the test function of the electric field.
  • mur and mui is the real and imaginary part of the magnetic permeability μ\mu.
  • epsr and epsi is the real and imaginary part of the electric permittivity ϵ\epsilon.
  • sigr and sigi is the real and imaginary part of the electric conductivity σ\sigma.
  • pmlterms is the list of pml terms.
  • precondtype is the type of precondition.
Examples

Example 1: predefinedemwave(dofE:expression, tfE:expression, mur:expression, mui:expression, epsr:expression, epsi:expression, sigr:expression, sigi:expression, precondtype:str="")

>>> ...
>>> E = field("hcurl", [2,3])
>>> ...
>>> maxwell += integral(sur, predefinedemwave(dof(E), tf(E), mu0,0, epsilon0,0, 0,0))

Example 2: predefinedemwave(dofE:expression, tfE:expression, mur:expression, mui:expression, epsr:expression, epsi:expression, sigr:expression, sigi:expression, pmlterms:List[expression], precondtype:str="")

This is the same as the previous example but with PML boundary conditions.

>>> ...
>>> pmlterms = [detDr, detDi, Dr, Di, invDr, invDi]
>>> maxwell += integral(sur, predefinedemwave(dof(E), tf(E), mu0,0, epsilon0,0, 0,0, pmlterms))

predefinedfluidstructureinteraction

def predefinedfluidstructureinteraction(
fsireg: int,
fluidreg: int,
dofv: expression,
tfv: expression,
v: field,
dofp: expression,
dofu: expression,
tfu: expression
) -> expression
def predefinedfluidstructureinteraction(
fsireg: int,
dofv: expression,
tfv: expression,
v: field,
dofu: expression,
tfu: expression
) -> expression

This function returns the formulation of fluid-structure interaction for incompressible flows:

The Navier-Stokes equation for incompressible flows in weak form is given by

ΩρtpdΩ+Ω(ρv)pdΩ=0 \int_{\Omega} \frac{\partial \rho}{\partial t} \cdot p^{\prime} d\Omega + \int_{\Omega} (\rho \nabla \cdot \mathbf{v}) {p^{\prime}} d\Omega = 0 ΩρvtvdΩ+Ωρ(vv)vdΩ=ΩρpvdΩΩμ(v+vT)vdΩ+Γμ(v+vT)nvdΓ\begin{align*} \int_{\Omega} \rho \frac{\partial \mathbf{v}}{\partial t} \cdot \mathbf{v^{\prime}} d\Omega + \int_{\Omega} \rho (\mathbf{v} \cdot \nabla \mathbf{v}) \cdot \mathbf{v^{\prime}} d\Omega = & - \int_{\Omega} \rho \nabla p \cdot \mathbf{v^{\prime}} d\Omega \\ & - \int_{\Omega} \mu (\nabla \mathbf{v} + \nabla \mathbf{v}^T)\cdot \nabla \mathbf{v^{\prime}} d\Omega \\ & + \int_{\Gamma} \mu (\nabla \mathbf{v} + \nabla \mathbf{v}^T)\cdot \mathbf{n} \cdot \mathbf{v^{\prime}} d\Gamma \\ \end{align*}

where v\mathbf{v} is the velocity, pp the pressure, u\mathbf{u} the displacement of the structure, Ω\Omega the fluid domain, and Γ\Gamma the whole boundary of the fluid domain. The dashed variables are the test functions in the weak formulation.

The no-slip boundary condition at the fluid-structure interface is applied by using a Lagrange multiplier (λ\boldsymbol{\lambda}) method as

Γfsi((vu˙)λ+λv)dΓfsi=0 \int_{\Gamma_{\text{fsi}}} \Bigl( \left(\mathbf{v} - \dot{\mathbf{u}} \right) \cdot \boldsymbol{\lambda^{\prime}} + \boldsymbol{\lambda} \cdot \mathbf{v}^{\prime} \Bigr) d\Gamma_{\text{fsi}} = 0

The Lagrange multiplier λ\boldsymbol{\lambda} naturally contains the viscous forces acting on the fluid, which can be applied directly to the structure with an inverted sign along with the normal pressure force as

Γfsi(pnλ)udΓfsi=0 \int_{\Gamma_{\text{fsi}}} (p \mathbf{n} -\boldsymbol{\lambda})\cdot \mathbf{u}^{\prime} d\Gamma_{\text{fsi}} = 0

where Γfsi\Gamma_{\text{fsi}} the boundary interface between fluid and structure domains.

Example
>>> mymesh = mesh("micropillar.msh")
>>> solid = 1 # physical region
>>> fluid = 2 # physical region
>>> fsinterface = 3 # Fluid-structure interface region
>>>
>>> v=field("h1xyz"); p=field("h1"); u=field("h1xyz")
>>> v.setorder(fluid, 2)
>>> p.setorder(fluid, 1) # Satisfies the LBB condition
>>>
>>> fsi = formulation()
>>> fsi += integral(solid, predefinedelasticity(dof(u), tf(u), H))
>>> fsi += integral(fluid, umesh, predefinednavierstokes(dof(v), tf(v), v, dof(p), tf(p), 8.9e-4, 1000, 0, 0))
>>> fsi += integral(fsinterface, umesh, predefinedfluidstructureinteraction(fsinterface, fluid, dof(v), tf(v), v, dof(p), dof(u), tf(u)))
See Also

predefinednavierstokes()

predefinedimpedancebcincompressible

def predefinedimpedancebcincompressible(
physreg: int,
dofv: expression,
tfv: expression,
v: field,
dofp: expression,
zharm: expression
) -> expression

This function returns the weak formulation of impedance boundary condition for incompressible flows:

p(ω)=Z(ω)(Vn) p(\omega) = Z(\omega) (\mathbf{V} \cdot \mathbf{n})

where pp is the pressure, V\mathbf{V} the velocity of the flow, n\mathbf{n} the normal vector to the boundary (pointing outward), and Z(ω)Z(\omega) the frequency-dependent impedance in harmonic form. It is assumed that the tabulated values of Z(ω)Z(\omega) are provided as function of frequency by the user.

Example
>>> # Fundamental frequency
>>> f = 100.0
>>> setfundamentalfrequency(f)
>>>
>>> # physical regions
>>> fluid=1, inlet=2, impedanceoutlet=3
>>>
>>> # Pressure field
>>> p = field("h1", [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
>>> p.setorder(all, 1)
>>>
>>> # Velocity field
>>> V = field("h1xyz", [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
>>> V.setorder(all, 2)
>>>
>>> p.harmonic(1).setconstraint(impedanceoutlet)
>>>
>>> form = formulation()
>>>
>>> omega = 2*getpi()*f
>>>
>>> zharm = makeharmonic([1,2,3,4,5,6,7,8,9,10,11], [zreal(0), zreal(f), zimag(f), zreal(2*f), zimag(2*f), zreal(3*f), zimag(3*f), zreal(4*f), zimag(4*f), zreal(5*f), zimag(5*f)])
>>>
>>> form += integral(impedanceoutlet, 5, predefinedimpedancebcincompressible(fluid, V, dof(V), tf(V), dof(p), zharm))
See Also

predefinedadmittancebcincompressible()

predefinedlinearpoissonwalldistance

def predefinedlinearpoissonwalldistance(
physreg: int,
wallreg: int,
interpolationorder: int = 1,
relresddmtol: float = 1e-12,
maxnumddmit: int = 500,
relerrnltol: float = 1e-06,
maxnumnlit: int = 100,
project: bool = True,
verbosity: int = 1
) -> expression
def predefinedlinearpoissonwalldistance(
physreg: int,
meshdeform: expression,
wallreg: int,
interpolationorder: int = 1,
relresddmtol: float = 1e-12,
maxnumddmit: int = 500,
relerrnltol: float = 1e-06,
maxnumnlit: int = 100,
project: bool = True,
verbosity: int = 1
) -> expression

This function calculates and returns distance values from the wall based on the linear Poisson wall distance equation:

(ϕ)+1=0on  Ωϕ=0on  Γudϕdn=0on  ΓN\begin{align*} \nabla \cdot (\nabla \phi) + 1 & = 0 \qquad &on \ \ &\Omega \\[5pt] \phi & = 0 \qquad &on \ \ &\Gamma_u \\[5pt] \frac{d \phi}{dn} & = 0 \qquad &on \ \ &\Gamma_{N} \end{align*}

where ϕ\phi is the approximate wall distance function. The distance is calculated for the physical region physreg from the walls defined in wallreg argument.

More information can be found in Computations of Wall Distances Based on Differential Equations Paul G. Tucker, Chris L. Rumsey, Philippe R. Spalart, Robert E. Bartels, and Robert T. Biedron AIAA Journal 2005 43:3, 539-549, https://doi.org/10.2514/1.8626 .

The system is linear and hence a linear solver is used. After calculating the distance function ϕ\phi, a better approximation of distance is obtained as follows:

d(ϕ)=ϕ±ϕ2+2ϕd(\phi) = - | \nabla \phi | \pm \sqrt{| \nabla \phi|^2 + 2 \phi}

This wall distance uses an above-zero limiter during calculation. Thus, to ensure that the distance value obtained is smooth, set the project argument to True. This will solve a projection of the distance values at the end and return a smooth solution.

Excerpts from the above paper: “The derivation of the above formula for d(ϕ)d(\phi) assumes extensive (infinite) coordinates in the non-normal wall directions. Hence, the distance is only accurate close to walls. However, turbulence models only need ‘d’ accurate close to walls.”

Example
>>> mymesh = mesh("2D_Flatplate_35x25.msh")
>>>
>>> # physical regions
>>> fluid=1, inlet=2, outlet=3, top=4, bot_upstream=5, plate=6
>>>
>>> linearpoisson_wd = predefinedlinearpoissonwalldistance(fluid, wall);
See Also

predefinedreciprocalwalldistance(), predefinednonlinearpoissonwalldistance()

predefinedmagnetostaticforce

def predefinedmagnetostaticforce(
input: expression,
H: expression,
mu: expression
) -> expression

This function defines the weak formulation term for magnetostatic forces. The first argument ist the mechanical displacement test function or its gradient, the second is the magnetic field expression and the third argument is the magnetic permeability ( must be a scalar).

Let us call T\boldsymbol{T} [N/m2N/m^2] the magnetostatic Maxwell stress tensor:

T=μ HH12μ(HH) I\boldsymbol{T} = \mu \ \boldsymbol{H} \otimes \boldsymbol{H} - \frac{1}{2} \mu \left( \boldsymbol{H} \cdot \boldsymbol{H} \right) \ \boldsymbol{I}

where μ\mu is the magnetic permeability, H\boldsymbol{H} is the magnetic field and I\boldsymbol{I} is the identity matrix. The magnetostatic force density is T[\nabla \cdot \boldsymbol{T} [N/m^3]] so that the loading for a mechanical problem can be obtained by adding the following term:

Ω(T)udΩ\int_{\Omega} \left( \nabla \cdot \boldsymbol{T} \right) \cdot \boldsymbol{u}^{\prime} d\Omega

where u\boldsymbol{u} is the mechanical displacement. The term can be rewritten in the form that is provided by this function:

ΩT ϵdΩ-\int_{\Omega} \boldsymbol{T} \ \boldsymbol{\epsilon}^{\prime} d\Omega

where ϵ\boldsymbol{\epsilon} is the infinitesimal strain tensor. This is identical to what is obtained using the virtual work principle. For details refer to ‘Domain decomposition techniques for the nonlinear, steady state, finite element simulation of MEMS ultrasonic transducer arrays’, page 40.

In this function, a region should be provided to the test function argument to compute the force only for the degrees of freedom associated to that specific region (in the example below with tf(u, top) the force only acts on the surface region ‘top’. In any case, a correct force calculation requires including in the integration domain all elements in the region where the force acts and in the element layer around it (in the example below ‘vol’ includes all volume elements touching surface ‘top’).

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; top=3
>>> phi=field("h1"); u=field("h1xyz")
>>> phi.setorder(vol,1)
>>> u.setorder(vol,2)
>>>
>>> elasticity = formulation()
>>> elasticity += integral(vol, predefinedelasticity(dof(u), tf(u), 150e9, 0.3))
>>> elasticity += integral(vol, predefinedelectrostaticforce(tf(u,top), -grad(phi), 4*getpi()*1e-7))
See Also

predefinedelectrostaticforce()

predefinednavierstokes

def predefinednavierstokes(
dofv: expression,
tfv: expression,
v: expression,
dofp: expression,
tfp: expression,
mu: expression,
rho: expression,
dtrho: expression,
gradrho: expression,
includetimederivs: bool = False,
isdensityconstant: bool = True,
isviscosityconstant: bool = True,
precondtype: str = ''
) -> tuple[expression, preconditioner]

This defines the weak formulation for the general (nonlinear) flow of Newtonian fluids:

{ρt+(ρ v)=0ρ(vt+vv)p+(μ(v+(v)T)23μ(v)I)\begin{cases} \frac{\partial \rho}{\partial t} + \nabla \cdot (\rho \ \boldsymbol{v}) = 0 \\[5pt] -\rho \left( \frac{\partial \boldsymbol{v}}{\partial t} + \boldsymbol{v} \cdot \nabla \boldsymbol{v} \right) - \nabla p + \nabla \cdot \left( \mu \left( \nabla \boldsymbol{v} + \left( \nabla \boldsymbol{v} \right)^T \right) - \frac{2}{3} \mu \left( \nabla \cdot \boldsymbol{v} \right) \boldsymbol{I} \right) \end{cases}

where,

  • ρ [kg/m3]\rho \ [kg/m^3] is the fluid density
  • μ [Pas]\mu \ [Pa \cdot s] is the dynamic viscosity of the fluid
  • p [Pa]p \ [Pa] is the pressure
  • v [m/s]\boldsymbol{v} \ [m/s] is the flow velocity

The formulation is provided in a form leading to a quadratic (Newton) convergence when solved iteratively in a loop. This formulation is only valid to simulate laminar as well as turbulent flows. Using it to simulate turbulent flows leads to a so- called DNS method (direct numerical simulation). DNS does not require any turbulence model since it takes into account the whole range of spatial and temporal scales of the turbulence. Therefore, it requires a spatial and time refinement that for industrial applications typically exceeds the computing power of the most advanced supercomputers. As an alternative, RANS and LES method can be used for turbulent flow simulation.

The transition from a laminar to a turbulent flow is linked to a threshold value of the Reynolds number. For a flow in pipes typical Reynolds number below which the flow is laminar is about 20002000.

Arguments dtrho and gradrho are respectively the time derivative and the gradient of the density while includetimederivs gives the option to include or not the time-derivative terms in the formulation. In case the density constant argument is set to True, the fluid is supposed incompressible and the Navier-Stokes equations are further simplified since the divergence of the velocity is zero. If the viscosity is constant in space (it does not have to be constant in time) the constant viscosity argument can be set to True. By default, the density and viscosity are supposed constant and the time-derivative terms are not included. Please note that to simulate the Stokes flow the LBB condition has to be satisfied. This is achieved by using nodal (h1) type shape functions with an interpolation order of at least one higher for the velocity field than for the pressure field. Alternatively, an additional isotropic diffusive term or other stabilization techniques can be used to overcome the LBB limitation.

Example
>>> mymesh = mesh("microvalve.msh")
>>> fluid = 2 # physical region
>>>
>>> v=field("h1xy"); p=field("h1")
>>> v.setorder(fluid, 2)
>>> p.setorder(fluid, 1) # Satisfies the LBB condition
>>>
>>> laminar = formulation()
>>> laminar += integral(fluid, predefinednavierstokes(dof(v), tf(v), v, dof(p), tf(p), 8.9e-4, 1000, 0, 0))
See Also

predefinedstokes()

predefinednavierstokescrosswindstabilization

def predefinednavierstokescrosswindstabilization(
dofv: expression,
tfv: expression,
v: expression,
p: expression,
diffusivity: expression,
rho: expression,
gradv: list[expression],
vorder: int
) -> expression

predefinednavierstokesstreamlinestabilization

def predefinednavierstokesstreamlinestabilization(
dofv: expression,
tfv: expression,
v: expression,
dofp: expression,
tfp: expression,
diffusivity: expression,
rho: expression,
gradv: list[expression],
vorder: int,
pspg: bool,
lsic: bool
) -> expression

predefinednonlinearpoissonwalldistance

def predefinednonlinearpoissonwalldistance(
physreg: int,
wallreg: int,
poissonparameter: int,
interpolationorder: int = 1,
relresddmtol: float = 1e-12,
maxnumddmit: int = 500,
relerrnltol: float = 1e-06,
maxnumnlit: int = 100,
project: bool = True,
verbosity: int = 1
) -> expression
def predefinednonlinearpoissonwalldistance(
physreg: int,
meshdeform: expression,
wallreg: int,
poissonparameter: int,
interpolationorder: int = 1,
relresddmtol: float = 1e-12,
maxnumddmit: int = 500,
relerrnltol: float = 1e-06,
maxnumnlit: int = 100,
project: bool = True,
verbosity: int = 1
) -> expression

This function calculates and returns distance values from the wall based on a generic p-Posison wall distance equation:

(ϕp2 ϕ)+1=0on  Ωϕ=0on  Γudϕdn=0on  ΓN\begin{align*} \nabla \cdot (| \nabla \phi |^{p-2} \ \nabla \phi) + 1 & = 0 \qquad &on \ \ &\Omega \\[5pt] \phi & = 0 \qquad &on \ \ &\Gamma_u \\[5pt] \frac{d \phi}{dn} & = 0 \qquad &on \ \ &\Gamma_{N} \end{align*}

where ϕ\phi is the approximate wall distance function and pp is the Poisson parameter. The distance is calculated for the physical region physreg from the walls defined in wallreg argument. The Poisson parameter pp must be larger than or equal to 2. Higher the parameter better the distance field approximation. The term ϕp2|\nabla \phi|^{p-2} represents an apparent diffusion coefficient. When p=2p=2, the equation reduces to the linear Poisson wall distance. See predefinedlinearpoissonwalldistance().

More information can be found in Wall-Distance Calculation for Turbulence Modelling, J. C. Bakker, Delft University of Technology. http://samofar.eu/wp-content/uploads/2018/10/Bakker_Jelle_BSc-thesis_2018.pdf .

The above system is non-linear and hence an iterative Newton solver is used. After calculating the distance function ϕ\phi, a better approximation of distance is obtained as follows:

d(ϕ)=ϕp1+(pp1ϕ+ϕp)p1pd(\phi) = -|\nabla \phi|^{p-1} + \left( \frac{p}{p-1} \phi + |\nabla \phi|^p \right)^{\frac{p-1}{p}}

This wall distance uses an above-zero limiter during calculation. Thus, to ensure that the distance value obtained is smooth, set the project argument to True. This will solve a projection of the distance values at the end and return a smooth solution.

Example
>>> mymesh = mesh("2D_Flatplate_35x25.msh")
>>>
>>> # physical regions
>>> fluid=1, inlet=2, outlet=3, top=4, bot_upstream=5, plate=6
>>>
>>> nonlinearpoisson_wd = predefinednonlinearpoissonwalldistance(fluid, wall, p=4);
See Also

predefinedreciprocalwalldistance(), predefinedlinearpoissonwalldistance()

predefinedreciprocalwalldistance

def predefinedreciprocalwalldistance(
physreg: int,
wallreg: int,
reflength: float,
smoothpar: float = 0.5,
interpolationorder: int = 1,
relresddmtol: float = 1e-12,
maxnumddmit: int = 500,
relerrnltol: float = 1e-06,
maxnumnlit: int = 100,
verbosity: int = 1
) -> expression
def predefinedreciprocalwalldistance(
physreg: int,
meshdeform: expression,
wallreg: int,
reflength: float,
smoothpar: float = 0.5,
interpolationorder: int = 1,
relresddmtol: float = 1e-12,
maxnumddmit: int = 500,
relerrnltol: float = 1e-06,
maxnumnlit: int = 100,
verbosity: int = 1
) -> expression

This function calculates and returns distance values from the wall based on the reciprocal wall distance (G=1/d) equation:

(GG)+(σ1) G(G)(1+2σ)G4=0\nabla \cdot (G \cdot \nabla G) + (\sigma - 1) \ G(\nabla \cdot \nabla G) - (1 + 2 \sigma)G^4 = 0 G=1Lref=Gwallon  ΓudGdn=0on  ΓNGinit=[0 .. 1e3]1Lrefon  Ω\begin{align*} G & = \frac{1}{L_{ref}} = G_{wall} \qquad &on \ \ &\Gamma_u \\[5pt] \frac{dG}{dn} & = 0 \qquad &on \ \ &\Gamma_{N} \\[5pt] G_{init} & = [0 \ .. \ 1e-3] \frac{1}{L_{ref}} \qquad &on \ \ &\Omega \end{align*}

The distance is calculated for the physical region physreg from the walls defined in wallreg argument.

More information can be found in Fares, E., and W. Schröder. “A differential equation for approximate wall distance.” International journal for numerical methods in fluids 39.8 (2002): 743-762.

Excerpts from the above paper: “The desired smoothing is controlled by the value of the smoothing parameter σ\sigma. The larger the σ\sigma value means a stronger smoothing at sharp edges (but also a large deviation from exact distances). The value of the wall boundary condition GwallG_{wall} influences the smoothing too.

Reference length LrefL_{ref} is relevant in the definition of the initial and boundary conditions. For geometries with just one-sided wall, LrefL_{ref} does not play a role- since the solution is the exact distance for all σ\sigma and GwallG_{wall}. This formulation promises an enhancement of turbulence models at strongly curved surfaces.”

Smaller σ\sigma and larger GwallG_{wall} allow for better approximations of distances although at times it can be difficult to obtain convergence. In such cases, lowering the GwallG_{wall} improves.

Example
>>> mymesh = mesh("2D_Flatplate_35x25.msh")
>>>
>>> # physical regions
>>> fluid=1, inlet=2, outlet=3, top=4, bot_upstream=5, plate=6
>>>
>>> reciprocal_wd = predefinedreciprocalwalldistance(fluid, wall, Lref=0.15);
See Also

predefinedlinearpoissonwalldistance(), predefinednonlinearpoissonwalldistance()

predefinedslipwall

def predefinedslipwall(
physreg: int,
dofv: expression,
tfv: expression,
dofp: expression,
tfp: expression,
diffusivity: expression,
rho: expression,
vorder: int
) -> expression

predefinedstabilization

def predefinedstabilization(
stabtype: str,
delta: expression,
f: expression,
v: expression,
diffusivity: expression,
residual: expression
) -> expression

This function defines the isotropic, streamline anisotropic, crosswind, crosswind shockwave, streamline Petrov_Galerkin and streamline upwind Petrov-Galerkin stabilization methods for the advection-diffusion problem:

ct(αc)+(cv)=0\frac{\partial c}{\partial t} - \nabla \cdot (\boldsymbol{\alpha} \nabla c) + \nabla \cdot (c \boldsymbol{v}) = 0

where cc is the scalar quantity of interest, v [m/s]\boldsymbol{v} \ [m/s] is the velocity that the quantity is moving with and α [m2/s]\boldsymbol{\alpha} \ [m^2/s] is the diffusivity tensor.

A characteristic number of advection-diffusion problems is the Peclet number:

Pe=diffusion timeadvection time=h vαP_e = \frac{\text{diffusion time}}{\text{advection time}} = \frac{h \ \| v \|}{\alpha}

where hh is the length of each mesh element. It quantifies the relative importance of advective and diffusive transport rates. When the Peclet number is large (Pe1P_e \gg 1) the problem is dominated by faster advection (higher advection transport) and prone to spurious oscillations in the solution. Although lowering the Peclet number can be achieved by refining the mesh, a classical alternative is to add stabilization terms to the original equation. A proper choice of stabilization should remove oscillations while changing the original problem as little as possible. In the most simple method proposed (isotropic diffusion), the diffusivity α\alpha is artificially increased to lower the Peclet number. The more advanced method proposed attempts to add artificial diffusion only where it is needed. In the crosswind shockwave, SPG and SUPG methods the residual of the advection-diffusion equation is used to quantify the local amount of diffusion to add. The terms provided by the proposed stabilization methods have the following form:

  • isotropic diffusion: δ h v c c\delta \ h \ \| \boldsymbol{v} \| \ \nabla c \ \nabla c^{\prime}
  • streamline anisotropic diffusion:δ hv(vc)(vc): \frac{\delta \ h}{\| \boldsymbol{v} \|} (\boldsymbol{v} \cdot \nabla c) (\boldsymbol{v} \cdot \nabla c^{\prime})
  • crosswind diffusion: δ h1.5 (c)T T c\delta \ h^{1.5} \ (\nabla c)^T \ \boldsymbol{T} \ \nabla c^{\prime}
  • crosswind shockwave: 12 max(0,δ1γ) h residualc(c)T T c,γ=vh2α,v=vcc2 c\frac{1}{2} \ max(0, \delta - \frac{1}{\gamma}) \ h \ \frac{|residual|}{\| \nabla c \|} (\nabla c)^T \ \boldsymbol{T} \ \nabla c^{\prime}, \quad \gamma = \frac{\| \boldsymbol{v}_{\parallel} \|h}{2 \alpha}, \quad \boldsymbol{v}_{\parallel} = \frac{\boldsymbol{v} \cdot \nabla c}{\| \nabla c \|^2} \ \nabla c
  • streamline Petrov-Galerkin (SPG): δ hv (residual) (vc)\frac{\delta \ h}{\| \boldsymbol{v} \|} \ (residual) \ (\boldsymbol{v} \cdot \nabla c^{\prime})
  • streamline upwind Petrov-Galerkin (SUPG): λ(λ>0)(residual)(vc),λ=δhvαv2\lambda(\lambda > 0) (residual) (\boldsymbol{v} \cdot \nabla c^{\prime}), \quad \lambda = \frac{\delta h}{\| \boldsymbol{v} \|} - \frac{\alpha}{\| \boldsymbol{v} \|^2}

where cc^{\prime} is the test function associated with field cc and T=I1v2vv\boldsymbol{T} = \mathbb{I} - \frac{1}{\| v \|^2} \boldsymbol{v} \otimes \boldsymbol{v}.

To understand the effect of the crosswind diffusion one can notice that for a 2D flow in the xx direction only, tensor T\boldsymbol{T} becomes

[1001][vx2vx2000][0001] \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix} - \begin{bmatrix} \frac{v_x^2}{v_x^2} & 0 \\ 0 & 0 \end{bmatrix} - \begin{bmatrix} 0 & 0 \\ 0 & 1 \end{bmatrix}

and the artificial diffusion is only added at places where c\nabla c has a component in the direction perpendicular to the flow.

How to use the predefined stabilization methods: Due to the large amount of artificial diffusion added by the isotropic diffusion method it should only be considered as a fallback option. In practice, a pair of one streamline and one crosswind method should be used with the smallest possible tuning factor δ\delta. If the problem allows, SUPG should be preferred over SPG and crosswind shockwave should be preferred over the crosswind because the amount of diffusion added tends to be lower.

Examples

The different stabilization methods are defined for the following simulation setup:

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> c = field("h1")
>>> v = field("h1xyz")
>>> c.setorder(vol, 1)
>>> v.setorder(vol, 1)
>>>
>>> # Diffusivity alpha (can be a tensor)
>>> alpha = expression(0.001)
>>>
>>> advdiff = formulation()
>>> advdiff += integral(vol, predefinedadvectiondiffusion(dof(c), tf(c), v, alpha, 1.0, 1.0))
>>>
>>> # Tuning factor (stablization parameter)
>>> delta = 0.5
>>>
>>> # isotropic diffusion
>>> advdiff += integral(vol, predefinedstablization("iso", delta, c, v, 0.0, 0.0))
>>>
>>> # streamline anisotropic diffusion
>>> advdiff += integral(vol, predefinedstabilization("aniso", delta, c, v, 0.0, 0.0))
>>>
>>> # crosswind diffusion
>>> advdiff += integral(vol, predefinedstabilization("cw", delta, c, v, 0.0, 0.0))

The following residual-based stabilizations require the strong-form residual. Neglecting the second-order space-derivative still leads to a good residual approximation.

>>> # The flow is supposed incompressible, i.e div(v) = 0
>>> dofresidual = dt(dof(c)) + v*grad(dof(c)) # residual at current iteration
>>> residual = dt(c) + v*grad(c) # residual at previous iteration
>>>
>>> # crosswind shockwave diffusion: residual at previous iteration must be considered
>>> advdiff += integral(vol, predefinedstabilization("cws", delta, c, v, alpha, residual))
>>>
>>> # streamline Petrov-Galerkin diffusion
>>> advdiff += integral(vol, predefinedstabilization("spg", delta, c, v, alpha, dofresidual))
>>>
>>> # streamline upwind Petrov-Galerkin diffusion
>>> advdiff += integral(vol, predefinedstabilization("supg", delta, c, v, alpha, dofresidual))

predefinedstabilizednavierstokes

def predefinedstabilizednavierstokes(
dofv: expression,
tfv: expression,
v: expression,
dofp: expression,
tfp: expression,
p: expression,
mu: expression,
rho: expression,
dtrho: expression,
gradrho: expression,
includetimederivs: bool,
isdensityconstant: bool,
isviscosityconstant: bool,
precondtype: str,
supg: bool,
pspg: bool,
lsic: bool,
cwnd: bool,
vorder: int,
gradv: list[expression]
) -> tuple[expression, preconditioner]

predefinedstokes

def predefinedstokes(
dofv: expression,
tfv: expression,
dofp: expression,
tfp: expression,
mu: expression,
rho: expression,
dtrho: expression,
gradrho: expression,
includetimederivs: bool = False,
isdensityconstant: bool = True,
isviscosityconstant: bool = True,
precondtype: str = ''
) -> tuple[expression, preconditioner]

This defines the weak formulation for the Stokes (creeping) flow, a linear form of Navier-Stokes where the advective term is ignored as the inertial forces are smaller compared to the viscous forces:

{ρt+(ρ v)=0ρvtp+(μ(v+(v)T)23μ(v)I)\begin{cases} \frac{\partial \rho}{\partial t} + \nabla \cdot (\rho \ \boldsymbol{v}) = 0 \\[5pt] -\rho \frac{\partial \boldsymbol{v}}{\partial t} - \nabla p + \nabla \cdot \left( \mu \left( \nabla \boldsymbol{v} + \left( \nabla \boldsymbol{v} \right)^T \right) - \frac{2}{3} \mu \left( \nabla \cdot \boldsymbol{v} \right) \boldsymbol{I} \right) \end{cases}

where,

  • ρ [kg/m3]\rho \ [kg/m^3] is the fluid density
  • μ [Pas]\mu \ [Pa \cdot s] is the dynamic viscosity of the fluid
  • p [Pa]p \ [Pa] is the pressure
  • v [m/s]\boldsymbol{v} \ [m/s] is the flow velocity

This formulation is only valid to simulate the flow of Newtonian fluids (air, water, …) with a very small Reynolds number (Re1Re \ll 1):

Re=ρ v LμRe = \frac{\rho \ v \ L}{\mu}

where L [m]L \ [m] is the characteristic length of the flow. Low flow velocities, high viscosities or small dimensions can lead to a valid Stokes flow approximation. Flows in microscale devices such as microvalves are also good candidates for Stokes flow simulations.

Arguments dtrho and gradrho are respectively the time derivative and the gradient of the density while includetimederivs gives the option to include or not the time-derivative terms in the formulation. In case the density constant argument is set to True, the fluid is supposed incompressible and the Navier-Stokes equations are further simplified since the divergence of the velocity is zero. If the viscosity in space (it does not have to be constant in time) the constant viscosity argument can be set to True. By default, the density and viscosity are supposed constant and the time-derivative terms are not included. Please note that to simulate the Stokes flow the LBB condition has to be satisfied. This is achieved by using nodal (h1) type shape functions with an interpolation order of at least one higher for the velocity field than for the pressure field. Alternatively, an additional isotropic diffusive term or other stabilization techniques can be used to over the LBB limitation.

Example
>>> mymesh = mesh("microvalve.msh")
>>> fluid = 2 # physical region
>>>
>>> v=field("h1xy"); p=field("h1")
>>> v.setorder(fluid, 2)
>>> p.setorder(fluid, 1) # Satisfies the LBB condition
>>>
>>> stokesflow = formulation()
>>> stokesflow += integral(fluid, predefinedstokes(dof(v), tf(v), dof(p), tf(p), 8.9e-4, 1000, 0, 0))
See Also

predefinednavierstokes()

predefinedstreamlinestabilizationparameter

def predefinedstreamlinestabilizationparameter(v: expression, diffusivity: expression) -> expression

predefinedturbulencecrosswindstabilization

def predefinedturbulencecrosswindstabilization(
v: expression,
rho: expression,
dofk: expression,
tfk: expression,
kp: expression,
gradk: expression,
productionk: expression,
dissipationknodof: expression,
diffusivityk: expression,
korder: int,
dofepsomega: expression,
tfepsomega: expression,
epsomegap: expression,
gradepsomega: expression,
productionepsomega: expression,
dissipationepsomeganodof: expression,
diffusivityepsomega: expression,
epsomegaorder: int,
fv1: expression,
cdkomega: expression
) -> expression

predefinedturbulencemodelsstkomega

def predefinedturbulencemodelsstkomega(
v: expression,
rho: expression,
viscosity: expression,
walldistance: expression,
dofk: expression,
tfk: expression,
kp: expression,
gradk: expression,
korder: int,
dofomega: expression,
tfomega: expression,
omegap: expression,
logomega: expression,
gradomega: expression,
omegaorder: int,
stabsupgkomega: bool,
stabcwdkomega: bool
) -> expression

predefinedturbulencestreamlinestabilization

def predefinedturbulencestreamlinestabilization(
v: expression,
rho: expression,
dofk: expression,
tfk: expression,
gradk: expression,
productionk: expression,
dissipationk: expression,
diffusivityk: expression,
korder: int,
dofepsomega: expression,
tfepsomega: expression,
gradepsomega: expression,
productionepsomega: expression,
dissipationepsomega: expression,
diffusivityepsomega: expression,
epsomegaorder: int,
fv1: expression,
cdkomega: expression
) -> expression

predefinedviscoelasticity

def predefinedviscoelasticity(
dofu: expression,
tfu: expression,
u: field,
Ep: expression,
nup: expression,
taup: expression,
physicalregion: int
) -> expression

predefinedviscoelasticwave

def predefinedviscoelasticwave(
dofu: expression,
tfu: expression,
rho: expression,
Hr: expression,
Hi: expression,
neperattenuation: expression,
pmlterms: list[expression],
precondtype: str = ''
) -> list[tuple[expression, int, preconditioner]]
def predefinedviscoelasticwave(
dofu: expression,
tfu: expression,
dofp: expression,
tfp: expression,
rho: expression,
Er: expression,
Ei: expression,
nu: expression,
neperattenuation: expression,
pmlterms: list[expression],
precondtype: str = ''
) -> list[tuple[expression, int, preconditioner]]
def predefinedviscoelasticwave(
dofu: expression,
tfu: expression,
rho: expression,
Ep: expression,
nup: expression,
taup: expression,
p: parameter,
neperattenuation: expression,
pmlterms: list[expression],
precondtype: str = ''
) -> list[tuple[expression, int, preconditioner]]

printonrank

def printonrank(rank: int, toprint: str) -> None

This function allows the string argument toprint to be printed only by the given rank number. This is useful during DDM simulations and the user wants to print a custom output to monitor the simulation. If the python built-in print function is used while running DDM simulations, then the input string will be printed by all the ranks resulting in the same text written multiple times.

Example
>>> ...
>>> Uz_max = abs(qs.compz(fld.u)).allmax(reg.solidmechanics_target, 5)[0] * 1e6
>>>
>>> # Assuming a DDM simulation is run on 4 nodes (ranks)
>>> # 1) The following is printed by all the ranks (so, it will be printed 4 times)
>>> print(f"Max. Z-deflection is {Uz_max} microns", flush=True)
>>>
>>> # 2) Alternatively, the following can be used to print the string only from a certain rank
>>> if (getrank() == 0):
>>> # below text is printed only by rank 0
>>> print(f"Max. Z-deflection is {Uz_max} microns", flush=True)
>>>
>>> # 3) Same as the second case, but achieved in a single line
>>> printonrank(0, f"Max. Z-deflection is {Uz_max} microns")

printtotalforce

def printtotalforce(
physreg: int,
EorH: expression,
epsilonormu: expression,
extraintegrationorder: int = 0
) -> list[float]
def printtotalforce(
physreg: int,
meshdeform: expression,
EorH: expression,
epsilonormu: expression,
extraintegrationorder: int = 0
) -> list[float]

This prints the total force and its unit. The total force value is returned.

Examples

Example 1: printtotalforce(physreg:int, EorH:expression, epsilonormu:expression, extraintegrationorder:int=0)

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> phi = field("h1")
>>> phi.setorder(vol, 2)
>>>
>>> mu0 = 4 * getpi() * 1e-7
>>> mu = parameter()
>>> mu.setvalue(vol, mu0)
>>> printtotalforce(vol, -grad(phi), mu)

Example 2: printtotalforce(physreg:int, meshdeform:expression, EorH:expression, epsilonormu:expression, extraintegrationorder:int=0)

This is similar to the above function but the total force is computed and returned on the mesh deformed by the field u.

>>> ...
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> printtotalforce(vol, u, -grad(phi), mu)
See Also

gettotalforce()

printvector

def printvector(input: list[float]) -> None
def printvector(input: list[int]) -> None
def printvector(input: list[bool]) -> None

This prints the input list as well as its values. The input can be a list of double/int/bool elements.

Example
>>> v = [2.4, 3.14, -0.1]
>>> printvector(v)
Vector size is 3
2.4 3.14 -0.1
See Also

loadvector(), writevector()

printversion

def printversion() -> None

ramp

def ramp(
rampuptime: expression,
holdtime: expression,
rampdowntime: expression,
delay: expression
) -> expression

scatterwrite

def scatterwrite(
filename: str,
xcoords: list[float],
ycoords: list[float],
zcoords: list[float],
compxevals: list[float],
compyevals: list[float] = [],
compzevals: list[float] = []
) -> None

This writes to the output file a scalar or vector values at given coordinates. If atleast one of the compyevals or compzevals is not empty then the values saved are vectors and not scalars. For scalars, only compxevals must be provided. If the length of all the list arguments are not identical, a RuntimeError is raised.

Example
>>> Define coordinates of three points: (0.0,0.0,0.0), (1.0,1.0,0.0) and (2.0,2.0,0.0)
>>> coordx = [0.0, 1.0, 2.0]
>>> coordy = [0.0, 1.0, 2.0]
>>> coordz = [0.0, 0.0, 0.0]
>>>
>>> vals = [10, 20, 30]
>>> scatterwrite("scalarvalues.vtk", coordx, coordy, coordz, vals)
>>>
>>> xvals = [10, 20, 30]
>>> yvals = [40, 50, 60]
>>> scatterwrite("vectorvalues.vtk", coordx, coordy, coordz, xvals, yvals)

selectall

def selectall() -> int

This returns a new or an existing physical region that covers the entire domain.

Example
>>> rega = 1; regb = 2
>>> qa = shape("quadrangle", rega, {0,0,0, 1,0,0, 1,1,0, 0,1,0}, {5,5,5,5})
>>> qb = shape("quadrangle", regb, {1,0,0, 2,0,0, 2,1,0, 1,1,0}, {5,5,5,5})
>>> mymesh = mesh([qa, qb])
>>> wholedomain = selectall()
>>> mymesh.write("mesh.msh")
See Also

selectunion(), selectintersection(), selectnooverlap(), shape, mesh

selectintersection

def selectintersection(physregs: list[int], intersectdim: int) -> int

This returns a new or an existing physical region that is the intersection of physical regions passed via the argument physregs. The intersectdim argument determines the dimensional data from the intersection that would be utilized in subsequent operations such as setting constraints on a physical region or writing a field or expression to the physical region. For,

  • intersectdim=3: from the intersection, only volumes are used in subsequent operations.
  • intersectdim=2: from the intersection, only surfaces are used in subsequent operations.
  • intersectdim=1: from the intersection, only lines are is used in subsequent operations.
  • intersectdim=0: from the intersection, only points are is used in subsequent operations.

This is useful in isolating only those dimensional data that might be necessary and ignoring the others arising from the intersection. Note that, the intersected region itself is not affected by intersectdim. This argument only determines which dimensional data is utilized when the intersected physical region is used in calculations or operations.

Examples
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3 # physical regions
>>>
>>> # Influence of <code>intersectdim</code>
>>> intersectdim = 3
>>> intersectedreg = selectintersection([vol, vol], intersectdim)
>>> expression(1).write(intersectedreg, "out_3D.vtk", 1) # uses only volume from the intersectedreg
>>>
>>> intersectdim = 2
>>> intersectedreg = selectintersection([vol, vol], intersectdim)
>>> expression(1).write(intersectedreg, "out_2D.vtk", 1) # uses only surfaces from the intersectedreg
>>>
>>> intersectdim = 1
>>> intersectedreg = selectintersection([vol, vol], intersectdim)
>>> expression(1).write(intersectedreg, "out_1D.vtk", 1) # uses only lines from the intersectedreg
>>>
>>> intersectdim = 0
>>> intersectedreg = selectintersection([vol, vol], intersectdim)
>>> expression(1).write(intersectedreg, "out_0D.vtk", 1) # uses only points from the intersectedreg
See Also

selectunion(), selectall(), selectnooverlap()

selectnooverlap

def selectnooverlap() -> int

This returns a new or an existing physical region that covers no-overlap domain in case of overlap DDM and the entire domain otherwise.

See Also

selectunion(), selectintersection(), selectall()

selectunion

def selectunion(physregs: list[int]) -> int

This returns a new or an existing physical region that is the union of physical regions passed via the argument physregs.

Example
>>> mymesh = mesh("disk.msh")
>>> sur=2; top=3 # physical regions
>>> surandtop = selectunion([2,3]) # unioned physical region
See Also

selectintersection(), selectall(), selectnooverlap()

setaxisymmetry

def setaxisymmetry() -> None

This call should be placed at the very beginning of the code. After the call everything will be solved assuming axisymmetry (works for 2D meshes in the xy plane only). All equations should be written in their 3D form.

In order to correctly take into account the cylindrical coordinate change, the appropriate space derivative operators should be used. For example, the gradient of a vector operator required in the mechanical strain calculation to compute the gradient of mechanical displacement should not be defined manually using dx(), dy() and dz() space derivatives. The grad() operator should instead be called on the mechanical displacement vector. Note that If the function is called after loading a mesh, a RuntimeError is raised.

Example
>>> setaxisymmetry()

setdata

def setdata(invec: vec) -> None

setfundamentalfrequency

def setfundamentalfrequency(f: float) -> None

This defines the fundamental frequency (in HzHz) required for multi-harmonic problems.

Example
>>> setfundamentalfrequency(50)

setoutputfield

def setoutputfield(
fieldname: str,
region: int,
expression: expression,
lagrangeorder: int,
step: Union[float, int, None] = None,
specifier: Union[str, None] = None
) -> None

Add a field to output for visualization. The function must be called by all ranks, so that all ranks write their own output files.

Example

>>> qs.setoutputfield("u", qs.selectall(), fld.u, 2)

Example transient

>>> while qs.gettime() < 1.0 - 1e-08 * 0.1:
>>> timestepper.allnext(relrestol=1e-06, maxnumit=1000, timestep=0.1, maxnumnlit=-1)
>>>
>>> qs.setoutputfield("u", reg.all, fld.u, 2, qs.gettime())

setoutputfieldiodata

def setoutputfieldiodata(
fieldname: str,
data: iodata,
step: Union[float, int, None] = None,
specifier: Union[str, None] = None
) -> None

Add a field to output for visualization using precalculated iodata object. The function must be called by all ranks, so that all ranks write their own output files.

Example radiation pattern

>>> patterndata = qs.computeradiationpattern(mesh.em_radiation_pattern_skin, mesh.em_radiation_pattern, fld.E, qs.getmu0(), qs.getepsilon0(), 20)
>>> qs.setoutputfieldiodata("Radiation pattern", patterndata)

setoutputfieldstate

def setoutputfieldstate(
fieldname: str,
region: int,
field: field,
step: Union[float, int, None] = None,
specifier: Union[str, None] = None
) -> None

Add raw field state to output. The function must be called by all ranks, so that all ranks write their own files.

Outputs written using this function can be used as inputs of other simulations using the field initializations feature.

Example

>>> qs.setoutputfieldstate("u", qs.selectall(), fld.u)

setoutputmesh

def setoutputmesh(
name: str,
regions: List[int],
option: int,
mesh: mesh,
step: Union[float, int, None] = None
) -> None

Add mesh output that will be available for users.

Arguments

name : Name for the mesh, used in the filename :  

regions : Physical regions that should be included in the mesh files :  

option : The option parameter given to mesh.write()(…) simcore function :  

mesh : Simcore mesh object :  

step: Current timestamp or the eigenvalue index. If not applicable, leave unset.

setoutputvalue

def setoutputvalue(
name: str,
value: Union[float, List[float]],
step: Union[float, int, None] = None,
specifier: Union[str, None] = None
) -> None

Add a value output. Only the main rank (0) will actually write the value.

Example

>>> qs.setoutputvalue("my scalar", 42.0)
>>> qs.setoutputvalue("my vector", [1.0, 2.0, 3.0, 4.0])

Example transient

>>> while qs.gettime() < 1.0 - 1e-08 * 0.1:
>>> timestepper.allnext(relrestol=1e-06, maxnumit=1000, timestep=0.1, maxnumnlit=-1)
>>>
>>> qs.setoutputvalue("my vector", [1.0, 2.0, 3.0, 4.0], qs.gettime())

setphysicalregionshift

def setphysicalregionshift(shiftamount: int) -> None

This shifts the physical region numbers by shiftamount x (1 + physical region dimension) when loading a mesh.

Example

In the example the point/line/face/volume (0D/1D/2D/3D) physical region numbers will be shifted by 1000/2000/3000/4000 when a mesh is loaded.

>>> setphysicalregionshift(1000)

settime

def settime(t: float) -> None

This sets the time variable t.

Example
>>> settime(1e-3)
See Also

gettime(), t()

settimederivative

def settimederivative(dtx: vec) -> None
def settimederivative(dtx: vec, dtdtx: vec) -> None

This allows us to provide the time derivative vectors to the universe.

Examples

Example 1: settimederivative(dtx:vec)

This provides the first-order time derivate vector to the universe and removes the second-order time derivative vector.

>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> v.setconstraint(sur)
>>>
>>> poisson = formulation()
>>> poisson += integral(vol, grad(dof(v))*grad(tf(v)))
>>> solt1=vec(poisson); solt2=vec(poisson)
>>> dtsol = 0.1 * (solt2 - solt1)
>>> settimederivative(dtsol)
>>> dt(v).write(vol, "dtv.pos", 1)

Example 2: settimederivative(dtx:vec, dtdtx:vec)

This provides the first and second-order time derivative vectors to the universe.

>>> ...
>>> settimederivative(dtsol, vec(poisson))
>>> dtdt(v).write(vol, "dtdtv.pos", 1)

sin

def sin(input: expression) -> expression

This returns an expression that is the sinsin of input. The input expression is in radians.

Example
>>> expr = sin(getpi()/2)
>>> expr.print()
Expression size is 1x1
@ row 0, col 0 :
1
>>>
>>> expr = sin(2)
>>> expr.print()
Expression size is 1x1
@ row 0, col 0 :
0.909297
See Also

cos(), tan(), asin(), acos(), atan()

sn

def sn(n: float) -> expression

This function takes as an argument the fundamental frequency multiplier. It is a shortform for sin(n2πft)sin(n * 2\pi f t).

Example
>>> f = 15.5 * 1e+9 # fundamnetal frequency
>>> setfundamentalfrequency(f)
>>> drivingsignal = sn(2) # same as sin(n* 2*getpi*f*t())
See Also

cn()

solve

def solve(
A: mat,
b: vec,
soltype: str = 'lu',
diagscaling: bool = False
) -> vec
def solve(
A: mat,
b: list[vec],
soltype: str = 'lu'
) -> list[vec]
def solve(
A: mat,
b: vec,
sol: vec,
relrestol: float,
maxnumit: int,
soltype: str = 'bicgstab',
precondtype: str = 'sor',
verbosity: int = 1,
diagscaling: bool = False
) -> None
def solve(
nltol: float,
maxnumnlit: int,
relaxvalue: float,
formuls: list[formulation],
verbosity: int = 1
) -> int

This function solves an algebraic problem. This function can solve both nonlinear and linear systems. Nonlinear problems are solved with a fixed-point iteration. Linear problems can be solved with both direct and iterative solvers. Depending on the algebraic problem and the solver needed, the overloaded solve function can be called with different numbers and types of arguments as shown in the examples below.

Examples

Example 1: solve(A:mat, b:vec, soltype:str="lu", diagscaling:bool=False) -> quanscient.vec

This solves a linear algebraic problem with a (possibly reused) LU or Cholesky factorization by calling the mumps parallel direct solver via PETSC. The matrix can be diagonally scaled for improved conditioning (especially in multiphysics problems). In the case of diagonal scaling the matrix AA is modified after the call.

>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2
>>>
>>> v=field("h1"); x=field("x")
>>> v.setorder(vol, 1)
>>>
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v) - x*tf(v)) # linear system
>>>
>>> projection.generate()
>>> sol = solve(projection.A(), projection.b()) # mumps direct solver

Example 2: solve(A:mat, b:List[vec], soltype:str="lu") -> List[vec]

This is same as the previous example but allows us to efficiently solve Ax=bAx = b for multiple right-hand side vectors bb.

>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2
>>>
>>> v=field("h1"); x=field("x")
>>> v.setorder(vol, 1)
>>>
>>> projection = formulation()
>>> projection += integral(vol, (dof(v) - x)*tf(v)) # linear system
>>>
>>> projection.generate()
>>> b0 = projection.b()
>>> b1 = 2 * b0
>>> b2 = 3 * b0
>>>
>>> sol = solve(projection.A(), [b0, b1, b2]) # mumps direct solver
>>>
>>> # solution for b0
>>> v.setdata(vol, sol[0])
>>> v.write(vol, "sol0.pos", 1)
>>>
>>> # solution for b1
>>> v.setdata(vol, sol[1])
>>> v.write(vol, "sol1.pos", 1)
>>>
>>> # solution for b2
>>> v.setdata(vol, sol[2])
>>> v.write(vol, "sol2.pos", 1)

Example 3: solve(A:mat, b:vec, sol:vec, relrestol:double, maxnumit:int, soltype:str="bicgstab", precondtype:str="sor", verbosity:int=1, diagscaling:bool=False)

This solves a linear algebraic problem with a preconditioned (ilu, sor, gamg) iterative solver (gmres or bicgstab). Vector sol is used as an initial guess and holds the solution at the end of the call. Values relrestol and maxnumit give the relative residual tolerance and the maximum number of iterations to be performed by the iterative solver. The matrix can be diagonally scaled for improved conditioning (especially in multiphysics problems). In the case of diagonal scaling the matrix AA is modified after the call.

>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2
>>>
>>> v=field("h1"); x=field("x")
>>> v.setorder(vol, 1)
>>>
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v) - x*tf(v)) # linear system
>>>
>>> projection.generate()
>>>
>>> initsol = vec(projection)
>>> solve(projection.A(), projection.b(), initsol, 1e-8, 200) # iterative solver
>>> v.setdata(vol, initsol)
>>> print(f"Max solution value is {v.max(vol, 5)[0]}")
Max solution value is 1.013415

Example 4: solve(nltol:double, maxnumnlit:int, realxvalue:double, formuls:List[formulation], verbosity:int=1) -> int

This solves a nonlinear problem with a fixed point iteration. A relaxation value can be provided with relaxvalue argument. Usually, a relaxation value less than 1.01.0 (under-relaxation) is used to avoid divergence of a solution.

>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2
>>>
>>> v=field("h1");
>>> v.setorder(vol, 1)
>>> v.setconstraint(sur, 0)
>>>
>>> electrostatics = formulation()
>>> electrostatics += integral(vol, grad(dof(v))*grad(tf(v)) + v*tf(v) )
>>>
>>> solve(1e-4, 100, 1.0, [electrostatics])
See Also

allsolve(), formulation.allsolve()

sqrt

def sqrt(input: expression) -> expression

This returns an expression that is the square root of the input expression.

Example
>>> expr = sqrt(2)
>>> expr.print()
Expression size is 1x1
@ row 0, col 0 :
1.41421

strain

def strain(input: expression) -> expression

This defines the (linear) engineering strains in Voigt form (ϵxx,ϵyy,ϵzz,γyz,γxz,γxy)(\epsilon_{xx},\epsilon_{yy},\epsilon_{zz},\gamma_{yz}, \gamma_{xz},\gamma_{xy}). The input can either be the displacement field or its gradient.

ϵ=12[u+(u)T]\boldsymbol{\epsilon} = \frac{1}{2} \Bigl[\nabla \boldsymbol{u} + \bigl(\nabla \boldsymbol{u}\bigr)^T \Bigr]
Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> u = field("h1xyz")
>>> engstrain = strain(u)
>>> engstrain.print()
See Also

greenlagrangestrain()

symmetrycondition

def symmetrycondition(
bndphysreg: int,
u: field,
lagmultorder: int = 0
) -> list[integration]
def symmetrycondition(
bndphysreg: int,
meshdeform: expression,
u: field,
lagmultorder: int = 0
) -> list[integration]

This defines a weak formulation of symmetry condition on the boundary region bndphysreg. Depending on, if the u field passed is a vector or scalar, the appropriate symmetry condition is formulated. For a vector and scalar field, the symmetry condition is respectively,

un=0un=0\boldsymbol{u} \cdot \boldsymbol{n} = 0\\ \nabla{u} \cdot \boldsymbol{n} = 0

This function uses a scalar Lagrange multiplier to enforce the symmetry boundary condition. The lagmultorder argument sets the interpolation order of the Lagrange multiplier.

Γ( λ(un)+λ(un) )dΓ=0,where u is a vector field\int_{\Gamma} \left( \ \lambda (\boldsymbol{u}^{\prime} \cdot \boldsymbol{n}) + {\lambda}^{\prime} (\boldsymbol{u} \cdot \boldsymbol{n}) \ \right) d \Gamma = 0 \qquad, \text{where $\boldsymbol{u}$ is a vector field} Γ( λ(un)+λ(un) )dΓ=0,where u is a scalar field\int_{\Gamma} \left( \ \lambda (\boldsymbol{\nabla} {u}^{\prime} \cdot \boldsymbol{n}) + {\lambda}^{\prime} (\boldsymbol{\nabla} {u} \cdot \boldsymbol{n}) \ \right) d \Gamma = 0 \qquad, \text{where $u$ is a scalar field}

where n\boldsymbol{n} is the unit normal vector. In fluid dynamics, the symmetry condition is equivalent to the slip-condition which states that there is no outflow through the boundary. This is equivalent to velocity vector normal to the boundary being zero. Here, the u field passed as the argument is the velocity vector.

Examples

Example 1: symmetrycondition_doc(bndphysreg:int, u:field, lagmultorder:int=0)

>>> mymesh = mesh("quarterdisk.msh")
>>> vol = 1; sur = 2; top = 3; sym=4
>>>
>>> u = field("h1xyz")
>>> u.setorder(vol, 2)
>>>
>>> u.setconstraint(sur)
>>>
>>> E=parameter(); nu=parameter()
>>> E.setvalue(vol,150e9); nu.setvalue(vol,0.3)
>>>
>>> elasticity = qs.formulation()
>>>
>>> elasticity += integral(vol, predefinedelasticity(dof(u), tf(u), E, nu))
>>> elasticity += integral(vol, array3x1(0,0,-10)*tf(u))
>>>
>>> # symmetry boundary condition
>>> elasticity += symmetrycondition(sym, u)
>>>
>>> elasticity.solve()

Note that the symmetry condition also works on fields with harmonics.

>>> ...
>>> u = field("h1xyz", [2,3])
>>> u.setorder(vol, 1)
>>> ...
>>> elasticity += symmetrycondition(sym, u)

The lagrangemultorder also determines the type of shape function used by the Lagrange multiplier. If the order is set to zero, shape function of type “one1” (in 2D) or “one2” (in 3D) is used. For order greater than 0, shape function of type “h1” is used.

>>> ...
>>> u = field("h1xyz")
>>>
>>> elasticity += symmetrycondition(sym, u) # uses "one2" shape function for Lagrange multiplier.
>>> elasticity += symmetrycondition(sym, u, 1) # uses "h1" shape function with interpolation order 1 for Lagrange multiplier.
>>> elasticity += symmetrycondition(sym, u, 2) # uses "h1" shape function with interpolation order 2 for Lagrange multiplier.

Example 2: symmetrycondition_doc(bndphysreg:int, meshdeform:expression, u:field, lagmultorder:int=0)

Here, the symmetry boundary condition of u is formulated on the mesh deformed by the field v.

>>> ...
>>> v = field("h1xyz")
>>> v.setorder(vol, 1)
>>> ...
>>> elasticity += symmetrycondition(sym, v, u, 1)
See Also

periodicitycondition(), continuitycondition()

t

def t() -> expression

This gives the time variable in the form of an expression. The evaluation gives a value equal to gettime().

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setconstraint(vol, sin(2*t()))
See Also

settime(), gettime()

tan

def tan(input: expression) -> expression

This returns an expression that is the tantan of input. The input expression is in radians.

Example
>>> expr = tan(getpi()/4)
>>> expr.print()
Expression size is 1x1
@ row 0, col 0 :
1
>>>
>>> expr = tan(1)
>>> expr.print()
Expression size is 1x1
@ row 0, col 0 :
1.55741
See Also

sin(), cos(), asin(), acos(), atan()

tangent

def tangent() -> expression

This defines a tangent vector with unit norm.

Example
>>> mymesh = mesh("disk.msh")
>>> top=3
>>> tangent().write(top, "tangent.vtk", 1)

tf

def tf(input: expression) -> expression
def tf(input: expression, physreg: int) -> expression

This declares a test function field. The test functions are defined only on the region physreg which when not provided is set to the element integration region.

Examples

Example 1: tf(input:expression)

>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v) - 2*tf(v))

Example 2: tf(input:expression, physreg:int)

>>> ...
>>> projection += integral(vol, dof(v)*tf(v, vol) - 2*tf(v))
See Also

dof()

trace

def trace(a: expression) -> expression

This computes the trace of a square matrix expression a. The returned expression is a scalar.

trace(A)=i=1nAiitrace(\boldsymbol{A}) = \sum_{i=1}^{n} A_{ii}
Example
>>> a = array2x2(1,2, 3,4)
>>> tracea = trace(a)
>>> tracea.print()
Expression size is 1x1
@ row 0, col 0 :
5

transpose

def transpose(input: expression) -> expression

This returns an expression that is the transpose of a vector or matrix expression.

Example
>>> colvec = array3x1(1,2,3)
>>> rowvec = transpose(colvec)
>>> rowvec.print()
Expression size is 1x3
@ row 0, col 0 :
1
@ row 0, col 1 :
2
@ row 0, col 2 :
3
>>> matexpr = expression(3,3, [1,2,3, 4,5,6, 7,8,9])
>>> transposed = transpose(matexpr)

vonmises

def vonmises(stress: expression) -> expression

This returns the von Mises stress expression corresponding to the 3D stress tensor provided as argument. The stress tensor should be provided in Voigt form (σxx,σyy,σzz,σyz,σxz,σxy)(\sigma_{xx},\sigma_{yy},\sigma_{zz},\sigma_{yz},\sigma_{xz},\sigma_{xy}).

For 2D plane stress problems all zz related components of the stress tensor are 00. For plane strain problems do not forget the term $\sigma_{zz} = \nu \cdot (\sigma_{xx} + \sigma_{yy}).

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> u.setconstraint(sur)
>>>
>>> # Material properties
>>> E = 150e9
>>> nu = 0.3
>>>
>>> # Elasticity matrix for isotropic materials
>>> H = expression(6,6, [1-nu,nu,nu,0,0,0, nu,1-nu,nu,0,0,0, nu,nu,1-nu,0,0,0, 0,0,0,0.5*(1-2*nu),0,0, 0,0,0,0,0.5*(1-2*nu),0, 0,0,0,0,0,0.5*(1-2*nu)])
>>> H = H * E/((1+nu)*(1-2*nu))
>>>
>>> elasticity = formulation()
>>> elasticity += integral(vol, predefinedelasticity(dof(u), tf(u), H))
>>>
>>> # Atmospheric pressure load (volumetric force) on top face deformed by field u (might require a nonlinear iteration)
>>> elasticity += integral(top, u, -normal(vol)*1e5 * tf(u))
>>>
>>> elasticity.solve()
>>>
>>> cauchystress = H * strain(u)
>>> vonmisesstress = vonmises(cauchystress)
>>>
>>> vonmisesstress.write(vol, "vonmises.vtk", 1)
>>> maxvonmises = vonmisesstress.max(vol, 5)[0]
>>> maxvonmises

wallfunction

def wallfunction(
fluidphysreg: int,
flds: list[field],
exprs: list[expression],
wftype: str
) -> list[expression]

wavelet

def wavelet(targetfreq: expression, delay: expression) -> expression

write

def write(filename: str, io: iodata) -> None

writecsvfile

def writecsvfile(
filename: str,
header: str,
values: list[str]
) -> None

writeradiationpattern

def writeradiationpattern(
skinregion: int,
region: int,
filename: str,
E: field,
mu: float,
epsilon: float,
numpts: int
) -> None

writeshapefunctions

def writeshapefunctions(
filename: str,
sftypename: str,
elementtypenumber: int,
maxorder: int,
allorientations: bool = False
) -> None

This writes to file all shape functions up to a requested order. It is a convenient tool to visualize the shape functions.

Example
>>> writeshapefunctions("sf.pos", "hcurl", 2, 2)

writevector

def writevector(
filename: str,
towrite: list[float],
delimiter: str = ',',
writesize: bool = False
) -> None

This writes all the entries of a list given in towrite to the file filename with the requested delimiter. The size of the list can also be written at the beginning of a file if writesize is set to True.

Example
>>> v = [2.4,3.14,-0.1]
>>> writevector("vecvals.txt", v)
2.4,3.14,-0.1
>>>
>>> writevector("vecvals.txt", v, ' ')
2.4 3.14 -0.1
>>>
>>> writevector("vecvals.txt", v, '\n', True)
3
2.4
3.14
-0.1
See Also

loadvector(), printvector()

zienkiewiczzhu

def zienkiewiczzhu(input: expression) -> expression

This defines a Zienkiewicz-Zhu type error indicator for the argument expression. The value of the returned expression is constant over each element. It equals the maximum of the argument expression value jump between that element and any neighbour. In the below example, the zienkiewiczzhu(grad(v)) expression quantifies the discontinuity of the field derivative. For a non-scalar arguments the function is applied to each entry and the norm is returned.

Example
>>> sur = 1
>>> q = shape("quadrangle", sur, [0,0,0, 5,0,0, 5,1,0, 0,1,0], [10,3,10,3])
>>> mymesh = mesh([q])
>>>
>>> v=field("h1"); x=field("x"); y=field("y")
>>> v.setorder(sur, 1)
>>>
>>> criterion = zienkiewiczzhu(grad(v))
>>> # Target max criterion is 0.05
>>> maxcrit = ifpositive(criterion-0.05, 1, 0)
>>> mymesh.setadaptivity(maxcrit, 0, 3)
>>>
>>> for i in range(10):
... fct = sin(3*x)/(x*x+1)*sin(getpi()*y)
... v.setvalue(sur, fct)
... v.write(sur, f"v{100+i}.vtk", 1)
... fieldorder(v).write(sur, f"fieldorder{100+i}.vtk", 1)
... criterion.write(sur, f"zienkiewiczzhu{100+i}.vtk", 1)
... relL2err = sqrt(pow(v-fct,2)).integrate(sur,5) / pow(fct,2).integrate(sur,5)
... adapt(2)

Classes

Class: densemat

def densemat(
self
)
def densemat(
self,
numberofrows: int,
numberofcolumns: int
)
def densemat(
self,
numberofrows: int,
numberofcolumns: int,
initvalue: float
)
def densemat(
self,
numberofrows: int,
numberofcolumns: int,
valvec: list[float]
)
def densemat(
self,
numberofrows: int,
numberofcolumns: int,
init: float,
step: float
)
def densemat(
self,
input: list[densemat]
)

The densemat object stores a row-major array of doubles that corresponds to a dense matrix. For storing an array of integers, see indexmat object.

Examples

There are several ways of instantiating a densemat object. They are listed below:

Example 1: densemat(numberofrows:int, numberofcolumns:int) The following creates a matrix with 2 rows and 3 columns. The entries may be undefined.

>>> B = densemat(2,3)

Example 2: densemat(numberofrows:int, numberofcolumns:int, initvalue:double) This creates a matrix with 2 rows and 3 columns. All entries are assigned the value initvalue.

>>> B = densemat(2,3, 12)
>>> B.print()
Matrix size is 2x3
12 12 12
12 12 12

Example 3: densemat(numberofrows:int, numberofcolumns:int, valvec:List[double]) This creates a matrix with 2 rows and 3 columns. The entries are assigned the values of valvec. The length of valvec is expected to be equal to the total count of entries in the matrix. So for creating a matrix of size 2×32 \times 3, length of valvec must be 6.

>>> B = densemat(2,3, [1,2,3,4,5,6])
>>> B.print()
Matrix size is 2x3
1 2 3
4 5 6

Example 4: densemat(numberofrows:int, numberofcolumns:int, init:double, step:double) This creates a matrix with 2 rows and 3 columns. The first entry is assigned the value init and the consecutive entries are assigned values that increase by steps of step.

>>> B = densemat(2,3, 0, 1)
>>> B.print()
Matrix size is 2x3
0 1 2
3 4 5

Example 5: densemat(input:List[densemat]) This creates a matrix that is the vertical concatenation of input matrices. Since, the concatenation occurs vertically, the number of columns in all the input matrices must match.

>>> A = densemat(2,3, 0)
>>> B = densemat(1,3, 2)
>>> AB = densemat([A,B])
>>> AB.print()
Matrix size is 3x3
0 0 0
0 0 0
2 2 2

Methods

count
def count(self) -> int

This counts and returns the total number of entries in the dense matrix.

count=(number of rows)×(number of columns)count = (number \ of \ rows) \times (number \ of \ columns)
Example
>>> B = densemat(2,3)
>>> B.count()
6
countcolumns
def countcolumns(self) -> int

This counts and returns the number of columns in the dense matrix.

Example
>>> B = densemat(2,3)
>>> B.countcolumns()
3
countrows
def countrows(self) -> int

This counts and returns the number of rows in the dense matrix.

Example
>>> B = densemat(2,3)
>>> B.countrows()
2
print
def print(self) -> None

This prints the entries of the dense matrix.

Example
>>> B = densemat(2,3, 0,1)
>>> B.print()
Matrix size is 2x3
0 1 2
3 4 5
printsize
def printsize(self) -> None

This prints the size of the dense matrix.

Example
>>> B = densemat(2,3)
>>> B.printsize()
Matrix size is 2x3

Class: eigenvalue

def eigenvalue(
self,
form: formulation
)
def eigenvalue(
self,
A: mat
)
def eigenvalue(
self,
A: mat,
B: mat
)
def eigenvalue(
self,
K: mat,
C: mat,
M: mat
)
def eigenvalue(
self,
inmats: list[mat]
)

The eigenvalue object allows us to solve classical, generalized and polynomial eigenvalue problems. The computation is done by SLEPc, a scalable library for eigenvalue problem computation.

Examples

Example 1: eigenvalue(A: mat)

This defines a classical eigenvalue problem:

Ax=λxAx = \lambda x

Example2.1: eigenvalue(A:mat, B:mat)

This defines a generalized eigenvalue problem Ax=λBxAx = \lambda Bx. Undamped mechanical resonance modes and resonance frequencies can be calculated with this since an undamped mechanical problem can be written in the form

Mx¨+Kx=0M\ddot{x} + Kx = 0

which for a harmonic excitation at angular frequency ω\omega can be rewritten as

Kx=ω2MxKx = {\omega}^{2}Mx

so that the generalized eigen value λ\lambda is equal to ω2{\omega}^2. To visualize the resonance frequencies of all calculated undamped modes the method eigenvalue.printeigenfrequencies() can be called.

>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3
>>>
>>> u = field("h1xyz")
>>> u.setorder(vol, 2)
>>> u.setconstraint(sur)
>>>
>>> elasticity = formulation()
>>>
>>> elasticity += integral(vol, predefinedelasticity(dof(u), tf(u), 150e-, 0.3))
>>> elasticity += integral(vol, -2300*dtdt(dof(u)) * tf(u)) # 2300 is mass density
>>>
>>> # Direct eigen solver (works only for non-DDM simulation)
>>> elasticity.generate()
>>> eig = eigenvalue(elasticity.K(), elasticity.M())
>>> eig.compute(5, 0)
>>>
>>> eig.printeigenfrequencies()
>>>
>>> eigenvalue_real = eig.geteigenvaluerealpart()
>>> eigenvalue_imag = eig.geteigenvalueimaginarypart()
>>>
>>> eigenvector_real = eig.geteigenvectorrealpart()
>>> eigenvector_imag = eig.geteigenvectorimaginarypart()

Example2.2: eigenvalue(form: formulation)

This is same as the example 2.1 but the eigen solution is obtained iteratively. This can be used for both non-DDM and DDM simulation case setup.

>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3
>>>
>>> u = field("h1xyz")
>>> u.setorder(vol, 2)
>>> u.setconstraint(sur)
>>>
>>> elasticity = formulation()
>>>
>>> elasticity += integral(vol, predefinedelasticity(dof(u), tf(u), 150e-, 0.3))
>>> elasticity += integral(vol, -2300*dtdt(dof(u)) * tf(u)) # 2300 is mass density
>>>
>>> # Iterative eigen solver
>>> eig = eigenvalue(elasticity)
>>> eig.settolerance(1e-6, 1000)
>>> eig.allcompute(1e-6, 1000, 5, 0)
>>>
>>> eig.printeigenfrequencies()
>>>
>>> eigenvalue_real = eig.geteigenvaluerealpart()
>>> eigenvalue_imag = eig.geteigenvalueimaginarypart()
>>>
>>> eigenvector_real = eig.geteigenvectorrealpart()
>>> eigenvector_imag = eig.geteigenvectorimaginarypart()

Example 3: eigenvalue(K: mat, C:mat, M:mat)

This defined a second-order polynomial eigenvalue problem (Mλ2+Cλ+K)x=0(M\lambda^2 + C\lambda + K)x = 0 which allows getting the resonance modes and resonance frequencies for damped mechanical problems. The input arguments are respectively the mechanical stiffness, damping matrix and mass matrix. A second-order polynomial eigenvalue problem attempts to find a solution of the form

x(t)=ueλt,λ=α+iβ=ζωiω1ζ2x(t) = ue^{\lambda t}, \lambda = \alpha + i\beta = -\zeta\omega - i\omega \sqrt{1-\zeta^2}

which corresponds to a damped oscillation at frequency fdamped=β2πf_{damped} = \frac{\beta}{2\pi} with a damping ratio

ζ=α(α2+β2)\zeta = \frac{-\alpha}{\sqrt{(\alpha^2 + \beta^2)}}

In the case of proportional damping (if and only if KM1CKM^{-1}C is symmetric) the oscillation of the undamped system is at ω\omega. The undamped oscillation frequency can then be calculated as

fundamped=(α2+β2)2πf_{undamped} = \frac{\sqrt{(\alpha^2 + \beta^2)}}{2\pi}

To visualize all relevant resonance information for the computed eigenvalues the method eigenvalue.printeigenfrequencies() can be called.

Example 4: eigenvalue(inmats: List[mat])

This defines an arbitraray order polynomial eigenvalue problem.

(inmats[0]+inmats[1]λ+inmats[2]λ2+inmats[3]λ3+...)x=0(inmats[0] + inmats[1]\lambda + inmats[2]\lambda^2 + inmats[3]\lambda^3 + ...)x = 0

Methods

allcompute
def allcompute(
self,
relrestol: float,
maxnumit: int,
numeigenvaluestocompute: int,
targeteigenvaluemagnitude: float = 0.0,
verbosity: int = 1
) -> None

This is an iterative eigen solver that attempts to compute the first numeigenvaluestocompute eigenvalues whose magnitude is closest to a target magnitude (0.00.0 by default). Note that the targeted values here are eigenvalue and not eigenfrequencies. There is no guarantee that SLEPc will return the exact number of eigenvalues requested. This can be used on both non-DDM and DDM simulation setup.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3
>>>
>>> u = field("h1xyz")
>>> u.setorder(vol, 2)
>>> u.setconstraint(sur)
>>>
>>> elasticity = formulation()
>>>
>>> elasticity += integral(vol, predefinedelasticity(dof(u), tf(u), 150e-, 0.3))
>>> elasticity += integral(vol, -2300*dtdt(dof(u)) * tf(u)) # 2300 is mass density
>>>
>>> # Iterative eigen solver
>>> eig = eigenvalue(elasticity)
>>> eig.settolerance(1e-6, 1000)
>>> eig.allcompute(1e-6, 1000, 5, 0)
See Also

eigenvalue.compute(), eigenvalue.allcomputeeigenfrequencies()

allcomputeeigenfrequencies
def allcomputeeigenfrequencies(
self,
relrestol: float,
maxnumit: int,
numeigenfrequenciestocompute: int,
targeteigenfrequency: float = 0.0,
verbosity: int = 1
) -> None

This is an iterative eigen solver that attempts to compute the first numeigenfrequenciestocompute eigenfrequencies whose magnitude is closest to a targeteigenfrequency (0.00.0 by default). Note that the targeted values are eigenfrequencies and not eigenvalues unlike in the eigenvalue.allcompute() method. There is no guarantee that SLEPc will return the exact number of eigenfrequencies requested. This can be used on both non-DDM and DDM simulation setup.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3
>>>
>>> u = field("h1xyz")
>>> u.setorder(vol, 2)
>>> u.setconstraint(sur)
>>>
>>> elasticity = formulation()
>>>
>>> elasticity += integral(vol, predefinedelasticity(dof(u), tf(u), 150e-, 0.3))
>>> elasticity += integral(vol, -2300*dtdt(dof(u)) * tf(u)) # 2300 is mass density
>>>
>>> # Iterative eigen solver
>>> eig = eigenvalue(elasticity)
>>> eig.settolerance(1e-6, 1000)
>>> eig.allcomputeeigenfrequency(1e-6, 1000, 5, 0)
See Also

eigenvalue.compute(), eigenvalue.allcompute()

compute
def compute(
self,
numeigenvaluestocompute: int,
targeteigenvaluemagnitude: float = 0.0
) -> None

This is a direct eigen solver that attempts to compute the first numeigenvaluestocompute eigenvalues whose magnitude is closest to a target magnitude (0.00.0 by default). There is no guarantee that SLEPc will return the exact number of eigenvalues requested. This should be used only for non-DDM simulations, i.e when using single node count.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3
>>>
>>> u = field("h1xyz")
>>> u.setorder(vol, 2)
>>> u.setconstraint(sur)
>>>
>>> elasticity = formulation()
>>>
>>> elasticity += integral(vol, predefinedelasticity(dof(u), tf(u), 150e-, 0.3))
>>> elasticity += integral(vol, -2300*dtdt(dof(u)) * tf(u)) # 2300 is mass density
>>>
>>> # Direct eigen solver (works only for non-DDM simulation)
>>> elasticity.generate()
>>> eig = eigenvalue(elasticity.K(), elasticity.M())
>>> eig.compute(5, 0)
See Also

eigenvalue.allcompute(), eigenvalue.allcomputeeigenfrequencies()

count
def count(self) -> int

This gets the number of eigenvalues found by SLEPc.

geteigenvalueimaginarypart
def geteigenvalueimaginarypart(self) -> list[float]

This gets the imaginary part of all eigenvalues found.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3
>>>
>>> u = field("h1xyz")
>>> u.setorder(vol, 2)
>>> u.setconstraint(sur)
>>>
>>> elasticity = formulation()
>>>
>>> elasticity += integral(vol, predefinedelasticity(dof(u), tf(u), 150e-, 0.3))
>>> elasticity += integral(vol, -2300*dtdt(dof(u)) * tf(u)) # 2300 is mass density
>>>
>>> # Iterative eigen solver
>>> eig = eigenvalue(elasticity)
>>> eig.settolerance(1e-6, 1000)
>>> eig.allcompute(1e-6, 1000, 5, 0)
>>>
>>> eig.printeigenfrequencies()
>>>
>>> eigenvalue_real = eig.geteigenvaluerealpart()
>>> eigenvalue_imag = eig.geteigenvalueimaginarypart()
>>>
>>> eigenvector_real = eig.geteigenvectorrealpart()
>>> eigenvector_imag = eig.geteigenvectorimaginarypart()
geteigenvaluerealpart
def geteigenvaluerealpart(self) -> list[float]

This gets the real part of all eigenvalues found.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3
>>>
>>> u = field("h1xyz")
>>> u.setorder(vol, 2)
>>> u.setconstraint(sur)
>>>
>>> elasticity = formulation()
>>>
>>> elasticity += integral(vol, predefinedelasticity(dof(u), tf(u), 150e-, 0.3))
>>> elasticity += integral(vol, -2300*dtdt(dof(u)) * tf(u)) # 2300 is mass density
>>>
>>> # Iterative eigen solver
>>> eig = eigenvalue(elasticity)
>>> eig.settolerance(1e-6, 1000)
>>> eig.allcompute(1e-6, 1000, 5, 0)
>>>
>>> eig.printeigenfrequencies()
>>>
>>> eigenvalue_real = eig.geteigenvaluerealpart()
>>> eigenvalue_imag = eig.geteigenvalueimaginarypart()
>>>
>>> eigenvector_real = eig.geteigenvectorrealpart()
>>> eigenvector_imag = eig.geteigenvectorimaginarypart()
geteigenvectorimaginarypart
def geteigenvectorimaginarypart(self) -> list[vec]

This gets the imaginary part of all eigenvectors found.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3
>>>
>>> u = field("h1xyz")
>>> u.setorder(vol, 2)
>>> u.setconstraint(sur)
>>>
>>> elasticity = formulation()
>>>
>>> elasticity += integral(vol, predefinedelasticity(dof(u), tf(u), 150e-, 0.3))
>>> elasticity += integral(vol, -2300*dtdt(dof(u)) * tf(u)) # 2300 is mass density
>>>
>>> # Iterative eigen solver
>>> eig = eigenvalue(elasticity)
>>> eig.settolerance(1e-6, 1000)
>>> eig.allcompute(1e-6, 1000, 5, 0)
>>>
>>> eig.printeigenfrequencies()
>>>
>>> eigenvalue_real = eig.geteigenvaluerealpart()
>>> eigenvalue_imag = eig.geteigenvalueimaginarypart()
>>>
>>> eigenvector_real = eig.geteigenvectorrealpart()
>>> eigenvector_imag = eig.geteigenvectorimaginarypart()
geteigenvectorrealpart
def geteigenvectorrealpart(self) -> list[vec]

This gets the real part of all eigenvectors found.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3
>>>
>>> u = field("h1xyz")
>>> u.setorder(vol, 2)
>>> u.setconstraint(sur)
>>>
>>> elasticity = formulation()
>>>
>>> elasticity += integral(vol, predefinedelasticity(dof(u), tf(u), 150e-, 0.3))
>>> elasticity += integral(vol, -2300*dtdt(dof(u)) * tf(u)) # 2300 is mass density
>>>
>>> # Iterative eigen solver
>>> eig = eigenvalue(elasticity)
>>> eig.settolerance(1e-6, 1000)
>>> eig.allcompute(1e-6, 1000, 5, 0)
>>>
>>> eig.printeigenfrequencies()
>>>
>>> eigenvalue_real = eig.geteigenvaluerealpart()
>>> eigenvalue_imag = eig.geteigenvalueimaginarypart()
>>>
>>> eigenvector_real = eig.geteigenvectorrealpart()
>>> eigenvector_imag = eig.geteigenvectorimaginarypart()
printeigenfrequencies
def printeigenfrequencies(self) -> None

This method provides a convenient way to print the eigenfrequencies associated with all eigenvalues calculated for a mechanical resonance problem. In case a generalized eigenvalue problem is used to calculate the resonance modes of an undamped mechanical problem, this method displays the resonance frequency of each calculated resonance mode. In case a second-order polynomial eigenvalue problem is used to calculate the resonance modes of a damped mechanical problem this function displays not only the damped resonance frequency of each resonance mode but also the undamped resonance frequency (only valid in case of proportional damping), the bandwidth, the damping ratio and the quality factor.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3
>>>
>>> u = field("h1xyz")
>>> u.setorder(vol, 2)
>>> u.setconstraint(sur)
>>>
>>> elasticity = formulation()
>>>
>>> elasticity += integral(vol, predefinedelasticity(dof(u), tf(u), 150e-, 0.3))
>>> elasticity += integral(vol, -2300*dtdt(dof(u)) * tf(u)) # 2300 is mass density
>>>
>>> # Direct eigen solver (works only for non-DDM simulation)
>>> elasticity.generate()
>>> eig = eigenvalue(elasticity.K(), elasticity.M())
>>> eig.compute(5, 0)
>>>
>>> eig.printeigenfrequencies()
printeigenvalues
def printeigenvalues(self) -> None

This prints the eigenvalues found.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3
>>>
>>> u = field("h1xyz")
>>> u.setorder(vol, 2)
>>> u.setconstraint(sur)
>>>
>>> elasticity = formulation()
>>>
>>> elasticity += integral(vol, predefinedelasticity(dof(u), tf(u), 150e-, 0.3))
>>> elasticity += integral(vol, -2300*dtdt(dof(u)) * tf(u)) # 2300 is mass density
>>>
>>> # Direct eigen solver (works only for non-DDM simulation)
>>> elasticity.generate()
>>> eig = eigenvalue(elasticity.K(), elasticity.M())
>>> eig.compute(5, 0)
>>>
>>> eig.printeigenvalues()
settolerance
def settolerance(
self,
reltol: float,
maxnumits: int
) -> None

This sets the tolerance and maximum number of iterations for the iterative eigen solver. The settolerance should be called only if eigenvalue.allcompute() is used to solve the eigen problem.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3
>>>
>>> u = field("h1xyz")
>>> u.setorder(vol, 2)
>>> u.setconstraint(sur)
>>>
>>> elasticity = formulation()
>>>
>>> elasticity += integral(vol, predefinedelasticity(dof(u), tf(u), 150e-, 0.3))
>>> elasticity += integral(vol, -2300*dtdt(dof(u)) * tf(u)) # 2300 is mass density
>>>
>>> # Iterative eigen solver
>>> eig = eigenvalue(elasticity)
>>> eig.settolerance(1e-6, 1000)
>>> eig.allcompute(1e-6, 1000, 5, 0)

Class: expression

def expression(
self
)
def expression(
self,
input: field
)
def expression(
self,
input: float
)
def expression(
self,
input: parameter
)
def expression(
self,
input: port
)
def expression(
self,
numrows: int,
numcols: int,
exprs: list[expression]
)
def expression(
self,
input: list[list[expression]]
)
def expression(
self,
condexpr: expression,
exprtrue: expression,
exprfalse: expression
)
def expression(
self,
spl: spline,
arg: expression
)
def expression(
self,
grd: grid,
args: list[expression]
)
def expression(
self,
pos: list[float],
exprs: list[expression],
tocompare: expression
)

The expression object holds a mathematical expression made of operators (such as +, -, *, /), fields, parameters, square operators, abs operators and so on.

Examples

An empty expression object can be created as:

>>> myexpression = expression()

An expression object can be a scalar:

>>> myexpression = expression(2)
>>> myexpression.print()
Expression size is 1x1
@ row 0, col 0 :
2

An expression object can also be a vector or a 2D array. For this three arguments are required. The first and second arguments specify the number of rows and the number of columns respectively. The expression object is filled with input expressions provided as a list in the third argument. The general syntax is expression(numrows:int, numcols:int, input:List[expression]

>>> myexpression = expression(1,3, [1,2,3])
>>> myexpression.print()
Expression size is 1x3
@ row 0, col 0 : 1
@ row 0, col 1 : 2
@ row 0, col 2 : 3

In a 2D array expression, the inputs are set in row-major order. In the example below, the entry at the index pair (1,0) in the created expression is set to 44 and the entry (1,2) to 66.

[123456789]\begin{bmatrix} 1 & 2 & 3 \cr 4 & 5 & 6 \cr 7 & 8 & 9 \end{bmatrix}
>>> myexpression = expression(3,3, [1,2,3, 4,5,6, 7,8,9]) # creates a 3x3 sized expression array
>>> myexpression.at(1,0).evaluate()
4.0
>>> myexpression.at(1,2).evaluate()
6.0

A symmetric expression array can also be created by only providing the input list corresponding to the lower triangular part:

[124235456]\begin{bmatrix} \color{red}1 & 2 & 4 \cr \color{red}2 & \color{red}3 & 5 \cr \color{red}4 & \color{red}5 & \color{red}6 \end{bmatrix}
>>> myexpression = expression(3,3, [1,2,3, 4,5,6])
>>> myexpression.print()

A diagonal expression array can also be created by only providing the input list corresponding to the diagonal elements:

[100020003]\begin{bmatrix} \color{red}1 & 0 & 0 \cr 0 & \color{red}2 & 0 \cr 0 & 0 & \color{red}3 \end{bmatrix}
>>> myexpression = expression(3,3, [1,2,3])
>>> myexpression.print()

Note that to create a symmetric or diagonal expression array, the size must correspond to a square array (number of rows = number of columns).

The expression input can also be made of fields. For example:

>>> mymesh = mesh("disk.msh")
>>> v = field("h1")
>>> myexpression = expression(2,3, [12,v,v*(1-v), 3,14-v,0])

An expression array object can be obtained from the row-wise and column-wise concatenation of input expressions using the syntax expression(input:[List[List[expression]]. Every element in the argument input (i.e. input[0], input[1], ..) is concatenated column-wise with others. Every expression in `input[i] (i.e input[i][0], input[i][1], ..) is concatenated row-wise with the other expressions in that List.

[1452v67389]\begin{bmatrix} \color{red}1 & \color{blue}4 & \color{blue}5 \cr \color{red}{2v} & 6 & 7 \cr \color{red}3 & 8 & 9 \end{bmatrix}
>>> mymesh = mesh("disk.msh")
>>> v = field("h1")
>>> blockleft = expression(3,1, [1,2*v,3])
>>> blockrighttop = expression(1,2, [4,5])
>>> blockrightbottom = expression(2,2, [6,7,8,9])
>>> exprconcatenated = expression([[blockleft], [blockrighttop,blockrightbottom]])
>>> exprconcatenated.print()
Expression size is 3x3
@ row 0, col 0 : 1
@ row 0, col 1 : 4
@ row 0, col 2 : 5
@ row 1, col 0 : field * 2
@ row 1, col 1 : 6
@ row 1, col 2 : 7
@ row 2, col 0 : 3
@ row 2, col 1 : 8
@ row 2, col 2 : 9

It is also useful in many cases to create a conditional expression. It takes the form expression(condexpr:expression, exprtrue:expression, exprfalse:expression). If the first argument is greater than equal to zero then the expression is equal to the expression provided in the second argument. If smaller than zero, then it is equal to the expression in the third argument.

>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3
>>> x = field("x"); y = field("y")
>>> expr = expression(x+y, 2*x, 0)
>>> expr.write(top, "conditionalexpr.vtk", 1)
>>> expr.print()
Expression size is 1x1
@ row 0, col 0 : (x + y) ? x * 2, 0)

An expression can be used to define an algebraic relation between two variables as in the example below:

>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3
>>> C = field("h1") # temperature field in Celsius
>>> F = field("h1") # temperature field in Fahrenheit
>>> F = (9.0/5.0)*C + 32 # Relation for converting Celsius to Fahrenheit
>>> F.write(top, "F.vtk", 1)

In the above example, an algebraic relation between two variables was already known allowing us to create an expression that is continuous. However, if the data is from an experiment, they are usually not continuous. As an application example, if measurements of a material stiffness (Young’s modulus EE) have been performed for a set of temperatures TT, then only a discrete data set exists between variable EE and TT. A discrete data set can be converted to a continuous function (EE as a function of TT) using cubic splines which allows us to interpolate EE at any value TT in the measured discrete temperature range. Using this spline object an expression can be defined that provides cubic spline interpolation of Young’s modulus EE in the measured discrete temperature range. Refer to the spline object for more details.

>>> discrete data set from measurement
>>> temperature = [273,300,320,340]
>>> youngsmodulus = [5e9,4e9,5e9,1e9]
>>>
>>> spline object: allows interpolation in the measured data range
>>> spl = spline(temperature, youngsmodulus)
>>>
>>> # expression interpolating E at temperature 310.
>>> # 'spl' holds the interpolation function information.
>>> E = expression(spl, 310)
>>> E.print()
Expression size is 1x1
@ row 0, col 0 :
spline(310)
>>>
>>> # expression interpolating E for space-dependent temperature profile.
>>> mymesh = mesh("disk.msh")
>>> T = field("h1") # space dependent temperature profile
>>> E = expression(spl, T) # space dependent Young's modulus
>>> E.write(top, "E.vtk", 1)

The expression object is much more versatile. Say, we have to define an electric supply voltage profile in time that is:

  • 0V for time before 1 sec.
  • increases linearly from 0V to 1V for time range [1,3] sec.
  • 1V for time after 3 sec. This can be created as shown below in the example. The following creates a conditional expression for the intervals defined in the first argument for the time variable t(). In the below example, the defined interval is [1.0,3.0]. This provides information in three intervals:
  • interval 1: from -\infty to 1.0
  • interval 2: between 1.0 to 3.0
  • interval 3: from 3.0 to +\infty The second argument holds three expressions, each valid in the sequence of the respective interval defined above. The third argument specifies the variable input (time in this case). Printing the expression object provides insight into the conditional expression created with these inputs.
>>> vsupply = expression([1.0,3.0], [0, 0.5*(t()-1), 1.0], t())
>>> vsupply.print()
Expression size is 1x1
@ row 0, col 0 : ((t + -3) ? 1, ((t + -1) ? (t + -1) * 0.5, 0))

Methods

allgridinterpolate
def allgridinterpolate(
self,
physreg: int,
xyzcoord: list[float],
errorifnotfound: bool = True,
toallranks: bool = True
) -> list[float]
allgridwrite
def allgridwrite(
self,
physreg: int,
filename: str,
bounds: list[float],
numsamples: list[int],
errorifnotfound: bool = True
) -> None
allintegrate
def allintegrate(
self,
physreg: int,
integrationorder: int
) -> float
def allintegrate(
self,
physreg: int,
meshdeform: expression,
integrationorder: int
) -> float

This is a collective MPI operation and hence must be called by all ranks. This integrates an expression over a physical region across all the DDM ranks. Integrate expression(1) to calculate volume/area/length. For axisymmetric problems, the value returned is the integral of the requested expression times the coordinate change Jacobian. In the case of axisymmetry, the volume/area/length of the 3D shape corresponding to the physical region on which to integrate can be obtained by integrating expression(1) and multiplying the output by 2π2\pi.

Example 1: allintegrate(physreg:int, integrationorder:int)

The integration is performed over the physical region physreg. The integration is exact up to the order of polynomials specified in the argument integrationorder

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> myexpression = expression(12.0)
>>> integralvalue = myexpression.allintegrate(vol, 4)

Example 2: allintegrate(physreg:int, meshdeform:expression, integrationorder:int)

Here, the integration is performed on the deformed mesh configuration meshdeform.

>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> integralvalueondeformedmesh = myexpression.allintegrate(vol, u, 4)
See Also

expression.integrate()

allinterpolate
def allinterpolate(
self,
physreg: int,
xyzcoord: list[float]
) -> list[float]
def allinterpolate(
self,
physreg: int,
meshdeform: expression,
xyzcoord: list[float]
) -> list[float]

This is a collective MPI operation and hence must be called by all ranks. Its functionality is as described in expression.interpolate() but considers the physical region partitioned across the DDM ranks. The argument must xyzcoord be the same for all ranks.

Example 1: allinterpolate(physreg:int, xyzcoord:List[double])

This interpolates the expression at a single point whose [x,y,z] coordinate is provided as an argument. The flattened interpolated expression values are returned if the point was found in the elements of the physical region physreg. If not found an empty list is returned.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> x=field("x"); y=field("y"); z=field("z")
>>> interpolated = array3x1(x,y,z).allinterpolate(vol, [0.5,0.6,0.05])
>>> interpolated
[0.5, 0.6, 0.05]

Example 2: allinterpolate(physreg:int, meshdeform:expression, xyzcoord:List[double])

An expression can also be interpolated on a deformed mesh by passing its corresponding field.

>>> # interpolation on the mesh deformed by field 'u'
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> interpolated = array3x1(x,y,z).allinterpolate(vol, u, xyzcoord)
See Also

expression.interpolate()

allmax
def allmax(
self,
physreg: int,
refinement: int,
xyzrange: list[float] = []
) -> list[float]
def allmax(
self,
physreg: int,
meshdeform: expression,
refinement: int,
xyzrange: list[float] = []
) -> list[float]

This is a collective MPI operation and hence must be called by all ranks. This returns a list with its first element containing the maximum value of an expression computed across all the DDM ranks over a geometric region. The remaining elements of the list provide the coordinates at which the maximum value was found. This is an overloaded method.

Example 1: allmax(physreg:int, refinement:int, xyzrange:List[double]=[])

The maximum value is obtained over the geometric region physreg by splitting all elements refinement times in each direction. Increasing the refinement will thus lead to a more accurate maximum value, but at an increased computational cost. The maximum value is exact when the refinement nodes added to the elements correspond to the position of maximum. For a first-order nodal shape function interpolation, on a mesh that is not curved, the maximum is always exact to machine precision. The default value of xyzrange is an empty list.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> x = field("x")
>>> maxdata = (2*x).allmax(vol, 1)
>>> maxdata[0]
2.0

The search of the maximum value can be restricted to a box delimited by the last argument xyzrange whose form is [xboxmin,xboxmax, yboxmin, yboxmin, zboxmax, zboxmin]. The output returned is a list of the form [maxvalue, xcoordmax, ycoordmax, zcoordmax] or an empty list if the physical region argument is empty or is not in the box provided. If the argument defining the box is not provided, then the whole geometric region is considered for evaluating the maximum value.

>>> maxdatainbox = (2*x).allmax(vol, 5, [-2,0, -2,2, -2,2])

Example 2: allmax(physreg:int, meshdeform:expression, refinement:int, xyzrange:List[double]=[])

The maximum value can also be evaluated on the geometry deformed by a field (possibly a curved mesh). The location and the delimiting box are on the undeformed mesh.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> maxdataondeformedmesh = (2*x).allmax(vol, u, 1)
See Also

expression.allmin(), expression.min(), expression.max()

allmin
def allmin(
self,
physreg: int,
refinement: int,
xyzrange: list[float] = []
) -> list[float]
def allmin(
self,
physreg: int,
meshdeform: expression,
refinement: int,
xyzrange: list[float] = []
) -> list[float]

This is a collective MPI operation and hence must be called by all ranks. This returns a list with its first element containing the minimum value of an expression computed across all the DDM ranks over a geometric region. The remaining elements of the list provide the coordinates at which the minimum value was found. This is an overloaded method.

Example 1: allmin(physreg:int, refinement:int, xyzrange:List[double]=[])

The minimum value is obtained over the geometric region physreg by splitting all elements refinement times in each direction. Increasing the refinement will thus lead to a more accurate minimum value, but at an increased computational cost. The minimum value is exact when the refinement nodes added to the elements correspond to the position of minimum. For a first-order nodal shape function interpolation, on a mesh that is not curved, the minimum is always exact to machine precision. The default value of xyzrange is an empty list.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> x = field("x")
>>> mindata = (2*x).allmin(vol, 1)
>>> mindata[0]
2.0

The search of the minimum value can be restricted to a box delimited by the last argument xyzrange whose form is [xboxmin,xboxmax, yboxmin, yboxmin, zboxmax, zboxmin]. The output returned is a list of the form [minvalue, xcoordmin, ycoordmin, zcoordmin] or an empty list if the physical region argument is empty or is not in the box provided. If the argument defining the box is not provided, then the whole geometric region is considered for evaluating the minimum value.

>>> mindatainbox = (2*x).allmin(vol, 5, [-2,0, -2,2, -2,2])

Example 2: allmin(physreg:int, meshdeform:expression, refinement:int, xyzrange:List[double]=[])

The minimum value can also be evaluated on the geometry deformed by a field (possibly a curved mesh). The minimum location and the delimiting box are on the undeformed mesh.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> mindataondeformedmesh = (2*x).allmin(vol, u, 1)
See Also

expression.allmax(), expression.max(), expression.min()

at
def at(
self,
row: int,
col: int
) -> expression

This returns the entry at the requested row and column.

Example
>>> myexpression = expression(2,2, [1,2, 3,4])
>>> myexpression.at(0,1)
2
atbarycenter
def atbarycenter(
self,
physreg: int,
onefield: field
) -> vec

This outputs a vec object whose structure is based on the field argument onefield and which contains the expression evaluated at the barycenter of each reference element of physical region physreg. The barycenter of the reference element might not be identical to the barycenter of the actual element in the mesh (for curved elements, for general quadrangles, hexahedra and prisms). The evaluation at barycenter is constant on each mesh element.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> x = field("x"); f = field("one")
>>>
>>> # Evaluating the expression
>>> (12*x).write(vol, "expression.vtk", 1)
>>>
>>>> # Evaluating the same expression at barycenter
>>> myvec = (12*x).atbarycenter(vol, f)
>>> f.setdata(vol, myvec)
>>> f.write(vol, "barycentervalues.vtk", 1)
countcolumns
def countcolumns(self) -> int

This counts the number of columns in an expression.

Example
>>> myexpression = expression(2,1, [0,1])
>>> myexpression.countcolumns()
1
countrows
def countrows(self) -> int

This counts the number of rows in an expression.

Example
>>> myexpression = expression(2,1, [0,1])
>>> myexpression.countrows()
2
evaluate
def evaluate(self) -> float

This evaluates a scalar, space-independent expression.

Example
>>> settime(0.5)
>>> expr = 2*abs(-5*t())+3
>>> expr.evaluate()
8.0
getcolumn
def getcolumn(self, colnum: int) -> expression

This returns for a matrix expression the column corresponding to the specified input index colnum.

Example
>>> myexpression = expression(2,2, [0,1, 2,3])
>>> subexpr = myexpression.getcolumn(0)
>>> subexpr.print()
Expression size is 2x1
@ row 0, col 0 : 0
@ row 0, col 1 : 2
getrow
def getrow(self, rownum: int) -> expression

This returns for a matrix expression the row corresponding to the specified input index rownum.

Example
>>> myexpression = expression(2,2, [0,1, 2,3])
>>> subexpr = myexpression.getrow(1)
>>> subexpr.print()
Expression size is 1x2
@ row 0, col 0 : 2
@ row 0, col 1 : 3
integrate
def integrate(
self,
physreg: int,
integrationorder: int
) -> float
def integrate(
self,
physreg: int,
meshdeform: expression,
integrationorder: int
) -> float

This integrates an expression over a physical region. Integrate expression(1) to calculate volume/area/length. For axisymmetric problems, the value returned is the integral of the requested expression times the coordinate change Jacobian. In the case of axisymmetry, the volume/area/length of the 3D shape corresponding to the physical region on which to integrate can be obtained by integrating expression(1) and multiplying the output by 2π2\pi.

Example 1: allintegrate(physreg:int, integrationorder:int)

The integration is performed over the physical region physreg. The integration is exact up to the order of polynomials specified in the argument integrationorder

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> myexpression = expression(12.0)
>>> integralvalue = myexpression.allintegrate(vol, 4)

Example 2: allintegrate(physreg:int, meshdeform:expression, integrationorder:int)

Here, the integration is performed on the deformed mesh configuration meshdeform.

>>> # integration on the mesh deformed by field 'u'
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> integralvalueondeformedmesh = myexpression.allintegrate(vol, u, 4)
See Also

expression.allintegrate()

interpolate
def interpolate(
self,
physreg: int,
xyzcoord: list[float],
interpolated: list[float],
isfound: list[bool]
) -> None
def interpolate(
self,
physreg: int,
meshdeform: expression,
xyzcoord: list[float],
interpolated: list[float],
isfound: list[bool]
) -> None
def interpolate(
self,
physreg: int,
xyzcoord: list[float]
) -> list[float]
def interpolate(
self,
physreg: int,
meshdeform: expression,
xyzcoord: list[float]
) -> list[float]

This interpolates the expression at a single point whose [x,y,z] coordinate is provided as an argument. The flattened interpolated expression values are returned if the point was found in the elements of the physical region physreg. If not found an empty list is returned.

Example 1: interpolate(physreg:int, xyzcoord:List[double])

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> x=field("x"); y=field("y"); z=field("z")
>>> interpolated = array3x1(x,y,z).interpolate(vol, [0.5,0.6,0.05])
>>> interpolated
[0.5, 0.6, 0.05]

Example 2: interpolate(physreg:int, meshdeform:expression, xyzcoord:List[double])

An expression can also be interpolated on a deformed mesh by passing its corresponding field.

>>> # interpolation on the mesh deformed by field 'u'
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> interpolated = array3x1(x,y,z).interpolate(vol, u, xyzcoord)
See Also

expression.allinterpolate()

isscalar
def isscalar(self) -> bool

This returns True if the expression is a scalar (i.e. has a single row and column).

Examples
>>> myexpression = expression(12.0)
>>> myexpression.isscalar()
True
>>> myexpression = expression(2,2, [0,1, 2,3])
>>> myexpression.isscalar()
False
iszero
def iszero(self) -> bool

This returns True if all the entries in the expression is zero, otherwise False.

Examples
>>> myexpression = expression(12.0)
>>> myexpression.iszero()
False
>>> myexpression = expression(0.0)
>>> myexpression.iszero()
True
>>> myexpression = expression(2,2, [0,0, 0,0])
>>> myexpression.iszero()
True
>>> myexpression = expression(2,2, [1,0, 0,0])
>>> myexpression.iszero()
False
max
def max(
self,
physreg: int,
refinement: int,
xyzrange: list[float] = []
) -> list[float]
def max(
self,
physreg: int,
meshdeform: expression,
refinement: int,
xyzrange: list[float] = []
) -> list[float]

This returns a list with its first element containing the maximum value of an expression computed over a geometric region. The remaining elements of the list provide the coordinates at which the maximum value was found. This is an overloaded method.

Example 1: max(physreg:int, refinement:int, xyzrange:List[double]=[])

The maximum value is obtained over the geometric region physreg by splitting all elements refinement times in each direction. Increasing the refinement will thus lead to a more accurate maximum value, but at an increased computational cost. The maximum value is exact when the refinement nodes added to the elements correspond to the position of maximum. For a first-order nodal shape function interpolation, on a mesh that is not curved, the maximum is always exact to machine precision. The default value of xyzrange is an empty list.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> x = field("x")
>>> maxdata = (2*x).max(vol, 1)
>>> maxdata[0]
2.0

The search of the maximum value can be restricted to a box delimited by the last argument xyzrange whose form is [xboxmin,xboxmax, yboxmin, yboxmin, zboxmax, zboxmin]. The output returned is a list of the form [maxvalue, xcoordmax, ycoordmax, zcoordmax] or an empty list if the physical region argument is empty or is not in the box provided. If the argument defining the box is not provided, then the whole geometric region is considered for evaluating the maximum value.

>>> maxdatainbox = (2*x).max(vol, 5, [-2,0, -2,2, -2,2])

Example 2: max(physreg:int, meshdeform:expression, refinement:int, xyzrange:List[double]=[])

The maximum value can also be evaluated on the geometry deformed by a field (possibly a curved mesh). The location and the delimiting box are on the undeformed mesh.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> maxdataondeformedmesh = (2*x).max(vol, u, 1)
See Also

expression.min(), expression.allmax(), expression.allmin()

min
def min(
self,
physreg: int,
refinement: int,
xyzrange: list[float] = []
) -> list[float]
def min(
self,
physreg: int,
meshdeform: expression,
refinement: int,
xyzrange: list[float] = []
) -> list[float]

This returns a list with its first element containing the minimum value of an expression computed over a geometric region. The remaining elements of the list provide the coordinates at which the minimum value was found. This is an overloaded method.

Example 1: min(physreg:int, refinement:int, xyzrange:List[double]=[])

The minimum value is obtained over the geometric region physreg by splitting all elements refinement times in each direction. Increasing the refinement will thus lead to a more accurate minimum value, but at an increased computational cost. The minimum value is exact when the refinement nodes added to the elements correspond to the position of minimum. For a first-order nodal shape function interpolation, on a mesh that is not curved, the minimum is always exact to machine precision. The default value of xyzrange is an empty list.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> x = field("x")
>>> mindata = (2*x).min(vol, 1)
>>> mindata[0]
2.0

The search of the minimum value can be restricted to a box delimited by the last argument xyzrange whose form is [xboxmin,xboxmax, yboxmin, yboxmin, zboxmax, zboxmin]. The output returned is a list of the form [minvalue, xcoordmin, ycoordmin, zcoordmin] or an empty list if the physical region argument is empty or is not in the box provided. If the argument defining the box is not provided, then the whole geometric region is considered for evaluating the minimum value.

>>> mindatainbox = (2*x).min(vol, 5, [-2,0, -2,2, -2,2])

Example 2: min(physreg:int, meshdeform:expression, refinement:int, xyzrange:List[double]=[])

The minimum value can also be evaluated on the geometry deformed by a field (possibly a curved mesh). The location and the delimiting box are on the undeformed mesh.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> mindataondeformedmesh = (2*x).min(vol, u, 1)
See Also

expression.max(), expression.allmax(), expression.allmin()

print
def print(self) -> None

This prints the expression to the console.

Example
>>> myexpression = expression(2,2, [0,1, 2,3])
>>> myexpression.print()
Expression size is 2x2
@ row 0, col 0 : 0
@ row 0, col 1 : 1
@ row 1, col 0 : 2
@ row 1, col 1 : 3
reordercolumns
def reordercolumns(self, neworder: list[int]) -> None

This reorders the columns of a matrix expression in the specified order neworder.

Example
>>> expr = expression(2,2, [0,1, 2,3])
0 1
2 3
>>> expr.reordercolumns([1,0])
1 0
3 2
reorderrows
def reorderrows(self, neworder: list[int]) -> None

This reorders the rows of a matrix expression in the specified order neworder.

Example
>>> expr = expression(2,2, [0,1, 2,3])
0 1
2 3
>>> expr.reorderrows([1,0])
2 3
0 1
resize
def resize(
self,
numrows: int,
numcols: int
) -> expression

This resizes an expression. Any newly created expression entry is set to zero.

Example
>>> myexpression = expression(2,2, [1,2, 3,4])
>>> resizedexpr = myexpression.resize(1,3)
>>> resizedexpr.print()
Expression size is 1x3
@ row 0, col 0 : 1
@ row 0, col 1 : 2
@ row 0, col 2 : 0
reuseit
def reuseit(self, istobereused: bool = True) -> None

In case an expression appears multiple times, say in a formulation, and requires much time to compute, then the expression can be reused by calling this method and setting istobereused=True. With this, the expression is computed only once to assemble a formulation block and reused as long as its value remains changed.

Example
>>> myexpression = expression(12.0)
>>> myexpression.resuseit() # myexpression.resuseit(True)
rotate
def rotate(
self,
ax: float,
ay: float,
az: float,
leftop: str = 'default',
rightop: str = 'default'
) -> None

This rotates a given expression by ax, ay and az angles about x, y, z axis respectively.

Let us call RR (3x3) the classical 3D rotation matrix used in the transformation of expression written in tensorial form and KK (6x6) the rotation matrix used in the transformation of expression written in Voigt form. For example, the mechanical stress tensor in Voigt form σv\sigma_v = (σxx,σyy,σzz,σyz,σzx,σxy\sigma_{xx}, \sigma_{yy}, \sigma_{zz}, \sigma_{yz}, \sigma_{zx}, \sigma_{xy}) is rotated as KσvK\sigma_v. Matrix RR is orthogonal (R1R^{-1} = RTR_{T}) but matrix KK is not orthogonal (K1KTK^{-1} \neq K^{T}).

This function left-multiplies the calling expression by lefttop and right-multiplies it by righttop (if any) - these arguments depend on the definition of the physics. Options for lefttop/righttop are "^" "^", "R"^"R^", "RT"^"RT^", "R^"R-1"1^", "R^"R-T"T^", "K"^"K^", "KT"^"KT^", "K^"K-1"1^", "K^"K-T"T^" respectively for a left/right multiplication by nothing RR, RTR^T, R1R^{-1}, RTR^{-T}, KK, KTK^T, K1K^{-1}, KTK^{-T}. The function can be called without providing the string argument ("^"default"^") to rotate 3x3 tensors and 3x1 vectors (both with the x,y,z component ordering).

The stress tensor in Voigt form σv\sigma_v is rotated as KσvK\sigma_v while the strain tensor in Voigt form ϵv\epsilon_v is rotated as (K1)Tϵv({K^{-1}})^{T}\epsilon_v. The rotation of strain Voigt form KϵvK\epsilon_v is different from the stress because of the factor 22 added to the off-diagonal strain terms. In any case (K1)T({K^{-1}})^{T} = (KT)1({K^{T}})^{-1}. Denoting the rotated quantities with a prime symbol `, one can deduce as an illustration the rotation formulas below.

In Voigt notation, the rotation of stress tensor σv\sigma_v and strain tensor ϵv\epsilon_v is:

σv=Kσv\sigma_v^{\prime} = K \sigma_v ϵv=(K1)Tϵv\epsilon_v^{\prime} = (K^{-1})^T \epsilon_v

The elasticity matrix HH is such that σv=Hϵv\sigma_v = H\epsilon_v and thus,

σv=Hϵv\sigma_v = H \epsilon_v K1σv=HKTϵvK^{-1} \sigma_v^{\prime} = H K^T \epsilon_v^{\prime} σv=KHKTϵv\sigma_v^{\prime} = K H K^T \epsilon_v^{\prime} H=KHKTH^{\prime} = K H K^T

Similarly, the compliance matrix C is such that ϵv=Cσv\epsilon_v = C \sigma_v and thus,

ϵv=Cσv\epsilon_v = C \sigma_v KTϵv=CK1σvK^T \epsilon_v^{\prime} = C K^{-1} \sigma_v^{\prime} ϵv=(K1)TCK1σv\epsilon_v^{\prime} = (K^{-1})^T C K^{-1} \sigma_v^{\prime} C=(K1)TCK1C^{\prime} = (K^{-1})^T C K^{-1}

The 6x3 piezoelectric coupling matrix [C/m2C/m^2] relating the electric field EE to induced stress is such that σv=CE\sigma_v = C E and with rotated electric field relation E=REE^{\prime} = R E, we have:

σv=CE\sigma_v = C E K1σv=CR1EK^{-1} \sigma_v^{\prime} = C R^{-1} E σv=KCR1E\sigma_v^{\prime} = K C R^{-1} E σv=KCRTE\sigma_v^{\prime} = K C R^T E C=KCRTC^{\prime} = K C R^T
Example
>>> # Diagonal relative permittivity matrix for PZT
>>> P = expression(3,3,[1704,1704,1433])
>>> P = P * 8.854e-12
>>>
>>> # Coupling matrix [C/m²] for PZT (6rows, 3columns)
>>> C = expression(6,3, [0,0,-6.62, 0,0,-6.62, 0,0,23.24, 0,17.03,0, 17.03,0,0, 0,0,0])
>>>
>>> # Anisotropic elasticity matrix [Pa] for PZT. Ordering is [exx,eyy,ezz,gyz,gxz,gxy] (Voigt form).
>>> # Lower triangular part (top to bottom and left to right ) provided since it is symmetric.
>>> H = expression(6,6, {1.27e11, 8.02e10,1.27e11, 8.46e10,8.46e10,1.17e11, 0,0,0,2.29 e10, 0,0,0,0,2.29 e10, 0,0,0,0,0,2.34 e10});
>>>
>>> # Rotate the PZT crystal 45 degress around z
>>> H.rotate(0,0,45,"K","KT")
>>> C.rotate(0,0,45,"K","RT")
>>> P.rotate(0,0,45)
streamline
def streamline(
self,
physreg: int,
filename: str,
startcoords: list[float],
stepsize: float,
downstreamonly: bool = False
) -> None

This follows and writes to disk all paths tangent to the expression vector that are starting at a set of points whose xx, yy and zz coordinates are provided in startcoords. These coordinates can for example be obtained via .getcoords() on a shape object. A fourth-order Runge-Kutta algorithm is used. The stepsize argument is related to the distance between two vector direction updates; decrease it to more accurately follow the paths. The paths will be followed as long as they remain in the physical region physreg. In case the vector norm is zero somewhere on the paths or a path is a closed loop then the function might enter an infinite loop and never return. To use this function on closed loops (for example to get magnetic field lines of a permanent magnet) a solution is to break the loops by excluding the permanent magnet domain from the physical region (selectexclusion) function can be called for that) and set the starting coordinates on the boundary of the magnet.

Example
>>> vol = 1
>>> mymesh = mesh("disk.msh")
>>> x=field("x"); y=field("y"); z=field("z")
>>> startcoords = 12*3*[0.05] # list of 36 elements with each element value being 0.05
>>> for i in range(0,12):
... startcoords[3*i+0] += 0.1 + 0.05*i
>>> array3x1(y+2*x, -y+2*x, 0).streamline(vol, "streamlines.vtk", startcoords, 1.0/100.0)
write
def write(
self,
physreg: int,
numfftharms: int,
filename: str,
lagrangeorder: int
) -> None
def write(
self,
physreg: int,
numfftharms: int,
meshdeform: expression,
filename: str,
lagrangeorder: int
) -> None
def write(
self,
physreg: int,
filename: str,
lagrangeorder: int,
numtimesteps: int = -1
) -> None
def write(
self,
physreg: int,
meshdeform: expression,
filename: str,
lagrangeorder: int,
numtimesteps: int = -1
) -> None

This evaluates an expression in the physical region physreg and writes it to the file filename. The lagrangeorder is the order of interpolation for evaluation of the expression.

Examples
>>> # setup
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> u = field("h1xyz")
>>> v = field("h1", [1,2,3])
>>> u.setorder(vol, 1)
>>> v.setorder(vol, 1)
>>>
>>> # interpolation order for writing an expression
>>> (1e8*u).write(vol, "uorder1.vtk", 1) # interpolation order is 1
>>> (1e8*u).write(vol, "uorder3.vtk", 3) # interpolation order is 3

In the example below, an additional integer input 1010 is passed in the second argument. The 1010 here means that the expression is treated as multi-harmonic, nonlinear in time variable and an FFT is performed to get the 1010 first harmonics. All harmonics whose magnitude is above a threshold are saved with ‘_harm i’ extension (except for time-constant harmonic).

>>> abs(v).write(vol, 10, "order1.vtk", 1) # interpolation order is 1
>>> (u*u).write(vol, 10, "order3.vtk", 3) # interpolation order is 3

In the example below, an additional integer input 5050 is instead passed as the last argument posterior to the interpolation order argument. This represents that numtimesteps (default=-1). For a positive value of nn, the multi-harmonic expression is saved at nn equidistant timesteps in the fundamental period and can then be visualized in time.

>>> (1e8*u).write(vol, "uintime.vtk", 2, 50)

The expressions can also be evaluated and written on a mesh deformed by a field. If field ‘v’ is the deformed mesh, then:

>>> (1e8*u).write(vol, v, "uorder1.vtk", 1)
>>> (u*u).write(vol, 10, v, "order3.vtk", 3)
>>> (1e8*u).write(vol, v, "uintime.vtk", 2, 50)

Class: field

def field(
self
)
def field(
self,
fieldtypename: str
)
def field(
self,
fieldtypename: str,
harmonicnumbers: list[int]
)
def field(
self,
fieldtypename: str,
spantree: spanningtree
)
def field(
self,
fieldtypename: str,
harmonicnumbers: list[int],
spantree: spanningtree
)

The field object holds the information of the finite element fields. The field object itself only holds a pointer to a ‘rawfield’ object.

Examples

Example 1: field(fieldtypename:str)

>>> mymesh = mesh("disk.h")
>>> v = field("h1")

This creates a field object with the specified shape functions. The full list of shape functions available are:

  • Nodal shape functions “h1” e.g. for electrostatic potential and acoustic or fluid pressure.
  • Two-components nodal shape functions “h1xy” e.g. for 2D mechanical displacements and 2D fluid velocity.
  • Three-components nodal shape functions “h1xyz” e.g. for 3D mechanical displacements and 3D fluid velocity.
  • Nedelec’s edge shape functions “hcurl” e.g. for the electric field in the E-formulation of electromagnetic wave propagation (here order 0 is allowed).
  • “one”, one0”, one1”, one2”, one3” (trailing “xy” or “xyz” allowed) shape functions have a single shape function equal to a constant one on respectively an n, 0, 1, 2, 3-dimensional element (n is the geometry dimension).
  • “h1d”, “h1d0”, “h1d1”, “h1d2”, “h1d3” (trailing “xy” or “xyz” allowed) shape functions are elementwise-”h1” shape functions that allow storing fields that are fully discontinuous between elements.

Additionally, types “x”, “y” and “z” can be used to define the x, y and z coordinate fields.

>>> mymesh = mesh("disk.h")
>>> x = field("x")
>>> y = field("y")
>>> z = field("z")

Example 2: field(fieldtypename:str, harmonicnumbers:List[int])

>>> mymesh = mesh("disk.msh")
>>> v = field("h1", [1,4,5,6])

Consider the infinite Fourier series of a field that is periodic in time:

v(x,t)=V1+V2sin(2πfot)+V3cos(2πfot)+V4sin(22πfot)+V5cos(22πfot)+..v(x, t) = V_1 + V_2 sin(2{\pi}{f_o}t) + V_3 cos(2{\pi}{f_o}t) + V_4 sin(2{\cdot}2{\pi}{f_o}t) + V_5 cos(2{\cdot}2{\pi}{f_o}t) + ..

where tt is the time variable, xx is the space variable and fof_o is the fundamental frequency of the periodic field. The ViV_i coefficients only depend on the space variable, not on the time variables which have now moved to the sines and cosines. In the example above, field vv is a multi-harmonic “h1” type field that includes 44 harmonic fields: the V1V_1, V4V_4, V5V_5 and V6V_6 fields in the truncated Fourier series above. All other harmonics in the infinite Fourier series are supposed to equal zero so that the field vv can be rewritten as:

v(x,t)=V1+V4sin(22πfot)+V5cos(22πfot)+V6sin(32πfot)v(x, t) = V_1 + V_4 sin(2{\cdot}2{\pi}{f_o}t) + V_5 cos(2{\cdot}2{\pi}{f_o}t) + V_6 sin(3{\cdot}2{\pi}{f_o}t)

This is the truncated multi-harmonic representation of field vv (which must be periodic in time). The following can be used to get the harmonic V4V_4 from field vv. It can then be used like any other field.

>>> v4 = v.harmonic(4)

Example 3: field(fieldtypename:str, spantree:spanningtree)

>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3
>>> spantree = spanningtree([sur, top])
>>> a = field("hcurl", spantree)

This adds the spanning tree input argument needed when the field has to be gauged. (e.g. for the magnetic vector potential formulation of the magnetostatic problem in 3D).

Example 3: field(fieldtypename:str, harmonicnumbers:List[int], spantree:spanningtree)

>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3
>>> spantree = spanningtree([sur, top])
>>> aharmonic = field("hcurl", [2,3], spantree)

This adds the spanning tree input argument needed when a multiharmonicmulti-harmonic field has to be gauged.

Methods

allintegrate
def allintegrate(
self,
physreg: int,
integrationorder: int
) -> float
def allintegrate(
self,
physreg: int,
meshdeform: expression,
integrationorder: int
) -> float

This is a collective MPI operation and hence must be called by all ranks. This integrates a field over a physical region across all the DDM ranks.

Example 1: allintegrate(physreg:int, integrationorder:int)

The integration is performed over the physical region physreg. The integration is exact up to the order of polynomials specified in the argument integrationorder

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1"); x=field("x")
>>> v.setorder(vol, 1)
>>> v.setvalue(vol, 12*x)
>>> integralvalue = v.allintegrate(vol, 4)

Example 2: allintegrate(physreg:int, meshdeform:expression, integrationorder:int)

Here, the integration is performed on the deformed mesh configuration meshdeform.

>>> # integrate on a mesh deformed by field 'u'
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> integralvalueondeformedmesh = v.allintegrate(vol, u, 4)
See Also

field.integrate()

allinterpolate
def allinterpolate(
self,
physreg: int,
xyzcoord: list[float]
) -> list[float]
def allinterpolate(
self,
physreg: int,
meshdeform: expression,
xyzcoord: list[float]
) -> list[float]

This is a collective MPI operation and hence must be called by all ranks. Its functionality is as described in field.interpolate() but considers the physical region partitioned across the DDM ranks. The argument xyzcoord must be the same for all ranks.

Example 1: allinterpolate(physreg:int, xyzcoord:List[double])

This interpolates the field at a single point whose [x,y,z] coordinate is provided as an argument. The flattened interpolated field values are returned if the point was found in the elements of the physical region physreg. If not found an empty list is returned.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1"); x=field("x")
>>> v.setorder(vol, 1)
>>> v.setvalue(vol, 12*x)
>>> xyzcoord = [0.5,0.6,0.05]
>>>
>>> interpolated = v.allinterpolate(vol, xyzcoord)
>>> interpolated
[5.954696754335098]

Example 2: allinterpolate(physreg:int, meshdeform:expression, xyzcoord:List[double])

A field can also be interpolated on a deformed mesh by passing its corresponding field.

>>> # interpolation on the mesh deformed by field 'u'
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> interpolated = u.allinterpolate(vol, u, xyzcoord)
See Also

field.interpolate()

allmax
def allmax(
self,
physreg: int,
refinement: int,
xyzrange: list[float] = []
) -> list[float]
def allmax(
self,
physreg: int,
meshdeform: expression,
refinement: int,
xyzrange: list[float] = []
) -> list[float]

This is a collective MPI operation and hence must be called by all ranks. This returns a list with its first element containing the maximum value of a field computed across all the DDM ranks over a geometric region. The remaining elements of the list provide the coordinates at which the maximum value was found. This is an overloaded method.

Example 1: allmax(physreg:int, refinement:int, xyzrange:List[double]=[])

The maximum value is obtained over the geometric region physreg by splitting all elements refinement times in each direction. Increasing the refinement will thus lead to a more accurate maximum value, but at an increased computational cost. The maximum value is exact when the refinement nodes added to the elements correspond to the position of maximum. For a first-order nodal shape function interpolation, on a mesh that is not curved, the maximum is always exact to machine precision. The default value of xyzrange is an empty list.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1"); x = field("x")
>>> v.setorder(vol, 1)
>>> v.setvalue(vol, 12*x)
>>> maxdata = v.allmax(vol, 1)
>>> maxdata[0]
12.0

The search of the maximum value can be restricted to a box delimited by the last argument xyzrange whose form is [xboxmin,xboxmax, yboxmin, yboxmin, zboxmax, zboxmin]. The output returned is a list of the form [maxvalue, xcoordmax, ycoordmax, zcoordmax] or an empty list if the physical region argument is empty or is not in the box provided. If the argument defining the box is not provided, then the whole geometric region is considered for evaluating the maximum value.

>>> ...
>>> maxdatainbox = v.allmax(vol, 5, [-2,0, -2,2, -2,2])

Example 2: allmax(physreg:int, meshdeform:expression, refinement:int, xyzrange:List[double]=[])

The maximum value can also be evaluated on the geometry deformed by a field (possibly a curved mesh). The location and the delimiting box are on the undeformed mesh.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> maxdataondeformedmesh = u.allmax(vol, u, 1)
See Also

field.max(), field.min(), field.allmin()

allmin
def allmin(
self,
physreg: int,
refinement: int,
xyzrange: list[float] = []
) -> list[float]
def allmin(
self,
physreg: int,
meshdeform: expression,
refinement: int,
xyzrange: list[float] = []
) -> list[float]

This is a collective MPI operation and hence must be called by all ranks. This returns a list with its first element containing the minimum value of a field computed across all the DDM ranks over a geometric region. The remaining elements of the list provide the coordinates at which the minimum value was found. This is an overloaded method.

Example 1: allmin(physreg:int, refinement:int, xyzrange:List[double]=[])

The minimum value is obtained over the geometric region physreg by splitting all elements refinement times in each direction. Increasing the refinement will thus lead to a more accurate minimum value, but at an increased computational cost. The minimum value is exact when the refinement nodes added to the elements correspond to the position of minimum. For a first-order nodal shape function interpolation, on a mesh that is not curved, the minimum is always exact to machine precision. The default value of xyzrange is an empty list.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1"); x = field("x")
>>> v.setorder(vol, 1)
>>> v.setvalue(vol, 12*x)
>>> mindata = v.allmin(vol, 1)
>>> mindata[0]
-2.0

The search of the minimum value can be restricted to a box delimited by the last argument xyzrange whose form is [xboxmin,xboxmax, yboxmin, yboxmin, zboxmax, zboxmin]. The output returned is a list of the form [minvalue, xcoordmin, ycoordmin, zcoordmin] or an empty list if the physical region argument is empty or is not in the box provided. If the argument defining the box is not provided, then the whole geometric region is considered for evaluating the minimum value.

>>> ...
>>> mindatainbox = v.allmin(vol, 5, [-2,0, -2,2, -2,2])

Example 2: allmin(physreg:int, meshdeform:expression, refinement:int, xyzrange:List[double]=[])

The min value can also be evaluated on the geometry deformed by a field (possibly a curved mesh). The min location and the delimiting box are on the undeformed mesh.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> mindataondeformedmesh = u.allmin(vol, u, 1)
See Also

field.min(), field.max(), field.allmax()

atbarycenter
def atbarycenter(
self,
physreg: int,
onefield: field
) -> vec

This outputs a vec object whose structure is based on the field argument onefield and which contains the field evaluated at the barycenter of each reference element of physical region physreg. The barycenter of the reference element might not be identical to the barycenter of the actual element in the mesh (for curved elements, for general quadrangles, hexahedra and prisms). The evaluation at barycenter is constant on each mesh element.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1"); f = field("one")
>>> v.setorder(vol, 1)
>>>
>>> # Evaluating the field
>>> v.write(vol, "expression.vtk", 1)
>>>
>>>> # Evaluating the same field at barycenter
>>> myvec = v.atbarycenter(vol, f)
>>> f.setdata(vol, myvec)
>>> f.write(vol, "barycentervalues.vtk", 1)
automaticupdate
def automaticupdate(self, updateit: bool) -> None

After this call, the field and all its subfields will have their value automatically updated after hp-adaptivity.

Example
>>> ...
>>> v.automaticupdate()
See Also

field.noautomaticupdate()

comp
def comp(self, component: int) -> field

This gets the xx, yy or zz component of a field with subfields.

Example
>>> mymesh = mesh("disk.msh")
>>> u = field("h1xyz")
>>> ux = u.comp(0)
>>> uy = u.comp(1)
>>> uz = u.comp(2)
compx
def compx(self) -> field

This gets the xx component of a field with multiple subfields.

Example
>>> mymesh = mesh("disk.msh")
>>> u = field("h1xyz")
>>> ux = u.compx()
compy
def compy(self) -> field

This gets the yy component of a field with multiple subfields.

Example
>>> mymesh = mesh("disk.msh")
>>> u = field("h1xyz")
>>> uy = u.compy()
compz
def compz(self) -> field

This gets the zz component of a field with multiple subfields.

Example
>>> mymesh = mesh("disk.msh")
>>> u = field("h1xyz")
>>> uz = u.compz()
cos
def cos(self, freqindex: int) -> field

This gets the “h1xyz” type field that is the coscos harmonic at freqindex times the fundamental frequency in field uu.

Example
>>> mymesh = mesh("disk.msh")
>>> u = field("h1xyz", [1,2,3,4,5])
>>> uc = u.cos(0) # gets the harmonic 1
See Also

field.sin()

countcomponents
def countcomponents(self) -> int

This returns the number of components in the field.

Example
>>> mymesh = mesh("disk.msh")
>>> E = field("hcurl")
>>> numcomp = E.countcomponents()
>>> numcomp
3
getharmonics
def getharmonics(self) -> list[int]

This returns the list of harmonics of the field object.

Example
>>> mymesh = mesh("disk.msh")
>>> v = field("h1", [1,4,5,6])
>>> myharms = v.getharmonics()
>>> myharms
[1, 4, 5, 6]
getnodalvalues
def getnodalvalues(self, nodenumbers: indexmat) -> densemat

This gets the values of a “h1” type field at a set of nodenumbers.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v.setorder(vol, 1)
>>> nodenums = indexmat(5,1, [0,1,2,3,4])
>>> outvals = v.getnodalvalues(nodenums) # returns a densemat
>>> outvals.print()
harmonic
def harmonic(self, harmonicnumber: int) -> field
def harmonic(self, harmonicnumbers: list[int]) -> field

This gets a “h1xyz” type field that includes the harmonicnumber(s).

Examples
>>> mymesh = mesh("disk.msh")
>>> u = field("h1xyz", [1,2,3])
>>> u2 = u.harmonic(2) # gets harmonic 2 of field u
>>> u23 = u.harmonic([1,3]) # gets harmonics 1 and 3 of field u

This gets a “h1xyz” type field that includes the harmonicnumber(s).

Examples
>>> mymesh = mesh("disk.msh")
>>> u = field("h1xyz", [1,2,3])
>>> u2 = u.harmonic(2) # gets harmonic 2 of field u
>>> u23 = u.harmonic([1,3]) # gets harmonics 1 and 3 of field u
integrate
def integrate(
self,
physreg: int,
integrationorder: int
) -> float
def integrate(
self,
physreg: int,
meshdeform: expression,
integrationorder: int
) -> float

This integrates a field over the physical region physreg. For axisymmetric problems, the value returned is the integral of the requested field times the coordinate change Jacobian.

Example 1: integrate(physreg:int, integrationorder:int)

The integration is performed over the physical region physreg. The integration is exact up to the order of polynomials specified in the argument integrationorder

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1"); x=field("x")
>>> v.setorder(vol, 1)
>>> v.setvalue(vol, 12*x)
>>> integralvalue = v.integrate(vol, 4)

Example 2: integrate(physreg:int, meshdeform:expression, integrationorder:int)

Here, the integration is performed on the deformed mesh configuration meshdeform.

>>> # integrate on a mesh deformed by field 'u'
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> integralvalueondeformedmesh = v.integrate(vol, u, 4)
See Also

field.allintegrate()

interpolate
def interpolate(
self,
physreg: int,
xyzcoord: list[float],
interpolated: list[float],
isfound: list[bool]
) -> None
def interpolate(
self,
physreg: int,
meshdeform: expression,
xyzcoord: list[float],
interpolated: list[float],
isfound: list[bool]
) -> None
def interpolate(
self,
physreg: int,
xyzcoord: list[float]
) -> list[float]
def interpolate(
self,
physreg: int,
meshdeform: expression,
xyzcoord: list[float]
) -> list[float]

This interpolates the field value at a single point whose [x,y,z] coordinate is provided as an argument. The flattened interpolated field values are returned if the point was found in the elements of the physical region physreg. If not found an empty list is returned.

Example 1: interpolate(physreg:int, xyzcoord:List[double])

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1"); x=field("x")
>>> v.setorder(vol, 1)
>>> v.setvalue(vol, 12*x)
>>> xyzcoord = [0.5,0.6,0.05]
>>>
>>> interpolated = v.interpolate(vol, xyzcoord)
>>> interpolated
[5.954696754335098]

Example 2: interpolate(physreg:int, meshdeform:expression, xyzcoord:List[double])

A field can also be interpolated on a deformed mesh by passing its corresponding field.

>>> # interpolation on the mesh deformed by field 'u'
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> interpolated = u.interpolate(vol, u, xyzcoord)
See Also

field.allinterpolate()

loadraw
def loadraw(
self,
filename: str,
isbinary: bool = False
) -> list[float]

This loads the .slz file created with the writeraw method. If the .slz file was written in the binary format then isbinary argument must be set to True else to False. The same mesh must be used when loading with loadraw as the one that was used during the corresponding writeraw call.

Example
>>> vol = 1
>>> mymesh = mesh("disk.msh")
>>> u = field("h1xyz")
>>> u.loadraw("v.slz.gz", True)
See Also

field.writeraw()

max
def max(
self,
physreg: int,
refinement: int,
xyzrange: list[float] = []
) -> list[float]
def max(
self,
physreg: int,
meshdeform: expression,
refinement: int,
xyzrange: list[float] = []
) -> list[float]

This returns a list with its first element containing the maximum value of a field computed over a geometric region. The remaining elements of the list provide the coordinates at which the maximum value was found. This is an overloaded method.

Example 1: max(physreg:int, refinement:int, xyzrange:List[double]=[])

The maximum value is obtained over the geometric region physreg by splitting all elements refinement times in each direction. Increasing the refinement will thus lead to a more accurate maximum value, but at an increased computational cost. The maximum value is exact when the refinement nodes added to the elements correspond to the position of maximum. For a first-order nodal shape function interpolation, on a mesh that is not curved, the maximum is always exact to machine precision. The default value of xyzrange is an empty list.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1"); x = field("x")
>>> v.setorder(vol, 1)
>>> v.setvalue(vol, 12*x)
>>> maxdata = v.max(vol, 1)
>>> maxdata[0]
12.0

The search of the maximum value can be restricted to a box delimited by the last argument xyzrange whose form is [xboxmin,xboxmax, yboxmin, yboxmin, zboxmax, zboxmin]. The output returned is a list of the form [maxvalue, xcoordmax, ycoordmax, zcoordmax] or an empty list if the physical region argument is empty or is not in the box provided. If the argument defining the box is not provided, then the whole geometric region is considered for evaluating the maximum value.

>>> ...
>>> maxdatainbox = v.max(vol, 5, [-2,0, -2,2, -2,2])

Example 2: max(physreg:int, meshdeform:expression, refinement:int, xyzrange:List[double]=[])

The maximum value can also be evaluated on the geometry deformed by a field (possibly a curved mesh). The location and the delimiting box are on the undeformed mesh.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> maxdataondeformedmesh = v.max(vol, u, 1)
See Also

field.min(), field.allmax(), field.allmin()

min
def min(
self,
physreg: int,
refinement: int,
xyzrange: list[float] = []
) -> list[float]
def min(
self,
physreg: int,
meshdeform: expression,
refinement: int,
xyzrange: list[float] = []
) -> list[float]

This returns a list with its first element containing the minimum value of a field computed over a geometric region. The remaining elements of the list provide the coordinates at which the minimum value was found. This is an overloaded method.

Example 1: min(physreg:int, refinement:int, xyzrange:List[double]=[])

The minimum value is obtained over the geometric region physreg by splitting all elements refinement times in each direction. Increasing the refinement will thus lead to a more accurate minimum value, but at an increased computational cost. The minimum value is exact when the refinement nodes added to the elements correspond to the position of minimum. For a first-order nodal shape function interpolation, on a mesh that is not curved, the minimum is always exact to machine precision. The default value of xyzrange is an empty list.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1"); x = field("x")
>>> v.setorder(vol, 1)
>>> v.setvalue(vol, 12*x)
>>> mindata = v.min(vol, 1)
>>> mindata[0]
-2.0

The search of the minimum value can be restricted to a box delimited by the last argument xyzrange whose form is [xboxmin,xboxmax, yboxmin, yboxmin, zboxmax, zboxmin]. The output returned is a list of the form [minvalue, xcoordmin, ycoordmin, zcoordmin] or an empty list if the physical region argument is empty or is not in the box provided. If the argument defining the box is not provided, then the whole geometric region is considered for evaluating the minimum value.

>>> ...
>>> mindatainbox = v.min(vol, 5, [-2,0, -2,2, -2,2])

Example 2: min(physreg:int, meshdeform:expression, refinement:int, xyzrange:List[double]=[])

The min value can also be evaluated on the geometry deformed by a field (possibly a curved mesh). The min location and the delimiting box are on the undeformed mesh.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> mindataondeformedmesh = u.min(vol, u, 1)
See Also

field.max(), field.allmax(), field.allmin()

noautomaticupdate
def noautomaticupdate(self) -> None

After this call, the field and all its subfields will not have their value automatically updated after hp-adaptivity. If the automatic update is not needed then this call is recommended to avoid a possible costly field value update.

Example
>>> ...
>>> v.noautomaticupdate()
See Also

field.automaticupdate()

print
def print(self) -> None

This prints the field name.

Example
>>> mymesh = mesh("disk.msh")
>>> v = field("h1")
>>> v.setname("velocity")
>>> v.print()
velocity
printharmonics
def printharmonics(self) -> None

This prints a string showing the harmonics in the field.

Example
>>> mymesh = mesh("disk.msh")
>>> v = field("h1", [1,4,5,6])
>>> v.printharmonics()
+vc0*cos(0*pif0t) +vs2*sin(4*pif0t) +vc2*cos(4*pif0t) +vs3*sin(6*pif0t)
setcohomologysources
def setcohomologysources(self, cutvalues: list[float]) -> None

This method assigns cohomology coefficients to the field. The field value is reset to zero on the cohomology regions before the coefficients are added on their respective regions.

Example
>>> mymesh = mesh()
>>> mymesh.setcohomologycuts([chreg1, chreg2])
>>> mymesh.load("disk.msh")
>>> v = field("hcurl")
>>> ...
>>> v.setcohomologysources([100, 50])
>>> v.write(chreg1, "v.pos", 1)
setconditionalconstraint
def setconditionalconstraint(
self,
physreg: int,
condexpr: expression,
valexpr: expression
) -> None
def setconditionalconstraint(
self,
physreg: int,
meshdeform: expression,
condexpr: expression,
valexpr: expression
) -> None

This forces the field value (i.e. Dirichlet constraint) on the region physreg to a value valexpr for all node-associated degrees of freedom for which the condition condexpr evaluates to greater than or equal to zero at the nodes. This should only be used for fields with “h1” type functions.

Example 1: setconditionalconstraint(physreg: int, condexpr: expression, valexpr: expression)

The conditional expression is computed on the undeformed mesh.

>>> mymesh = mesh("disk.msh")
>>> vol=1; top=3
>>> v=field("h1"); x=field("x"); y=field("y")
>>> v.setorder(vol, 1)
>>> v.setconditionalconstraint(vol, x+y, 12)
>>>
>>> form = formulation()
>>> form += integral(vol, dof(v)*tf(v) - 1*tf(v))
>>> form.generate()
>>> sol = solve(form.A(), form.b()) # returns a vec object
>>> v.setdata(vol, sol)
>>> v.write(top, "v.vtk", 1)

Example 2: setconditionalconstraint(physreg: int, meshdeform: expression, condexpr: expression, valexpr: expression)

The conditional expression is computed on a mesh deformed by meshdeform.

>>> mymesh = mesh("disk.msh")
>>> vol=1; top=3
>>> v=field("h1"); x=field("x"); y=field("y")
>>> u = field("h1xyz")
>>> v.setorder(vol, 1)
>>> v.setconditionalconstraint(vol, u, x+y, 12)
>>>
>>> form = formulation()
>>> form += integral(vol, u, dof(v)*tf(v) - 1*tf(v))
>>> form.generate()
>>> sol = solve(form.A(), form.b()) # returns a vec object
>>> v.setdata(vol, sol)
>>> v.write(top, "v.vtk", 1)
See Also

field.setconstraint()

setconstraint
def setconstraint(
self,
physreg: int,
input: expression,
extraintegrationdegree: int = 0
) -> None
def setconstraint(
self,
physreg: int,
meshdeform: expression,
input: expression,
extraintegrationdegree: int = 0
) -> None
def setconstraint(
self,
physreg: int,
input: list[expression],
extraintegrationdegree: int = 0
) -> None
def setconstraint(
self,
physreg: int,
meshdeform: expression,
input: list[expression],
extraintegrationdegree: int = 0
) -> None
def setconstraint(
self,
physreg: int,
numfftharms: int,
input: expression,
extraintegrationdegree: int = 0
) -> None
def setconstraint(
self,
physreg: int,
numfftharms: int,
meshdeform: expression,
input: expression,
extraintegrationdegree: int = 0
) -> None
def setconstraint(self, physreg: int) -> None

This forces the field value (i.e. Dirichlet condition) on the region physreg to input expression. An extra int argument extraintegrationdegree can be used to increase or decrease the default integration order when computing the projection of the expression on the field. Increasing it can give a more accurate computation of the expression but might take longer. The default integration order is equal to “field order ×2+2\times 2 + 2“. Dirichlet constraints have priority over conditional constraints and gauge conditions. Defining any of these on a Dirichlet constrained region has no effect.

Examples

Example 1: field.setconstraint(physreg:int, input:expression, extraintegrationdegree:int=0)

This forces the field value (i.e Dirichlet constraint) on region vol to input expression (here 12+ww12+w*w).

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> w = field("h1")
>>> v.setconstraint(vol, 12+w*w)

Example 2: field.setconstraint(physreg:int, meshdeform:expression, input:expression, extraintegrationdegree:int=0)

This forces the field value on region vol to input expression (here 1212) but on a mesh deformed by meshdeform.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> u = field("h1xyz")
>>> v.setconstraint(vol, u, expression(12))

Example 3: field.setconstraint(physreg:int, input:List[expression], input:expression, extraintegrationdegree:int=0)

This sets a Dirichlet constraint with the given value for each corresponding field harmonic.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1", [2,3])
>>> v.setconstraint(vol, [1,0]) # sets 1 for harmonic 2, 0 for harmonic 3

Example 4: field.setconstraint(physreg:int, meshdeform:expression, input:List[expression], extraintegrationdegree:int=0)

This sets a Dirichlet constraint for each corresponding field harmonic with the given expression computed on a mesh deformed by meshdeform.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1", [2,3])
>>> u = field("h1xyz")
>>> v.setconstraint(vol, u, [1,0]) # sets 1 for harmonic 2, 0 for harmonic 3

Example 5: field.setconstraint(physreg:int, numfftharms:int, input:expression, extraintegrationdegree:int=0)

This calls an FFT for the calculation required for nonlinear multi-harmonic expressions. The FFT is computed at numfftharms timesteps.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v1 = field("h1", [2,3])
>>> v2 = field("h1", [1,4,5])
>>> v2.setconstraint(vol, 5, v1*v1)
>>> v2.write(vol, "v2.vtk", 1)

Example 6: field.setconstraint(physreg:int, numfftharms:int, meshdeform:expression, input:expression, extraintegrationdegree:int=0)

This calls an FFT for the calculation and the expression is evaluated on a mesh deformed by meshdeform.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v1 = field("h1", [2,3])
>>> v2 = field("h1", [1,4,5])
>>> u = field("h1xyz")
>>> v2.setconstraint(vol, 5, u, v1*v1)

Example 7: field.setconstraint(physreg:int)

This forces the field value (i.e. Dirichlet condition) on region vol to 00.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setconstraint(vol)
See Also

field.setconditionalconstraint()

setdata
def setdata(
self,
physreg: int,
myvec: vec,
op: str = 'set'
) -> None
def setdata(
self,
physreg: int,
myvec: vectorfieldselect,
op: str = 'set'
) -> None

This either sets or adds the data in the vector to the field. If the argument op is “set”, then the vector data is set and if it is “add” then the vector data is added to the existing field values.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1"); w = field("h1")
>>> v.setorder(vol, 1)
>>>
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v) - 2*tf(v))
>>> projection.generate()
>>> sol = solve(projection.A(), projection.b())
>>> v.setdata(vol, sol)
setgauge
def setgauge(self, physreg: int) -> None

This sets a gauge condition on regionphysreg. It must be used e.g. for the magnetic vector potential formulation of the magnetostatic problem in 3D since otherwise, the algebraic system to solve is singular. It is only defined for edge shape functions (“hcurl”). Its effect is to constrain to zero all degrees of freedom corresponding to:

  • gradient type shape functions.
  • lowest order edge-shape functions for all edges on the spanning tree provided.
Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3
>>> spantree = spanningtree([sur, top])
>>> a = field("hcurl", spantree)
>>> a.setgauge(vol)
setname
def setname(self, name: str) -> None

This gives a name to the field. Useful when printing expressions including fields.

>>> mymesh = mesh("disk.msh")
>>> v = field("h1")
>>> v.setname("velocity")
>>> v.print()
velocity
setnodalvalues
def setnodalvalues(
self,
nodenumbers: indexmat,
values: densemat
) -> None

This sets the values of a “h1” type field at a set of nodenumbers to values.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> nodenums = indexmat(5,1, [0,1,2,3,4])
>>> nodevals = densemat(5,1, [10,11,12,13,14])
>>> v.setnodalvalues(nodenums, nodevals)
setorder
def setorder(
self,
physreg: int,
interpolorder: int
) -> None
def setorder(
self,
criterion: expression,
loworder: int,
highorder: int
) -> None
def setorder(
self,
targeterror: float,
loworder: int,
highorder: int,
absthres: float
) -> None

This sets the specified interpolation order of the field object.

Examples

Example 1: field.setorder(physreg:int, interpolorder:int)

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 3)

This sets the interpolation order to 33 on the physical region ‘vol’. When using different interpolation orders on different physical regions for a given field it is only allowed to set the interpolation orders in a decreasing way. i.e starting with the physical region with the highest order and ending with the physical region with the lowest order. This is required to enforce field continuity and is due to the fact the interpolation order on the interface between multiple physical regions must be the one of lowest touching region.

Example 2: field.setorder(criterion:expression, loworder:int, highorder:int)

>>> all=1; inn=2; out=3
>>> n = 20
>>> q0 = shape("quadrangle", all, [0,0,0, 1,0,0, 1,0.3,0, 0,0.3,0], [n,n,n,n])
>>> q1 = shape("quadrangle", all, [1,0,0, 2,0,0, 2,0.3,0, 1,0.3,0], [n,n,n,n])
>>> linein = q0.getsons()[3]
>>> linein.setphysicalregion(inn)
>>> lineout = q1.getsons()[0]
>>> lineout.setphysicalregion(out)
>>> mymesh = mesh([q0, q1, linein, lineout])
>>>
>>> v = field("h1")
>>> v.setname("v")
>>> v.setorder(all, 1)
>>> v.setorder(norm(grad(v)), 1, 5)
>>> v.setconstraint(inn, 1)
>>> v.setconstraint(out)
>>>
>>> electrostatics = formulation()
>>> electrostatics += integral(all, 8.854e-12 * grad(dof(v))*grad(tf(v)))
>>>
>>> for i in range(5):
... electrostatics.solve()
... v.write(all, f"v{100+i}.pos", 5)
... (-grad(v)).write(all, f"E{100+i}.pos", 5)
... fieldorder(v).write(all, f"vorder{100+i}.pos", 1)
...
... adapt(2)

In the above example, the field interpolation order will be adapted on each mesh element (of the entire geometry) based on the value of a positive criterion (p-adaptivity). The max range of the criterion is split into a number of intervals equal to the number of orders in range ‘loworder’ to ‘highorder’. All intervals have the same size. The barycenter value of the criterion on each mesh element is considered to select the interval, and therefore the corresponding interpolation order to assign to the field on each element. As an example, for a criterion with the highest value of 900 over the entire domain and a low/high order requested of 1/3 the field on elements with criterion values in range 0 to 300, 300 to 600, 600 to 900 will be assigned order 1, 2, 3 respectively.

Example 3: field.setorder(targeterror:double, loworder:int, highorder:int, absthres:double)

>>> sur = 1
>>> q = shape("quadrangle", sur, [0,0,0, 1,0,0, 1,1,0, 0,1,0], [20,20,20,20])
>>> mymesh = mesh([q])
>>> v=field("h1xy"); x=field("x"); y=field("y")
>>> v.setorder(sur, 1)
>>> v.setorder(1e-5, 1, 5, 0.001)
>>> for i in range(5):
... v.setvalue(sur, array2x1(0,cos(10*x*y)))
... adapt()
... v.write(sur, f"v{i}.vtu", 5)
... fieldorder(v).write(sur, f"fov{i}.vtu", 1)

The field interpolation order will be adapted on each mesh element (of the entire geometry) based on a criterion measuring the Legendre expansion decay. The target error gives the fraction of the total shape function weight that does not need to be captured. The low order is used on all elements where the total weight is lower than the absolute threshold provided.

setpatternedport
def setpatternedport(
self,
physreg: int,
pattern: expression
) -> list[port]
setport
def setport(
self,
physreg: int,
primal: port,
dual: port
) -> None

This function associates a primal-dual pair of ports to the field on the requested physical region. As a side effect, it lowers the field order on that region to the minimum possible. Ports have priority over Dirichlet constraints, conditional constraints and gauge conditions}. Defining any of these on a port region has no effect.

Ports that have been associated to a field with a setport call and unassociated ports are visible to a formulation only if they appear in a port relation (in the example below: electrokinetic += I - 1.0 ). The primal and dual of associated ports are always made visible together even if only of them appears in a port relation. Unassociated ports are not connected to the weak form terms: the primal can be used as the lumped field value on the associated region while the dual can be used as the total contribution over that region of the Neumann term in the formulation. The field value is considered constant by the formulation over the region of each associated port visible to it.

To illustrate the meaning of the dual port let us consider the below DC current flow simulation example code. The strong form to solve is

(σv)=0\nabla \cdot (\sigma \nabla v) = 0

where σ\sigma is the electric conductivity and vv is the electric potential field. The corresponding weak form is

(σv) vdΩ=0\int \nabla \cdot (\sigma \nabla v) \ v^{\prime} d \Omega = 0

which after integration by parts can be rewritten as

ΩσvvdΩ+Γσ nv v dΓ=0-\int_{\Omega} \sigma \nabla v \cdot \nabla v^{\prime} d \Omega + \int_{\Gamma} \sigma \ \partial_{\boldsymbol{n}} v \ v^{\prime} \ d \Gamma = 0

where,

  • Γ\Gamma is the boundary of Ω\Omega,
  • n\boldsymbol{n} is the unit normal pointing outward from Ω\Omega and
  • σ nv= σ vn=\sigma \ \partial_{\boldsymbol{n}} v = \ \sigma \ \nabla v \cdot {\boldsymbol{n}} = σEn=- \sigma \boldsymbol{E} \cdot \boldsymbol{n} = Jn- \boldsymbol{J} \cdot \boldsymbol{n}.

The Neumann term is the second term of the weak formulation. The dual port II in the below example, therefore equals the total current flowing through the electrode and thus II can be used to impose a total current source condition on the electrode. More details about the associated mathematics can be found in the paper ‘Coupling of local and global quantities in various finite element formulations and their application to electrostatics, magnetostatics and magnetodynamics’, Dular et al.

Example
>>> sur=1; left=2; right=3
>>> q = shape("quadrangle", sur, [0,0,0, 1,0,0, 1,1,0, 0,1,0], [10,10,10,10])
>>> ll = q.getsons()[3]
>>> ll.setphysicalregion(left)
>>> rl = q.getsons()[1]
>>> rl.setphysicalregion(right)
>>> mymesh = mesh([q, ll, rl])
>>>
>>> v = field("h1")
>>> y = field("y")
>>> v.setorder(sur, 2)
>>>
>>> # Electric conductivity increasing with the y-coordinate
>>> sigma = expression(0.01*(1+2*y))
>>>
>>> # Ground the right electrode
>>> v.setconstraint(right)
>>>
>>> V = port() # primal port
>>> I = port() # dual port
>>> v.setport(left, V, I)
>>> # The dual port holds the global Neumann term on the port region.
>>> # For an electrokinetic formulation this equals the total current.
>>>
>>> electrokinetic = formulation()
>>> # Set a 1A current flowing in through the left electrode with the port relation I - 1.0 = 0:
>>> elctrokinetic += I - 1.0 # port relation
>>> # Define the weak formulation for the DC current flow:
>>> electrokinetic += integral(sur, -sigma * grad(dof(v) * grad(tf(v))))
>>>
>>> electrokinetic.solve()
>>> v.write(sur, "v.pos", 2)
>>> (-grad(v)*sigma).write(sur, "j.pos", 2)
>>>
>>> resistance = V.getvalue()/I.getvalue()
>>> print(f"Resistance is {resistance} Ohm")
setupdateaccuracy
def setupdateaccuracy(self, extraintegrationorder: int) -> None

This method allows tuning the integration order in the projection used to update the field value after hp-adaptivity. A positive/negative argument increases/decreases the accuracy but slowsdown/speeds up the update.

Example
>>> ...
>>> v.setupdateaccuracy(2)
setvalue
def setvalue(
self,
physreg: int,
input: expression,
extraintegrationdegree: int = 0
) -> None
def setvalue(
self,
physreg: int,
meshdeform: expression,
input: expression,
extraintegrationdegree: int = 0
) -> None
def setvalue(
self,
physreg: int,
numfftharms: int,
input: expression,
extraintegrationdegree: int = 0
) -> None
def setvalue(
self,
physreg: int,
numfftharms: int,
meshdeform: expression,
input: expression,
extraintegrationdegree: int = 0
) -> None
def setvalue(self, physreg: int) -> None

This sets the field value on the region physreg to input expression. An extra int argument extraintegrationdegree can be used to increase or decrease the default integration order when computing the projection of the expression on the field. Increasing it can give a more accurate computation of the expression but might take longer. The default integration order is equal to “field order ×2+2\times 2 + 2“.

Examples

Example 1: field.setvalue(physreg:int, input:expression, extraintegrationdegree:int=0)

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> v.setvalue(vol, 12)

This sets the field value on region vol to 1212.

Example 2: field.setvalue(physreg:int, meshdeform:expression, input:expression, extraintegrationdegree:int=0)

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> u = feild("h1xyz")
>>> v.setorder(vol, 1)
>>> u.setorder(vol, 1)
>>> v.setvalue(vol, u, expression(12))

This sets the field value on region vol to expression 1212 but on a mesh deformed by meshdeform.

Example 3: field.setvalue(physreg:int, numfftharms:int, input:expression, extraintegrationdegree:int=0)

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v1 = field("h1", [2,3])
>>> v2 = field("h1", [1,4,5])
>>> v1.setorder(vol, 1)
>>> v2.setorder(vol, 1)
>>> v2.setvalue(vol, 5, v1*v1)
>>> v2.write(vol, "v2.vtk", 1)

This calls an FFT for the calculation required for nonlinear multi-harmonic expressions. The FFT is computed at numfftharms timesteps.

Example 4: field.setvalue(physreg:int, numfftharms:int, meshdeform:expression, input:expression, extraintegrationdegree:int=0)

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v1 = field("h1", [2,3])
>>> v2 = field("h1", [1,4,5])
>>> u = field("h1xyz")
>>> v1.setorder(vol, 1)
>>> v2.setorder(vol, 1)
>>> u.setorder(vol, 1)
>>> v2.setvalue(vol, 5, u, v1*v1)

This calls an FFT for the calculation and the expression is evaluated on a mesh deformed by meshdeform.

Example 5: field.setvalue(physreg:int)

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> v.setvalue(vol)

This sets the field value on region vol to 00.

sin
def sin(self, freqindex: int) -> field

This gets the “h1xyz” type field that is the sinsin harmonic at freqindex times the fundamental frequency in field uu.

Example
>>> mymesh = mesh("disk.msh")
>>> u = field("h1xyz", [1,2,3,4,5])
>>> us = u.sin(2) # gets the harmonic 4
See Also

field.cos()

write
def write(
self,
physreg: int,
numfftharms: int,
filename: str,
lagrangeorder: int
) -> None
def write(
self,
physreg: int,
numfftharms: int,
meshdeform: expression,
filename: str,
lagrangeorder: int
) -> None
def write(
self,
physreg: int,
filename: str,
lagrangeorder: int,
numtimesteps: int = -1
) -> None
def write(
self,
physreg: int,
meshdeform: expression,
filename: str,
lagrangeorder: int,
numtimesteps: int = -1
) -> None

This evaluates a field in the physical region physreg and writes it to the file filename. The lagrangeorder is the order of interpolation for the evaluation of the field values.

Examples
>>> # setup
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> u = field("h1xyz")
>>> v = field("h1", [1,2,3])
>>> u.setorder(vol, 1)
>>> v.setorder(vol, 1)
>>>
>>> # interpolation order for writing a field
>>> u.write(vol, "uorder1.vtk", 1) # interpolation order is 1
>>> u.write(vol, "uorder3.vtk", 3) # interpolation order is 3

In the example below, an additional integer input 1010 is passed in the second argument. The 1010 here means that the field is treated as multi-harmonic, nonlinear in time variable and an FFT is performed to get the 1010 first harmonics. All harmonics whose magnitude is above a threshold are saved with ‘_harm i’ extension (except for time-constant harmonic).

>>> abs(v).write(vol, 10, "order1.vtk", 1) # interpolation order is 1
>>> u.write(vol, 10, "order3.vtk", 3) # interpolation order is 3

In the example below, an additional integer input 5050 is instead passed as the last argument posterior to the interpolation order argument. This represents that numtimesteps (default=-1). For a postive value of nn, the multi-harmonic field is saved at nn equidistant timestpes in the fundamental period and can then be visualized in time.

>>> u.write(vol, "uintime.vtk", 2, 50)

The field can also be evaluated and written on a mesh deformed by a field. If field ‘v’ is the deformed mesh, then:

>>> u.write(vol, v, "uorder1.vtk", 1)
>>> u.write(vol, 10, v, "order3.vtk", 3)
>>> u.write(vol, v, "uintime.vtk", 2, 50)
writeraw
def writeraw(
self,
physreg: int,
filename: str,
isbinary: bool = False,
extradata: list[float] = []
) -> None

This writes a (possibly multi-harmonic) field on a given region to disk in the compact .slz sparselizard format. If isbinary=False the output format is in ASCII and with isbinary=True the output is in binary format. In the latter case, the .slz.gz extension can also be used to write to gz compressed -slz format (the most compact version). While the binary file is more compact on disk it might be less portable across different platforms than the ASCII version. The last input argument allows storing extra data (timestep, parameter values, ..) that can be loaded back from the loadraw output.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v=field("h1xyz"); x=field("x"); y=field("y"); z=field("z")
>>> v.setorder(vol, 2)
>>> v.setvalue(vol, array3x1(x*x, y*y, z*z))
>>> v.writeraw(vol, "v.slz.gz", True)
See Also

field.loadraw()

Class: formulation

def formulation(
self
)

The formulation object holds the port relations and the weak form terms of the problem to solve.

The following creates an empty formulation object:

>>> mymesh = mesh("disk.msh")
>>> myformulation = formulation()

Using the += operator, a port relation can be added to the formulation or can be coupled to weak-form terms.

Examples

Adding a port relation to the formulation: formulation.operator+=(integrationobject: expression)

>>> mymesh = mesh("disk.msh")
>>> A = port(); B = port()
>>> linearsystem = formulation()
>>> linearsystem += A + B - 1.0; # A + B -1 = 0
>>> linearsystem += B - 5.0; # B - 5 = 0
>>> linearsystem.solve()
>>> A.getvalue()
-4.0
>>> B.getvalue()
5.0

Adding a weak form term to the formulation: formulation.operator+=(integration integrationobject). All terms are added together and their sum equals zero. There are twelve += calls that are listed below.

Basic version

In the following, the term is assembled for unknowns (dofs) and test functions (tf). The first argument vol is the element integration region. Hence, the assembly is for integration on all elements in the region vol. When no region is specified for the dof() or tf() then the element integration region (vol in this case) is used by default.

>>> projection += integral(vol, dof(v)*tf(v)) # same as integral(vol, dof(v, vol)*tf(v, vol))

In the following, the unknowns (dofs) are defined on region vol while the test functions are defined only on region sur. In the third argument, an extra integer is added. This specifies the extra number that should be added to the default integration order to perform the numerical integration in the assembly process. The default integration order is equal to (the order of the unknown + order of the test function + 2). In case there is no unknown then it is equal to (order of the test function x 2 + 2). By increasing the order a more accurate assembly can be obtained, at the expense of an increased assembling time.

>>> projection += integral(vol, 2*dof(v)*tf(v,sur), +1)

Each weak form term can be assigned a contribution number or block number. The default value is 00. In the below example, it is set to 22. This can be of interest when the formulation is generated since one can choose exactly which block numbers to generate and which ones to not generate.

>>> projection += integral(vol, compx(u)*dof(v,sur)*tf(v) - 2*tf(v), +1, 2)

Assemble on the mesh deformed by field uu Assembly of weak form terms can also be performed on the deformed mesh by providing an additional field input as the second argument. The following shows the previous 3 examples but with the assembly performed on the mesh deformed by field uu.

>>> projection += integral(vol, u, dof(v)*tf(v))
>>> projection += integral(vol, u, 2*dof(v)*tf(v,sur), +1)
>>> projection += integral(vol, u, compx(u)*dof(v,sur)*tf(v) - 2*tf(v), +1, 2)

Assemble with a call to FFT to compute the first 20 harmonics If the additional input in the second argument is a positive integer a Fast Fourier Transform (FFT) is called during the assembly and the first 20 harmonics will be computed. The harmonics whose magnitude is below a threshold are disregarded. This must be called when assembling a multi-harmonic formulation term that is nonlinear in the time variable.

>>> projection += integral(vol, 20, (1-v)*dof(v)*tf(v) + v*tf(v)
>>> projection += integral(sur, 20, v*v*dof(v)*tf(v) - tf(v), +3)
>>> projection += integral(vol, 20, v*dof(,sur)*tf(v), +1, 2)

Assemble with a call to FFT to compute the first 20 harmonics on the mesh deformed by field uu Assembly of a multi-harmonic formulation can be performed on the deformed mesh by providing the field input in the third argument.

>>> projection += integral(vol, 20, u, (1-v)*dof(v)*tf(v) + v*tf(v)
>>> projection += integral(sur, 20, u, v*v*dof(v)*tf(v) - tf(v), +3)
>>> projection += integral(vol, 20, u, v*dof(,sur)*tf(v), +1, 2)

Methods

A
def A(
self,
keepfragments: bool = False,
distributed: bool = False
) -> mat

This gives the matrix AA (of Ax=bAx = b) that was assembled during the formulation.generate() call. By default the keepfragments argument is False which means that the generated matrix is no longer kept in the formulation after returning it to a mat object. However, if you select True for keepfragments it means the generated matrix is kept in the formulation and will be added to the matrix assembled in any subsequent formulation.generate() call.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>>
>>> projection += integral(vol, dof(v)*tf(v), 0, 2)
>>> projection += integral(vol, dt(dof(v))*tf(v) - 2*tf(v))
>>> projection += integral(vol, dtdt(dof(v))*tf(v))
>>>
>>> projection.generate()
>>> A = projection.A(); # equivalent to A = projection.A(keepfragments=False)
See Also

formulation.rhs(), formulation.b()

C
def C(
self,
keepfragments: bool = False,
distributed: bool = False
) -> mat

This gives the damping matrix CC that was assembled during the formulation.generate() call. The damping matrix CC is a matrix that is assembled with only those terms in the formulation which have a dof and that dof has a first-order time derivative applied to it (i.e dtdt). For multi-harmonic simulations damping matrix CC is empty.

By default, the keepfragments argument is False which means that the generated matrix is no longer kept in the formulation after returning it to a mat object. However, if you select True for keepfragments it means the generated matrix is kept in the formulation and will be added to the matrix assembled in any subsequent formulation.generate() call.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>>
>>> projection += integral(vol, dof(v)*tf(v), 0, 2)
>>> projection += integral(vol, dt(dof(v))*tf(v) - 2*tf(v))
>>> projection += integral(vol, dtdt(dof(v))*tf(v))
>>>
>>> projection.generate()
>>> C = projection.C(); # equivalent to C = projection.C(keepfragments=False)
See Also

formulation.K(), formulation.M()

K
def K(
self,
keepfragments: bool = False,
distributed: bool = False
) -> mat

This gives the stiffness matrix KK that was assembled during the formulation.generate() call. The stiffness matrix KK is a matrix that is assembled with only those terms in the formulation which have a dof and that dof has no time derivative applied to it. For multi-harmonic formulations, the stiffness matrix KK holds the assembly of all the terms.

By default, the keepfragments argument is False which means that the generated matrix is no longer kept in the formulation after returning it to a mat object. However, if you select True for keepfragments it means the generated matrix is kept in the formulation and will be added to the matrix assembled in any subsequent formulation.generate() call.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>>
>>> projection += integral(vol, dof(v)*tf(v), 0, 2)
>>> projection += integral(vol, dt(dof(v))*tf(v) - 2*tf(v))
>>> projection += integral(vol, dtdt(dof(v))*tf(v))
>>>
>>> projection.generate()
>>> K = projection.K(); # equivalent to K = projection.K(keepfragments=False)
See Also

formulation.C(), formulation.M()

M
def M(
self,
keepfragments: bool = False,
distributed: bool = False
) -> mat

This gives the mass matrix MM that was assembled during the formulation.generate() call. The mass matrix MM is a matrix that is assembled with only those terms in the formulation which have a dof and that dof has a second-order time derivative applied to it (i.e dtdtdtdt). For multi-harmonic simulations mass matrix MM is empty.

By default, the keepfragments argument is False which means that the generated matrix is no longer kept in the formulation after returning it to a mat object. However, if you select True for keepfragments it means the generated matrix is kept in the formulation and will be added to the matrix assembled in any subsequent formulation.generate() call.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>>
>>> projection += integral(vol, dof(v)*tf(v), 0, 2)
>>> projection += integral(vol, dt(dof(v))*tf(v) - 2*tf(v))
>>> projection += integral(vol, dtdt(dof(v))*tf(v))
>>>
>>> projection.generate()
>>> M = projection.M(); # equivalent to M = projection.M(keepfragments=False)
See Also

formulation.K(), formulation.C()

allcountdofs
def allcountdofs(self) -> int

This is a collective MPI operation and hence must be called by all the ranks. It returns on every rank the global number of degrees of freedom defined in the scattered formulation. The count is exact if for each field the number of unknowns associated to each element matches across touching ranks. It is an estimation otherwise.

Example
>>> vol=1; sur=2
>>> mymesh = mesh("disk.msh")
>>>
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v) - 2*tf(v))
>>> numdofs = projection.allcountdofs()
See Also

formulation.countdofs()

allislinear
def allislinear(self) -> bool

This is a collective MPI operation and hence must be called by all the ranks. This returns True if the formulation defined is linear, otherwise, it returns False.

Examples
>>> vol=1; sur=2
>>> mymesh = mesh("disk.msh")
>>>
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v) - 2*tf(v))
>>> projection.islinear()
True
allsolve
def allsolve(
self,
relrestol: float,
maxnumit: int,
soltype: str = 'lu',
verbosity: int = 1
) -> list[float]
def allsolve(
self,
relrestol: float,
maxnumit: int,
nltol: float,
maxnumnlit: int,
relaxvalue: float = 1.0,
soltype: str = 'lu',
verbosity: int = 1
) -> int
def allsolve(
self,
relrestol: float,
maxnumit: int,
nltol: float,
maxnumnlit: int,
relaxvalue: float,
rhsblocks: list[list[int]],
soltype: str = 'lu',
verbosity: int = 1
) -> list[vec]

This is a collective MPI operation and hence must be called by all the ranks. This solves the formulation on all the ranks using DDM. The initial solution is taken from the fields’ state. The relative residual history is returned. This method can be used for both linear and nonlinear problems.

Examples

Example 1: formulation.allsolve(relrestol:double, maxnumit:int, soltype:str="lu", verbosity:int=1)

This is used for linear problems. The relrestol and maxnumit arguments correspond to the stopping criteria for DDM solver. The DDM iterations stop if either relative residual tolerance is less than relrestol or if the number of DDM iteration reaches maxnumit.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>>
>>> projection += integral(vol, dof(v)*tf(v) - 2*tf(v))
>>> projection.allsolve(1e-8, 500)
>>> v.write(vol, f"v_{getrank()}.vtu", 1)

Example 2: formulation.allsolve(relrestol:double, maxnumit:int, nltol:double, maxnumnlit:int, relaxvalue:double=1, soltype:str="lu", verbosity:int=1)

This is used for nonlinear problems. The relrestol and maxnumit arguments correspond to the stopping criteria for DDM solver. The DDM iteration stops if either relative residual tolerance is less than relrestol or if the number of DDM iterations reaches maxnumit. A nonlinear fixed-point iteration is performed for at most maxnumnlit or until the relative error (norm of relative solution vector change) is smaller than the tolerance prescribed in nltol. A relaxation value can be provided with relaxvalue argument. Usually, a relaxation value less than 1.01.0 (under-relaxation) is used to avoid divergence of a solution.

>>> ...
>>> projections.allsolve(1e-8, 500, 1e-6, 200, 0.75)
b
def b(
self,
keepvector: bool = False,
dirichletandportupdate: bool = True,
distributed: bool = False
) -> vec

This returns the rhs vector bb that was assembled during the formulation.generate() call. By default the keepvector argument is False which means that the generated rhs vector is no longer kept in the formulation after returning it to a vec object. However, if you select True for keepvector it means the generated rhs vector is kept in the formulation and will be added to the rhs vector assembled in any subsequent formulation.generate() call.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>>
>>> projection += integral(vol, dof(v)*tf(v), 0, 2)
>>> projection += integral(vol, dt(dof(v))*tf(v) - 2*tf(v))
>>> projection += integral(vol, dtdt(dof(v))*tf(v))
>>>
>>> projection.generate()
>>> b = projection.b(); # equivalent to b = projection.b(keepvector=False)
See Also

formulation.rhs(), formulation.A()

countdofs
def countdofs(self) -> int

This returns the number of degrees of freedom defined in the formulation.

Example
>>> vol=1; sur=2
>>> mymesh = mesh("disk.msh")
>>>
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v) - 2*tf(v))
>>> numdofs = projection.countdofs()
82
See Also

formulation.allcountdofs()

generate
def generate(self) -> None
def generate(self, contributionnumbers: list[int]) -> None
def generate(self, contributionnumber: int) -> None

This assembles all the terms in the formulation.

Examples
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>>
>>> projection += integral(vol, dof(v)*tf(v), 0, 2) # here 0 is the extra integration order, 2 is the block number assigned.
>>> projection += integral(vol, dt(dof(v))*tf(v) - 2*tf(v)) # default block number is 0
>>> projection += integral(vol, dtdt(dof(v))*tf(v)) # default block number is 0
>>>
>>> projection.generate()

A block number can be passed as an argument to generate only the necessary terms in the formulation. For example, the following generates only the block number 2. (i.e the first integral term)

>>> projection.generate(2)

A list of block numbers can also be passed as an argument. For example, the following generates all terms with block numbers 0 and 2. For this formulation, it means all terms are generated since these are the only block numbers existing. and 0 (default) block numbers.

>>> projection.generate([0,2])
See Also

formulation.generatestiffnessmatrix(), formulation.generatedampingmatrix(), formulation.generatemassmatrix(), formulation.rhs()

generatedampingmatrix
def generatedampingmatrix(self) -> None

This assembles only those terms in the formulation which have a dof and that dof has a first-order time derivative applied to it (i.e dtdt). For multi-harmonic simulations, it generates nothing.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>>
>>> projection += integral(vol, dof(v)*tf(v), 0, 2)
>>> projection += integral(vol, dt(dof(v))*tf(v) - 2*tf(v))
>>> projection += integral(vol, dtdt(dof(v))*tf(v))
>>>
>>> projection.generatedampingmatrix() # Here it only generates 'dt(dof(v))*tf(v)'
See Also

formulation.generate(), formulation.generatestiffnessmatrix(), formulation.generatemassmatrix(), formulation.rhs()

generatemassmatrix
def generatemassmatrix(self) -> None

This assembles only those terms in the formulation which have a dof and that dof has a second-order time derivative applied to it (i.e dtdtdtdt). For multi-harmonic formulations, it generates nothing.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>>
>>> projection += integral(vol, dof(v)*tf(v), 0, 2)
>>> projection += integral(vol, dt(dof(v))*tf(v) - 2*tf(v))
>>> projection += integral(vol, dtdt(dof(v))*tf(v))
>>>
>>> projection.generatemassmatrix() # Here it only generates 'dtdt(dof(v))*tf(v)'
See Also

formulation.generate(), formulation.generatestiffnessmatrix(), formulation.generatedampingmatrix(), formulation.rhs()

generaterhs
def generaterhs(self) -> None

This assembles only the terms in the formulation which have no dof.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>>
>>> projection += integral(vol, dof(v)*tf(v), 0, 2)
>>> projection += integral(vol, dt(dof(v))*tf(v) - 2*tf(v))
>>> projection += integral(vol, dtdt(dof(v))*tf(v))
>>>
>>> projection.generaterhs() # Here it only generates '-2*tf(v)'
See Also

formulation.generate(), formulation.generatestiffnessmatrix(), formulation.generatedampingmatrix(), formulation.generatemassmatrix()

generatestiffnessmatrix
def generatestiffnessmatrix(self) -> None

This assembles only those terms in the formulation which have a dof and that **dof has no time derivative applied to it. For multi-harmonic formulations it generates all the terms.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>>
>>> projection += integral(vol, dof(v)*tf(v), 0, 2)
>>> projection += integral(vol, dt(dof(v))*tf(v) - 2*tf(v))
>>> projection += integral(vol, dtdt(dof(v))*tf(v))
>>>
>>> projection.generatestiffnessmatrix() # Here it only generates dof(v)*tf(v)
See Also

formulation.generate(), formulation.generatedampingmatrix(), formulation.generatemassmatrix(), formulation.rhs()

getmatrix
def getmatrix(
self,
KCM: int,
keepfragments: bool = False,
additionalconstraints: list[indexmat] = [],
distributed: bool = False
) -> mat

Depending on KCM argument value, it returns the corresponding matrix as follows:

  • KCM=0KCM = 0, returns the stiffness matrix KK
  • KCM=1KCM = 1, returns the damping matrix CC
  • KCM=2KCM = 2, returns the mass matrix MM

By default, the keepfragments argument is False which means that the generated matrix is no longer kept in the formulation after returning it to a mat object. However, if you select True for keepfragments it means the generated matrix is kept in the formulation and will be added to the matrix assembled in any subsequent formulation.generate() call.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>>
>>> projection += integral(vol, dof(v)*tf(v), 0, 2)
>>> projection += integral(vol, dt(dof(v))*tf(v) - 2*tf(v))
>>> projection += integral(vol, dtdt(dof(v))*tf(v))
>>>
>>> projection.generate()
>>> K = projection.getmatrix(0); # equivalent to K = projection.getmatrix(KCM=0, keepfragments=False) or just K = projection.K()
>>> C = projection.getmatrix(1); # equivalent to C = projection.getmatrix(KCM=1, keepfragments=False) or just C = projection.C()
>>> M = projection.getmatrix(2); # equivalent to M = projection.getmatrix(KCM=2, keepfragments=False) or just M = projection.M()
See Also

formulation.K(), formulation.C(), formulation.M()

lump
def lump(
self,
physregs: list[int],
harmonicnumbers: list[int] = []
) -> list[field]
rhs
def rhs(
self,
keepvector: bool = False,
dirichletandportupdate: bool = True,
distributed: bool = False
) -> vec

This returns the rhs vector bb that was assembled during the formulation.generate() call. By default the keepvector argument is False which means that the generated rhs vector is no longer kept in the formulation after returning it to a vec object. However, if you select True for keepvector it means the generated rhs vector is kept in the formulation and will be added to the rhs vector assembled in any subsequent formulation.generate() call. This is the same as formulation.b().

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>>
>>> projection += integral(vol, dof(v)*tf(v), 0, 2)
>>> projection += integral(vol, dt(dof(v))*tf(v) - 2*tf(v))
>>> projection += integral(vol, dtdt(dof(v))*tf(v))
>>>
>>> projection.generate()
>>> rhs = projection.rhs(); # equivalent to rhs = projection.rhs(keepvector=False)
See Also

formulation.b(), formulation.A()

solve
def solve(
self,
soltype: str = 'lu',
diagscaling: bool = False,
blockstoconsider: list[int] = [-1]
) -> None

This generates the formulation, solves the algebraic problem Ax=bAx = b with a direct solver then saves all the data in vector xx to the fields defined in the formulation.

The direct solver type can be set to “lu” or “cholesky” through the soltype argument. If the diagscaling is set to True, then the diagonal scaling preconditioning is applied. The blockstoconsider is a list of integral blocks considered for solving. Default is -11 meaning all the blocks are considered.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>>
>>> projection += integral(vol, dof(v)*tf(v) - 2*tf(v))
>>> projection.solve(); # equivalent to projection.solve("lu", False, -1);
>>> v.write(vol, "v.vtu", 1)

Class: genalpha

def genalpha(
self,
formul: formulation,
dtxinit: vec,
dtdtxinit: vec,
verbosity: int = 3,
isrhskcmconstant: list[bool] = []
)

This defines the genalpha timestepper object to solve in time the formulation formul with the fields’ state as an initial solution for xx, dtxinit for x˙\dot{x}, dtdtxinit for x¨\ddot{x}. The isrhskcmconstant list can be used to specify whether the RHS vector, K matrix, C matrix and M matrix are constant in time or not. However, this argument is optional and need not be provided by the user: in which case the timestepper algorithm automatically determines at each timestep whether the correponding vector/matrix is constant in time or not. If constant, the generated vector/matrix are reused, otherwise, they are regenerated again at each timestep. If the K, C and M matrices are constant in time, the factorization of the algebraic problem is also reused.

>>> timestepper = qs.genalpha(form, qs.vec(form), qs.vec(form))

The user has the flexibility to specify the isrhskcmconstant argument. For example:

>>> timestepper = qs.genalpha(form, qs.vec(form), qs.vec(form), isrhskcmconst=[False, False, False, False])
>>> # or
>>> timestepper = qs.genalpha(form, qs.vec(form), qs.vec(form), [True, True, True, True])
>>> # or
>>> timestepper = qs.genalpha(form, qs.vec(form), qs.vec(form), [False, True, False, True])

The boolean argument in rhskcconstant[i]:

  • i=0i = 0 corresponds to the rhs vector
  • i=1i = 1 corresponds to the K matrix
  • i=2i = 2 corresponds to the C matrix
  • i=3i = 3 corresponds to the M matrix

The genalpha object allows performing a generalized alpha time resolution for a problem of the form Mx¨+Cx˙+Kx=0M\ddot{x} + C\dot{x} + Kx = 0, be it linear or nonlinear. The solutions for xx as well as x˙\dot{x} and x¨\ddot{x} are made available. For nonlinear problems, a fixed-point iteration is performed at every timestep until the relative error (norm of relative solution vector change) is less than the prescribed tolerance.

The generalized alpha method comes with four parameters (β\beta, γ\gamma, αf\alpha_f and αm\alpha_m) that can be tuned to adjust the properties of the time resolution method (convergence order, stability, high frequency, damping and so on). When both α\alpha parameters are set to zero, a classical Newmark iteration is obtained. By default, the parameters are set to (β=0.25\beta = 0.25, γ=0.50\gamma = 0.50, αf=0.0\alpha_f = 0.0 and αm=0.0\alpha_m = 0.0) which corresponds to an unconditionally stable Newmark iteration.

A convenient way proposed to set the four parameters is to specify a high-frequency dissipation level and let the four parameters be deduced accordingly. This gives a set of parameters leading to an unconditionally stable, second-order accurate algorithm possessing an optimal combination of high-frequency and low-frequency dissipation. More information on the generalized alpha method can be found in the paper A time integration algorithm for structural dynamics with improved numerical dissipation: the generalized-alpha method.

Note that even if the rhs vector can be reused the Dirichlet constraints will nevertheless be recomputed at each timestep.

Methods

allnext
def allnext(
self,
relrestol: float,
maxnumit: int,
timestep: float
) -> None
def allnext(
self,
relrestol: float,
maxnumit: int,
timestep: float,
maxnumnlit: int
) -> int

This is a collective MPI operation and hence must be called by all ranks. It is similar to the genalpha.next() function but the resolution is performed on all ranks using DDM. This method runs the generalized alpha algorithm for one timestep. After the call, the time and field values are updated on all the DDM ranks. This method can be used for both linear and nonlinear problems.

Examples

Example 1: genalpha.allnext(relrestol: double, maxnumit: int, timestep: double) This is used for linear problems. The relrestol and maxnumit arguments correspond to the stopping criteria for DDM solver. The DDM iterations stop if either relative residual tolerance is less than relrestol or if the number of DDM iterations reaches maxnumit. Use timestep=-1 for automatic time adaptivity.

Example 2: genalpha.next(relrestol: double, maxnumit: int, timestep: double, maxnumnlit:int) This is used for nonlinear problems. The relrestol and maxnumit arguments correspond to the stopping criteria for DDM solver. The DDM iteration stops if either relative residual tolerance is less than relrestol or if the number of DDM iterations reaches maxnumit. A nonlinear fixed-point iteration is performed for at most maxnumnlit or until the relative error (norm of relative solution vector change) is smaller than the tolerance prescribed in genalpha.settolerance(). Set timestep=-1 for automatic time-adaptivity and maxnumnlit=-1 for unlimited nonlinear iterations. This method returns the number of nonlinear iterations performed.

See Also

genalpha.next()

count
def count(self) -> int

This counts the total number of steps computed.

gettimederivative
def gettimederivative(self) -> list[vec]

This returns a list containing current time derivative solutions. The first element in the list contains the solution of the first time derivative x˙\dot{x} and the second element contains the solution of the second time derivative x¨\ddot{x}.

See Also

genalpha.settimederivative()

gettimes
def gettimes(self) -> list[float]

This returns all the time values stepped through.

gettimestep
def gettimestep(self) -> float

This returns the current timestep.

isrhskcmreusable
def isrhskcmreusable(self) -> list[bool]

This returns a list of booleans that provides information about the reusability of the rhs vector, K matrix, C matrix and M matrix. They are usually resuable if constant in time otherwise must be regenerated.

Example
>>> timestepper = qs.genalpha(form, qs.vec(form), qs.vec(form))
>>> ...
>>> ...
>>> timestepper.isrhskcmreusable()
next
def next(self, timestep: float) -> None
def next(
self,
timestep: float,
maxnumnlit: int
) -> int

This runs the generalized alpha algorithm for one timestep. After the call, the time and field values are updated. This method can be used for both linear and nonlinear problems.

Examples

Example 1: genalpha.next(timestep: double) This is used for linear problems. Use -11 for automatic time-adaptivity.

Example 2: genalpha.next(timestep: double, maxnumnlit:int) This is used for nonlinear problems. A nonlinear fixed-point iteration is performed for at most maxnumnlit or until the relative error (norm of relative solution vector change) is smaller than the tolerance prescribed in genalpha.settolerance(). Set timestep=-1 for automatic time-adaptivity and maxnumnlit=-1 for unlimited nonlinear iterations. This method returns the number of nonlinear iterations performed.

postsolve
def postsolve(self, formuls: list[formulation]) -> None

This defines the set of formulations that must be solved afterafter every resolution of the formulation provided to the genalpha constructor. The formulations provided here must lead to a system of the form Ax=bAx = b (no damping or mass matrix allowed).

See Also

genalpha.presolve()

presolve
def presolve(self, formuls: list[formulation]) -> None

This defines the set of formulations that must be solved beforebefore every resolution of the formulation provided to the genalpha constructor. The formulations provided here must lead to a system of the form Ax=bAx = b (no damping or mass matrix allowed).

See Also

genalpha.postsolve()

setadaptivity
def setadaptivity(
self,
tol: float,
mints: float,
maxts: float,
reffact: float = 0.5,
coarfact: float = 2.0,
coarthres: float = 0.5
) -> None

This sets the configuration for automatic time adaptivity. The timestep Δt\Delta{t} will be adjusted between the minimum timestep mints and maximum timesteps maxts to reach the requested relative error tolerance tol. To measure the relative deviation from a constant time derivative, the relative error is defined as

Δtx˙n+1x˙n2xn+12\Delta{t} \cdot \frac{{\lVert \dot{x}_{n+1} - \dot{x}_{n} \rVert}_2}{{\lVert x_{n+1} \rVert}_2}

Arguments reffact and coarfact give the factor to use when the time step is refined or coarsened respectively. The timestep is refined when the relative error is above tol or when the maximum number of nonlinear iterations is reached. The timestep is coarsened when the relative error is below the product coarthes ×\times tol and the nonlinear loop has converged in less than the maximum number of iterations.

setparameter
def setparameter(
self,
b: float,
g: float,
af: float,
am: float
) -> None
def setparameter(self, rinf: float) -> None

This is used to set the parameters of the generalized alpha method.

To set the four parameters (β\beta, γ\gamma, αf\alpha_f and αm\alpha_m), four arguments are passed, one for each parameter:

>>> genalpha.setparameter(b: double, g: double, ad: double, am: double)

To set the high-frequency dissipation (ρ\rho_{\infty}), only one argument is passed:

>>> genalpha.setparameter(rinf: double)

The range of high-frequency dissipation is in the range 0ρ10 \leq \rho_{\infty} \leq 1. The four generalized alpha parameters are optimally deduced from (ρ\rho_{\infty}). The deduced parameters lead to an unconditionally stable, second-order accurate algorithm possessing an optimal combination of high-frequency and low-frequency dissipation. Lower (ρ\rho_{\infty}) values lead to more dissipation.

setrelaxationfactor
def setrelaxationfactor(self, relaxfact: float) -> None

This sets the relaxation factor for the fixed-point nonlinear iteration performed at every timestep for nonlinear problems. If the relaxation factor is not set, the default value of 1.01.0 is set. If xsolx_{sol} is the solution obtained at a current iteration, xoldx_{old} is solution at previous iteration, then the new solution xnewx_{new} at the current iteration is updated as

xnew=η xsol+(1η)xoldx_{new} = {\eta} \ x_{sol} + (1-\eta)x_{old}

where η\eta is the relaxation factor.

settimederivative
def settimederivative(self, sol: list[vec]) -> None

This sets the current solution for the time derivatives x˙\dot{x} and x¨\ddot{x} to sol[0] and sol[1] respectively.

See Also

genalpha.gettimederivative()

settimestep
def settimestep(self, timestep: float) -> None

This sets the current timestep.

settolerance
def settolerance(self, nltol: float) -> None

This sets the tolerance for the fixed-point nonlinear iteration performed at every timestep for nonlinear problems. If the tolerance is not set, the default value of 10310^{-3} is considered.

setverbosity
def setverbosity(self, verbosity: int) -> None

This sets the verbosity level. For debugging, higher verbosity is recommended. If the verbosity is not set, the default value of 33 is considered.

Class: grid

def grid(
self
)
def grid(
self,
filename: str,
delimiter: str = '\n'
)
def grid(
self,
gridticks: list[list[float]],
gridvalues: list[float]
)

Methods

countvariables
def countvariables(self) -> int
evalat
def evalat(self, input: list[float]) -> float
def evalat(self, input: densemat) -> densemat
getgridmaxs
def getgridmaxs(self) -> list[float]
getgridmins
def getgridmins(self) -> list[float]
set
def set(
self,
gridticks: list[list[float]],
gridvalues: list[float]
) -> None
write
def write(self, filename: str) -> None

Class: impliciteuler

def impliciteuler(
self,
formul: formulation,
dtxinit: vec,
verbosity: int = 3,
isrhskcconstant: list[bool] = []
)

This defines the impliciteuler timestepper object to solve in time the formulation formul with the fields’ state as an initial solution for xx and dtxinit for x˙\dot{x}. The isrhskcconstant list can be used to specify whether the RHS vector, K matrix and C matrix are constant in time or not. However, this argument is optional and need not be provided by the user: in which case the timestepper algorithm automatically determines at each timestep whether the correponding vector/matrix is constant in time or not. If constant, the generated vector/matrix are reused, otherwise, they are regenerated again at each timestep. If the K and C matrices are constant in time, the factorization of the algebraic problem is also reused.

>>> timestepper = qs.impliciteuler(form, qs.vec(form), qs.vec(form))

The user has the flexibility to specify the isrhskcconstant argument. For example:

>>> timestepper = qs.impliciteuler(form, qs.vec(form), isrhskcmconst=[False, False, False])
>>> # or
>>> timestepper = qs.impliciteuler(form, qs.vec(form), [True, True, True])
>>> # or
>>> timestepper = qs.impliciteuler(form, qs.vec(form), [False, True, False])

The boolean argument in rhskcconstant[i]:

  • i=0i = 0 corresponds to the rhs vector
  • i=1i = 1 corresponds to the K matrix
  • i=2i = 2 corresponds to the C matrix

The impliciteuler object allows performing an implicit (backward) Euler time resolution for a problem of the form Cx˙+Kx=bC\dot{x} + Kx = b, be it linear or nonlinear. The solutions for xx, as well as x˙\dot{x}, are made available. For nonlinear problems, a fixed-point iteration is performed at every timestep until the relative error (norm of relative solution vector change) is less than the prescribed tolerance. Note that even if the rhs vector can be reused the Dirichlet constraints will nevertheless be recomputed at each timestep.

Methods

allnext
def allnext(
self,
relrestol: float,
maxnumit: int,
timestep: float
) -> None
def allnext(
self,
relrestol: float,
maxnumit: int,
timestep: float,
maxnumnlit: int
) -> int

This is a collective MPI operation and hence must be called by all ranks. It is similar to the impliciteuler.next() function but the resolution is performed on all ranks using DDM. This method runs the implicit Euler algorithm for one timestep. After the call, the time and field values are updated on all the DDM ranks. This method can be used for both linear and nonlinear problems.

Examples

Example 1: impliciteuler.allnext(relrestol: double, maxnumit: int, timestep: double) This is used for linear problems. The relrestol and maxnumit arguments correspond to the stopping criteria for DDM solver. The DDM iterations stop if either relative residual tolerance is less than relrestol or if the number of DDM iterations reaches maxnumit. Use timestep=-1 for automatic time adaptivity.

Example 2: impliciteuler.next(relrestol: double, maxnumit: int, timestep: double, maxnumnlit:int) This is used for nonlinear problems. The relrestol and maxnumit arguments correspond to the stopping criteria for DDM solver. The DDM iteration stops if either relative residual tolerance is less than relrestol or if the number of DDM iterations reaches maxnumit. A nonlinear fixed-point iteration is performed for at most maxnumnlit or until the relative error (norm of relative solution vector change) is smaller than the tolerance prescribed in impliciteuler.settolerance(). Set timestep=-1 for automatic time-adaptivity and maxnumnlit=-1 for unlimited nonlinear iterations. This method returns the number of nonlinear iterations performed.

See Also

impliciteuler.next()

count
def count(self) -> int

This counts the total number of steps computed.

gettimederivative
def gettimederivative(self) -> vec

This returns the current solution for the first time derivative x˙\dot{x}.

See Also

impliciteuler.settimederivative()

gettimes
def gettimes(self) -> list[float]

This returns all the time values stepped through.

gettimestep
def gettimestep(self) -> float

This returns the current timestep.

isrhskcreusable
def isrhskcreusable(self) -> list[bool]

This returns a list of booleans that provides information about the reusability of the rhs vector, K matrix and C matrix. They are usually resuable if constant in time otherwise must be regenerated.

Example
>>> timestepper = qs.impliciteuler(form, qs.vec(form), qs.vec(form))
>>> ...
>>> ...
>>> timestepper.isrhskcreusable()
next
def next(self, timestep: float) -> None
def next(
self,
timestep: float,
maxnumnlit: int
) -> int

This runs the implicit Euler algorithm for one timestep. After the call, the time and field values are updated. This method can be used for both linear and nonlinear problems depending on the number of arguments passed.

Examples

Example 1: implicit.next(timestep: double) This is used for linear problems. Use -11 for automatic time-adaptivity.

Example 2: implicit.next(timestep: double, maxnumnlit:int) This is used for nonlinear problems. A nonlinear fixed-point iteration is performed for at most maxnumnlit or until the relative error (norm of relative solution vector change) is smaller than the tolerance prescribed in impliciteuler.settolerance(). Set timestep=-1 for automatic time-adaptivity and maxnumnlit=-1 for unlimited nonlinear iterations. This method returns the number of nonlinear iterations performed.

postsolve
def postsolve(self, formuls: list[formulation]) -> None

This defines the set of formulations that must be solved afterafter every resolution of the formulation provided to the impliciteuler constructor. The formulations provided here must lead to a system of the form Ax=bAx = b (no damping or mass matrix allowed).

See Also

impliciteuler.presolve()

presolve
def presolve(self, formuls: list[formulation]) -> None

This defines the set of formulations that must be solved beforebefore every resolution of the formulation provided to the impliciteuler constructor. The formulations provided here must lead to a system of the form Ax=bAx = b (no damping or mass matrix allowed).

See Also

impliciteuler.postsolve()

setadaptivity
def setadaptivity(
self,
tol: float,
mints: float,
maxts: float,
reffact: float = 0.5,
coarfact: float = 2.0,
coarthres: float = 0.5
) -> None

This sets the configuration for automatic time adaptivity. The timestep Δt\Delta{t} will be adjusted between the minimum timestep mints and maximum timesteps maxts to reach the requested relative error tolerance tol. To measure the relative deviation from a constant time derivative, the relative error is defined as

Δtx˙n+1x˙n2xn+12\Delta{t} \cdot \frac{{\lVert \dot{x}_{n+1} - \dot{x}_{n} \rVert}_2}{{\lVert x_{n+1} \rVert}_2}

Arguments reffact and coarfact give the factor to use when the time step is refined or coarsened respectively. The timestep is refined when the relative error is above tol or when the maximum number of nonlinear iterations is reached. The timestep is coarsened when the relative error is below the product coarthes ×\times tol and the nonlinear loop has converged in less than the maximum number of iterations.

setrelaxationfactor
def setrelaxationfactor(self, relaxfact: float) -> None

This sets the relaxation factor for the fixed-point nonlinear iteration performed at every timestep for nonlinear problems. If the relaxation factor is not set, the default value of 1.01.0 is set. If xsolx_{sol} is the solution obtained at a current iteration, xoldx_{old} is solution at previous iteration, then the new solution xnewx_{new} at the current iteration is updated as

xnew=η xsol+(1η)xoldx_{new} = {\eta} \ x_{sol} + (1-\eta)x_{old}

where η\eta is the relaxation factor.

settimederivative
def settimederivative(self, sol: vec) -> None

This sets the current solution for the first time derivatives x˙\dot{x} to sol[0].

See Also

impliciteuler.gettimederivative()

settimestep
def settimestep(self, timestep: float) -> None

This sets the current timestep.

settolerance
def settolerance(self, nltol: float) -> None

This sets the tolerance for the fixed-point nonlinear iteration performed at every timestep for nonlinear problems. If the tolerance is not set, the default value of 10310^{-3} is considered.

setverbosity
def setverbosity(self, verbosity: int) -> None

This sets the verbosity level. For debugging, higher verbosity is recommended. If the verbosity is not set, the default value of 33 is considered.

Class: indexmat

def indexmat(
self
)
def indexmat(
self,
numberofrows: int,
numberofcolumns: int
)
def indexmat(
self,
numberofrows: int,
numberofcolumns: int,
initvalue: int
)
def indexmat(
self,
numberofrows: int,
numberofcolumns: int,
valvec: list[int]
)
def indexmat(
self,
numberofrows: int,
numberofcolumns: int,
init: int,
step: int
)
def indexmat(
self,
input: list[indexmat]
)

The indexmat object stores a row-major array of integers that corresponds to a dense matrix. For storing an array of doubles, see densemat object.

Examples

There are many ways of instantiating an indexmat object. There are listed below:

Example 1: indexmat(numberofrows:int, numberofcolumns:int) The following creates a matrix with 2 rows and 3 columns. The entries may be undefined.

>>> B = indexmat(2,3)

Example 2: indexmat(numberofrows:int, numberofcolumns:int, initvalue:int) This creates a matrix with 2 rows and 3 columns. All entries are assigned the value initvalue.

>>> B = indexmat(2,3, 12)
>>> B.print()
Matrix size is 2x3
12 12 12
12 12 12

Example 3: indexmat(numberofrows:int, numberofcolumns:int, valvec:List[int]) This creates a matrix with 2 rows and 3 columns. The entries are assigned the values of valvec. The length of valvec is expected to be equal to the total count of entries in the matrix. So for creating a matrix of size 2×32 \times 3, length of valvec must be 6.

>>> B = indexmat(2,3, [1,2,3,4,5,6])
>>> B.print()
Matrix size is 2x3
1 2 3
4 5 6

Example 4: indexmat(numberofrows:int, numberofcolumns:int, init:int, step:int) This creates a matrix with 2 rows and 3 columns. The first entry is assigned the value init and the consecutive entries are assigned values that increase by steps of step.

>>> B = indexmat(2,3, 0, 1)
>>> B.print()
Matrix size is 2x3
0 1 2
3 4 5

Example 5: indexmat(input:List[indexmat]) This creates a matrix that is the vertical concatenation of input matrices. Since the concatenation occurs vertically, the number of columns in all the input matrices must match.

>>> A = indexmat(2,3, 0)
>>> B = indexmat(1,3, 2)
>>> AB = indexmat([A,B])
>>> AB.print()
Matrix size is 3x3
0 0 0
0 0 0
2 2 2

Methods

count
def count(self) -> int

This counts and returns the total number of entries in the dense matrix.

count=(number of rows)×(number of columns)count = (number \ of \ rows) \times (number \ of \ columns)
Example
>>> B = indexmat(2,3)
>>> B.count()
6
countcolumns
def countcolumns(self) -> int

This counts and returns the number of columns in the dense matrix.

Example
>>> B = indexmat(2,3)
>>> B.countcolumns()
3
countrows
def countrows(self) -> int

This counts and returns the number of rows in the dense matrix.

Example
>>> B = indexmat(2,3)
>>> B.countrows()
2
print
def print(self) -> None

This prints the entries of the dense matrix.

Example
>>> B = indexmat(2,3, 0,1)
>>> B.print()
Matrix size is 2x3
0 1 2
3 4 5
printsize
def printsize(self) -> None

This prints the size of the dense matrix.

Example
>>> B = indexmat(2,3)
>>> B.printsize()
Matrix size is 2x3

Class: integration

This is an internal container class for integration.

Class: iodata

This is an internal container class. iodata can be generated by some methods like computeradiationpattern() and written as field outputs with setoutputfieldiodata() or directly to a file with write().

Class: mat

def mat(
self
)
def mat(
self,
matsize: int,
rowaddresses: indexmat,
coladdresses: indexmat,
vals: densemat
)
def mat(
self,
myformulation: formulation,
rowaddresses: indexmat,
coladdresses: indexmat,
vals: densemat
)

The mat object holds a sparse algebriac square matrix. Before creating a mat object, ensure that a mesh object is available. If a mesh object is not already available, create an empty mesh object. If a mesh object is not available before creating a mat object, a RuntimeError is raised.

Examples

There are many ways of instantiating an indexmat object. There are listed below:

Example 1: mat(matsize:int, rowaddresses:indexmat, coladdresess:indexmat, vals:densemat) This creates a sparse matrix object of size matsize×\timesmatsize. The rowaddresses and coladdresess provide the location (row, col) of non-zero values in the sparse matrix. The non-zero values are provided in the dense matrix vals. Note that a mesh object must already be available before instantiating mat object.

>>> rows = indexmat(7,1, [0,0,1,1,1,2,2])
>>> cols = indexmat(7,1, [0,1,0,1,2,1,2])
>>> vals = densemat(7,1, [11,12,13,14,15,16,17])
>>>
>>> mymesh = mesh()
>>> A = mat(3, rows, cols, vals)
>>> A.print()
A block 3x3:
Mat Object: 1 MPI processes
type: seqaij
row 0: (0, 11.) (1, 12.)
row 1: (0, 13.) (1, 14.) (2, 15.)
row 2: (1, 16.) (2, 17.)

Example 2: mat(myformulation:formulation, rowaddresses:indexmat, coladdresses:indexmat, vals:densemat) This creates a sparse matrix object whose dof() structure is the one in the formulation projection. The rowaddresses and coladdresess provide the location (row, col) of non-zero values in the sparse matrix. The non-zero values are provided in the dense matrix vals.

>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>>
>>> addresses = indexmat(numberofrows=projection.countdofs(), numberofcolumns=1, init=0, step=1)
>>> vals = densemat(numberofrows=projection.countdofs(), numberofcolumns=1, init=12)
>>>
>>> A = mat(formulation=projection, rowaddresses=addresses, coladdresses=addresses, densemat=vals)

Methods

copy
def copy(self) -> mat

This creates a full copy of the matrix. Only the values are copied. (E.g: the mat.reusefactorization() is set back to the default no reuse.)

Example
>>> A =
>>> copiedmat = A.copy()
countcolumns
def countcolumns(self) -> int

This counts and returns the number of columns in the matrix.

Example
>>> numcols = A.countcolumns()
countnnz
def countnnz(self) -> int

This counts and returns the number of non-zero entries in the matrix Aa\boldsymbol{A_a} which is the sub-matrix of A\boldsymbol{A} with eliminated Dirichlet constraints. Refer mat.getainds().

If the requested information is not available, then -11 is returned.

Example
>>> numnnz = A.countnnz()
countrows
def countrows(self) -> int

This counts and returns the number of rows in the matrix.

Example
>>> numrows = A.countrows()
getainds
def getainds(self) -> indexmat

Let us call dinds the set of unknowns that have a Dirichlet constraint and ainds the remaining unknowns. The mat object A\boldsymbol{A} holds sub-matrices Aa\boldsymbol{A_a} and Ad\boldsymbol{A_d} such that

A=[AaAd01]\boldsymbol{A} = \begin{bmatrix} \boldsymbol{A_a} & \boldsymbol{A_d}\\ \boldsymbol{0} & \boldsymbol{1}\end{bmatrix}

where Aa\boldsymbol{A_a} is a square matrix equal to A\boldsymbol{A} with eliminated Dirichlet constraints. 0\boldsymbol{0} is an all zero matrix and 1\boldsymbol{1} is the square identity matrix of all Dirichlet constraints. Matrices Aa\boldsymbol{A_a} and Ad\boldsymbol{A_d} are stored with their local indexing. The methods mat.getainds() and mat.getdinds() gives the global indexing (i.e index in A\boldsymbol{A}) of each local index in Aa\boldsymbol{A_a} and Ad\boldsymbol{A_d}.

Example
>>> ainds = A.getainds()
See Also

mat.getdinds()

getdinds
def getdinds(self) -> indexmat

This outputs dinds.

Example
>>> dinds = A.getdinds()
See Also

mat.getainds()

print
def print(self) -> None

This prints the matrix size and values.

Example
>>> A.print()
reusefactorization
def reusefactorization(self) -> None

The matrix factorization will be reused in allsolve().

Class: mesh

def mesh(
self
)
def mesh(
self,
name: str,
verbosity: int = 1
)
def mesh(
self,
name: str,
globalgeometryskin: int,
numoverlaplayers: int,
verbosity: int = 1
)
def mesh(
self,
mergeduplicates: bool,
meshfiles: list[str],
verbosity: int = 1
)
def mesh(
self,
inputshapes: list[shape],
verbosity: int = 1
)
def mesh(
self,
inputshapes: list[shape],
globalgeometryskin: int,
numoverlaplayers: int,
verbosity: int = 1
)

The mesh object holds the finite element mesh of the geometry.

Examples

A mesh object based on a mesh file can be created through the native reader or via the GMSH API. To get more information on the physical regions of the mesh, the verbosity argument can be set to 22.

>>> # Creating a mesh object with the native reader:
>>> mymesh = mesh("disk.msh")
>>>
>>> # Creating a mesh object with GMSH API:
>>> mymesh = mesh("gmsh:disk.msh")

In the domain decomposition framework, creating a mesh object requires two additional arguments: globalgeometryskin and numoverlaplayers. Furthermore, the mesh is treated as a part of a global mesh. Each MPI rank owns only a part of the global mesh and all ranks must perform the call collectively. The argument globalgeometryskin is the part of the global mesh skin that belongs to the current rank. It can only hold elements of dimension one lower than the geometry dimension. The global mesh skin cannot intersect itself. The mesh parts are overlapped by the number of overlap layers requested. More than one overlap layer cannot be guaranteed everywhere as the overlapping is limited to the direct neighbouring domains. mesh(filename:str, globalgeometryskin:int, numoverlaplayers:int, verbosity:int=1)

In the above examples, the mesh objects were created based on a mesh file. Similarly, mesh objects can be created based on shape objects.

>>> # define physical regions
>>> faceregionnumber=1; lineregionnumber=2
>>>
>>> # define a quadrangle shape object
>>> quadface = shape("quadrangle", faceregionnumber, [0,0,0, 1,0,0, 1,1,0, 0,1,0], [10,6,10,6])
>>>
>>> # get the leftline from the contour of the quad shape object
>>> contourlines = quadface.getsons() # returns a list
>>> leftline = contourlines[3]
>>> leftline.setphysicalregion(lineregionnumber)
>>>
>>> # create mesh object based on the quadrangle shape and its left-side line
>>> mymesh = mesh([quadface, leftline])
>>> mymesh.write("quadmesh.msh")

Creating a mesh object from the shape object can be carried out also in the domain decomposition framework using the following syntax: mesh(inputshapes:List[shape], globalgeometryskin:int, numoverlaplayers:int, verbosity:int=1)

It is also possible to combine multiple meshes. Elements shared by the input meshes can either be merged or not by setting the bool value for argument mergeduplicates. For every input mesh, a new physical region containing all elements is created. Set verbosity equal to 2 to get information on physical regions in the mesh.

>>> mymesh = mesh("disk.msh")
>>> mymesh.shift(2,1,0) # all entities are shifted by x,y,z amount
>>> mymesh.write("shifted.msh")
>>>
>>> mergedmesh = mesh(True, ["disk.msh", "shifted.msh"])
>>> mergedmesh.write("merged.msh")
>>>
>>> mergedmesh = mesh("merged.msh", 2)

Methods

extrude
def extrude(
self,
newphysreg: int,
newbndphysreg: int,
bnd: int,
extrudelens: list[float]
) -> None

This extrudes the boundary region bnd. After the extrusion process, newphysreg will contain the extruded region and newbndphysreg will contain the extrusion end boundary. the extrudelens is a list specifying the size of each layer in the extrusion. The length of list determines the number of mesh layers in the extrusion. If -11 is given as the extrusion length for each layer, an optimal value is automatically calculated.

Example
>>> vol=1; sur=2; top=3; circle=4 # physical regions defined in disk.msh
>>> volextruded=5; bndextruded=6; # new physical regions that will be utilized in extrusion
>>> mymesh = mesh()
>>>
>>> # predefine extrusion
>>> mymesh.extrude(newphysreg = volextruded, newbndphysreg = bndextruded,
... bnd = sur, extrudelens = [0.1,0.05])
>>>
>>> # extrusion is performed when the mesh is loaded.
>>> mymesh.load("disk.msh")
>>> mymesh.write("diskpml.msh")
See Also

getextrusiondata()

getdimension
def getdimension(self) -> int

This returns the dimension of the highest dimension element in the mesh 0D, 1D, 2D or 3D.

Example
>>> mymesh = mesh("disk.msh")
>>> dim = mymesh.getdimension()
>>> dim
3
getdimensions
def getdimensions(self) -> list[float]

This returns the x, y and z mesh dimensions in meters.

Example
>>> mymesh = mesh("disk.msh")
>>> dims = mymesh.getdimensions()
>>> dims
[2.0, 2.0, 0.1]
getphysicalregionnumbers
def getphysicalregionnumbers(self, dim: int = -1) -> list[int]

This returns all physical region numbers of a given dimension. Use -11 or no argument to get the regions of all dimensions.

Example
>>> mymesh = mesh("disk.msh")
>>> allphysregs = mymesh.getphysicalregionnumbers()
[4, 2, 3, 1]
load
def load(
self,
name: str,
verbosity: int = 1
) -> None
def load(
self,
name: str,
globalgeometryskin: int,
numoverlaplayers: int,
verbosity: int = 1
) -> None
def load(
self,
mergeduplicates: bool,
meshfiles: list[str],
verbosity: int = 1
) -> None
def load(
self,
inputshapes: list[shape],
verbosity: int = 1
) -> None
def load(
self,
inputshapes: list[shape],
globalgeometryskin: int,
numoverlaplayers: int,
verbosity: int = 1
) -> None

This method allows an empty mesh object to be populated with mesh data. It takes in the same corresponding arguments as required in instantiating a mesh object directly. The only difference with direct instantiation is that this method requires that an empty mesh object is already created. If this method is called by a non-empty mesh object any existing mesh data are lost.

Examples
>>> # Create an empty mesh object
>>> mymesh = mesh()
>>>
>>> # Load a mesh file with the native reader:
>>> mymesh.load("disk.msh", 2)
>>>
>>> # Load a mesh file with GMSH API:
>>> mymesh = mesh("gmsh:disk.msh", 2)

Loading a mesh from the shape objects:

>>> # define physical regions
>>> faceregionnumber=1; lineregionnumber=2
>>>
>>> # define a quadrangle shape object
>>> quadface = shape("quadrangle", faceregionnumber, [0,0,0, 1,0,0, 1,1,0, 0,1,0], [10,6,10,6])
>>>
>>> # get the leftline from the contour of the quad shape object
>>> contourlines = quadface.getsons() # returns a list
>>> leftline = contourlines[3]
>>> leftline.setphysicalregion(lineregionnumber)
>>>
>>> # Load a mesh from the quadrangle shape and its left-side line
>>> mymesh = mesh() # creates an empty mesh object
>>> mymesh.load([quadface, leftline])
>>> mymesh.write("quadmesh.msh")

Combing multiple meshes with load method

>>> mymesh = mesh("disk.msh")
>>> mymesh.shift(2,1,0) # all entities are shifted by x,y,z amount
>>> mymesh.write("shifted.msh")
>>>
>>> mergedmesh = mesh()
>>> mergedmesh.load(True, ["disk.msh", "shifted.msh"])
>>> mergedmesh.write("merged.msh")
>>>
>>> mergedmesh = mesh("merged.msh", 2)
move
def move(
self,
physreg: int,
u: expression
) -> None
def move(self, u: expression) -> None

This moves the whole or part of the mesh object by the x, y and z components of expression u in the x, y and z direction.

Examples
>>> vol = 1
>>> mymesh = mesh("disk.msh")
>>> x=field("x"); y=field("y")
>>>
>>> # Move the whole mesh object
>>> mymesh.move(array3x1(0,0, sin(x*y)))
>>> mymesh.write("moved.msh")
>>>
>>> # Move only the mesh part on the physical region 'vol'
>>> mymesh.move(vol, array3x1(0,0, sin(x*y)))
>>> mymesh.write("moved.msh")
partition
def partition(self) -> None
def partition(
self,
groupsphysregs: list[list[int]],
groupsnumranks: list[int]
) -> None
def partition(
self,
newlayerregion: int,
newrestregion: int,
growthstart: list[int],
numlayers: int
) -> None
def partition(self, numxyzslices: list[int]) -> None

This requests a DDM partition of the mesh. This is an overloaded function and can be instantiated in several ways.

Example 1: partition()

This automatically partitions a mesh into parts equal to the number of nodes selected in allsolve.

>>> mymesh = mesh()
>>> mymesh.partition()
>>> mymesh.load("disk.msh")

Example 2: partition(groupsphysregs: List[List[int]], groupsnumranks: List[int])

Here the user has the flexibility to group physical regions and also specify the number of ranks into which the grouped physical region is partitioned. In the below example, the “top+middle” is grouped into one region and is stored in 11 rank. The “bottom” region is partitioned into 22 ranks. If a simulation is run on nn ranks, any remaining ungrouped regions will be paritioned into nn-11-22 ranks.

>>> top=1; middle=2; bottom=3; other=4;
>>> mymesh = mesh()
>>> mymesh.partition([[top, middle], [bottom]], [1, 2])

In the below example, the “top” region will be partitioned into 44 ranks. The “middle+bottom” region is grouped into one region and will be partitioned into 33 ranks. If a simulation is run on 15 ranks, any remaining ungrouped regions will be paritioned into 1515-44-33=88 ranks.

>>> top=1; middle=2; bottom=3; other=4;
>>> mymesh = mesh()
>>> mymesh.partition([[top], [middle,bottom]], [4, 3])
printdimensions
def printdimensions(self) -> list[float]

This prints and returns the x, y and z mesh dimensions.

Example
>>> mymesh = mesh("disk.msh")
>>> mymesh.printdimensions()
Mesh dimensions:
x: 2 m
y: 2 m
z: 0.1 m
[2.0, 2.0, 0.1]
rotate
def rotate(
self,
physreg: int,
x: float,
y: float,
z: float
) -> None
def rotate(
self,
x: float,
y: float,
z: float
) -> None

This rotates the whole or part of the mesh object first by ax degrees around x axis followed by ay degrees around the y-axis and then by az degrees around the z-axis.

Examples
>>> vol = 1
>>> mymesh = mesh("disk.msh")
>>>
>>> # rotate the whole mesh object
>>> mymesh.rotate(20,60,90)
>>> mymesh.write("rotated.msh")
>>>
>>> # rotate only the mesh part on the physical region 'vol'
>>> mymesh.rotate(vol, 20,60,90)
>>> mymesh.write("rotated.msh")
scale
def scale(
self,
physreg: int,
x: float,
y: float,
z: float
) -> None
def scale(
self,
x: float,
y: float,
z: float
) -> None

This scales the whole or part of the mesh object first by a factor x, y and z respectively in the x, y and z direction.

Examples
>>> vol = 1
>>> mymesh = mesh("disk.msh")
>>>
>>> # scale the whole mesh object
>>> mymesh.scale(0.1,0.2,1.0)
>>> mymesh.write("scaled.msh")
>>>
>>> # scale only the mesh part on the physical region 'vol'
>>> mymesh.scale(vol, 0.1,0.2,1.0)
>>> mymesh.write("scaled.msh")
selectanynode
def selectanynode(
self,
newphysreg: int,
physregtoselectfrom: int
) -> None
def selectanynode(self, newphysreg: int) -> None

This tells the mesh object to create a new physical region newphysreg that contains a single node arbitrarily chosen in the region physregtoselectfrom. If no region is selected (i.e. if physregtoexcludefrom is empty) or if the argument physregtoexcludefrom is not provided, then the arbitrary node is chosen considering the whole domain. The new region newphysreg is created when the mesh.load() method is called on the mesh object.

Examples

Example 1: mesh.selectanynode(newphysreg:int, physregtoselectfrom:int)

>>> vol=1; anynode=12
>>> mymesh = mesh()
>>> mymesh.selectanynode(anynode, vol]) # a node is chosen from 'vol' region
>>> mymesh.load("disk.msh")
>>> mymesh.write("out.msh")

Example 2: mesh.selectanynode(newphysreg:int)

>>> vol=1; anynode=12
>>> mymesh = mesh()
>>> mymesh.selectanynode(anynode) # a node is chosen from the whole domain
>>> mymesh.load("disk.msh")
>>> mymesh.write("out.msh")
selectbox
def selectbox(
self,
newphysreg: int,
physregtobox: int,
selecteddim: int,
boxlimit: list[float]
) -> None
def selectbox(
self,
newphysreg: int,
selecteddim: int,
boxlimit: list[float]
) -> None

This tells the mesh object to create a new physical region newphysreg that contains elements of the region physregtoselectfrom that are in the box delimited by [x1x_1,x2x_2, y1y_1,y2y_2, z1z_1,z2z_2] given in boxlimit. If no region is selected (i.e. if physregtoselectfrom is empty) or if the argument physregtoselectfrom is not provided, then the box region is created considering the whole domain.

The new region newphysreg is created when the mesh.load() method is called on the mesh object. The elements populated in the new region newphysreg are of dimension selecteddim.

Examples

Example 1: mesh.selectbox(newphysreg:int, physregtobox:int, selecteddim:int, boxlimit:List[double])

>>> vol=1; boxregion=12
>>> mymesh = mesh()
>>> mymesh.selectbox(boxregion, vol, 3, [0,1,0, 1,0,0.1]) # select box region from the 'vol' region
>>> mymesh.load("disk.msh")
>>>
>>> v=field("h1"); x=field("x"); y=field("y"); z=field("z")
>>> v.setorder(vol, 1)
>>> v.setvalue(vol, x*y*z)
>>> v.write(vol, "v.vtk", 1)
>>> v.write(boxregion, "vboxregion.vtk", 1)

Example 2: mesh.selectbox(newphysreg:int, selecteddim:int, boxlimit:List[double])

>>> vol=1; boxregion=12
>>> mymesh = mesh()
>>> mymesh.selectbox(boxregion, 3, [0,1,0, 1,0,0.1]) # select box region from the whole domain
>>> mymesh.load("disk.msh")
>>>
>>> v=field("h1"); x=field("x"); y=field("y"); z=field("z")
>>> v.setorder(vol, 1)
>>> v.setvalue(vol, x*y*z)
>>> v.write(vol, "v.vtk", 1)
>>> v.write(boxregion, "vboxregion.vtk", 1)
selectexclusion
def selectexclusion(
self,
newphysreg: int,
physregtoexcludefrom: int,
physregstoexclude: list[int]
) -> None
def selectexclusion(
self,
newphysreg: int,
physregstoexclude: list[int]
) -> None

This tells the mesh object to create a new physical region newphysreg that contains the elements of the region physregtoexcludefrom that are not in physregtoexclude. If no region is selected (i.e. if physregtoexcludefrom is empty) or if the argument physregtoexcludefrom is not provided, then the new region is created considering the whole domain. The new region newphysreg is created when the mesh.load() method is called on the mesh object.

Examples

Example 1: mesh.selectexclusion(newphysreg

, physregtoexlcudefrom
, physregtoexclude
)`

>>> vol=1; sur=2; top=3; box=11; excluded=12
>>> mymesh = mesh()
>>> mymesh.selectbox(box, vol, 3, [0,2, -2,2, -2,2])
>>> mymesh.selectexclusion(excluded, vol, [box]) # physregtoexcludefrom = 'vol'
>>> mymesh.load("disk.msh")
>>> mymesh.write("out.msh")

Example 2: mesh.selectexclusion(newphysreg

, physregtoexclude
)`

>>> vol=1; sur=2; top=3; box=11; excluded=12
>>> mymesh = mesh()
>>> mymesh.selectbox(box, vol, 3, [0,2, -2,2, -2,2])
>>> mymesh.selectexclusion(excluded, [box]) # physregtoexcludefrom = whole domain
>>> mymesh.load("disk.msh")
>>> mymesh.write("out.msh")
selectlayer
def selectlayer(
self,
newphysreg: int,
physregtoselectfrom: int,
physregtostartgrowth: int,
numlayers: int
) -> None
def selectlayer(
self,
newphysreg: int,
physregtostartgrowth: int,
numlayers: int
) -> None

This tells the mesh object to create a new physical region newphysreg that contains the layer of elements of the region physregtoselectfrom that touches the region physregtostartgrowth. If no region is selected (i.e. if physregtoselectfrom is empty) or if the argument physregtoselectfrom is not provided, then the layer region is created considering the whole domain. When multiple layers are requested through the argument numlayers, they are grown on top of each other. The new region newphysreg is created when the mesh.load() method is called on the mesh object.

Examples

Example 1: mesh.selectlayer(newphysreg:int, physregtoselectfrom:int, physregtostartgrowth:int, numlayers:int)

>>> vol=1; sur=2; top=3; layerregion=12
>>> mymesh = mesh()
>>> mymesh.selectlayer(layerregion, vol, sur, 1) # select layer region from the 'vol' region
>>> mymesh.load("disk.msh")
>>> mymesh.write("out.msh")

Example 2: mesh.selectlayer(newphysreg:int, physregtostartgrowth:int, numlayers:int)

>>> vol=1; sur=2; top=3; layerregion=12
>>> mymesh = mesh()
>>> mymesh.selectlayer(layerregion, sur, 1) # select layer region from the whole domain
>>> mymesh.load("disk.msh")
>>> mymesh.write("out.msh")
selectskin
def selectskin(
self,
newphysreg: int,
physregtoskin: int
) -> None
def selectskin(self, newphysreg: int) -> None

This tells the mesh object to create a new physical region newphysreg that contains elements that form the skin of the selected physical regions. If no region is selected (i.e. if physregtoselectfrom is empty) or if the argument physregtoselectfrom is not provided, then the skin region is created considering the whole domain.

The skin region newphysreg is created when the mesh.load() method is called on the mesh object. The dimension of the skin region is always one dimension less than that of the physical regions selected. Note that space derivatives or ‘hcurl’ field evaluations on a surface do not usually lead to the same values as a volume evaluation.

Examples

Example 1: mesh.selectskin(newphysreg:int, physregtoskin)

>>> vol=1; skin=12
>>> mymesh = mesh()
>>> mymesh.selectskin(skin, vol) # select skin region from the 'vol' region
>>>
>>> mymesh.load("disk.msh")
>>> v=field("h1"); x=field("x"); y=field("y"); z=field("z")
>>> v.setorder(vol, 1)
>>> v.setvalue(vol, x*y*z)
>>> v.write(vol, "v.vtk", 1)
>>> v.write(skin, "vskin.vtk", 1)

Example 2: mesh.selectskin(newphysreg:int)

>>> vol=1; skin=12
>>> mymesh = mesh()
>>> mymesh.selectskin(skin) # select skin region from the whole domain
>>>
>>> mymesh.load("disk.msh")
>>> v=field("h1"); x=field("x"); y=field("y"); z=field("z")
>>> v.setorder(vol, 1)
>>> v.setvalue(vol, x*y*z)
>>> v.write(vol, "v.vtk", 1)
>>> v.write(skin, "vskin.vtk", 1)s
selectsphere
def selectsphere(
self,
newphysreg: int,
physregtosphere: int,
selecteddim: int,
centercoords: list[float],
radius: float
) -> None
def selectsphere(
self,
newphysreg: int,
selecteddim: int,
centercoords: list[float],
radius: float
) -> None

This tells the mesh object to create a new physical region newphysreg that contains elements of the region physregtoselectfrom that are in the sphere of prescribed radius and of center [xcx_c, ycy_c,zcz_c] as given in centercoords. If no region is selected (i.e. if physregtoselectfrom is empty) or if the argument physregtoselectfrom is not provided, then the sphere region is created considering the whole domain.

The new region newphysreg is created when the mesh.load() method is called on the mesh object. The elements populated in the new region newphysreg are of dimension selecteddim.

Examples

Example 1: mesh.selectsphere(newphysreg:int, physregtosphere:int, selecteddim:int, centercoords:List[double], radius:double)

>>> vol=1; sphereregion=12
>>> mymesh = mesh()
>>> mymesh.selectsphere(sphereregion, vol, 3, [1,0,0], 1) # select sphere region from the 'vol' region
>>> mymesh.load("disk.msh")
>>>
>>> v=field("h1"); x=field("x"); y=field("y"); z=field("z")
>>> v.setorder(vol, 1)
>>> v.setvalue(vol, x*y*z)
>>> v.write(vol, "v.vtk", 1)
>>> v.write(sphereregion, "vsphereregion.vtk", 1)

Example 2: mesh.selectsphere(newphysreg:int, selecteddim:int, centercoords:List[double], radius:double)

>>> vol=1; sphereregion=12
>>> mymesh = mesh()
>>> ymesh.selectsphere(sphereregion, 3, [1,0,0], 1) # select sphere region from the whole domain
>>> mymesh.load("disk.msh")
>>>
>>> v=field("h1"); x=field("x"); y=field("y"); z=field("z")
>>> v.setorder(vol, 1)
>>> v.setvalue(vol, x*y*z)
>>> v.write(vol, "v.vtk", 1)
>>> v.write(sphereregion, "vsphereregion.vtk", 1)
setadaptivity
def setadaptivity(
self,
criterion: expression,
lownumsplits: int,
highnumsplits: int
) -> None

Each element in the mesh will be adapted (refined/coarsened) based on the value of a positive criterion (h-adaptivity). The max range of the criterion is split into a number of intervals equal to the number of refinement levels in the range lownumsplits and highnumsplits. All intervals have the same size. The barycenter value of the criterion on each element is considered to select the interval, and therefore the corresponding refinement of each mesh element. As an example, for a criterion with the highest value of 900 over the entire domain and a low/high refinement level requested of 1/3 the refinement on mesh elements with criterion value in the range 0 to 300, 300 to 600, 600 to 900 will be 1, 2, 3 levels respectively.

Example
>>> all = 1
>>> q = shape("quadrangle", all, [0,0,0, 1,0,0, 1.2,1,0, 0,1,0], [5,5,5,5])
>>> mymesh = mesh([q])
>>> x = field("x"); y = field("y")
>>> criterion = 1 + sin(10*x)*sin(10*y)
>>>
>>> mymesh.setadaptivity(criterion, 0, 5)
>>>
>>> for i in range(5):
... criterion.write(all, f"criterion_{100+i}.vtk", 1)
... adapt(1)
setcohomologycuts
def setcohomologycuts(self, cutregs: list[int]) -> None
def setcohomologycuts(
self,
cutregs: list[int],
closedloopregs: list[list[int]],
startnoderegs: list[int],
startdirections: list[list[float]]
) -> None
def setcohomologycuts(
self,
physregstocut: list[int],
subdomains: list[int],
closedloopregs: list[list[int]],
startnoderegs: list[int],
startdirections: list[list[float]]
) -> None

This makes the mesh object aware of the cohomology cut regions.

Example
>>> mymesh = mesh()
>>> mymesh.setcohomologycuts([chreg1, chreg2])
>>> mymesh.load("disk.msh")
setphysicalregions
def setphysicalregions(
self,
dims: list[int],
nums: list[int],
geometryentities: list[list[int]]
) -> None
shift
def shift(
self,
physreg: int,
x: float,
y: float,
z: float
) -> None
def shift(
self,
x: float,
y: float,
z: float
) -> None

This translates the whole or part of the mesh object by x, y and z amount in the x, y and z direction.

Examples
>>> vol = 1
>>> mymesh = mesh("disk.msh")
>>> x=field("x"); y=field("y")
>>>
>>> # shift/translate the whole mesh object
>>> mymesh.shift(1.0, 2.0, 3.0)
>>> mymesh.write("shifted.msh")
>>>
>>> # shift/translate only the mesh part on physical region 'vol'
>>> mymesh.shift(vol, 1.0, 2.0, 3.0)
>>> mymesh.write("shifted.msh")
split
def split(self, n: int = 1) -> None

This splits each element in the mesh n times. Element quality is maximized and element curvature is taken into account. Each element is split recursively n times as follows:

  • point \rightarrow 1 point
  • line \rightarrow 2 lines
  • triangle \rightarrow 4 triangles
  • quadrangle \rightarrow 4 quadrangles
  • tetrahedron \rightarrow 8 tetrahedra
  • hexahedron \rightarrow 8 hexahedra
  • prism \rightarrow 8 prisms
  • pyramid \rightarrow 6 pyramids + 4 tetrahedra
Example
>>> mymesh = mesh()
>>> mymesh.split()
>>> mymesh.load("disk.msh")
>>> mymesh.write("splitdisk.msh")
use
def use(self) -> None

This allows one to select which mesh to use in case multiple meshes are available. This call invalidates all objects that are based on the previously selected mesh for as long as the latter is not selected again.

Example
>>> finemesh = mesh()
>>> finemesh.split(2)
>>> finemesh.load("disk.msh")
>>> coarsemesh = mesh("disk.msh")
>>> finemesh.use()
write
def write(
self,
physreg: int,
name: str
) -> None
def write(
self,
name: str,
physregs: list[int] = [-1],
option: int = 1
) -> None

This writes the mesh object to a given input filename.

Examples
>>> # mesh data
>>> mymesh = mesh("disk.msh")
>>> vol=1; top=2; sur=3

If a physical region is passed in the first argument, then only part of the mesh object included in that physical region is written:

>>> mymesh.write(vol, "out.msh")

If only the file name is provided as an argument, then all the physical regions of the mesh are written.

>>> mymesh.write("out.msh)
>>> # or equivalently:
>>> mymesh.write("out1.msh", physregs=[-1], option=1)

The argument physregs is the list of physical regions that will be written if the argument option=1. If option=-1 then all the physical regions except the ones in the list physregs will be written. The default value for physregs=-1 which is equivalent to considering all the physical regions (the option argument is ignored when physregs=-1).

>>> mymesh.write("out2.msh", [-1], 1) # all physical regions are written
>>> mymesh.write("out3.msh", [-1], -1) # all physical region will be written, ignores 'option' argument
>>> mymesh.write("out4.msh", [1,2], 1) # physical regions 1 and 2 will be written
>>> mymesh.write("out5.msh", [1,2], -1) # all physical regions except 1 and 2 will be written

Class: parameter

def parameter(
self
)
def parameter(
self,
numrows: int,
numcols: int
)

The parameter object can hold different expression objects on different geometric regions.

Examples

A parameter object can be a scalar. The following creates an empty object.

>>> mymesh = mesh("disk.msh")
>>> E = parameter()

A parameter object can also be a 2D array. The following creates an empty object.

>>> E = parameter(3,3) # a 3x3 parameter matrix

Methods

addvalue
def addvalue(
self,
physreg: int,
input: expression
) -> None

This adds the ìnput expression to the parameter’s existing value on the physical region physreg.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> E = parameter()
>>> E.setvalue(vol, 150e9)
>>> E.addvalue(vol, 50e9) # now E holds a value equal to 150e9 + 50e9 = 200e9
See Also

parameter.setvalue()

allintegrate
def allintegrate(
self,
physreg: int,
integrationorder: int
) -> float
def allintegrate(
self,
physreg: int,
meshdeform: expression,
integrationorder: int
) -> float

This is a collective MPI operation and hence must be called by all ranks. This integrates a parameter over a physical region across all the DDM ranks.

Example 1: allintegrate(physreg:int, integrationorder:int)

The integration is performed over the physical region physreg. The integration is exact up to the order of polynomials specified in the argument integrationorder

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> p = parameter()
>>> p.setvalue(vol, 12)
>>> integralvalue = p.allintegrate(vol, 4)

Example 2: allintegrate(physreg:int, meshdeform:expression, integrationorder:int)

Here, the integration is performed on the deformed mesh configuration meshdeform.

>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> p = parameter()
>>> p.setvalue(vol, 12)
>>> integralvalueondeformedmesh = p.allintegrate(vol, u, 4)
See Also

parameter.integrate()

allinterpolate
def allinterpolate(
self,
physreg: int,
xyzcoord: list[float]
) -> list[float]
def allinterpolate(
self,
physreg: int,
meshdeform: expression,
xyzcoord: list[float]
) -> list[float]

This is a collective MPI operation and hence must be called by all ranks. Its functionality is as described in parameter.interpolate() but considers the physical region partitioned across the DDM ranks. The argument xyzcoord must be the same for all ranks.

Example 1: allinterpolate(physreg:int, xyzcoord:List[double])

This interpolates the parameter at a single point whose [x,y,z] coordinate is provided as an argument. The flattened interpolated parameter values are returned if the point was found in the elements of the physical region physreg. If not found an empty list is returned.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> x=field("x"); y=field("y"); z=field("z")
>>> p = parameter()
>>> p.setvalue(vol, array3x1(x,y,z))
>>> interpolated = p.allinterpolate(vol, [0.5,0.6,0.05])
>>> interpolated
[0.5, 0.6, 0.05]

Example 2: allinterpolate(physreg:int, meshdeform:expression, xyzcoord:List[double])

A parameter can also be interpolated on a deformed mesh by passing its corresponding field.

>>> ...
>>> # interpolation on the mesh deformed by field 'u'
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> interpolated = p.allinterpolate(vol, u, xyzcoord)
See Also

parameter.interpolate()

allmax
def allmax(
self,
physreg: int,
refinement: int,
xyzrange: list[float] = []
) -> list[float]
def allmax(
self,
physreg: int,
meshdeform: expression,
refinement: int,
xyzrange: list[float] = []
) -> list[float]

This is a collective MPI operation and hence must be called by all ranks. This returns a list with its first element containing the maximum value of a parameter computed across all the DDM ranks over a geometric region. The remaining elements of the list provide the coordinates at which the maximum value was found. This is an overloaded method.

Example 1: allmax(physreg:int, refinement:int, xyzrange:List[double]=[])

The maximum value is obtained over the geometric region physreg by splitting all elements refinement times in each direction. Increasing the refinement will thus lead to a more accurate maximum value, but at an increased computational cost. The maximum value is exact when the refinement nodes added to the elements correspond to the position of maximum. For a first-order nodal shape function interpolation, on a mesh that is not curved, the maximum is always exact to machine precision. The default value of xyzrange is an empty list.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> x = field("x")
>>> p = parameter()
>>> p.setvalue(vol, 2*x)
>>> maxdata = p.allmax(vol, 1)
>>> maxdata[0]
2.0

The search of the maximum value can be restricted to a box delimited by the last argument xyzrange whose form is [xboxmin,xboxmax, yboxmin, yboxmin, zboxmax, zboxmin]. The output returned is a list of the form [maxvalue, xcoordmax, ycoordmax, zcoordmax] or an empty list if the physical region argument is empty or is not in the box provided. If the argument defining the box is not provided, then the whole geometric region is considered for evaluating the maximum value.

>>> ...
>>> maxdatainbox = p.allmax(vol, 5, [-2,0, -2,2, -2,2])

Example 2: allmax(physreg:int, meshdeform:expression, refinement:int, xyzrange:List[double]=[])

The maximum value can also be evaluated on the geometry deformed by a field (possibly a curved mesh). The location and the delimiting box are on the undeformed mesh.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> p = parameter()
>>> p.setvalue(vol, 2*x)
>>> maxdataondeformedmesh = p.allmax(vol, u, 1)
See Also

parameter.allmin(), parameter.min(), parameter.max()

allmin
def allmin(
self,
physreg: int,
refinement: int,
xyzrange: list[float] = []
) -> list[float]
def allmin(
self,
physreg: int,
meshdeform: expression,
refinement: int,
xyzrange: list[float] = []
) -> list[float]

This is a collective MPI operation and hence must be called by all ranks. This returns a list with its first element containing the minimum value of a parameter computed across all the DDM ranks over a geometric region. The remaining elements of the list provide the coordinates at which the minimum value was found. This is an overloaded method.

Example 1: allmin(physreg:int, refinement:int, xyzrange:List[double]=[])

The minimum value is obtained over the geometric region physreg by splitting all elements refinement times in each direction. Increasing the refinement will thus lead to a more accurate minimum value, but at an increased computational cost. The minimum value is exact when the refinement nodes added to the elements correspond to the position of minimum. For a first-order nodal shape function interpolation, on a mesh that is not curved, the minimum is always exact to machine precision. The default value of xyzrange is an empty list.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> x = field("x")
>>> p = parameter()
>>> p.setvalue(vol, 2*x)
>>> mindata = p.allmin(vol, 1)
>>> mindata[0]
2.0

The search of the minimum value can be restricted to a box delimited by the last argument xyzrange whose form is [xboxmin,xboxmax, yboxmin, yboxmin, zboxmax, zboxmin]. The output returned is a list of the form [minvalue, xcoordmin, ycoordmin, zcoordmin] or an empty list if the physical region argument is empty or is not in the box provided. If the argument defining the box is not provided, then the whole geometric region is considered for evaluating the minimum value.

>>> ...
>>> mindatainbox = p.allmin(vol, 5, [-2,0, -2,2, -2,2])

Example 2: allmin(physreg:int, meshdeform:expression, refinement:int, xyzrange:List[double]=[])

The minimum value can also be evaluated on the geometry deformed by a field (possibly a curved mesh). The minimum location and the delimiting box are on the undeformed mesh.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> p = parameter()
>>> p.setvalue(vol, 2*x)
>>> mindataondeformedmesh = p.allmin(vol, u, 1)
See Also

parameter.allmax(), parameter.max(), parameter.min()

atbarycenter
def atbarycenter(
self,
physreg: int,
onefield: field
) -> vec

This outputs a vec object whose structure is based on the field argument onefield and which contains the parameter evaluated at the barycenter of each reference element of physical region physreg. The barycenter of the reference element might not be identical to the barycenter of the actual element in the mesh (for curved elements, for general quadrangles, hexahedra and prisms). The evaluation at barycenter is constant on each mesh element.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> x = field("x"); f = field("one")
>>>
>>> # Evaluating the parameter
>>> p = parameter()
>>> p.setvalue(vol, 12*x)
>>> p.write(vol, "parameter.vtk", 1)
>>>
>>>> # Evaluating the same parameter at barycenter
>>> myvec = p.atbarycenter(vol, f)
>>> f.setdata(vol, myvec)
>>> f.write(vol, "barycentervalues.vtk", 1)
countcolumns
def countcolumns(self) -> int

This returns the number of columns in the parameter.

Example
>>> mymesh = mesh("disk.msh")
>>> E = parameter(2,3)
>>> E.countcolumns()
3
countrows
def countrows(self) -> int

This returns the number of rows in the parameter.

Example
>>> mymesh = mesh("disk.msh")
>>> E = parameter(2,3)
>>> E.countrows()
2
integrate
def integrate(
self,
physreg: int,
integrationorder: int
) -> float
def integrate(
self,
physreg: int,
meshdeform: expression,
integrationorder: int
) -> float

This integrates a parameter over a physical region.

Example 1: allintegrate(physreg:int, integrationorder:int)

The integration is performed over the physical region physreg. The integration is exact up to the order of polynomials specified in the argument integrationorder

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> p = parameter()
>>> p.setvalue(vol, 12)
>>> integralvalue = p.allintegrate(vol, 4)

Example 2: allintegrate(physreg:int, meshdeform:expression, integrationorder:int)

Here, the integration is performed on the deformed mesh configuration meshdeform.

>>> # integration on the mesh deformed by field 'u'
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> p = parameter()
>>> p.setvalue(vol, 12)
>>> integralvalueondeformedmesh = p.allintegrate(vol, u, 4)
See Also

parameter.allintegrate()

interpolate
def interpolate(
self,
physreg: int,
xyzcoord: list[float],
interpolated: list[float],
isfound: list[bool]
) -> None
def interpolate(
self,
physreg: int,
meshdeform: expression,
xyzcoord: list[float],
interpolated: list[float],
isfound: list[bool]
) -> None
def interpolate(
self,
physreg: int,
xyzcoord: list[float]
) -> list[float]
def interpolate(
self,
physreg: int,
meshdeform: expression,
xyzcoord: list[float]
) -> list[float]

This interpolates the parameter at a single point whose [x,y,z] coordinate is provided as an argument. The flattened interpolated parameter values are returned if the point was found in the elements of the physical region physreg. If not found an empty list is returned.

Example 1: interpolate(physreg:int, xyzcoord:List[double])

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> x=field("x"); y=field("y"); z=field("z")
>>> p = parameter()
>>> p.setvalue(vol, array3x1(x,y,z))
>>> interpolated = p.interpolate(vol, [0.5,0.6,0.05])
>>> interpolated
[0.5, 0.6, 0.05]

Example 2: interpolate(physreg:int, meshdeform:expression, xyzcoord:List[double])

A parameter can also be interpolated on a deformed mesh by passing its corresponding field.

>>> ...
>>> # interpolation on the mesh deformed by field 'u'
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> interpolated = p.interpolate(vol, u, xyzcoord)
See Also

parameter.allinterpolate()

max
def max(
self,
physreg: int,
refinement: int,
xyzrange: list[float] = []
) -> list[float]
def max(
self,
physreg: int,
meshdeform: expression,
refinement: int,
xyzrange: list[float] = []
) -> list[float]

This returns a list with its first element containing the maximum value of a parameter computed over a geometric region. The remaining elements of the list provide the coordinates at which the maximum value was found. This is an overloaded method.

Example 1: max(physreg:int, refinement:int, xyzrange:List[double]=[])

The maximum value is obtained over the geometric region physreg by splitting all elements refinement times in each direction. Increasing the refinement will thus lead to a more accurate maximum value, but at an increased computational cost. The maximum value is exact when the refinement nodes added to the elements correspond to the position of maximum. For a first-order nodal shape function interpolation, on a mesh that is not curved, the maximum is always exact to machine precision. The default value of xyzrange is an empty list.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> x = field("x")
>>> p = parameter()
>>> p.setvalue(vol, 2*x)
>>> maxdata = p.max(vol, 1)
>>> maxdata[0]
2.0

The search of the maximum value can be restricted to a box delimited by the last argument xyzrange whose form is [xboxmin,xboxmax, yboxmin, yboxmin, zboxmax, zboxmin]. The output returned is a list of the form [maxvalue, xcoordmax, ycoordmax, zcoordmax] or an empty list if the physical region argument is empty or is not in the box provided. If the argument defining the box is not provided, then the whole geometric region is considered for evaluating the maximum value.

>>> ...
>>> maxdatainbox = p.max(vol, 5, [-2,0, -2,2, -2,2])

Example 2: max(physreg:int, meshdeform:expression, refinement:int, xyzrange:List[double]=[])

The maximum value can also be evaluated on the geometry deformed by a field (possibly a curved mesh). The location and the delimiting box are on the undeformed mesh.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> p = parameter()
>>> p.setvalue(vol, 2*x)
>>> maxdataondeformedmesh = p.max(vol, u, 1)
See Also

parameter.min(), parameter.allmax(), parameter.allmin()

min
def min(
self,
physreg: int,
refinement: int,
xyzrange: list[float] = []
) -> list[float]
def min(
self,
physreg: int,
meshdeform: expression,
refinement: int,
xyzrange: list[float] = []
) -> list[float]

This returns a list with its first element containing the minimum value of a parameter computed over a geometric region. The remaining elements of the list provide the coordinates at which the minimum value was found. This is an overloaded method.

Example 1: min(physreg:int, refinement:int, xyzrange:List[double]=[])

The minimum value is obtained over the geometric region physreg by splitting all elements refinement times in each direction. Increasing the refinement will thus lead to a more accurate minimum value, but at an increased computational cost. The minimum value is exact when the refinement nodes added to the elements correspond to the position of minimum. For a first-order nodal shape function interpolation, on a mesh that is not curved, the minimum is always exact to machine precision. The default value of xyzrange is an empty list.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> x = field("x")
>>> p = parameter()
>>> p.setvalue(vol, 2*x)
>>> mindata = p.min(vol, 1)
>>> mindata[0]
2.0

The search of the minimum value can be restricted to a box delimited by the last argument xyzrange whose form is [xboxmin,xboxmax, yboxmin, yboxmin, zboxmax, zboxmin]. The output returned is a list of the form [minvalue, xcoordmin, ycoordmin, zcoordmin] or an empty list if the physical region argument is empty or is not in the box provided. If the argument defining the box is not provided, then the whole geometric region is considered for evaluating the minimum value.

>>> ...
>>> mindatainbox = p.min(vol, 5, [-2,0, -2,2, -2,2])

Example 2: min(physreg:int, meshdeform:expression, refinement:int, xyzrange:List[double]=[])

The minimum value can also be evaluated on the geometry deformed by a field (possibly a curved mesh). The location and the delimiting box are on the undeformed mesh.

>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> u = field("h1xyz")
>>> u.setorder(vol, 1)
>>> p = parameter()
>>> p.setvalue(vol, 2*x)
>>> mindataondeformedmesh = p.min(vol, u, 1)
See Also

parameter.max(), parameter.allmax(), parameter.allmin()

print
def print(self) -> None

This prints the information on the parameter to the console.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> E = parameter()
>>> E.setvalue(vol, 150e9)
>>> E.print()
setvalue
def setvalue(
self,
physreg: int,
input: expression
) -> None

This sets the ìnput expression to the parameter on the physical region physreg.

Example
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> E = parameter()
>>> E.setvalue(vol, 150e9)
See Also

parameter.addvalue()

write
def write(
self,
physreg: int,
numfftharms: int,
filename: str,
lagrangeorder: int
) -> None
def write(
self,
physreg: int,
numfftharms: int,
meshdeform: expression,
filename: str,
lagrangeorder: int
) -> None
def write(
self,
physreg: int,
filename: str,
lagrangeorder: int,
numtimesteps: int = -1
) -> None
def write(
self,
physreg: int,
meshdeform: expression,
filename: str,
lagrangeorder: int,
numtimesteps: int = -1
) -> None

This evaluates a parameter in the physical region physreg and writes it to the file filename. The lagrangeorder is the order of interpolation for the evaluation of the parameter.

Examples
>>> # setup
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> u = field("h1xyz")
>>> v = field("h1", [1,2,3])
>>> u.setorder(vol, 1)
>>> v.setorder(vol, 1)
>>>
>>> # interpolation order for writing a parameter
>>> p = parameter()
>>> p.setvalue(1e8*u)
>>> p.write(vol, "uorder1.vtk", 1) # interpolation order is 1
>>> p.write(vol, "uorder3.vtk", 3) # interpolation order is 3

Class: port

def port(
self
)
def port(
self,
harmonicnumbers: list[int]
)

The port object represents a scalar lumped quantity.

Examples

A port object with an initial zero value is created as:

>>> V = port()

A multi-harmonic port object with an initial zero value can be created by passing a list of harmonic numbers. Refer to the multi-harmonic field constructor for the meaning of the harmonic numbers.

>>> V = port([2,3])

Methods

cos
def cos(self, freqindex: int) -> port

This gets a port that is the sinsin harmonic at freqindex times the fundamental frequency in port V.

Example
>>> V = port([1,2,3,4,5])
>>> Vs = V.cos(0)
>>> Vs.getharmonics()
1
See Also

port.sin()

getharmonics
def getharmonics(self) -> list[int]

This returns the list of harmonics of the port object.

Example
>>> V = port([1,2,3])
>>> harms = V.getharmonics()
>>> harms
[1, 2, 3]
getname
def getname(self) -> str

This gets the name of the port object.

Example
>>> V = port()
>>> V.setname("LumpedMass")
>>> V.getname()
'LumpedMass'
See Also

port.setname()

getvalue
def getvalue(self) -> float

This returns the value of the port object.

Example
>>> V = port()
>>> V.setvalue(-1.2)
>>> val = V.getvalue()
>>> val
-1.2
See Also

port.setvalue()

harmonic
def harmonic(self, harmonicnumber: int) -> port
def harmonic(self, harmonicnumbers: list[int]) -> port

This returns a port that is the harmonic/list of harmonics of the port object.

Example
>>> V = port([1,2,3])
>>> V23 = V.harmonic([2,3])
>>> V23.getharmonics()
[2, 3]
print
def print(self) -> None

This prints the information of the port object.

Example
>>> V = port([2,3]) # create a multi-harmonic port object
>>> V.harmonic(2).setvalue(1) # set the value of 2nd harmonic
>>> V.harmonic(3).setvalue(0.5) # set the value of 3rd harmonic
>>> V.print()
Port harmonic 2 has value 1
Port harmonic 3 has value 0.5
setname
def setname(self, name: str) -> None

This sets the name for the port object.

Example
>>> V = port()
>>> V.setname("LumpedMass")
>>> V.print()
Port LumpedMass has value 0
See Also

port.getname()

setvalue
def setvalue(self, portval: float) -> None

This sets the value of the port object.

Examples
>>> V = port()
>>> V.setvalue(10.0)
>>> V.print()
Port has value 10

To set the value of a multi-harmonic port:

>>> V = port([2,3]) # create a multi-harmonic port object
>>> V.harmonic(2).setvalue(1) # set the value of 2nd harmonic
>>> V.harmonic(3).setvalue(0.5) # set the value of 3rd harmonic
>>> V.print()
Port harmonic 2 has value 1
Port harmonic 3 has value 0.5
See Also

port.harmonic(), port.getvalue()

sin
def sin(self, freqindex: int) -> port

This gets a port that is the sinsin harmonic at freqindex times the fundamental frequency in port V.

Example
>>> V = port([1,2,3,4,5])
>>> Vs = V.sin(2)
>>> Vs.getharmonics()
4
See Also

port.cos()

Class: shape

def shape(
self
)
def shape(
self,
shapename: str,
physreg: int,
coords: list[float]
)
def shape(
self,
shapename: str,
physreg: int,
coords: list[float],
nummeshpts: int
)
def shape(
self,
shapename: str,
physreg: int,
coords: list[float],
nummeshpts: list[int]
)
def shape(
self,
shapename: str,
physreg: int,
subshapes: list[shape],
nummeshpts: int
)
def shape(
self,
shapename: str,
physreg: int,
subshapes: list[shape],
nummeshpts: list[int]
)
def shape(
self,
shapename: str,
physreg: int,
subshapes: list[shape]
)
def shape(
self,
shapename: str,
physreg: int,
centercoords: list[float],
radius: float,
nummeshpts: int
)
def shape(
self,
shapename: str,
physreg: int,
centerpoint: shape,
radius: float,
nummeshpts: int
)

The shape objects are meshed geometric entities. The mesh created based on shapes can be written in .msh format at any time for visualization in GMSH. It might be needed to change the color and visibility options in the menu Tools > Options > Mesh of GMSH.

Examples

Depending on the number and type of arguments, different shape objects can be created for different purposes.

  • Creates a shape with the coordinates of all nodes provided as input:
    • Example 2: for points and lines: myshape = shape(shapename:str, physreg:int, coords:List[double])
  • Creates a shape based on the coordinates of the corner nodes in the shape
    • Example 3: for lines and arcs: myshape = shape(shapename:str, physreg:int, coords:List[double], nummeshpts:int)
    • Example 4: for triangles and quadrangles: myshape = shape(shapename:str, physreg:int, coords:List[double], nummeshpts:List[int])
  • Creates a shape based on sub-shapes provided.
    • Example 5: for lines and arcs: myshape = shape(shapename:str, physreg:int, subshapes:List[shape], nummeshpts:int)
    • Example 6: for straight-edged triangles and quadrangles: myshape = shape(shapename:str, physreg:int, subshapes:List[shape], nummeshpts:List[int])
    • Example 7: for curved triangles and quadrangles. Also, union of several shapes: myshape = shape(shapename:str, physreg:int, subshapes:List[shape])
  • Creates a disk shape.
    • Example 8: myshape = shape(shapename:str, physreg:int, centercoords:List[double], radius:double, nummeshpts:int)
    • Example 9: myshape = shape(shapename:str, physreg:int, centerpoint:shape, radius:double, nummeshpts:int)

Example 1: Creating an empty shape object

>>> myshape = shape()

Example 2: myshape = shape(shapename:str, physreg:int, coords:List[double]). This can be used to create a line going through a list of nodes whose x,y,z coordinates are provided. A physical region number is also provided to have access to the geometric regions of interest in the finite element simulation.

>>> linephysicalregion = 1
>>> myline = shape("line", linephysicalregion, [0,0,0, 0.5,0.5,0, 1,1,0, 1.5,1,0, 2,1,0])
>>> mymesh = mesh([myline])
>>> mymesh.write("meshed.msh")

If the nodes in the mesh need to be accessed, a point shape object can be created with corresponding nodal coordinates. The nodes can then be accessed through the physical region provided.

>>> pointphysicalregion = 2
>>> point1_coords = [0,0,0]
>>> point2_coords = [2,1,0]
>>> mypoint1 = shape("point", pointphysicalregion, point1_coords)
>>> mypoint2 = shape("point", pointphysicalregion, point2_coords)
>>> # Points 1 and 2 are now available in the `pointphysicalregion=2`.
>>>
>>> p = field("h1")
>>> p.setorder(linephysicalregion, 1)
>>> p.setconstraint(pointphysicalregion, 2) # Dirichlet boundary constraint will be applied on points 1 and 2

Example 3: myshape = shape(shapename:str, physreg:int, coords:List[double], nummeshpts:int) This can be used to create: a straight line between the first (x1,y1,z1) and last point (x2,y2,z2) provided. a circular arc between the first (x1,y1,z1) and second point (x2,y2,z2) whose center is the third point (x3,y3,z3). The nummeshpts argument corresponds to the number of nodes in the meshed shape. At least two nodes are expected.

>>> linephysicalregion=1; arcphysicalregion=1
>>> myline = shape("line", linephysicalregion, [0,0,0, 1,-1,1], 10) # creates a line mesh with 10 nodes
>>> myarc = shape("arc", arcphysicalregion, [1,0,0, 0,1,0, 0,0,0], 8) # creates an arc mesh with 8 nodes
>>> mymesh = mesh([myline, myarc])
>>> mymesh.write("meshed.msh")

Example 4: myshape = shape(shapename:str, physreg:int, coords:List[double], nummeshpts:List[int]) This can be used to create: a straight-edge quadrangle with a full quadrangle structured mesh. a straight-edge triangle with s structured mesh made of triangles along the edge linking the second and third node and quadrangles everywhere else.

>>> quadranglephysicalregion=1; trianglephysicalregion=2
>>> myquadrangle = shape("quadrangle", quadranglephysicalregion, [0,0,0, 1,0,0, 1,1,0, -0.5,1,0], [12,10,12,10])
>>> mytriangle = shape("triangle", trianglephysicalregion, [1,0,0, 2,0,0, 1,1,0], [10,10,10])
>>> mymesh = mesh([myquadrangle, mytriangle])
>>> mymesh.write("meshed.msh")
The <code>coords</code> argument provides the <code>x,y,z</code>coordinates of the corner nodes. E.g. (0,0,0), (1,0,0), (1,1,0) and (-0.5,1,0) for the quadrangle.
The <code>nummeshpts</code> argument specifies the number of nodes to mesh each of the contour lines. At least two nodes are expected for each contour line.
All contour lines must have the same number of nodes for the triangle shape while for the quadrangle shape the contour lines facing each other
must have the same number of nodes.

Example 5: myshape = shape(shapename:str, physreg:int, subshapes:List[shape], nummeshpts:int) This can be used to create the following shapes from the list of subshapes provided: a straight line between the first (x1,y1,z1) and last point (x2,y2,z2) provided. a circle arc between the first (x1,y1,z1) and second point (x2,y2,z2) whose center is the third point. The nummeshpts argument corresponds to the number of nodes in the meshed shape.

>>> # Point subshapes
>>> point1 = shape("point", -1, [0,0,0])
>>> point2 = shape("point", -1, [1,0,0])
>>> point3 = shape("point", -1, [0,1,0])
>>> point4 = shape("point", -1, [1,-1,1])
>>>>
>>> # Creating line and arc shapes from point subshapes
>>> linephysicalregion=1; arcphysicalregion=2
>>> myline = shape("line", linephysicalregion, [point1, point4], 10)
>>> myarc = shape("arc", arcphysicalregion, [point2, point3, point1], 8)
>>> mymesh = mesh([myline, myarc])
>>> mymesh.write("meshed.msh")

Example 6: myshape = shape(shapename:str, physreg:int, subshapes:List[shape], nummeshpts:List[int]) This can be used to create the following shapes from the list of subshpaes provided: a straight-edge quadrangle with a full quadrangle structured mesh a straight-edge triangle with a structured mesh made of triangles along the edge linking the second and third nodes and quadrangles everywhere. The subshapes argument provides the list of corner point shapes. The nummeshptsargument gives the number of nodes to mesh each of the contour lines. At least two nodes are expected for each contour line. All contour lines must have the same number of nodes for the triangle shape while for the quadrangle shape the contour lines facing each other must have the same number of nodes.

>>> # Point subshapes
>>> point1 = shape("point", -1, [0,0,0])
>>> point2 = shape("point", -1, [1,0,0])
>>> point3 = shape("point", -1, [1,1,0])
>>> point4 = shape("point", -1, [0,1,0])
>>> point5 = shape("point", -1, [2,0,0])
>>>
>>> # Creating triangle and quadrangle shape from subshapes of corner points
>>> quadranglephysicalregion=1; trianglephysicalregion=2
>>> myquadrangle = shape("quadrangle", quadranglephysicalregion, [point1, point2, point3, point4], [6,8,6,8])
>>> mytriangle = shape("triangle", trianglephysicalregion, [point2, point5, point3], [8,8,8])
>>> mymesh = mesh([myquadrangle, mytriangle])
>>> mymesh.write("meshed.msh")

Example 7: myshape = shape(shapename:str, physreg:int, subshapes:List[shape]) This can be used to create: a curved quadrangle with full quadrangle structured mesh. a curved triangle with structured mesh made of triangles along the edge linking the second and third nodes and quadrangles everywhere. a shape that is the union of several shapes of the same dimension. The subshapes argument provides the contour shapes (clockwise or anti-clockwise). All contour lines must have the same number of nodes for the triangle shape while for quadrangle shape the contour lines facing each other must have the same number of nodes.

>>> # Creating subshapes
>>> line1 = shape("line", -1, [-1,-1,0, 1,-1,0], 10)
>>> arc2 = shape("arc", -1, [1,-1,0, 1,1,0, 0,0,0], 12)
>>> line3 = shape("line", -1, [1,1,0, -1,1,0], 10)
>>> line4 = shape("line", -1, [-1,1,0, -1,-1,0], 12)
>>> line5 = shape("line", -1, [1,-1,0, 3,-1,0], 12)
>>> arc6 = shape("arc", -1, [3,-1,0, 1,1,0, 1.6,-0.4,0], 12)
>>>
>>> quadranglephysicalregion=1; trianglephysicalregion=2; unionphysicalregion=3
>>> myquadrangle = shape("quadrangle", quadranglephysicalregion, [line1, arc2, line3, line4])
>>> mytriangle = shape("triangle", trianglephysicalregion,[line5, arc6, arc2])
>>> myunion = shape("union", unionphysicalregion, [line1, arc2, line3, line4])
>>>
>>> mymesh = mesh([myquadrangle, mytriangle, myunion])
>>> mymesh.write("meshed.msh")

Example 8: myshape = shape(shapename:str, physreg:int, centercoords:List[double], radius:double, nummeshpts:int) This is used to create a 2D disk with structured mesh centered around centercoords. The nummeshptsargument corresponds to the number of nodes in the contour circle of the disk. Since the disk has a structured mesh, the number of mesh nodes must be a multiple of 4. The radius argument provides the radius of the disk.

>>> diskphysicalregion=1
>>> mydisk = shape("disk", diskphysicalregion, [1,0,0], 2, 40)
>>> mymesh = mesh([mydisk])
>>> mymesh.write("meshed.msh")

Example 9: myshape = shape(shapename:str, physreg:int, centerpoint:shape, radius:double, nummeshpts:int) This is used to create a 2D disk with structured mesh centered around point shape centerpoint. The nummeshpts argument corresponds to the number of nodes in the contour circle of the disk. Since the disk has a structured mesh, the number of mesh nodes must be a multiple of 4. The radius argument provides the radius of the disk.

>>> diskphysicalregion=1
>>> centerpoint = shape("point", -1, [1,0,0])
>>> mydisk = shape("disk", diskphysicalregion, centerpoint, 2, 40)
>>> mymesh = mesh([mydisk])
>>> mymesh.write("meshed.msh")

Methods

duplicate
def duplicate(self) -> shape

This outputs a shape that is a duplicate of the initial shape. All the subshapes are duplicated recursively as well but the object equality relations between subshapes are identical between a shape and its duplicate.

Example
>>> myquadrangle = shape("quadrangle", 1, [-1,-1,0, 1,-1,0, 1,1,0, -1,1,0], [6,8,6,8])
>>> otherquadrangle = myquadrangle.duplicate()
See Also

shape.move(), shape.shift(), shape.scale(), shape.rotate()

extrude
def extrude(
self,
physreg: int,
height: float,
numlayers: int,
extrudedirection: list[float] = [0.0, 0.0, 1.0]
) -> shape
def extrude(
self,
physreg: list[int],
height: list[float],
numlayers: list[int],
extrudedirection: list[float] = [0.0, 0.0, 1.0]
) -> list[shape]

A given shape is extruded in the direction specified by the unit vector argument extrudedirection (ZZ-axis by default) to form a higher dimensional shape. The extrude function works for 0D, 1D and 2D shapes. The physreg is the physical region to which the extruded shape is set. The argument height is the height of extrusion in the direction of extrusion. The number of node layers the extruded mesh should contain is specified by numlayers.

Examples

Example 1: myshape = shape.extrude(physreg:int, height:double, numlayers:int, extrudedirection:List[double])

>>> myquadrangle = shape("quadrangle", 1, [-1,-1,0, 1,-1,0, 1,1,0, -1,1,0], [2,2,2,2])
>>> volumephysicalregion = 100
>>> myvolume = myquadrangle.extrude(volumephysicalregion, 1.4, 6, [0,0,1])
>>> mymesh = mesh([myvolume])
>>> mymesh.write("meshed.msh")

Example 2: myshape = shape.extrude(physreg:List[int], height:List[double], numlayers:[int], extrudedirection:List[double]).

This extends the extrude function to multiblock extrusion.

>>> mytriangle = shape("triangle", 1, [0,0,0, 1,0,0, 0,1,0], [6,6,6])
>>>
>>> '''
>>> Creating multiblock extrusion:
>>> ---------|----------|----------|-----------
>>> block | physreg | height | numlayers
>>> ---------|----------|----------|-----------
>>> Block 1: | 11 | 0.5 | 3
>>> Block 2: | 12 | 0.3 | 5
>>> ---------|----------|----------|-----------
>>> In block 1, the initial shape is extruded to a height of 0.5 and contains 3 node layers and the extruded shape is set to physical region 11.
>>> In block 2, the initial shape is extruded to a height of 0.3 (starting from height 0.5) and contains 5 node layers and the extruded shape is set to physical region 12.
>>> '''
>>> myvolumes = mytriangle.extrude([11,12], [0.5,0.3], [3,5], [0,0,1]) # creates two extruded shapes
>>> mymesh = mesh(myvolumes)
>>> mymesh.write("meshed.msh")
getcoords
def getcoords(self) -> list[float]

This returns the coordinates of all nodes in the shape mesh.

Examples
>>> myquadrangle = shape("quadrangle", 555, [0,0,0, 1,0,0, 1,1,0, 0,1,0], [2,2,2,2])
>>> myquadrangle.getcoords()
[0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0]
getcurvatureorder
def getcurvatureorder(self) -> int

This returns the curvature order of a given shape.

Example
>>> q = shape("quadrangle", 1, [0,0,0, 1,0,0, 1,1,0, 0,1,0], [2,2,2,2])
>>> q.getcurvatureorder()
1
getdimension
def getdimension(self) -> int

This gives the shape dimension (0D, 1D, 2D or 3D).

Examples
>>> mypoint = shape("point", 111, [0,0,0])
>>> mypoint.getdimension()
0
>>>
>>> myline = shape("line", 222, [0,0,0, 0.5,0.5,0, 1,1,0, 1.5,1,0, 2,1,0])
>>> myline.getdimension()
1
>>>
>>> myarc = shape("arc", 333, [1,0,0, 0,1,0, 0,0,0], 8)
>>> myarc.getdimension()
1
>>>
>>> mytriangle = shape("triangle", 444, [1,0,0, 2,0,0, 1,1,0], [10,10,10])
>>> mytriangle.getdimension()
2
>>>
>>> myquadrangle = shape("quadrangle", 555, [0,0,0, 1,0,0, 1,1,0, 0,1,0], [6,8,6,8])
>>> myquadrangle.getdimension()
2
>>>
>>> mylines = myquadrangle.getsons()
>>> mylines[0].getdimension()
1
getname
def getname(self) -> str

This returns the name of the shape.

Examples
>>> mypoint = shape("point", 111, [0,0,0])
>>> mypoint.getname()
'point'
>>>
>>> myline = shape("line", 222, [0,0,0, 0.5,0.5,0, 1,1,0, 1.5,1,0, 2,1,0])
>>> myline.getname()
'line'
>>>
>>> myarc = shape("arc", 333, [1,0,0, 0,1,0, 0,0,0], 8)
>>> myarc.getname()
'arc'
>>>
>>> mytriangle = shape("triangle", 444, [1,0,0, 2,0,0, 1,1,0], [10,10,10])
>>> mytriangle.getname()
'triangle'
>>>
>>> myquadrangle = shape("quadrangle", 555, [0,0,0, 1,0,0, 1,1,0, 0,1,0], [6,8,6,8])
>>> myquadrangle.getname()
'quadrangle'
>>>
>>> mylines = myquadrangle.getsons()
>>> mylines[0].getname()
'line'
getphysicalregion
def getphysicalregion(self) -> int

This gives the physical region number for a given shape. The physical region is used in the finite element simulation to identify a region. The method returns -1 if the physical region was not set, else a corresponding positive integer.

Examples
>>> myquadrangle = shape("quadrangle", 111, [0,0,0, 1,0,0, 1,1,0, 0,1,0], [6,8,6,8])
>>> mylines = myquadrangle.getsons()
>>>
>>> myline1 = mylines[0]
>>> myline1.setphysicalregion(2)
>>> myline1.getphysicalregion()
2
>>>
>>> myline2 = mylines[1]
>>> myline1.getphysicalregion()
-1
See Also

shape.setphysicalregion(), shape.getsons()

getsons
def getsons(self) -> list[shape]

This returns a list containing the direct subshapes of the shape object. For a quadrangle, its 4 contour lines are returned. For a triangle, its 3 contour lines are returned.

Example
>>> myquadrangle = shape("quadrangle", 111, [0,0,0, 1,0,0, 1,1,0, 0,1,0], [6,8,6,8])
>>> mylines = myquadrangle.getsons()
>>> for myline in mylines:
... myline.setphysicalregion(2)
...
>>> mymesh = mesh(mylines)
>>> mymesh.write("meshed.msh")
move
def move(self, u: expression) -> None

This moves the shape (and all its subshapes recursively) in the x,y and z direction by a value provided in the 3x1 expression array. When moving multiple shapes that share common subshapes, ensure that subshapes are not moved multiple times.

Parameter

u: expression 3x1 array expression that specifies the values by which shape is moved in x,y,z direction

Example
>>> x=field("x"); y=field("y"); z=field("z")
>>> myquadrangle = shape("quadrangle", 1, [-1,-1,0, 1,-1,0, 1,1,0, -1,1,0], [12,16,12,16])
>>> myquadrangle.move(array3x1(0,x,sin(x*y)))
>>> mymesh = mesh([myquadrangle])
>>> mymesh.write("meshed.msh")
See Also

shape.shift(), shape.scale(), shape.rotate(), shape.duplicate()

rotate
def rotate(
self,
alphax: float,
alphay: float,
alphaz: float
) -> None

This rotates the shape (and all its subshapes recursively) first by alphax degrees around the x-axis, then alphay degrees around the y-axis and finally alphaz degrees around the z-axis. When rotating multiple shapes that share common subshapes make sure that subshapes are not rotated multiple times.

Example
>>> myquadrangle = shape("quadrangle", 1, [-1,-1,0, 1,-1,0, 1,1,0, -1,1,0], [6,8,6,8])
>>> myquadrangle.rotate(0,0,45)
>>> mymesh = mesh([myquadrangle])
>>> mymesh.write("meshed.msh")
See Also

shape.move(), shape.shift(), shape.scale(), shape.duplicate()

scale
def scale(
self,
scalex: float,
scaley: float,
scalez: float
) -> None

This scales the shape (and all its subspaces recursively) in the x, y and z directions by a given factor provided respectively by scalex, scaley, and scalez. A factor of 1 keeps the shape unchanged. When scaling multiple shapes that share common subshapes make sure the subshapes are not scaled multiple times.

Example
>>> myquadrangle = shape("quadrangle", 1, [-1,-1,0, 1,-1,0, 1,1,0, -1,1,0], [6,8,6,8])
>>> myquadrangle.scale(2,0.5,2)
>>> mymesh = mesh([myquadrangle])
>>> mymesh.write("meshed.msh")
See Also

shape.move(), shape.shift(), shape.rotate(), shape.duplicate()

setphysicalregion
def setphysicalregion(self, physreg: int) -> None

This sets, for a given shape, a physical region number provided by physreg. Subshapes are not affected. The physical region is used in the finite element simulation to identify a region.

Example
>>> quadphysicalregion=1; linephysicalregion=2
>>> myquadrangle = shape("quadrangle", quadphysicalregion, [0,0,0, 1,0,0, 1,1,0, 0,1,0], [6,8,6,8])
>>> myline = myquadrangle.getsons()[0] # the shape 'myline' is not associated with any physical region yet.
>>> myline.setphysicalregion(linephysicalregion)
>>> mymesh = mesh([myquadrangle, myline])
>>> mymesh.write("meshed.msh")
See Also

shape.getphysicalregion(), shape.getsons()

shift
def shift(
self,
shiftx: float,
shifty: float,
shiftz: float
) -> None

This shifts the shape (and all its subshapes recursively) in the x, y, and z directions by a value provided respectively by shiftx, shifty, and shiftz. When shifting multiple shapes that share common subshapes make sure the subspaces are not shifted multiple times.

Example
>>> myquadrangle = shape("quadrangle", 1, [-1,-1,0, 1,-1,0, 1,1,0, -1,1,0], [6,8,6,8])
>>> myquadrangle.shift(1,1,2)
>>> mymesh = mesh([myquadrangle])
>>> mymesh.write("meshed.msh")
See Also

shape.move(), shape.scale(), shape.rotate(), shape.duplicate()

Class: spanningtree

def spanningtree(
self,
physregs: list[int]
)

The spanningtree object holds a spanning tree whose edges go through all nodes in the mesh without forming a loop. The physregs is the list of physical regions where the spanning tree is first fully grown before being extended everywhere.

Example

A spanning tree object is created by passing the physical regions ‘sur’ and ‘top’. Hence, here the tree is first fully grown on face regions ‘sur’ and ‘top’ before extending everywhere.

>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3; # physical regions
>>> spantree = spannintree([sur, top])

Methods

countedgesintree
def countedgesintree(self) -> int

This returns the number of edges in the spanning tree.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3; # physical regions
>>> spantree = spannintree([sur, top])
>>> spantree.countedgesintree()
1859
write
def write(self, filename: str) -> None

This writes the tree into a file for visualization. The filename is the name of the file to which the data samples are written.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2; top=3; # physical regions
>>> spantree = spannintree([sur, top])
>>> spantree.write("spantree.vtk")

Class: spline

def spline(
self
)
def spline(
self,
filename: str,
delimiter: str = '\n'
)
def spline(
self,
xin: list[float],
yin: list[float]
)

The spline object allows interpolation in a discrete data set using cubic (natural) splines. Before creating a spline object, ensure that a mesh object is available. If a mesh object is not already available, create an empty mesh object.

Examples

Say the data samples are in a text file, with each data separated by ”,” as shown below:

>>> 273,5e9,300,4e9,320,2.5e9,340,1e9

A spline object can then be created by reading the x-y data contained in the text file.

>>> mymesh = mesh() # if a mesh object is not already available.
>>> spl1 = spline(filename="measured.txt", delimiter="\n")

The x-y data samples can also be provided in two separate lists or tuples. In that case, a spline object is created as follows:

>>> mymesh = mesh() # if a mesh object is not already available.
>>> temperature = (273, 300, 320, 340)
>>> youngsmodulus = (5e9, 4e9, 2.5e9, 1e9)
>>> spl2 = spline(xin=temperature, yin=youngsmodulus)

Note that the ordering of the samples provided does not matter. Internally they are always sorted in the ascending order of xx data.

Methods

evalat
def evalat(self, input: float) -> float
def evalat(self, input: list[float]) -> list[float]
def evalat(self, input: densemat) -> densemat

This method interpolates at given input xx point(s) that falls within the original data range provided. A RuntimeError is raised if at least one of the inputs for interpolation is outside the range (xmin,xmax)(x_{min}, x_{max}) of the original data samples provided.

Examples
>>> mymesh = mesh() # if a mesh object is not already available.
>>>
>>> temperature = (320, 273, 340, 300) # original x input
>>> youngsmodulus = (2.5e9, 5e9, 1e9, 4e9) # original y input
>>> spl = spline(temperature, youngsmodulus)
>>>
>>> # Example 1:
>>> spl.evalat(303)
4199390995.630462
>>>
>>> # Example 2:
>>> spl.evalat([298,304,275])
[3912277862.548358, 4278701622.971286, 4835693423.344276]
>>>
>>> # Example 3:
>>> spl.evalat(250)
RuntimeError: Error in 'spline' object: data requested in the interval (250,250) is out of the provided data range (273,340)
>>>
>>> # Example 4:
>>> spl.evalat([290, 310, 400])
RuntimeError: Error in 'spline' object: data requested in the interval (290,400) is out of the provided data range (273,340)
getderivative
def getderivative(self) -> spline

This returns the derivative of the spline.

The spline polynomial y(x)y(x) and its derivative dydx\frac{dy}{dx}:

y(x)=ax3+bx2+cx+dy(x) = ax³ + bx² + cx + d dydx=3ax2+2bx+c\frac{dy}{dx} = 3ax² + 2bx + c
Example
>>> mymesh = mesh() # if a mesh object is not already available.
>>>
>>> temperature = (273, 300, 320, 340)
>>> youngsmodulus = (5e9, 4e9, 2.5e9, 1e9)
>>>
>>> spl = spline(temperature, youngsmodulus)
>>> spl.write("spline_data.txt", 0, ",")
273,5000000000,300,4000000000,320,5000000000,340,1000000000
>>>
>>> dspl = spl.getderivative()
>>> dspl.write("splinederivative_data.txt", 0, ",")
273,-82402205.576362908,300,53693300.041614674,320,-58198085.726175644,340,-270900957.13691229
getxmax
def getxmax(self) -> float

This returns the maximum value that xx input takes in the original data provided.

Example
>>> mymesh = mesh() # if a mesh object is not already available.
>>>
>>> temperature = (320, 273, 340, 300) # original x input
>>> youngsmodulus = (2.5e9, 5e9, 1e9, 4e9) # original y input
>>> spl = spline(temperature, youngsmodulus)
>>> spl.getxmax()
340
See Also

spline.getxmin()

getxmin
def getxmin(self) -> float

This returns the minimum value that xx input takes in the original data provided.

Example
>>> mymesh = mesh() # if a mesh object is not already available.
>>>
>>> temperature = (320, 273, 340, 300) # original x input
>>> youngsmodulus = (2.5e9, 5e9, 1e9, 4e9) # original y input
>>> spl = spline(temperature, youngsmodulus)
>>> spl.getxmin()
273
See Also

spline.getxmax()

set
def set(
self,
xin: list[float],
yin: list[float]
) -> None

This method defines a spline object based on the x-y data samples provided in two separate lists or tuples.

>>> mymesh = mesh() # if a mesh object is not already available.
>>> temperature = (273, 300, 320, 340)
>>> youngsmodulus = (5e9, 4e9, 2.5e9, 1e9)
>>>
>>> myspline = spline() # create an empty spline object
>>> myspline.set(xin=temperature, yin=youngsmodulus)
write
def write(
self,
filename: str,
numsplits: int,
delimiter: str = '\n'
) -> None

This writes to file a refined version of the original data samples with xx data sorted in ascending order. It can be used to visualize the interpolation obtained with cubic splines. The argument filename is the name of the file to which the data samples are written. The numsplits is the number of additional points between two successive xx input considered for evaluation and subsequent writing. Minimum value of numsplits required is 00. The delimiter is a string specifying the separation between the output columns in the written file.

Examples

Create a spline object:

>>> mymesh = mesh() # if a mesh object is not already available.
>>>
>>> temperature = (320, 273, 340, 300) # original x input
>>> youngsmodulus = (2.5e9, 5e9, 1e9, 4e9) # original y input
>>> spl = spline(temperature, youngsmodulus)

Example 1: If numsplits = 0, no additional points are considered and the original data samples are written.

>>> numsplits = 0
>>> spl.write("spline_data.txt", numsplits, ",")
273,5000000000,300,4000000000,320,5000000000,340,1000000000

Example 2: If numsplits = 1, between two successive xx inputs, one additional point is considered for evaluation and subsequent writing.

>>> numsplits = 1
>>> spl.write("spline_data.txt", numsplits, "\n")
273
5000000000
286.5
4040677668.5393257
300
4000000000
310
4779728464.4194756
320
5000000000
330
3531757178.5268416
340
1000000000

Similarly, if numsplits = 2, between two successive xx inputs, two additional points are considered for evaluation and subsequent writing.

Class: vec

def vec(
self
)
def vec(
self,
formul: formulation
)
def vec(
self,
vecsize: int,
addresses: indexmat,
vals: densemat
)

The vec object holds a vector, be it the solution vector of an algebraic problem or its right-hand side.

Examples

Example 1: vec(formul:formulation) This creates an all-zero vector whose structure and size is the one of formulation projection.

>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v) - 2*tf(v))
>>>
>>> b = vec(projection)

Example 2: vec(vecsize:int, addresses:indexmat, vals:densemat) This creates a vector with given values at given addresses.

>>> allinitialize()
>>> addresses = indexmat(3,1, [0,1,2])
>>> vals = densemat(3,1, [5,10,20])
>>>
>>> b = vec(3, addresses, vals)
>>> b.print()
Vec Object: 1 MPI processes
type: seq
5.
10.
20.
>>> allfinalize()

Methods

copy
def copy(self) -> vec

This creates a full copy of the vector object.

Examples
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v) - 2*tf(v))
>>> sol = vec(projection)
>>>
>>> copiedvec = sol.copy()
getallvalues
def getallvalues(self) -> densemat

This gets the values of all the entries of the vector in sequential order. It returns a column matrix with number of rows equal to the the vector object size.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2 >>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v) - 2*tf(v))
>>>
>>> myvec = vec(projection) # creates a zero vector
>>>
>>> vals = densemat(myvec.size(),1, 12)
>>> myvec.setallvalues(vals) # all the entries now contain a value of 12
>>>
>>> vecvals = myvec.getallvalues()
See Also

vec.setvalue(), vec.setvalues(), vec.setallvalues(), vec.getvalue(), vec.getvalues()

getvalue
def getvalue(self, address: int) -> float
def getvalue(self, prt: port) -> float

This gets the value of the vector object at the given address. The address provides the index at which the entry of a vector is requested. It returns a matrix of size 1×11 \times 1.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2 >>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v) - 2*tf(v))
>>>
>>> myvec = vec(projection) # creates a zero vector
>>>
>>> vals = densemat(myvec.size(),1, 12)
>>> myvec.setallvalues(vals) # all the entries now contain a value of 12
>>>
>>> vecvals = myvec.getvalue(2)
See Also

vec.setvalue(), vec.setvalues(), vec.setallvalues(), vec.getvalues(), vec.getallvalues()

getvalues
def getvalues(self, addresses: indexmat) -> densemat

This gets the values in the vector that are at the indices given in addresses. The addresses is the column matrix storing the indices at which the entries of a vector are requested. It returns a column matrix with number of rows equal to the length of the addresses.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2 >>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v) - 2*tf(v))
>>>
>>> myvec = vec(projection) # creates a zero vector
>>>
>>> vals = densemat(myvec.size(),1, 12)
>>> myvec.setallvalues(vals) # all the entries now contain a value of 12
>>>
>>> addresses = indexmat(myvec.size(),1, 0,1)
>>> vecvals = myvec.getvalues(addresses)
See Also

vec.setvalue(), vec.setvalues(), vec.setallvalues(), vec.getvalue(), vec.getallvalues()

load
def load(self, filename: str) -> None

This loads the data of a vector object from a file (either .bin or .txt ASCII format). This only works correctly if the dof structure of the calling vector object is the same as that of the vector object from which the file was written to the disk. In other words, the same set of formulation contributions must be defined in the same order.

Examples
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v) - 2*tf(v))
>>> sol = vec(projection)
>>> sol.write("vecdata.bin") # writes the vector data to a file
>>>
>>> loadedvec = vec(projection)
>>> loadedvec.load("vecdata.bin") # loads the data to the vector object
See Also

vec.write()

noautomaticupdate
def noautomaticupdate(self) -> None

After this call, the vector object will not have its value automatically updated after hp-adaptivity. If the automatic update is not needed then this call is recommended to avoid a possibly costly update to the vector values.

Examples
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v) - 2*tf(v))
>>> sol = vec(projection)
>>>
>>> sol.noautomaticupdate()
norm
def norm(self, type: str = '2') -> float

This returns the 11, 22 or \infty norm of the vector. The default is the 22 norm.

Examples
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v) - 2*tf(v))
>>> sol = vec(projection)
>>>
>>> vals = densemat(sol.size(),1, 12)
>>> sol.setallvalues(vals) # all the entries now contain a value of 12
>>>
>>> sol.norm()
108.6646
See Also

vec.sum()

permute
def permute(
self,
rowpermute: indexmat,
invertit: bool = False
) -> None

This rearranges the vector in the order of indices prescribed in rowpermute. The inverse permutation is performed if the boolean flag invertit is set to True. The rowpermute describes the mapping or inverse mapping function.

Example
>>> rows = indexmat(6,1, [0,1,2,3,4,5])
>>> vals = densemat(6,1, [00,10,20,30,40,50])
>>>
>>> v = vec(6, rows, vals)
>>> permuterows = indexmat(6,1, [3,1,4,5,0,2])
>>> v.permute(permuterows, invertit=False)
>>> v.print()
Vec Object: 1 MPI processes
type: seq
30.
10.
40.
50.
0.
20.
>>>
>>> # Inverting the permutation on the above will give back the original order of the vector.
>>> v = vec(6, rows, vals)
>>> permuterows = indexmat(6,1, [3,1,4,5,0,2])
>>> v.permute(permuterows, invertit=True)
>>> v.print()
Vec Object: 1 MPI processes
type: seq
0.
10.
20.
30.
40.
50.
print
def print(self) -> None

This prints the values of the vector object.

Examples
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v) - 2*tf(v))
>>> sol = vec(projection)
>>>
>>> sol.print()
setallvalues
def setallvalues(
self,
valsmat: densemat,
op: str = 'set'
) -> None

This replaces all the entries in the vector object by the values in valsmat. The addresses of valsmat are assumed to be in sequential order. If op='set', the values are replaced and if op='add' the values are instead added to existing ones. This method works on all the entries. The valsmat is a column matrix storing the values that are replaced in or added to the vector object.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2 >>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v) - 2*tf(v))
>>>
>>> myvec = vec(projection) # creates a zero vector
>>>
>>> vals = densemat(myvec.size(),1, 12)
>>> myvec.setallvalues(vals)
See Also

vec.setvalue(), vec.setvalues(), vec.getvalue(), vec.getvalues(), vec.getallvalues()

setdata
def setdata(
self,
physreg: int,
myfield: field,
op: str = 'set'
) -> None
def setdata(self) -> None

This sets to the vector the data from the fields and ports defined in the formulation.

Examples
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v) - 2*tf(v))
>>> sol = vec(projection) # creates a zero vector
Replace the vector data with the data from field <code>myfield</code> from physical region <code>physreg</code>.
>>> sol.setdata(vol, v); # populates the vector with the data from the field v

Example 2: vec.setdata() Replace the vector data with the data from all the fields and ports defined in the associated formulation.

>>> ...
>>> sol.setdata();
setvalue
def setvalue(
self,
address: int,
value: float,
op: str = 'set'
) -> None
def setvalue(
self,
prt: port,
value: float,
op: str = 'set'
) -> None

This replaces the value in the vector object at the given address with the values in value. The ‘address’ provides the index at which the entry is replaced by value. If op='set', the value is replaced and if op='add' the value is added to the existing one. This method works only on a given single entry. The address is the index at which the entry of a vector is replaced/added. The value is the value that is set/added in the vector object.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2 >>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v) - 2*tf(v))
>>>
>>> myvec = vec(projection) # creates a zero vector
>>>
>>> myvec.setvalue(2, 2.32)
See Also

vec.setvalues(), vec.setallvalues(), vec.getvalue(), vec.getvalues(), vec.getallvalues()

setvalues
def setvalues(
self,
addresses: indexmat,
valsmat: densemat,
op: str = 'set'
) -> None

This replaces the values in the vector object at the given addresses with the values in valsmat. If op='set', the values are replaced and if op='add' the values are instead added to existing ones. This method works only on entries given in the addresses. The addresses is a column matrix storing the indices at which the entries of a vector are replaced/added. The valsmat is a column matrix storing the values that are replaced in or added to the vector object.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2 >>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v) - 2*tf(v))
>>>
>>> myvec = vec(projection) # creates a zero vector
>>>
>>> addresses = indexmat(myvec.size(),1, 0,1)
>>> vals = densemat(myvec.size(),1, 12)
>>>
>>> myvec.setvalues(addresses, vals) # op is the default 'set'. All entries are replaced by value 12.
>>> myvec.setvalues(addresses, vals, 'set') # All entries are replaced by value 12.
>>> myvec.setvalues(addresses, vals, 'add') # Value 12 is added to all entries.
See Also

vec.setvalue(), vec.setallvalues(), vec.getvalue(), vec.getvalues(), vec.getallvalues()

size
def size(self) -> int

This returns the size of the vector object. If the vector was instantiated from a formulation, then the vector size is equal to the number of dofs in that formulation.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v) - 2*tf(v))
>>>
>>> b = vec(projection)
>>> b.size()
82
sum
def sum(self) -> float

This returns the sum of all the values in the vector.

Examples
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v) - 2*tf(v))
>>> sol = vec(projection)
>>>
>>> vals = densemat(sol.size(),1, 12)
>>> sol.setallvalues(vals) # all the entries now contain a value of 12
>>>
>>> sol.sum()
984.0
See Also

vec.norm()

updateconstraints
def updateconstraints(self) -> None

This updates the values of all Dirichlet constraint entries in the vector.

Example
>>> mymesh = mesh("disk.msh")
>>> vol=1; sur=2
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v) - 2*tf(v))
>>>
>>> b = vec(projection)
>>>
>>> v.setconstraint(sur, 1)
>>> b.updateconstraints()
write
def write(self, filename: str) -> None

This writes all the data in the vector object to disk in a lossless and compact form. The file can be written in binary .bin format (extremely compact but less portable) or in ASCII .txt format (portable). The filename is the name of the file to which the data from the vector object is written.

Examples
>>> mymesh = mesh("disk.msh")
>>> vol = 1
>>> v = field("h1")
>>> v.setorder(vol, 1)
>>> projection = formulation()
>>> projection += integral(vol, dof(v)*tf(v) - 2*tf(v))
>>> sol = vec(projection)
>>>
>>> sol.write("vecdata.txt") # writes the vector data to a file
See Also

vec.load()

Class: wallclock

def wallclock(
self
)

This initializes the wall clock object.

Methods

pause
def pause(self) -> None

This pauses the clock. The wallclock.pause() and wallclock.resume() functions allow to time selected operations in loop.

Example
>>> myclock = wallclock()
>>> myclock.pause()
>>> # Do something
>>> myclock.resume()
>>> myclock.print()
See Also

wallclock.resume()

print
def print(self, toprint: str = '') -> None

This prints the time elapsed in the most appropriate format (nsns, μs\mu s, msms or ss). It also prints the message passed in the argument toprint (if any).

Example
>>> myclock = wallclock()
>>> myclock.print("Time elapsed") # or myclock.print()
resume
def resume(self) -> None

This resumes the clock. The wallclock.pause() and wallclock.resume() functions allow to time selected operations in loop.

Example
>>> myclock = wallclock()
>>> myclock.pause()
>>>
>>> for i in range (0, 10):
>>> myclock.resume()
>>> # Do something and time it
>>> myclock.pause()
>>> # Do something else
>>> myclock.print()
See Also

wallclock.pause()

tic
def tic(self) -> None

This resets the clock.

Example
>>> myclock = wallclock()
>>> myclock.tic()
See Also

wallclock.toc()

toc
def toc(self) -> float

This returns the time elapsed (in nsns).

Example
>>> myclock = wallclock()
>>> timeelapsed = myclock.toc()
See Also

wallclock.tic()


Generated by pdoc 0.10.0 (https://pdoc3.github.io).