ParticleSystem
FluxRender.entities.ParticleSystem
ParticleSystem(vec_function, color_mapper: ColorMapper = ColorMapper(), count: int = 10000, lifetime: float = 3.0, radius: float = 2, color_property: Property = Property.VELOCITY, opacity_function=None, speed: float = 1.0, normalize_speed=False, speed_scale: ScaleType = ScaleType.LINEAR, max_speed_percentile: float = 95.0, emitter=None, color_clipping_percentiles: Sequence[float] = (5.0, 95.0), scale_function=None, base_angle_vector=None, custom_color_function=None)
Bases: VectorEntity
A dynamic visualizer that simulates thousands of particles flowing through a vector field.
While a VectorField uses static arrows to show the direction of the math, a ParticleSystem
brings it to life. It drops thousands of tiny "tracers" into your mathematical fluid and lets
them flow. This is perfect for visualizing aerodynamics, fluid dynamics, or simply creating
hypnotic, beautiful animations of your equations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
vec_function
|
Callable / VectorMathEngine
|
The math driving the flow. A function taking (x, y) and optionally (t) returning vector components (dx, dy), or a pre-built VectorMathEngine. IMPORTANT REFERENCE NOTE: If you pass an existing VectorMathEngine instance
AND simultaneously provide overriding parameters (such as |
required |
color_mapper
|
ColorMapper
|
The palette used to paint the particles based on their physical properties. (Default: ColorMapper()) |
ColorMapper()
|
count
|
int
|
How many particles to simulate at once. Higher numbers look denser but require more GPU power. (Default: 10000) |
10000
|
lifetime
|
float
|
How many seconds a particle lives before it fades out and respawns at a new location. Keeps the screen from getting too chaotic. (Default: 3.0) |
3.0
|
radius
|
float
|
The visual size (thickness) of each particle dot in pixels. (Default: 2.0) |
2
|
color_property
|
Property
|
What mathematical aspect decides the particle's color (e.g., VELOCITY for speed, CURL for rotation, ANGLE for direction). (Default: Property.VELOCITY) |
VELOCITY
|
opacity_function
|
Callable
|
A custom fade-in/fade-out curve. A function that takes a particle's normalized age (0.0 = just born, 1.0 = about to die) and returns its opacity (0.0 to 1.0). If None, the default smoothing function will be applied. |
None
|
speed
|
float
|
The global "gas pedal" for the animation. Multiplies how fast particles move across the screen. (Default: 1.0) |
1.0
|
normalize_speed
|
bool
|
If True, ignores the mathematical length of the vectors—every particle moves at exactly the same speed (great for analyzing pure direction). If False, particles in strong currents move fast, and those in weak currents move slowly. (Default: False) |
False
|
speed_scale
|
ScaleType
|
How the raw math translates to visual speed on screen. Use LOGARITHMIC or EXPONENTIAL if your math produces extreme values. (Default: ScaleType.LINEAR) |
LINEAR
|
max_speed_percentile
|
float
|
Outlier protection (0 to 100). Clips the fastest physical vectors at this percentile. Prevents mathematical singularities (like dividing by zero) from shooting particles off the screen at infinite speed. (Default: 95.0) |
95.0
|
emitter
|
SpatialRegion or dict
|
Controls where new particles are born:
• Single region: Pass a |
None
|
color_clipping_percentiles
|
tuple
|
The (min, max) percentiles used to ignore extreme outliers when mapping colors, ensuring your color gradient isn't ruined by a single infinite value. (Default: (5.0, 95.0)) |
(5.0, 95.0)
|
scale_function
|
Callable
|
[Speed Scale CUSTOM] A user-defined function mapping normalized vector lengths [0, 1] to output speeds [0, 1]. Used only when speed_scale is ScaleType.CUSTOM and normalize_speed is False. |
None
|
base_angle_vector
|
tuple, list, or Callable
|
[Property.ANGLE] The reference baseline for calculating angles. Can be a static [x, y] list or a dynamic vector function of (x, y, t). |
None
|
custom_color_function
|
Callable
|
[Property.CUSTOM] A user-defined function for calculating colors. To maximize performance, the engine strictly avoids redundant math. Since the VectorField or ParticleSystem already evaluates the primary vector function to render arrows or move particles, it passes these exact, pre-calculated results directly into your color function. The function must accept 4 or 5 parameters:
Should return a scalar or a NumPy array. Use vectorized NumPy operations for maximum efficiency. |
None
|
Example
Basic usage with a simple rotational field:
import FluxRender as fr
# [Initializing the scene and coordinate system]
def rotational_field(x, y):
return -y, x
particle_system = ParticleSystem(vec_function = rotational_field)
scene.add(particle_system)
The same example, but with particles colored by their angle and a custom color palette:
import FluxRender as fr
def rotational_field(x, y):
return -y, x
# Blue Shade Color Mapping
my_color_mapper = fr.ColorMapper(max_hue = 180, min_hue = 240, max_saturation = 1, min_saturation = 0.8, max_lightness = 0.6, min_lightness = 0.45)
particle_system = fr.ParticleSystem(
vec_function = rotational_field,
color_mapper = my_color_mapper,
color_property = fr.Property.ANGLE # Color based on vector angle relative to base_angle_vector (default [1.0, 0.0])
)
scene.add(particle_system)
Source code in FluxRender/entities.py
1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 | |
evaluate_angle_vector
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
)
scene.add(field)
# 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
evaluate_scalar_function
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_functionaccepts 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),
)
scene.add(field)
# 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
evaluate_vector_field
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
)
scene.add(field)
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
evaluate_vector_function
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_functionaccepts 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),
)
scene.add(field)
# 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
)