About JavaScript User Subroutines

User subroutines add complex programmatic input possibilities when the usual data input methods alone may be too restrictive.

The following topics are discussed:

This page discusses:

User-defined Fields

User subroutines implemented using JavaScript are scalar or vector input fields that hold floating point values. The length of the array depends on the number of facets in a boundary surface, or the number of elements in a volumetric condition.

Fields

The fields that user subroutines implement are all scalar fields that hold an array of floating point values. The length of the array is equal to the number of facets in a boundary surface or the number of elements in a volumetric condition. When you run a simulation in parallel, this number is further limited to the number of facets or elements held on the processor for the condition's support.

Vector field subroutines are not supported directly; instead, you can store a separate scalar field for each vector component. In practice, this approach means that you will need to define three user subroutines for a velocity inlet condition if you want to specify all components of velocity.

Units

The units for field quantities within user subroutines are always stored and processed in SI. These values may differ from the units used to define the inputs in the user interface.

Field Expressions

Expressions are available to simplify mathematically manipulating field data.

You can create a new expression (using the syntax defined in Field Expression Syntax), and evaluate the expression on a particular support. For example, a parabolic variation with respect to the second component of the coordinate field COORD is represented by 1 - $$COORD[2]**2. To apply this expression as an inlet velocity within a subroutine, define the subroutine as:

var e = new Expression('1 - $$COORD[2]**2');
e.apply(support, this.values);

A more terse example that includes caching of the expression definition (see Caching Information) is given by:

(this._e || (this._e = new Expression('1 - $$COORD[2]**2'))).apply(support, this.values);

User subroutines currently require an array of scalar values to be written to this.values. You must ensure that the result of the expression is a scalar field, not a vector field.

Limitations

The following expression constructs are not supported:

  • Gradient operations such as grad or div.
  • Tensor access or properties.
  • Functions that rely on an AREA field.

JavaScript Implementation

User subroutines are written in JavaScript and consist only of the body of the function that implements the field behavior. There is no special function name; instead, the JavaScript function is associated directly with the condition field being defined. The app can read user subroutines from a file, or you can define a user subroutine in a text input area launched from the dialog box for the feature.

Function Definition

User subroutines written in JavaScript are just the body of the function that implements the field behavior. There is no function name assigned because only the body of the function is required. The contents of the class can be referenced using the JavaScript this reference.

The Flow solver calls user subroutines once per iteration. This approach means that the user subroutine could be called thousands of times during a simulation. This frequency of calls, coupled with the potential to address large portions of the model, requires that care be taken to implement user subroutines that are computationally efficient.

Function Arguments

Two arguments are always passed to the JavaScript function defined by the user. The first argument, support, describes the object on which the user subroutine is defined (for example, boundary or volume). The second argument, timeParameters, describes the current simulation time and increment information.

JavaScript Classes

A number of classes are exposed as part of the user subroutine API, and these classes have a number of properties and methods that are available for use by the user subroutine implementer. The Field class contains the code provided by the user subroutine implementer.

Properties are data members of the class. Each class has a unique set of properties. Only the values property of the Field class can be modified by the implementer. All other properties are read only.

Some classes have methods that can be used for querying, which enables you to compute information related to field computation. The methods on the support class instance passed in as an argument can be used to query current Flow solver state information, such as current temperature or velocity.

Flow solver state information is retrieved using two methods, getScalar and getVector. The method used depends on the algebraic type of the data being queried. For example, the getVector method would be used to query coordinates because that data is stored as a vector. The following arguments are passed to the two query methods: a string that corresponds to the quantity being requested, and an enumeration that describes the location at which the information is required.

Examples: Simple User Subroutines

The following example shows how you can define a heat source that increases linearly as the simulation time progresses:

for (var i = 0; i < this.values.length; i++) {
    this.values[i] = 100.0 * timeParameters.time/timeParameters.totalTime;
}

The following example defines a parabolic inlet velocity:

coord = support.getVector('COORD', CenteringType.FACE);
for (var i = 0; i < this.values.length; i++) {
    this.values[i] = 1.0 - coord[i][1]*coord[i][1];
}

Global Variables

One global object, cfd, is available for use within the user subroutine implementation. This object is used to access system-related resources such as parallel processing and file logging. There are no console or window global objects available (even though they are common in JavaScript code) because this code is not being run in the context of a web browser.

Parallel Execution

User subroutines are executed in parallel when the Flow solver is run in parallel. The Flow solver uses MPI for its parallel execution model, which means that each MPI process will call the user subroutine. The difference between all of these calls is that each user subroutine in each process will address a different part of the boundary or volume. The portion of the model for which a user subroutine is responsible is determined by the solver domain decomposition. It is possible that some processors may not be responsible for any portion of the model.

In many workflows the user subroutine in each process can do its work independently of the other MPI processes. This independence enables the user subroutines to be used for both serial and parallel processing. If coordination between processors is required, additional parallel API calls must be used to ensure that the information is shared appropriately.

