16 User Python Scripting Guide
NeuralFlow provides an embedded Python scripting interface that allows
users to dynamically control physical time-stepping schedules and
customize spatially or temporally varying boundary conditions.
Transitioning away from legacy C/C++ user-defined functions (UDFs) that
required manual compilation, NeuralFlow automatically initializes a
pyScripts folder within the active working directory
whenever a process is spawned, generating a sample
UserScripts.py file as a starting template.
However, users are not obligated to use this default template. By selecting Load User Python Script from the UDF Menu, a file browse dialog opens, allowing users to select and import any pre-existing, custom, or updated Python script into NeuralFlow. At solver startup, NeuralFlow executes the designated Python module and injects high-level helper functions alongside high-performance NumPy array interfaces directly into the runtime environment.
16.1 Injected Helper Functions
During solver execution, NeuralFlow exposes several built-in callback functions to query the current simulation state and access boundary grid or solution fields. These functions are automatically available within the Python execution scope:
| Function Name | Return Type | Description / Units |
|---|---|---|
TIME() |
float |
Current physical simulation time |
ITERATION() |
int |
Current global solver iteration counter. |
TIME_STEP() |
float |
Current/previous accepted physical time
step |
TIME_STEP_INDEX() |
int |
Number of completed physical time steps. |
SUB_ITERATION() |
int |
Current implicit transient sub-iteration index. |
FACE_CENTERS() |
numpy.ndarray |
Coordinates of face centers |
FACE_PROFILE() |
numpy.ndarray |
Writable output buffer for selected boundary profile. |
FACE_PRESSURE() |
numpy.ndarray |
Read-only current face pressure values
|
FACE_TEMPERATURE() |
numpy.ndarray |
Read-only current face temperature values
|
FACE_VELOCITY() |
numpy.ndarray |
Read-only face velocity components |
Important Rule on Array Usage:
The helper functions returning mesh or field arrays (FACE_CENTERS,FACE_PRESSURE,FACE_TEMPERATURE,FACE_VELOCITY) are read-only and available only inside boundary-profile callbacks. OnlyFACE_PROFILE()is writable.
16.2
Defining Custom Time Step Size (DEFINE_DELTAT)
The physical transient time step (DEFINE_DELTAT() inside the user Python script.
16.2.1 DEFINE_DELTAT
Contract and Rules
To ensure correct time integration and solver stability, the
DEFINE_DELTAT() callback must comply with the following
constraints:
Function Signature: Exactly one function named
DEFINE_DELTAT()taking no arguments must be defined.Return Value: Must return a single positive finite
floatrepresenting the new physical time step in seconds.Scope:
DEFINE_DELTAT()is global to the entire domain. Do not call face-local mesh/field functions (FACE_CENTERS(),FACE_PROFILE(), etc.) inside this function.State Querying: Use
TIME(),TIME_STEP(), andTIME_STEP_INDEX()to establish time-dependent or growth-rate-limited schedules.
16.2.2 GUI Activation and Fallback Mechanics
To enable user-defined time stepping in NeuralFlow:
Navigate to the UDF Menu in GUIX-H and click Load User Python Script to select and import your custom Python file (
.py) into NeuralFlow.Open the Transient Solution Control menu by selecting Time Algorithm as Transient.
Click the f
button located next to the Time Step Size [s] line edit field to enable user-defined time stepping via Python. Specify a safe fallback value in the Time Step Size [s] field.
Fallback Logic: The GUI Time Step Size[s] value is used during the very first step (
TIME_STEP_INDEX() == 0) and serves as an automatic fallback ifDEFINE_DELTAT()is disabled, not found in the script, or returns an invalid/non-positive value.
16.2.3 Practical Code Examples for
DEFINE_DELTAT
16.2.3.1 Example 1: Bounded Growing Time Step
In transient startup scenarios, jumping directly to a large time step
can cause numerical divergence. The following script starts with a small
initial time step
def DEFINE_DELTAT():
dt_initial = 1.0e-7
dt_min = 1.0e-8
dt_max = 1.0e-5
growth = 1.05
# Use initial dt for the first time step
if TIME_STEP_INDEX() == 0:
return dt_initial
# Scale time step based on previously accepted step
dt_candidate = growth * TIME_STEP()
return max(dt_min, min(dt_max, dt_candidate))
16.2.3.2 Example 2: Physical-Time Schedule
When simulating physical phenomena with distinct time scales (e.g.,
rapid shock opening followed by steady relaxation), time steps can be
scheduled based on the current physical time TIME():
def DEFINE_DELTAT():
t = TIME()
if t < 1.0e-6:
return 1.0e-7
elif t < 1.0e-5:
return 1.0e-6
else:
return 1.0e-5
16.3
Defining Custom Boundary Conditions
(BoundaryProfile_*)
Boundary profile callbacks allow users to prescribe spatially non-uniform or time-dependent boundary conditions (e.g., velocity profiles, transient temperature ramps, or pressure-dependent scalars).
16.3.1 Boundary Profile Contract and Execution Flow
Functions intended for boundary condition assignment must follow specific naming conventions and execution contracts:
Loading the Script: Navigate to the UDF Menu in GUIX-H and click Load User Python Script to import your custom Python file (
.py).Selection in GUI: Once defined in the script, these functions automatically populate the drop-down selection menus for individual boundary scalar variables in GUIX-H.
Invocation Scope: The solver executes the selected callback once per boundary face section and once per requested scalar variable.
Buffer Population: The callback must populate exactly one scalar value per boundary face. This can be achieved by:
Writing values directly into the provided
FACE_PROFILE()NumPy array and returningNone.Returning a 1D NumPy array or scalar value matching the number of section faces.
16.3.2 Practical Code Examples for Boundary Profiles
16.3.2.1 Example 1: Time-Dependent Linear Ramp
Prescribes a boundary variable that grows linearly with physical
simulation time using TIME():
def BoundaryProfile_time_ramp():
"""
Applies a time-dependent linear scalar ramp across all faces.
"""
ramp_slope = 100.0 # Slope [units/s]
prof = FACE_PROFILE()
prof[:] = ramp_slope * TIME()
return None
16.3.2.2 Example 2: Local Velocity Magnitude Profile
Computes a scalar boundary profile based on the magnitude of the
local velocity vector queried via FACE_VELOCITY():
def BoundaryProfile_velocity_magnitude():
"""
Calculates scalar profile from local 2D/3D velocity magnitude.
"""
v = FACE_VELOCITY()
prof = FACE_PROFILE()
if v.shape[1] == 2:
# 2D Flow: sqrt(u^2 + v^2)
prof[:] = (v[:, 0]**2 + v[:, 1]**2) ** 0.5
else:
# 3D Flow: sqrt(u^2 + v^2 + w^2)
prof[:] = (v[:, 0]**2 + v[:, 1]**2 + v[:, 2]**2) ** 0.5
return None
16.3.2.3 Example 3: Direct Pressure Field Transfer
Demonstrates reading local thermodynamic pressure via
FACE_PRESSURE() and mapping it into the target profile
buffer:
def BoundaryProfile_pressure_copy():
"""
Sets the selected boundary scalar equal to the local face pressure.
"""
prof = FACE_PROFILE()
prof[:] = FACE_PRESSURE()
return None