4.5.1. String Functions

String functions are one of the most flexible generic expressions supported in Aria for most material properties, initial conditions, sources, boundary conditions, and post-processors. String functions can be used to define scalar, vector, and tensor expressions as a function of position, time, and any expression that has been registered from the input deck. Currently the arguments for the string function must be scalars, although in the future vectors and tensors will be supported. If the function contains commas or spaces, it should be enclosed in quotes. Sensitivities for these string functions are calculated using finite-differences.

Valid inputs to a string function (not case-sensitive) are:

  • x - X coordinate (includes deformation)

  • xref - X coordinate (non-deformed)

  • y - Y coordinate (includes deformation)

  • yref - Y coordinate (non-deformed)

  • z - Z coordinate (includes deformation)

  • zref - Z coordinate (non-deformed)

  • t - Current time

  • dt - Current time step

  • t_old - Time at the prior time step

  • Any valid scalar expression name (e.g. DENSITY), old state of a valid scalar expression (e.g. OLD_DENSITY), or scalar component of a vector (e.g. GRAD_TEMPERATURE_X)

String functions that you define automatically provide their corresponding gradient and old time values, and time-dependent inputs are shifted automatically. For example, if you define

density = scalar_string_function f = "temperature + t"

Aria will automatically also define OLD_DENSITY = OLD_TEMPERATURE + T_OLD, so they can safely be used in terms that require a time derivative or gradient.

4.5.1.1. Scalar String Functions

Some examples of correct syntactic usage of these scalar string functions are listed below. For example, to define a spatially varying density you could use:

density = scalar_string_function f = "2*x"

Here, the material property density was assigned a value depending on the x coordinate. To define thermal conductivity as a function of temperature, you could use:

density = scalar_string_function f = "5*ln(temperature)"

Using User Expressions in combination with string functions lets you create more complicated property models. For example, to combine some tabulated values from a User Function called DensityScaling and an analytical function to get thermal conductivity you could do:

user expression = user_function user_tag = "rho_scaling" \$
    name = "DensityScaling" X = "Density"

user expression = scalar_string_function user_tag = "intrinsic_k" \$
    f = "3 + 4*ln(TEMPERATURE) - 3e-2*TEMPERATURE"

thermal conductivity = scalar_string_function \$
    f = "intrinsic_k * rho_scaling"

You can also use vector components and dot products with the string functions. For example, to use components of the temperature gradient you could do:

thermal conductivity = scalar_string_function f = "5 + GRAD_TEMPERATURE_X"

To take a dot product of two vector quantities and use it in another function:

user expression = dot_product user_tag = "GTdotGV" \$
    A = "Grad_Temperature" B = "Grad_Voltage"

thermal conductivity = scalar_string_function f = "5 + GTdotGV"

These string functions can also be used for boundary conditions and initial conditions.

BC dirichlet for temperature on surface_1 = scalar_string_function \$
    f = "density*sin(t)"

Here, a dirichlet boundary condition was applied on a surface for temperature as a function of the material property density (defined in the first example) and time. You can insert any expression that exists in your input deck as an argument for the function. If the expression is not found, the program will exit with an error about a missing expression.

String functions can also be used for initial conditions:

IC for temperature on block_1 = scalar_string_function f = "x*y*z"

Here, an initial condition for temperature is set also as a function of the spatial coordinates. This is for a three-dimensional case as the “z” component is included. If this line was used in a two-dimensional geometry, the program would exit because it could not find the “z” coordinate.

4.5.1.2. Vector String Functions

Vector string functions work in the same manner as their scalar counterpart, but must have functions specified for each vector component. For example, you could use a vector string function to apply a Dirichlet BC for velocity or any other vector quantity.

BC dirichlet for velocity on surface_1 = vector_string_function \$
    f_x = "1-x" f_y = "viscosity*sin(t)" f_z = "0.0"

4.5.1.3. Tensor String Functions

Tensor string functions work in the same manner as vectors, but must have functions specified for each tensor component.

Intrinsic Permeability = tensor_string_function \$
    f_xx = "1-x"                                \$
    f_yy = "1e-12*exp(t)"                       \$
    f_xy = "solid_phase_porosity*1e-12"         \$
    f_yx = "0"

