User-defined dynamics#

User-defined dynamics (UDD) is a powerful feature in Flow360 that allows you to implement custom control laws and coupled physics within your simulations. You define state variables that evolve according to mathematical expressions you specify, creating feedback loops where flow solution quantities influence the simulation in real-time.

Overview#

User Defined Dynamics enables you to:

  • Read from the flow solution: Access flow quantities such as forces, moments, pressure coefficients, or other solution variables

  • Compute custom quantities: Calculate derived values using state variables, constants, and input variables

  • Control simulation parameters: Influence boundary conditions, rotation rates, angles of attack, or other simulation inputs

This creates a feedback loop where the flow solution affects the dynamics, and the dynamics affect the flow solution, enabling simulations of complex coupled phenomena.

Web UI Implementation#

Important: Currently, User Defined Dynamics must be configured using JSON files in the Web UI. The Web UI does not yet provide a graphical interface for setting up UDD parameters. You will need to manually edit the case JSON file to add the User Defined Dynamics configuration.

Recommendation: We recommend using the Python API for implementing User Defined Dynamics, as it provides more flexibility, better debugging capabilities, and a more intuitive interface for complex dynamic scenarios.

Basic Structure#

A User Defined Dynamics system consists of the following components:

  • name: A descriptive identifier for the dynamics system. Results are saved in results/udd_NAME_v2.csv where NAME matches this value.

  • input_vars: List of flow solution variables that your dynamics system reads from the simulation. Available variables include:

    • Force and moment coefficients: CL, CD

    • Force components: forceX, forceY, forceZ

    • Moment components: momentX, momentY, momentZ (X/Y/Z moments with respect to the moment center defined in ReferenceGeometry)

    • BET disk variables: bet_NUM_torque, bet_NUM_thrust (where NUM is the index of the BET disk starting from 0)

  • constants: Named constant values used throughout your expressions. These can include physical constants (density, speed of sound), geometric parameters (rotation centers, axes), control parameters (proportional gain Kp, integral gain Ki, target values), structural parameters (stiffness K, damping ratio zeta, natural frequency omega_N, moment of inertia I), or any other values needed for your calculations.

  • state_vars_initial_value: Initial values for each state variable. State variables are referenced as state[0], state[1], state[2], etc. in your expressions. Each initial value can be a numeric constant (e.g., "0.0"), an expression using constants, or an expression referencing simulation variables (e.g., "alphaAngle").

  • update_law: List of C-syntax expressions that define how each state variable evolves over time. These expressions are evaluated at each pseudo-step and compute new values for the state variables based on the current state, input variables, constants, and simulation variables (pseudoStep, physicalStep, timeStepSize, t, etc.).

    Important: The update_law is used exclusively to update state variables. It does not directly control input or output variables. State variables serve as internal memory for your dynamics system, enabling you to implement control laws, structural dynamics, accumulators, and other custom mathematical relationships.

  • output_vars: Dictionary mapping output variable names to expressions that compute their values from the updated state variables. Output variables directly control simulation parameters. Available variables include:

    • alphaAngle: Angle of attack (global, no entity required)

    • betaAngle: Sideslip angle (global, no entity required)

    • theta: Rotation angle (in radians) for rotating volume zones (requires output_target)

    • omega: Angular velocity (in radians per second) for rotating volume zones (requires output_target)

    • omegaDot: Angular acceleration (in radians per second squared) for rotating volume zones (requires output_target)

    • bet_NUM_omega: Angular velocity for BET disk (NUM is the index of the BET disk starting from 0)

    • actuatorDisk_<DISK_ENTITY_NAME>_thrustMultiplier: Thrust multiplier for actuator disk (where <DISK_ENTITY_NAME> is the name of the actuator disk entity)

    • actuatorDisk_<DISK_ENTITY_NAME>_torqueMultiplier: Torque multiplier for actuator disk (where <DISK_ENTITY_NAME> is the name of the actuator disk entity)

    Caution: Modifications to output variables directly affect the simulation.

  • input_boundary_patches: (Optional) List of surface boundaries where input variables should be computed. If specified, quantities like CL or CD are computed only from these surfaces. For input variables that already specify their source in the name (like bet_NUM_torque), this parameter has no effect.

  • output_target: (Optional) The target entity where output variables apply. Required for output variables that are associated with specific entities, such as theta, omega, or omegaDot which control rotation of a volume zone. In these cases, specify the target entity (e.g., a rotating volume zone such as a Cylinder). For global output variables like alphaAngle and betaAngle, output_target should be None or omitted.