The parallel API toolkit provided for user subroutine developers is loosely based on a subset of the MPI API. It is important that these functions be called by all user subroutines in all processors. Failure to do so will result in the program becoming deadlocked, and the simulation will not progress. This is true even for processors that have not been allocated a portion of the model to process.

The following parallel functions are available:

  • Methods for computing global sum, min and max.
  • A method for broadcasting information from one processor to all others.
Single values or arrays of values can be passed to all of these functions. The type of return values always matches the type of the values passed in as arguments. Parallel functions are exposed to the user subroutine implementer via the mpcomm property of the cfd global object.

Example: User Subroutine Suitable for both Serial and Parallel Execution Scenarios

This example illustrates a user subroutine that does a global sum to compute a total heat source. This user subroutine is suitable for both serial and parallel execution scenarios.

volume = support.getScalar('EVOL', CenteringType.ELEMENT);
var vtotal = 0.0;
for (var i = 0; i < volume.length; i++) {
    vtotal += volume[i];
}
vtotal = cfd.mpcomm.globalSum(vtotal);
this.values[0] = vtotal*200.0;

Caching Information

Some of the information computed by a user subroutine is useful to subsequent calls. JavaScript's ability to add information dynamically to the Field class instance itself is useful in this regard. New properties can be added using the this reference that will persist from call to call. Testing to see whether or not the property has been added can be done using the method hasOwnProperty with the custom property name passed as the argument.

Example: Caching Technique

This caching technique can be used to reduce the overhead execution time in the parabolic inlet velocity example:

if (!this.hasOwnProperty('coord')) {
    this.coord = support.getVector('COORD', CenteringType.FACE);
}
for (var i = 0; i < this.values.length; i++) {
    this.values[i] = 1.0 - this.coord[i][1]*this.coord[i][1];
}

API Details

Table 1. Properties and Methods Available in the API
Class Name Property Type Description
SMACfd mpcomm Mpcomm Multi-processor communication object
Mpcomm rank integer MPI rank
size integer Number of MPI processes
Field name string Name of the field type; for example, TEMP
values vector of doubles Field values. In general the length of values equals to the number of elements in the selected support. One exception is the total heat power, where the length of values is one; that is, the field value is assigned to this.values[0].
Boundary surface Surface Surface used to define the boundary
conditions vector of Condition objects Condition objects applied at the boundary
Zone elset ElementSet Element set object
conditions vector of Condition objects Condition object applied on the domain
Surface name string Surface name
elements vector of integers User element labels
faces vector of integers Face identifiers
ElementSet name string Element set name
elements vector of integers User element labels
Condition field Field Field object applied by the condition
TimeParameters dt double Current time increment
time double Current simulation time
totalTime double Total time for the simulation
increment integer Increment number
maxIncrement integer Maximum increment count
Expression definition string Definition of the expression
Table 2. Methods on the Available Classes
Class Name Method Return Type Description
Boundary getScalar(string field, CenteringType centeringType) vector of doubles Get scalar field values
getVector(string field, CenteringType centeringType) vector of double[3] Get vector field values
getLocalCoordinates() vector of double[3] Get facet centroid coordinates in local axis system (Cartesian: X, Y, Z. Cylindrical: ρ, θ, Z). Returns global coordinates if the field is defined in global axis system.
Zone getScalar(string field, CenteringType centeringType) vector of doubles Get scalar field values
getVector(string field, CenteringType centeringType) vector of double[3] Get vector field values
getLocalCoordinates() vector of double[3] Get element centroid coordinates in local axis system (Cartesian: X, Y, Z. Cylindrical: ρ, θ, Z). Returns global coordinates if the field is defined in global axis system.
Mpcomm globalSum(integer | double | Array) Same as input Compute a global sum across all processors
globalMin(integer | double | Array) Same as input Compute a global minimum across all processors
globalMax(integer | double | Array) Same as input Compute a global maximum across all processors
broadcast(integer | double | Array, integer rank) Same as first argument Broadcast information in first argument from specified rank
Expression evaluate(Boundary | Zone support) vector of doubles or vector of double[3] Compute the result of expression evaluated on support.
apply(Boundary | Zone support, Array output) void Fill output with result of expression evaluated on support. output must have the correct length when passed into method.
Table 3. Flow Solver State Information That Can Be Queried
Class Name Method Centering Variable Description
Boundary getScalar FACE TEMP Temperature at boundary facet
PRESSUREGAU Gauge pressure at boundary facet
getVector COORD Facet centroid coordinates in global axis system
V Velocity at boundary facet
Normal Face normal vector
AREA Facet area
Mesh Motion Boundary getVector NODE Normal Face normal vector
Zone getScalar ELEMENT DENSITY Density at element centroid
PRESSUREGAU Gauge pressure at element centroid
TEMP Temperature at element centroid
EVOL Volume at element centroid
VISCOSITY Viscosity at element centroid
getVector COORD Element centroid coordinates in global axis system
V Velocity at element centroid