Skip to content

SmokeSystem

FluxRender.entities.SmokeSystem

SmokeSystem(vec_function, dissipation_factor: float = 0.9995, grid_downscale: int = 4, solid_color: Sequence[float] = (1.0, 1.0, 1.0, 1.0), color_mapper: ColorMapper = None, color_property: Property = Property.VELOCITY, color_clipping_percentiles: Sequence[float] = (5.0, 95.0), emission_edge: Sequence[EmissionEdge] = (EmissionEdge.LEFT, EmissionEdge.RIGHT, EmissionEdge.TOP, EmissionEdge.BOTTOM), smoke_pattern: SmokePattern = SmokePattern.SMOOTH, base_angle_vector=None, custom_color_function=None)

Bases: VectorEntity

A visual entity that simulates and renders fluid advection using a grid-based smoke system.

The system uses a decoupled architecture to maintain performance: physical velocity field evaluation is performed on a downscaled grid via CPU (NumPy), while semi-Lagrangian advection, emission, and bilinear interpolation are executed at full screen resolution on the GPU (Taichi).

Parameters:

Name Type Description Default
vec_function Callable or VectorMathEngine

The mathematical function driving the fluid flow, returning vector components (dx, dy).

required
dissipation_factor float

The rate at which smoke density fades per frame. Values closer to 1.0 make the smoke last longer. (Default: 0.9995)

0.9995
grid_downscale int

The factor by which the physical evaluation grid is reduced relative to the screen resolution to save CPU cycles. The closer the value is to 1, the more accurate the behavior and coloring of the smoke. (Default: 4)

4
solid_color Sequence[float]

A static RGBA color applied to the emitted smoke. If None, the system defaults to dynamic pattern colors. (Default: (1.0, 1.0, 1.0, 1.0))

(1.0, 1.0, 1.0, 1.0)
color_mapper ColorMapper

Maps specific field properties (e.g., velocity, curl) to colors. Overrides solid_color and dynamic patterns if provided.

None
color_property Property

The physical property used by the color_mapper. Only relevant if color_mapper is not None. (Default: Property.VELOCITY)

VELOCITY
color_clipping_percentiles Sequence[float]

Lower and upper percentiles used to clip extreme values before applying the color_mapper. (Default: (5.0, 95.0))

(5.0, 95.0)
emission_edge Sequence[EmissionEdge]

The screen boundaries from which smoke is continuously emitted.

(LEFT, RIGHT, TOP, BOTTOM)
smoke_pattern SmokePattern

The mathematical structural pattern applied to the smoke density at the emission edges. (Default: SmokePattern.SMOOTH)

SMOOTH
base_angle_vector tuple, list, or Callable

The reference vector used for calculating angles when color_property is set to ANGLE.

None
custom_color_function Callable

A user-defined mathematical function replacing standard property evaluation when color_property is set to CUSTOM.

None
Example
import FluxRender as fr

scene = fr.create_workspace()

# 1. Configure boundary conditions
inflow_boundary = fr.BoundaryConfiguration(fr.BoundaryType.INFLOW, inflow_velocity_x=0.05)
outflow_boundary = fr.BoundaryConfiguration(fr.BoundaryType.OPEN_OUTFLOW)

# 2. Initialize the fluid sandbox
with fr.FluidSandbox(
    domain_x_range=(-6, 6),
    domain_y_range=(-6, 6),
    left_boundary=inflow_boundary,
    right_boundary=outflow_boundary,
) as sandbox:

    # 3. Define a solid obstacle
    fr.EquationCollider(equation_function=lambda x, y: (abs(x) ** (2/3) + abs(y) ** (2/3)) <= 1)

# 4. Create Smoke
fr.SmokeSystem(sandbox, solid_color=(0, 1, 1, 1))   # You can set solid_color to None if you want colored smoke

scene.run()

Smoke colored by Property