Note: All expressions and variables entered in User Defined Dynamics must be C-syntax compatible. For details on the syntax requirements and supported operations, see the Flow360 Python API documentation.

Creating a JSON Configuration#

The JSON structure mirrors the Python API, with the following key fields:

  • input_vars: Array of strings (e.g., ["momentY", "CL"])

  • constants: Object mapping constant names to numeric values

  • output_vars: Object mapping output variable names to expression strings

  • state_vars_initial_value: Array of strings (expressions or numeric values)

  • update_law: Array of expression strings

  • input_boundary_patches: (Optional) Object containing entity references

  • output_target: (Optional) Object containing entity reference

Example JSON Configuration#

The following example shows a complete JSON configuration for a spring-mass-damper system controlling rotation:

{
    "input_vars": [
        "momentY"
    ],
    "constants": {
        "I": 0.443768309310345,
        "zeta": 0.005,
        "K": 0.005,
        "omegaN": 0.10614678867902483,
        "theta0": 0.08726646259971647
    },
    "output_vars": {
        "omegaDot": "state[0];",
        "omega": "state[1];",
        "theta": "state[2];"
    },
    "state_vars_initial_value": [
        "-0.008849191649331902",
        "0.0",
        "0.8726646259971648"
    ],
    "update_law": [
        "(pseudoStep == 0) ? ((momentY - K * ( state[2] - theta0 ) - 2 * zeta * omegaN * I *state[1] ) / I) : (state[0]);",
        "(pseudoStep == 0) ? (state[1] + state[0] * timeStepSize) : (state[1]);",
        "(pseudoStep == 0) ? (state[2] + state[1] * timeStepSize) : (state[2]);"
    ],
    "input_boundary_patches": {
        "stored_entities": [
            {
                "name": "plateBlock/noSlipWall"
            }
        ]
    },
    "output_target": {
        "name": "plateBlock"
    }
}

Corresponding Python Code#

The JSON configuration above corresponds to the following Python API code:

dynamic = fl.UserDefinedDynamic(
    name="dynamicTheta",
    input_vars=["momentY"],
    constants={
        "I": I,
        "zeta": zeta,
        "K": K,
        "omegaN": omegaN, 
        "theta0": theta0,
    },
    output_vars={
        "omegaDot": "state[0];",
        "omega": "state[1];",
        "theta": "state[2];",
    },
    state_vars_initial_value=[str(initOmegaDot), "0.0", str(initTheta)],
    update_law=[
        "if (pseudoStep == 0) (momentY - K * ( state[2] - theta0 ) - 2 * zeta * omegaN * I *state[1] ) / I; else state[0];",
        "if (pseudoStep == 0) state[1] + state[0] * timeStepSize; else state[1];",
        "if (pseudoStep == 0) state[2] + state[1] * timeStepSize; else state[2];",
    ],
    input_boundary_patches=vm["plateBlock/noSlipWall"],
    output_target=vm["plateBlock"],
)

Note: In the JSON version, the conditional expressions in update_law use ternary operator syntax (condition) ? (true_value) : (false_value) instead of the if-else statements shown in the Python code.

Best Practices#

  1. Start with simple motions and gradually add complexity

  2. Test your UDD implementation with coarse meshes before running full simulations

  3. Ensure mesh rotation settings are appropriate for the expected range of motion

  4. Monitor forces and moments during motion to ensure physical behavior

  5. Use the Python API when possible for easier debugging and more intuitive configuration