Here we define the intrinsic permeability as a tensor string function for a two-dimensional geometry. For the tensor_string_function, the user must supply all components of the tensor. However, one can use the tensor_string_function_symmetric` command which enforces symmetry on the off-diagonal tensor component such that f_yx = f_xy.

Intrinsic Permeability = tensor_string_function_symmetric \$
    f_xx = "1-x"                                          \$
    f_yy = "1e-12*exp(t)"                                 \$
    f_xy = "solid_phase_porosity*1e-12"

4.5.1.4. Global Variables In String Functions

You can access global variables in an expression string function by adding the *.global suffix to the variable argument in the string function. For example, given a global variable named my_gvar, we will use it in the density property of a material:

density = scalar_string_function f = "temperature/my_gvar.global"

This will use the current value of my_gvar in this density model. Note, if this global is obtained through a postprocessor, it will use the global value of the previous timestep.

If you were using a global user expression to do this previously, you can replace that with the current approach.

4.5.1.5. String Functions for Contact, Bulk Node and Interface Boundary Conditions

There are several use cases where boundary conditions are being defined at an interface between two blocks: contact enforcement, bulk node boundary conditions, and interface boundary conditions. Those boundary conditions often need to be able to reference quantities from the block on either side of that interface. This is supported in string functions via variable_name.location syntax, for example:

BC bulknode_flux for energy on surface_1 = scalar_string_function bulk_node="bulk1" f="10 * (temperature.block_1 - temperature.bulk1)"

would apply a convective condition between surface_1 and the bulk node bulk_1 on a mesh where surface_1 is on the exterior of block_1.

Enforcement for current = scalar_string_function f="10 * (voltage.current - voltage.opposite)"

would apply a contact resistance condition between the two sides of the interface.

The following options are always valid location specifiers for a variable in any of the interface condition types:

  • The name of one of the blocks on either side of the interface, either a user-specified name in the mesh or canonical name (i.e. block_1)

  • A mesh group or assembly name that uniquely identifies the block (i.e. the mesh group cannot contain the blocks on both sides of the interface)

In addition for bulk node boundary conditions you can use:

  • The name of the bulk node

And for contact enforcement you can use:

  • “current” to refer to the side of the interface the enforcement condition is being applied to.

  • “opposite” to refer to the side of the interface found via the contact search algorithm.

Note that generalized contact enforcement is symmetric so the string function condition will be applied once in each direction.

4.5.1.6. String Function Syntax Guide

String function evaluation is handled using the STK expression evaluator. In addition to the registered variables mentioned in the prior sections, there are a large number of standard functions and operations available in the evaluator. The following section shows a number of different operators with examples and descriptions.

4.5.1.6.1. Powers

Powers can be specified using the pow function or the standard a^b notation.

3^2
pow(3,2)

4.5.1.6.2. Min and Max Functions

There are min and max functions that take 2 to 4 comma-separated inputs.

min(1,2)
min(1,2,3,4)
max(1,2)
max(1,2,3,4)

4.5.1.6.3. Inline Variables

You can define variables inline in the function string, although doing this with aprepro in your input file is often more straightforward.

a=1;b=2;a+b+4

4.5.1.6.4. Ramps and Pulses

There are a number of pre-defined ramp and step functions.

cycloidal_ramp(t,ts,te)
haversine_pulse(t,ts,te)
cosine_ramp(t)
cosine_ramp(t,te)
cosine_ramp(t,ts,te)
linear_ramp(t,ts,te)
sign(x)
unit_step(t,ts,te)
point2d(x,y,r,w)
point3d(x,y,z,r,w)

Cosine Ramp

The cosine_ramp function provides a smooth ramp from 0 to 1. It can be used with 1, 2, or 3 inputs. The form with one input uses a start and end time of 0 and 1. The form with two inputs specifies the end time, but uses a start time of 0. Note that cos_ramp is also a valid function, and is equivalent to cosine_ramp.

\mathrm{cosine\_ramp}(t,t_s,t_e) =
  \begin{cases}
    0, & t \le t_s \\
    \frac{1}{2}\left(1 - \cos\left(\frac{\pi(t-t_s)}{t_e-t_s}\right) \right), & t_s < t < t_e \\
    1, & t \ge t_e
  \end{cases}

Linear Ramp

The linear_ramp function provides a linear ramp from 0 to 1.

\mathrm{linear\_ramp}(t,t_s,t_e) =
  \begin{cases}
    0, & t \le t_s \\
    \frac{t - t_s}{t_e - t_s}, & t_s < t < t_e \\
    1, & t \ge t_e
  \end{cases}

Cycloidal Ramp

The cycloidal_ramp function is another smooth ramp function from 0 to 1. Unlike the cosine_ramp function, it requires all three arguments.

\mathrm{cycloidal\_ramp}(t,t_s,t_e) =
  \begin{cases}
      0, & t \le t_s \\
      \frac{t-t_s}{t_e-t_s} - \frac{1}{2\pi}\sin\left(\frac{2\pi(t-t_s}{t_e-t_s}\right), & t_s < t < t_e \\
      1, & t \ge t_e
  \end{cases}

Haversine Pulse

The haversine_pulse function is a smooth sinusoidal finite width pulse, defined by

\mathrm{haversine\_pulse}(t,t_s,t_e) =
  \begin{cases}
      0, & t \le t_s \\
      \sin\left(\frac{\pi(t-t_s)}{t_e-t_s} \right)^2, & t_s < t < t_e \\
      0, & t \ge t_e
  \end{cases}

Sign

The sign function returns the sign of its single argument

\mathrm{sign}(x) =
  \begin{cases}
      1, & x \ge 0 \\
      -1, & x \lt 0
  \end{cases}

Unit Step

The unit_step function is a sharp square step function

\mathrm{unit\_step}(t,t_s,t_e) =
  \begin{cases}
      0, & t \le t_s \\
      1, & t_s < t < t_e \\
      0, & t \ge t_e
  \end{cases}

2D Point

The point2d function provides a 2D point mask for applying a function in a certain spatial window. It is equivalent to a longer call to cosine_ramp where

\mathrm{point2d}(x,y,r,w) = 1 - \mathrm{cosine\_ramp}(\sqrt{x^2+y^2},r-w/2,r+w/2)

3D Point

The point3d function provides a 3D point mask for applying a function in a certain spatial window. It is equivalent to a longer call to cosine_ramp where

\mathrm{point3d}(x,y,z,r,w) = 1 - \mathrm{cosine\_ramp}(\sqrt{x^2+y^2+z^2},r-w/2,r+w/2)

4.5.1.6.5. Basic Math

There are standard mathematical functions. Most of these require no explanation, although it is worth noting that log is the natural log (equivalent to ln not log10).

exp(x)
ln(x)
log(x)
log10(x)
pow(a,b)
sqrt(x)
erfc(x)
erf(x)
acos(x)
asin(x)
asinh(x)
atan(x)
atan2(y,x)
atanh(x)
cos(x)
cosh(x)
acosh(x)
sin(x)
sinh(x)
tan(x)
tanh(x)

4.5.1.6.6. Rounding Functions

There are a variety of rounding and numerical manipulation routines available.

ceil(x)
floor(x)
abs(x)
mod(x,y)
ipart(x)
fpart(x)

You can round up with ceil and round down with floor. You can separate a number into its integer part (ipart) and floating point part (fpart). You can compute the modulus with mod and get the absolute value with abs.

4.5.1.6.7. Polar Coordinate and Angle Helpers

When working with polar and rectangular coordinates there are helper functions for converting coordinate systems as well as for converting degrees to radians and back.

poltorectx(r,theta)
poltorecty(r,theta)
deg(r)
rad(d)
recttopola(x,y)
recttopolr(x,y)

The conversion functions are

\mathrm{poltorectx}(r,\theta) = r \cos(\theta)

\mathrm{poltorecty}(r,\theta) = r \sin(\theta)

\mathrm{recttopola}(x,y) = \mathrm{atan2}(y,x)

\mathrm{recttopolr}(x,y) = \sqrt{x^2 + y^2}

The angle returned by recttopola is adjusted to be between 0 and 2\pi.

4.5.1.6.8. Distributions and Random Sampling

There are several functions available for generating pseudo-random output with different distributions. When using random output in a string function, be careful using it in places where it can affect nonlinear convergence.

For example, setting an initial condition using a random distribution is acceptable since it is only evaluated once. However, using that to define a material property (for example, thermal conductivity) or boundary condition would result in that property varying not just in time and space, but also from one nonlinear iteration to the next, which could keep a Newton solver from converging. The ts_random and ts_normal functions are designed to help with this problem.

weibull_pdf(x,shape,scale)
normal_pdf(x,mu,sigma)
gamma_pdf(x,shape,scale)
log_uniform_pdf(x,xmin,xmax)
exponential_pdf(x,beta)
random()
random(seed)
time()
ts_random(t,x,y,z)
ts_normal(t,x,y,z,mu,sigma,minR,maxR)

Weibull Distribution

The weibull_pdf function returns a Weibull distribution, where \lambda is the scale parameter and k is the shape parameter.

\mathrm{weibull\_pdf}(x,k,\lambda) =
  \begin{cases}
      \frac{k}{\lambda} \left( \frac{x}{\lambda} \right)^{k-1} e^{-(x/\lambda)^k}, & x \ge 0 \\
      0, & x < 0
  \end{cases}

Normal Distribution

The normal_pdf function returns a normal distribution, where \mu is the mean and \sigma is the standard deviation.

\mathrm{normal\_pdf}(x,\mu,\sigma) = \frac{1}{\sqrt{2\pi\sigma^2}}\exp\left(-\frac{(x-\mu)^2}{2\sigma^2}\right)

Gamma Distribution

The gamma_pdf function returns a gamma distribution, where \theta is the scale parameter and k is the shape parameter.

\mathrm{gamma\_pdf}(x,k,\theta) = \frac{1}{\Gamma(k) \theta^k} x^{k-1} e^{-x/\theta}

Log Uniform Distribution

The log_uniform_pdf function returns a log-uniform distribution

\mathrm{log\_uniform\_pdf}(x,x_0,x_1) = \frac{1}{(\ln(x_1) - \ln(x_0))x}

Exponential Distribution

The exponential_pdf function returns a one-parameter exponential distribution

\mathrm{exponential\_pdf}(x,\beta) = \frac{1}{\beta} e^{-x/\beta}

Random Values

There are several functions for generating pseudo-random numbers. These can be used as inputs to any of the distributions, or as standalone values to get a uniform distribution.

random() calls a platform-independent fast pseudo-random number algorithm. It is seeded with a static constant, so the first call to it will always produce the same value. The value returned will be between 0 and 1.

random(seed) calls the same algorithm as random() but sets the seed first then returns a number between 0 and 1. The seed in this case can be any floating point number.

time() returns the current time integer. This can used to provide a random number seed to make the distributions non-deterministic from run to run.

ts_random(t,x,y,z) returns a uniformly distributed random number from 0 to 1 that is unique in time and space. This means that repeated evaluations of this function at the same point in time and space will produce the same output. This is useful for applying randomness on boundary conditions, for example, where the value should not change during nonlinear iterations at a given time.

ts_normal(t,x,y,z,mu,sigma,minR,maxR) returns a clipped normally distributed random number with a mean of mu and standard deviation of sigma. The output is clipped to be between minR and maxR and, like ts_random, the output is deterministic in time and space.

4.5.1.6.9. Boolean and Ternary Logic

In addition to all these functions, you can make more complicated functions using ternary statements and boolean logic.

You can use ternary operators for simple conditional assignment. The basic structure of a ternary operator is boolean ? value_if_true : value_if_false For example, the following if-else code

if x > 2.2:
  return 1.2*y
else
  return 0.1*y

can be converted to a ternary operator that returns the same thing

(x > 2.2 ? 1.2 : 0.1) * y

You can also use boolean operations in the string functions. By default, booleans that evaluate to true are treated as 1 and booleans that are false are 0. The prior example can be expressed using boolean statements as

(x>2.2)*1.2*y + (x<=2.2)*0.1*y
((x>2.2)*1.1 + 0.1)*y

These operations can be combined (multiplication of boolean statements is equivalent to a logical and). To replicate the unit_step function described earlier you can use either ternary or boolean operators. The following three functions all produce the same result - a step of height 5 between 1 and 2.

5*unit_step(x,1,2)
((x>1 && x<2) ? 5 : 0)
5*(x>1)*(x<2)

Warning

Note that chained inequality syntax is not allowed:

1 < x < 2       // parse error
(1<x) && (x<2)  // correct

4.5.1.6.10. Language Idiosyncrasies

There are a few places where the language rules used in the STK evaluator differ from those used in common tools like Python or Excel. These are noted in the sections below.

Modulus

For negative numbers, there is not a consensus among programming languages about the appropriate behavior for the modulus operator. Consider the statement -30 % 360 - should this return 330 or -30? In Python and Excel this would return 330, but in C and the string functions this will return -30.

To produce a Python-like modulus in a string function for x % 360 you would need to use x - 360*floor(x/360) instead.

Powers of Negative Constants

An expression like -1^2 has some ambiguity - it could be treated as (-1)^2 or -(1^2). Again, here the string function differs from Python and Excel - which treat the - as a leading prefix and not as part of the numeric constant (so they return -1). The STK evaluator treats the - as part of the numeric constant, so it evaluates it as (-1)^2 and returns 1.

However, the STK evaluator treats the - as a subtraction operator when it joins two terms, so in an expression like 0 - 1^2 it would return -1.

Warning

Always use parentheses in expressions like these to ensure consistent and un-ambiguous behavior across languages.