mapper = fr.ColorMapper()
fr.SmokeSystem(sandbox, color_mapper=mapper, color_property=fr.Property.CURL)
Source code in FluxRender/entities.py
def __init__(self,
            vec_function,
            dissipation_factor: float = 0.9995,
            grid_downscale: int = 4,
            solid_color: Sequence[float] = (1.0, 1.0, 1.0, 1.0),
            color_mapper: ColorMapper = None,
            color_property: Property = Property.VELOCITY,
            color_clipping_percentiles: Sequence[float] = (5.0, 95.0), # from 0 to 100, (min_percentile, max_percentile)
            emission_edge: Sequence[EmissionEdge] = (EmissionEdge.LEFT, EmissionEdge.RIGHT, EmissionEdge.TOP, EmissionEdge.BOTTOM),
            smoke_pattern: SmokePattern = SmokePattern.SMOOTH,

            # Parameters specific to Property.ANGLE color_property
            base_angle_vector = None,

            # Parameters specific to custom color function mode (when color_property is Property.CUSTOM_FUNCTION)
            custom_color_function = None

):
    """
    Args:
        vec_function (Callable or VectorMathEngine): The mathematical function driving the fluid flow,
            returning vector components (dx, dy).
        dissipation_factor (float): The rate at which smoke density fades per frame.
            Values closer to 1.0 make the smoke last longer. (Default: 0.9995)
        grid_downscale (int): The factor by which the physical evaluation grid is reduced relative
            to the screen resolution to save CPU cycles. The closer the value is to 1, the more accurate the behavior and coloring of the smoke. (Default: 4)
        solid_color (Sequence[float]): A static RGBA color applied to the emitted smoke.
            If None, the system defaults to dynamic pattern colors. (Default: (1.0, 1.0, 1.0, 1.0))
        color_mapper (ColorMapper, optional): Maps specific field properties (e.g., velocity, curl)
            to colors. Overrides solid_color and dynamic patterns if provided.
        color_property (Property): The physical property used by the color_mapper. Only relevant if color_mapper is not None. (Default: Property.VELOCITY)
        color_clipping_percentiles (Sequence[float]): Lower and upper percentiles used to clip
            extreme values before applying the color_mapper. (Default: (5.0, 95.0))
        emission_edge (Sequence[EmissionEdge]): The screen boundaries from which smoke is continuously emitted.
        smoke_pattern (SmokePattern): The mathematical structural pattern applied to the smoke density at the emission edges. (Default: SmokePattern.SMOOTH)
        base_angle_vector (tuple, list, or Callable, optional): The reference vector used for calculating
            angles when color_property is set to ANGLE.
        custom_color_function (Callable, optional): A user-defined mathematical function replacing
            standard property evaluation when color_property is set to CUSTOM.


    Example:
        ```python
        import FluxRender as fr

        scene = fr.create_workspace()

        # 1. Configure boundary conditions
        inflow_boundary = fr.BoundaryConfiguration(fr.BoundaryType.INFLOW, inflow_velocity_x=0.05)
        outflow_boundary = fr.BoundaryConfiguration(fr.BoundaryType.OPEN_OUTFLOW)

        # 2. Initialize the fluid sandbox
        with fr.FluidSandbox(
            domain_x_range=(-6, 6),
            domain_y_range=(-6, 6),
            left_boundary=inflow_boundary,
            right_boundary=outflow_boundary,
        ) as sandbox:

            # 3. Define a solid obstacle
            fr.EquationCollider(equation_function=lambda x, y: (abs(x) ** (2/3) + abs(y) ** (2/3)) <= 1)

        # 4. Create Smoke
        fr.SmokeSystem(sandbox, solid_color=(0, 1, 1, 1))   # You can set solid_color to None if you want colored smoke

        scene.run()
        ```

        ## Smoke colored by Property
        ```python
        mapper = fr.ColorMapper()
        fr.SmokeSystem(sandbox, color_mapper=mapper, color_property=fr.Property.CURL)
        ```
    """


    super().__init__(vec_function, color_mapper, color_property, color_clipping_percentiles)

    self.scene = cr.get_scene()
    self.coords = self.scene.coords

    # Main grid (full resolution)
    self.grid_width = self.scene.width
    self.grid_height = self.scene.height

    # Phisical grid (downscaled for performance)
    self.grid_downscale = grid_downscale
    self._vel_width = max(1, self.grid_width // self.grid_downscale)
    self._vel_height = max(1, self.grid_height // self.grid_downscale)

    self.dissipation_factor = dissipation_factor
    self.time_step = 1.0
    self.base_angle_vector = base_angle_vector
    self.custom_color_function = custom_color_function
    self.solid_color = solid_color
    self._apply_solid_color = self.solid_color is not None
    self._gpu_solid_color = ti.Vector(self.solid_color, dt=ti.f32) if self.solid_color else ti.Vector((0, 0, 0, 0), dt=ti.f32)

    self.emission_edge = emission_edge
    self._emission_edge_top = EmissionEdge.TOP in emission_edge
    self._emission_edge_bottom = EmissionEdge.BOTTOM in emission_edge
    self._emission_edge_left = EmissionEdge.LEFT in emission_edge
    self._emission_edge_right = EmissionEdge.RIGHT in emission_edge
    self.smoke_pattern = smoke_pattern

    # 1-Dimensional scalar fields for smoke density
    self._density_old = ti.field(dtype=float, shape=(self.grid_width, self.grid_height))
    self._density_new = ti.field(dtype=float, shape=(self.grid_width, self.grid_height))

    # 4-Dimensional vector fields for RGBA color advection
    self._color_old = ti.Vector.field(4, dtype=float, shape=(self.grid_width, self.grid_height))
    self._color_new = ti.Vector.field(4, dtype=float, shape=(self.grid_width, self.grid_height))

    # self._mapper_color = np.zeros((self.grid_width, self.grid_height, 4), dtype=np.float32)
    self._property_colors = ti.Vector.field(4, dtype=ti.f32, shape=(self._vel_width, self._vel_height))
    self._property_colors_np = np.zeros((self._vel_width, self._vel_height, 4), dtype=np.float32)

    # Downscaled grid for velocity field to improve performance
    self._velocity = ti.Vector.field(2, dtype=ti.f32, shape=(self._vel_width, self._vel_height))
    self._velocity_np = np.zeros((self._vel_width, self._vel_height, 2), dtype=np.float32)

evaluate_angle_vector

evaluate_angle_vector(x: float, y: float) -> tuple

Evaluates the base reference angle vector at the specified spatial coordinates.

This method determines whether the reference angle vector is a static coordinate pair or a dynamically evaluated mathematical function. If it is a callable function, it safely executes it, automatically injecting the current time if the function signature requires it.

Parameters:

Name Type Description Default
x float or ndarray

The x-coordinate(s) in the mathematical world space.

required
y float or ndarray

The y-coordinate(s) in the mathematical world space.

required

Returns:

Name Type Description
tuple tuple

A tuple (component_x, component_y) representing the evaluated reference vector components.

Notes
  • Broadcasting: This method fully supports NumPy broadcasting. You can pass single float values for pinpoint evaluation, or large multidimensional arrays (like those generated by numpy.meshgrid) to evaluate the entire mathematical space simultaneously.
Example

Evaluating a dynamically rotating reference angle at the origin:

import FluxRender as fr
import numpy as np

# [Initializing the scene and coordinate system]

def rotating_reference(x, y, t):
    direction_x = np.cos(t)
    direction_y = np.sin(t)
    return direction_x, direction_y

field = fr.VectorField(
    vec_function = lambda x, y: (y, -x),
    color_property = fr.Property.ANGLE,
    base_angle_vector = rotating_reference
)

# The engine automatically handles the underlying time injection
angle_vector_x, angle_vector_y = field.evaluate_angle_vector(0.0, 0.0)

print(f"Reference angle vector at the origin: ({angle_vector_x}, {angle_vector_y})")

Source code in FluxRender/entities.py
def evaluate_angle_vector(self, x: float, y: float) -> tuple:
    """Evaluates the base reference angle vector at the specified spatial coordinates.

    This method determines whether the reference angle vector is a static
    coordinate pair or a dynamically evaluated mathematical function. If it is
    a callable function, it safely executes it, automatically injecting the
    current time if the function signature requires it.

    Args:
        x (float or numpy.ndarray): The x-coordinate(s) in the mathematical world space.
        y (float or numpy.ndarray): The y-coordinate(s) in the mathematical world space.

    Returns:
        tuple: A tuple (component_x, component_y) representing the evaluated reference vector components.

    Notes:
        * **Broadcasting:** This method fully supports NumPy broadcasting. You can pass single
          float values for pinpoint evaluation, or large multidimensional arrays (like those
          generated by `numpy.meshgrid`) to evaluate the entire mathematical space simultaneously.

    Example:
        Evaluating a dynamically rotating reference angle at the origin:
        ```python
        import FluxRender as fr
        import numpy as np

        # [Initializing the scene and coordinate system]

        def rotating_reference(x, y, t):
            direction_x = np.cos(t)
            direction_y = np.sin(t)
            return direction_x, direction_y

        field = fr.VectorField(
            vec_function = lambda x, y: (y, -x),
            color_property = fr.Property.ANGLE,
            base_angle_vector = rotating_reference
        )

        # The engine automatically handles the underlying time injection
        angle_vector_x, angle_vector_y = field.evaluate_angle_vector(0.0, 0.0)

        print(f"Reference angle vector at the origin: ({angle_vector_x}, {angle_vector_y})")
        ```
    """

    return self.math_engine.evaluate_angle_vector(x, y)

evaluate_field_and_property

evaluate_field_and_property(property_type: Property | None, spatial_coordinate_x: float, spatial_coordinate_y: float)

Evaluates the primary vector function and the specified property at the given spatial coordinates.

This method behaves exactly like evaluate_vector_field, but additionally calculates a scalar value given by property_type (e.g. divergence, rotation, velocity).

Parameters:

Name Type Description Default
property_type Property | None

The specific property to calculate based on the evaluated vector field. If set to None, the method will only evaluate the primary vector function and bypass any property calculations for maximum performance when only vector components are needed.

required
spatial_coordinate_x float / ndarray

The x-coordinate(s) in the mathematical world space.

required
spatial_coordinate_y float / ndarray

The y-coordinate(s) in the mathematical world space.

required

Returns:

Name Type Description
tuple

A tuple (vector_x, vector_y, property_value) where: - vector_x (float / ndarray): The x-component(s) of the evaluated vector field. - vector_y (float / ndarray): The y-component(s) of the evaluated vector field. - property_value (float / ndarray or None): The calculated property value based on the specified property_type. This will be None if property_type is set to None, indicating that no property calculation was performed.

Notes
  • Performance Optimization This method is optimized for performance. If the caller only requires the vector components without any derived properties, they can set property_type to None to skip the property evaluation step entirely, which can significantly reduce computation time, especially for complex properties that require additional function evaluations.
  • Time Injection If the primary vector function or the property evaluator function is time-dependent, this method will automatically inject the current simulation time during their evaluation, allowing for dynamic, time-evolving fields without requiring the user to manage time parameters manually.
Example

Evaluating the vector field and its velocity property at a single point:


Source code in FluxRender/entities.py
def evaluate_field_and_property(self, property_type: Property | None, spatial_coordinate_x: float, spatial_coordinate_y: float):
    """
    Evaluates the primary vector function and the specified property at the given spatial coordinates.

    This method behaves exactly like evaluate_vector_field, but additionally calculates
    a scalar value given by property_type (e.g. divergence, rotation, velocity).

    Args:
        property_type (Property | None): The specific property to calculate based on the evaluated vector field. If set to None, the method will only evaluate the primary vector function and bypass any property calculations for maximum performance when only vector components are needed.
        spatial_coordinate_x (float / ndarray): The x-coordinate(s) in the mathematical world space.
        spatial_coordinate_y (float / ndarray): The y-coordinate(s) in the mathematical world space.

    Returns:
        tuple: A tuple (vector_x, vector_y, property_value) where:
            - vector_x (float / ndarray): The x-component(s) of the evaluated vector field.
            - vector_y (float / ndarray): The y-component(s) of the evaluated vector field.
            - property_value (float / ndarray or None): The calculated property value based on the specified property_type. This will be None if property_type is set to None, indicating that no property calculation was performed.

    Notes:
        * **Performance Optimization** This method is optimized for performance. If the caller only requires the vector components without any derived properties, they can set property_type to None to skip the property evaluation step entirely, which can significantly reduce computation time, especially for complex properties that require additional function evaluations.
        * **Time Injection** If the primary vector function or the property evaluator function is time-dependent, this method will automatically inject the current simulation time during their evaluation, allowing for dynamic, time-evolving fields without requiring the user to manage time parameters manually.

    Example:
        Evaluating the vector field and its velocity property at a single point:
        ```python

        ```

    """

    return self.math_engine.evaluate_field_and_property(property_type, spatial_coordinate_x, spatial_coordinate_y)

evaluate_scalar_function

evaluate_scalar_function(user_defined_function, *spatial_arguments) -> np.ndarray

Evaluates a user-provided mathematical scalar function, automatically handling time injection and vectorization.

This method serves as a robust adapter for custom user logic that maps spatial coordinates (and potentially vector components) to a single scalar value. It analyzes the signature of the provided function to dynamically inject the simulation time if required. It attempts to execute the function using native NumPy vectorization for maximum performance, automatically falling back to numpy.vectorize if strictly scalar Python operations are detected.

Parameters:

Name Type Description Default
user_defined_function Callable

The custom scalar function or lambda to evaluate.

required
*spatial_arguments

The base spatial and/or vector arrays to pass into the function (e.g., evaluated_vector_x, evaluated_vector_y, world_x, world_y).

()

Returns:

Type Description
ndarray

numpy.ndarray: A single NumPy array containing the computed scalar values, properly sanitized and ready for rendering or further mathematical processing.

Notes
  • Time Injection: If user_defined_function accepts exactly one parameter more than the number of provided *spatial_arguments, the current scene time is automatically injected during execution.
  • Vectorization Fallback: You do not need to write strictly vectorized NumPy code. Regular Python scalar operations will be caught and vectorized automatically, though writing native NumPy code is highly recommended for optimal rendering performance.
  • Scalar Output: Unlike its vector counterpart, this method strictly expects the user's function to return a single value (or a single array) per spatial coordinate, not a tuple.
Example

Calculating and printing a custom physical metric (like kinetic energy) at specific points, while the field continues to render its default colors visually:

import FluxRender as fr
import numpy as np

# [Initializing the scene and coordinate system]

field = fr.VectorField(
    vec_function = lambda x, y: (y, -x),
)

# Custom function that internally queries the field for vector data and calculates a scalar property (e.g., kinetic energy = 0.5 * (vx^2 + vy^2))
def calculate_kinetic_energy(x, y):
    vector_dx, vector_dy = field.evaluate_vector_field(x, y)
    kinetic_energy = 0.5 * (vector_dx**2 + vector_dy**2)
    return kinetic_energy

# Define the exact spatial points we want to analyze
target_coordinates_x = np.array([0.0, 1.0, 2.0])
target_coordinates_y = np.array([0.0, 1.0, 2.0])

# Evaluate the custom metric across all points simultaneously
energy_results = field.evaluate_scalar_function(
    calculate_kinetic_energy,
    target_coordinates_x,
    target_coordinates_y
)

print(f"Kinetic energy at points (0,0), (1,1) and (2,2): {energy_results}")

Source code in FluxRender/entities.py
def evaluate_scalar_function(self, user_defined_function, *spatial_arguments) -> np.ndarray:
    """Evaluates a user-provided mathematical scalar function, automatically handling time injection and vectorization.

    This method serves as a robust adapter for custom user logic that maps spatial coordinates (and potentially
    vector components) to a single scalar value. It analyzes the signature of the provided function to dynamically
    inject the simulation time if required. It attempts to execute the function using native NumPy vectorization
    for maximum performance, automatically falling back to `numpy.vectorize` if strictly scalar Python operations
    are detected.

    Args:
        user_defined_function (Callable): The custom scalar function or lambda to evaluate.
        *spatial_arguments: The base spatial and/or vector arrays to pass into the function
            (e.g., evaluated_vector_x, evaluated_vector_y, world_x, world_y).

    Returns:
        numpy.ndarray: A single NumPy array containing the computed scalar values, properly
            sanitized and ready for rendering or further mathematical processing.

    Notes:
        * **Time Injection:** If `user_defined_function` accepts exactly one parameter more than the
          number of provided `*spatial_arguments`, the current scene time is automatically injected
          during execution.
        * **Vectorization Fallback:** You do not need to write strictly vectorized NumPy code. Regular
          Python scalar operations will be caught and vectorized automatically, though writing native
          NumPy code is highly recommended for optimal rendering performance.
        * **Scalar Output:** Unlike its vector counterpart, this method strictly expects the user's
          function to return a single value (or a single array) per spatial coordinate, not a tuple.

    Example:
        Calculating and printing a custom physical metric (like kinetic energy) at specific points,
        while the field continues to render its default colors visually:
        ```python
        import FluxRender as fr
        import numpy as np

        # [Initializing the scene and coordinate system]

        field = fr.VectorField(
            vec_function = lambda x, y: (y, -x),
        )

        # Custom function that internally queries the field for vector data and calculates a scalar property (e.g., kinetic energy = 0.5 * (vx^2 + vy^2))
        def calculate_kinetic_energy(x, y):
            vector_dx, vector_dy = field.evaluate_vector_field(x, y)
            kinetic_energy = 0.5 * (vector_dx**2 + vector_dy**2)
            return kinetic_energy

        # Define the exact spatial points we want to analyze
        target_coordinates_x = np.array([0.0, 1.0, 2.0])
        target_coordinates_y = np.array([0.0, 1.0, 2.0])

        # Evaluate the custom metric across all points simultaneously
        energy_results = field.evaluate_scalar_function(
            calculate_kinetic_energy,
            target_coordinates_x,
            target_coordinates_y
        )

        print(f"Kinetic energy at points (0,0), (1,1) and (2,2): {energy_results}")
        ```
    """

    return self.math_engine._safe_evaluate_scalar_function(user_defined_function, *spatial_arguments)

evaluate_vector_field

evaluate_vector_field(x: float, y: float) -> tuple

Evaluates the primary vector field function at the specified spatial coordinates.

This method acts as a safe execution wrapper for the user-defined vector function. It delegates the execution to the internal evaluation handler, which manages potential numpy broadcasting issues, scalar fallbacks, and automatic time-parameter injection.

Parameters:

Name Type Description Default
x float or ndarray

The x-coordinate(s) in the mathematical world space.

required
y float or ndarray

The y-coordinate(s) in the mathematical world space.

required

Returns:

Name Type Description
tuple tuple

A tuple (vector_x, vector_y) representing the evaluated vector field components.

Notes
  • Broadcasting: This method fully supports NumPy broadcasting. You can pass single float values for pinpoint evaluation, or large multidimensional arrays (like those generated by numpy.meshgrid) to evaluate the entire mathematical space simultaneously.
Example

Evaluating the field at a single focal point:

import FluxRender as fr

# [Initializing the scene and coordinate system]

field = fr.VectorField(
    vec_function = lambda x, y: (y, -x),
    color_property = fr.Property.VELOCITY
)

vector_component_x, vector_component_y = field.evaluate_vector_field(1.0, 0.0)

print(f"Vector field at (1.0, 0.0): ({vector_component_x}, {vector_component_y})")
# Result: (0.0, -1.0)

Evaluating the field at multiple points simultaneously using numpy arrays:

import numpy as np

# Define the exact spatial points we want to analyze
target_coordinates_x = np.array([0.0, 1.0, 2.0])
target_coordinates_y = np.array([0.0, 1.0, 2.0])

x_vectors, y_vectors = vec_field.evaluate_vector_field(
    target_coordinates_x,
    target_coordinates_y
)
print(f"Vector field at points (0,0), (1,1) and (2,2): [{x_vectors[0]}, {y_vectors[0]}] | [{x_vectors[1]}, {y_vectors[1]}] | [{x_vectors[2]}, {y_vectors[2]}]")

Source code in FluxRender/entities.py
def evaluate_vector_field(self, x: float, y: float) -> tuple:
    """Evaluates the primary vector field function at the specified spatial coordinates.

    This method acts as a safe execution wrapper for the user-defined vector
    function. It delegates the execution to the internal evaluation handler,
    which manages potential numpy broadcasting issues, scalar fallbacks, and
    automatic time-parameter injection.

    Args:
        x (float or numpy.ndarray): The x-coordinate(s) in the mathematical world space.
        y (float or numpy.ndarray): The y-coordinate(s) in the mathematical world space.

    Returns:
        tuple: A tuple (vector_x, vector_y) representing the evaluated vector field components.

    Notes:
        * **Broadcasting:** This method fully supports NumPy broadcasting. You can pass single
          float values for pinpoint evaluation, or large multidimensional arrays (like those
          generated by `numpy.meshgrid`) to evaluate the entire mathematical space simultaneously.

    Example:
        Evaluating the field at a single focal point:
        ```python
        import FluxRender as fr

        # [Initializing the scene and coordinate system]

        field = fr.VectorField(
            vec_function = lambda x, y: (y, -x),
            color_property = fr.Property.VELOCITY
        )

        vector_component_x, vector_component_y = field.evaluate_vector_field(1.0, 0.0)

        print(f"Vector field at (1.0, 0.0): ({vector_component_x}, {vector_component_y})")
        # Result: (0.0, -1.0)
        ```

        Evaluating the field at multiple points simultaneously using numpy arrays:
        ```python
        import numpy as np

        # Define the exact spatial points we want to analyze
        target_coordinates_x = np.array([0.0, 1.0, 2.0])
        target_coordinates_y = np.array([0.0, 1.0, 2.0])

        x_vectors, y_vectors = vec_field.evaluate_vector_field(
            target_coordinates_x,
            target_coordinates_y
        )
        print(f"Vector field at points (0,0), (1,1) and (2,2): [{x_vectors[0]}, {y_vectors[0]}] | [{x_vectors[1]}, {y_vectors[1]}] | [{x_vectors[2]}, {y_vectors[2]}]")
        ```
    """

    return self.math_engine.evaluate_primary_vector_function(x, y)

evaluate_vector_function

evaluate_vector_function(user_defined_function, *spatial_arguments) -> tuple

Evaluates a user-provided mathematical function, automatically handling time injection and vectorization.

This method serves as a robust adapter for custom user logic. It analyzes the signature of the provided function to dynamically inject the simulation time if required. Furthermore, it attempts to execute the function using native NumPy vectorization for maximum performance. If the function is strictly scalar (e.g., uses standard Python math modules instead of numpy), it automatically falls back to numpy.vectorize to ensure compatibility across large coordinate grids.

Parameters:

Name Type Description Default
user_defined_function Callable

The custom function or lambda to evaluate.

required
*spatial_arguments

The base spatial arrays to pass into the function (typically world_x, world_y).

()

Returns:

Name Type Description
tuple tuple

A tuple (evaluated_vector_x, evaluated_vector_y) containing the computed field components as sanitized NumPy arrays.

Notes
  • Time Injection: If user_defined_function accepts exactly one parameter more than the number of provided *spatial_arguments (e.g., taking x, y, and t), the current scene time is automatically injected during execution.
  • Vectorization Fallback: You do not need to write strictly vectorized NumPy code. Regular Python scalar operations will be caught and vectorized automatically, though writing native NumPy code is highly recommended for optimal rendering performance.

Examples:

Evaluating a custom mathematical perturbation with automatic time injection:

import FluxRender as fr
import numpy as np

# [Initializing the scene and coordinate system]

field = fr.VectorField(
    vec_function = lambda x, y: (y, -x),
)

# Notice the third parameter 't'. The engine detects this and injects it.
def custom_wind_perturbation(x, y, t):
    perturbation_x = np.sin(x + t)
    perturbation_y = np.cos(y + t)
    return perturbation_x, perturbation_y

spatial_coordinates_x = np.array([0.0, 1.0, 2.0])
spatial_coordinates_y = np.array([0.0, 1.0, 2.0])

result_vectors_x, result_vectors_y = field.evaluate_vector_function(
    custom_wind_perturbation,
    spatial_coordinates_x,
    spatial_coordinates_y
)

Source code in FluxRender/entities.py
def evaluate_vector_function(self, user_defined_function, *spatial_arguments) -> tuple:
    """Evaluates a user-provided mathematical function, automatically handling time injection and vectorization.

    This method serves as a robust adapter for custom user logic. It analyzes the signature of the
    provided function to dynamically inject the simulation time if required. Furthermore, it attempts
    to execute the function using native NumPy vectorization for maximum performance. If the function
    is strictly scalar (e.g., uses standard Python `math` modules instead of `numpy`), it automatically
    falls back to `numpy.vectorize` to ensure compatibility across large coordinate grids.

    Args:
        user_defined_function (Callable): The custom function or lambda to evaluate.
        *spatial_arguments: The base spatial arrays to pass into the function (typically world_x, world_y).

    Returns:
        tuple: A tuple (evaluated_vector_x, evaluated_vector_y) containing the computed field components
            as sanitized NumPy arrays.

    Notes:
        * **Time Injection:** If `user_defined_function` accepts exactly one parameter more than the
          number of provided `*spatial_arguments` (e.g., taking x, y, and t), the current scene time
          is automatically injected during execution.
        * **Vectorization Fallback:** You do not need to write strictly vectorized NumPy code. Regular
          Python scalar operations will be caught and vectorized automatically, though writing native
          NumPy code is highly recommended for optimal rendering performance.


    Examples:
        Evaluating a custom mathematical perturbation with automatic time injection:
        ```python
        import FluxRender as fr
        import numpy as np

        # [Initializing the scene and coordinate system]

        field = fr.VectorField(
            vec_function = lambda x, y: (y, -x),
        )

        # Notice the third parameter 't'. The engine detects this and injects it.
        def custom_wind_perturbation(x, y, t):
            perturbation_x = np.sin(x + t)
            perturbation_y = np.cos(y + t)
            return perturbation_x, perturbation_y

        spatial_coordinates_x = np.array([0.0, 1.0, 2.0])
        spatial_coordinates_y = np.array([0.0, 1.0, 2.0])

        result_vectors_x, result_vectors_y = field.evaluate_vector_function(
            custom_wind_perturbation,
            spatial_coordinates_x,
            spatial_coordinates_y
        )
        ```
    """

    return self.math_engine._safe_evaluate_vector_function(user_defined_function, *spatial_arguments)