NeuralFlowTheory & User Reference Manual
User Python Scripting Guide
LIKUA HomeManual Home
NeuralFlow User's Guide v2.0 — Chapter 16

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:

Injected Helper Functions Provided by NeuralFlow
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 , shape .
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 , shape .

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. Only FACE_PROFILE() is writable.

16.2 Defining Custom Time Step Size (DEFINE_DELTAT)

The physical transient time step () can be dynamically computed at the beginning of each physical time step by defining a global function named 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 float representing 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(), and TIME_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:

  1. 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.

  2. Open the Transient Solution Control menu by selecting Time Algorithm as Transient.

  3. Click the f button located next to the Time Step Size [s] line edit field to enable user-defined time stepping via Python.

  4. 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 if DEFINE_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 and progressively ramps up the time step size by a growth factor until a specified ceiling is reached:

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:

    1. Writing values directly into the provided FACE_PROFILE() NumPy array and returning None.

    2. 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