Particle Swarms

Swarm

class underworld3.swarm.Swarm[source]

Bases: Stateful, uw_object

A basic particle swarm implementation for Lagrangian particle tracking and data storage.

The UW Swarm class provides a simplified particle management system that uses PETSc’s DMSWARM_BASIC type. Unlike the standard Swarm class, this implementation does not rely on PETSc to determine ranks for particle migration but instead uses our own kdtree neighbour-domain computations.

This class is preferred for most operations except where particle / cell relationships are always required.

Parameters:
  • mesh (uw.discretisation.Mesh) – The mesh object that defines the computational domain for particle operations. Particles will be associated with this mesh for spatial queries and operations.

  • recycle_rate (int, optional) – Particle recycling (streak swarms) is NOT implemented: values > 1 raise NotImplementedError. The parameter is retained so that existing calls passing the default (0 or 1, meaning no recycling) keep working.

  • verbose (bool, optional) – Enable verbose output for debugging and monitoring particle operations. Default is False.

Examples

Create a basic swarm and populate with particles:

>>> import underworld3 as uw
>>> mesh = uw.meshing.UnstructuredSimplexBox(minCoords=(0,0), maxCoords=(1,1))
>>> swarm = uw.swarm.Swarm(mesh=mesh)
>>> swarm.populate(fill_param=2)

Add custom particle data:

>>> temperature = swarm.add_variable("temperature", 1)
>>> velocity = swarm.add_variable("velocity", mesh.dim)

Particle migration after coordinate updates:

Note: writing particle coordinates (via swarm._particle_coordinates.data or the coords setter) marks the swarm for migration; the collective migrate() itself is DEFERRED to the next collective point — a migration_control() context exit, an explicit swarm.migrate(), or solve entry — never run per-write (uneven writes would deadlock).

Note: swarm.populate uses a the mesh point locations for discontinuous interpolants to determine the particle locations.

instances = 0
__init__(mesh, recycle_rate=0, verbose=False, clip_to_mesh=True)[source]
property mesh

The mesh this swarm operates on

property local_size

Number of particles on this MPI rank.

Returns:

Local particle count.

Return type:

int

See also

dm.getLocalSize

Underlying PETSc method.

property data

Particle coordinates (alias for points; read-only snapshot).

Deprecated since version 0.99.0: Use coords instead.

Returns:

Particle coordinate array of shape (n_particles, dim).

Return type:

numpy.ndarray

property points

Swarm particle coordinates in physical units (read-only snapshot).

Deprecated since version 0.99.0: Read coordinates via coords; write them via the coords setter (physical units) or swarm._particle_coordinates.data (model units).

The returned array is a detached, read-only copy. The previous writable wrapper ran collective particle migration from inside a per-write callback, which deadlocks when ranks write unevenly, and reading it could force a collective migration after mesh changes — so a read performed on some ranks only could hang (#379). Like mesh.points (BF-18), the write path is removed rather than repaired.

Returns:

Particle coordinates (with units if mesh.units is set).

Return type:

numpy.ndarray or UnitAwareArray

property coords

Swarm particle coordinates in physical units.

This is the primary public interface for accessing particle coordinates. Coordinates are automatically converted from internal model units to physical units based on the model’s reference quantities.

Returns:

Particle coordinates in physical units with shape (n_particles, dim). If model has reference quantities, returns UWQuantity with appropriate length units. Otherwise returns plain array.

Return type:

UWQuantity or numpy.ndarray

Notes

  • Coordinates are converted from model units to physical units automatically

  • For internal use with model units, access swarm._particle_coordinates.data

  • Setting coordinates accepts either physical units or plain numbers

Examples

>>> coords_physical = swarm.coords  # Get physical coordinates
>>> swarm.coords = new_coords_with_units  # Set from physical units

See also

swarm.units

Get the unit specification for coordinates

property units

Unit specification for swarm coordinates.

Returns the physical unit string for coordinates based on the model’s reference quantities. This indicates what units the coordinates are in when accessed via the coords property.

Returns:

Unit string for coordinates (e.g., ‘kilometer’, ‘meter’), or None if no reference quantities are set

Return type:

str or None

Examples

>>> print(swarm.units)  # 'kilometer' if length_scale was set in km
>>> coords = swarm.coords  # Coordinates in kilometers
property clip_to_mesh

Whether particles are clipped to remain within mesh boundaries.

When True (default), particles that move outside the mesh domain during advection or coordinate updates are removed or repositioned to stay within bounds. When False, particles can exist outside the mesh domain.

Returns:

Current clipping state.

Return type:

bool

See also

dont_clip_to_mesh

Context manager to temporarily disable clipping.

dont_clip_to_mesh()[source]

Context manager that temporarily disables mesh clipping for the swarm. swarm.migrate is called automatically when exiting the context.

Usage:
with swarm.dont_clip_to_mesh():

# swarm operations that should not be clipped to mesh swarm.data = new_positions

migration_disabled()[source]

Legacy context manager that completely disables migration. Use migration_control(disable=True) for new code.

Context manager that temporarily disables particle migration for the swarm. Migration is NOT called when exiting the context. Writes made inside the context are packed to PETSc at exit (only the migration is suppressed — data is never discarded).

Usage:
with swarm.migration_disabled():

# swarm operations that should not trigger migration swarm.data = new_positions # … other operations … # migrate() will be skipped during these operations

migration_control(disable=False)[source]

Context manager to control particle migration behavior.

Parameters:

disable (bool) – If False (default), migration is deferred until context exit. If True, migration is completely disabled.

Examples

Defer migration until end (default):

coords = swarm._particle_coordinates.data
with swarm.migration_control():
    coords[mask1] += delta1
    coords[mask2] *= scale
    # Migration happens HERE on exit

Completely disable migration:

with swarm.migration_control(disable=True):
    # Operations where migration should never happen
    # No migration on exit

In both modes, variable/coordinate writes made inside the context are flushed to the underlying DMSwarm at exit — only the migration itself is deferred (default) or skipped (disable=True).

populate(**kwargs)

Populate the swarm with particles throughout the domain.

Parameters:

fill_param – Parameter determining the particle count per cell (per dimension) for the given layout, using the mesh degree.

Raises:

RuntimeError – If the swarm has already been initialized with particles.

migrate(**kwargs)

Migrate swarm across processes after coordinates have been updated.

The algorithm uses a global kD-tree for the centroids of the domains to decide the particle mpi.rank (send to the closest) If the particles are mis-assigned to a particular mpi.rank, the next choice is the second-closest and so on.

A few particles are still not found after this distribution process which probably means they are just outside the mesh. If some points remain lost, they will be deleted if delete_lost_points is set.

Implementation note:

We retained (above) the name DMSwarmPIC_coor for the particle field to allow this routine to be inherited by a PIC swarm which has this field pre-defined. (We’d need to add a cellid field as well, and re-compute it upon landing)

Note: This is a COLLECTIVE operation - all MPI ranks must call it.

add_particles_with_coordinates(coordinatesArray)[source]

Add particles at given coordinates, keeping only locally-owned points.

Each rank filters the input array and adds only the points that fall within its local domain partition. Non-local points are silently ignored, so it is safe to pass the same global coordinate array to every rank — no duplicates will be created.

This is the recommended method for user code. For pre-partitioned data where each rank already holds only its own points, this method also works correctly.

Parameters:

coordinatesArray (numpy.ndarray) – Coordinates of new particles, shape (n, dim).

Returns:

npoints – Number of points actually added on this rank.

Return type:

int

add_particles_with_global_coordinates(globalCoordinatesArray, migrate=True, delete_lost_points=True)[source]

Insert the full coordinate array on every rank (low-level primitive).

Every rank inserts all supplied points into its local swarm. There is no locality filtering and no deduplication: migration is a scatter that routes each inserted particle to the rank owning its location, so passing the same array on every rank at np processes yields np copies of every point.

Correct usage is one of:

  • migrate=False, where each rank deliberately keeps a full copy of the points (the global-evaluation / mesh-transfer pattern), or

  • input that is pre-partitioned across ranks — or supplied on rank 0 only, with empty (0, dim) arrays elsewhere — followed by migration to route each point to its owner.

For general use, prefer add_particles_with_coordinates(), which accepts a rank-identical array and filters non-local points so each point is added exactly once.

Parameters:
  • globalCoordinatesArray (numpy.ndarray) – Coordinates of new particles, shape (n, dim).

  • migrate (bool) – Run PETSc swarm migration after insertion (default True).

  • delete_lost_points (bool) – Remove particles that fall outside any rank’s domain during migration (default True).

Returns:

npoints – Number of points added on this rank before migration.

Return type:

int

save(filename, compression=False, compressionType='gzip', force_sequential=False)[source]

Save the swarm coordinates to a h5 file.

Parameters:
  • filename (int) – The filename of the swarm checkpoint file to save to disk.

  • compression (bool | None) – Add compression to the h5 files (saves space but increases write times with increasing no. of processors)

  • compressionType (str | None) – Type of compression to use, ‘gzip’ and ‘lzf’ supported. ‘gzip’ is default. Compression also needs to be set to ‘True’.

read_timestep(base_filename, swarm_id, index, outputPath='', migrate=True)[source]

Restore the swarm’s particle coordinates from a saved timestep.

Adds particles at the coordinates saved by Swarm.write_timestep; call this on a freshly-built swarm before restoring variable values with SwarmVariable.read_timestep. File read (matching write_timestep(filename, swarmname, index, outputPath=...)):

{outputPath}/{base_filename}.{swarm_id}.{index:05}.h5

Parameters:
  • base_filename (str) – Base name used when the checkpoint was written (filename on write_timestep; note SwarmVariable.read_timestep spells it data_filename — the two signatures predate a common convention).

  • swarm_id (str) – Swarm identifier used when the checkpoint was written (swarmname on write_timestep).

  • index (int) – Timestep index (zero-padded to five digits in the filename).

  • outputPath (str, optional) – Directory holding the checkpoint files (default: current directory).

  • migrate (bool, optional) – True (default): every rank reads the saved coordinates and keeps only the points it owns — the parallel restore path. False: every rank keeps a full copy of the saved swarm, including points outside the local (or entire) domain — useful for debugging, visualisation, or when the mesh has been adapted since the save.

add_variable(name, size=1, dtype=<class 'float'>, proxy_degree=2, units=None)[source]

Add a variable to the swarm.

Variables must be created before the swarm is populated with particles. Once swarm.populate() or similar methods are called, PETSc finalizes field registration and no new variables can be added.

Parameters:
  • name (str) – Variable name

  • size (int, default 1) – Number of components (1 for scalar, 2-3 for vector, etc.)

  • dtype (type, default float) – Data type (float or int)

  • proxy_degree (int, default 2) – Degree for mesh proxy variable interpolation

  • units (str, optional) – Physical units for this variable (e.g., “kg/m^3”, “m/s”)

Returns:

The created swarm variable

Return type:

SwarmVariable

Raises:

RuntimeError – If swarm is already populated with particles

Examples

Correct usage: >>> swarm = uw.swarm.Swarm(mesh) >>> material = swarm.add_variable(“material”, 1, dtype=int) >>> temperature = swarm.add_variable(“temperature”, 1) >>> swarm.populate(fill_param=3) # Populate after creating variables

Incorrect usage (will raise error): >>> swarm = uw.swarm.Swarm(mesh) >>> swarm.populate(fill_param=3) >>> material = swarm.add_variable(“material”, 1) # ERROR!

petsc_save_checkpoint(swarmName, index, outputPath='')[source]

Use PETSc to save the swarm and attached data to a .pbin and xdmf file.

Parameters:
  • swarmName (str) – Name of the swarm to save.

  • index (int) – An index which might correspond to the timestep or output number (for example).

  • outputPath (str | None) – Path to save the data. If left empty it will save the data in the current working directory.

write_timestep(filename, swarmname, index, swarmVars=None, outputPath='', time=None, compression=False, compressionType='gzip', force_sequential=False)[source]

Save data to h5 and a corresponding xdmf for visualisation using h5py.

Parameters:
  • swarmName – Name of the swarm to save.

  • swarmVars (list | None) – List of swarm objects to save.

  • index (int) – An index which might correspond to the timestep or output number (for example).

  • outputPath (str | None) – Path to save the data. If left empty it will save the data in the current working directory.

  • time (int | None) – Attach the time to the generated xdmf.

  • compression (bool | None) – Whether to compress the h5 files [bool].

  • compressionType (str | None) – The type of compression to use. ‘gzip’ and ‘lzf’ are the supported types, with ‘gzip’ as the default.

  • filename (str)

  • swarmname (str)

  • force_sequential (bool | None)

property vars

List of SwarmVariables attached to this swarm.

Returns:

List of SwarmVariable objects defined on this swarm.

Return type:

list

snapshot_payload()[source]

Return a self-contained dict describing this swarm’s state.

Captured: per-rank particle coordinates (from DMSwarmPIC_coor) and every user swarm-variable’s data array. PETSc-internal variables (DMSwarmPIC_coor, DMSwarm_X0) are excluded — their contents either come from the captured coords or are regenerated on the next solve.

Return type:

dict

apply_snapshot_payload(payload)[source]

Rebuild this swarm’s local particle population from a payload.

Algorithm:

  1. Drop every current local particle (dm.removePoint from the end is O(1) per call, O(N) total).

  2. Add the captured-rank’s particles back via the raw PETSc primitives — addNPoints then writing the coord field directly. We deliberately bypass add_particles_with_coordinates() because that method filters via points_in_domain (slow) and triggers dm.migrate (unnecessary — saved coords were already local at capture time, and the mesh hasn’t changed).

  3. Write captured per-variable data back. The local particle count matches the captured count because we just put the same particles back in the same order.

This bumps _population_generation once (from the addNPoints step in restore), which is correct: the population did just change. Downstream consumers that care can compare against the captured value in payload['captured_population_generation'].

Parameters:

payload (dict)

Return type:

None

access(*writeable_vars)[source]

Dummy access manager that provides deferred sync for backward compatibility. Uses NDArray_With_Callback.delay_callbacks_global() internally.

This is a compatibility wrapper that allows existing code using the access() context manager to work with the new direct-access variable interfaces. All variable modifications are deferred and synchronized at context exit.

Parameters:

writeable_vars (SwarmVariable) – Variables that will be modified (ignored - all variables are writable with the new interface, this parameter is kept for API compatibility)

Return type:

Context manager that defers variable synchronization until exit

Notes

This method is deprecated. New code should access variable.data or variable.array directly without requiring an access context.

advection(V_fn, delta_t, order=2, corrector=False, restore_points_to_domain_func=None, evalf=False, step_limit=False)[source]

Advect the particle swarm through one timestep of a velocity field.

Particle positions are updated with an explicit Runge-Kutta step of \(\dot{\mathbf{x}} = \mathbf{V}(\mathbf{x})\). Velocity is sampled with a global evaluation (off-rank sample points are resolved collectively), so the call is MPI-collective: every rank must make it, including ranks holding no particles. On completion the swarm is migrated so each particle lands on the rank owning its new location; particles that leave the domain are removed (unless restore_points_to_domain_func or the mesh’s own return_coords_to_bounds returns them).

Parameters:
  • V_fn (vector UW function / sympy expression) – Velocity field, evaluated at particle locations. Any expression accepted by uw.function.evaluate(), e.g. stokes.u.sym.

  • delta_t (float or UWQuantity) – Timestep. Non-dimensionalised internally (uw.scaling.non_dimensionalise), so a dimensional time (e.g. uw.quantity(1000, "year")) is valid when scaling is active; a plain float is taken to be in model units, consistent with the model-unit velocity.

  • order (int, optional) – Runge-Kutta order: 2 (default) is the midpoint scheme; any other value falls back to first-order forward Euler.

  • corrector (bool, optional) – Historical predictor-corrector option; currently inert (the corrector block is disabled). Retained for call compatibility. Default False.

  • restore_points_to_domain_func (callable, optional) – Maps an (n, dim) coordinate array back into the domain (periodic wrap, boundary projection, …). Applied to the updated positions each substep, in addition to the mesh’s own return_coords_to_bounds.

  • evalf (bool, optional) – Historical flag selecting RBF (evalf) velocity sampling; currently inert — velocity is always sampled with uw.function.global_evaluate. Default False.

  • step_limit (bool, optional) – If True, split delta_t into substeps no larger than estimate_dt() (roughly one element crossing per substep). Default False here; note NodalPointSwarm.advection() defaults it to True.

estimate_dt(V_fn)[source]

Calculates an appropriate advective timestep for the given mesh and velocity configuration.

SwarmVariable

class underworld3.swarm.SwarmVariable[source]

Bases: DimensionalityMixin, MathematicalMixin, Stateful, uw_object

Variable supported by a particle swarm (point cloud).

A SwarmVariable stores values at discrete particle locations and provides a mesh-based proxy representation for use in symbolic expressions. This enables Lagrangian tracking of material properties through deformation.

Parameters:
  • name (str) – Identifier for this variable (must be unique within the swarm).

  • swarm (Swarm) – The supporting particle swarm.

  • size (int or tuple, optional) – Shape specification: int for vectors, tuple for matrices. If None, inferred from vtype.

  • vtype (VarType, optional) – Variable type (SCALAR, VECTOR, TENSOR, SYM_TENSOR, MATRIX). If None, inferred from size.

  • dtype (type, default=float) – Data type for storage (float or int).

  • proxy_degree (int, default=1) – Polynomial degree for the mesh proxy variable.

  • proxy_continuous (bool, default=True) – Whether the proxy uses continuous (True) or discontinuous (False) interpolation.

  • varsymbol (str, optional) – LaTeX symbol for display. Defaults to name.

  • rebuild_on_cycle (bool, default=True) – No effect. Retained for backward compatibility with the removed particle-recycling (streak swarm) feature.

  • units (str or pint.Unit, optional) – Physical units for this variable (e.g., ‘kelvin’, ‘Pa’). Requires reference quantities to be set on the model.

See also

MeshVariable

Variable supported by mesh nodes.

Swarm

Container for particle locations.

Examples

Create a temperature field on a swarm:

>>> swarm = uw.swarm.Swarm(mesh)
>>> T = swarm.add_variable("T", size=1, vtype=uw.VarType.SCALAR)
>>> T.data[:] = 1600.0  # Set initial temperature

Create a velocity field:

>>> v = swarm.add_variable("v", size=mesh.dim, vtype=uw.VarType.VECTOR)

Notes

SwarmVariables are essential for tracking material properties that advect with the flow. The mesh proxy enables their use in finite element formulations while particle storage preserves Lagrangian history.

__init__(name, swarm, size=None, vtype=None, dtype=<class 'float'>, proxy_degree=1, proxy_continuous=True, _register=True, _proxy=True, varsymbol=None, rebuild_on_cycle=True, units=None, units_backend=None)[source]

Initialize dimensionality tracking attributes

property units

Return the units associated with this variable.

property has_units

Check if this variable has units.

use_legacy_array()[source]

Deprecated: Array interface is now unified using NDArray_With_Callback

use_enhanced_array()[source]

Deprecated: Array interface is now unified using NDArray_With_Callback

sync_disabled(description='batch operation')[source]

Context manager to disable automatic synchronization for batch operations. Now uses NDArray_With_Callback’s delay_callback mechanism.

Parameters:

description (str) – Description of the batch operation for debugging

pack_uw_data_to_petsc(data_array, sync=None)[source]

Enhanced pack method that directly accesses PETSc field without access() context. Designed for the new swarmVariable.array interface.

Parameters:
  • data_array (numpy.ndarray) – Array data to pack into PETSc field

  • sync (deprecated) – Never had an effect; deprecated (one DeprecationWarning if passed).

unpack_uw_data_from_petsc(squeeze=True, sync=None)[source]

Enhanced unpack method that directly accesses PETSc field without access() context. Designed for the new swarmVariable.array interface.

Parameters:
  • squeeze (bool) – Whether to squeeze singleton dimensions (default True)

  • sync (deprecated) – Never had an effect; deprecated (one DeprecationWarning if passed).

pack_raw_data_to_petsc(data_array, sync=None)[source]

Pack data array to PETSc using traditional data shape (-1, num_components). Direct PETSc access without access() context for backward compatibility.

Parameters:
  • data_array (numpy.ndarray) – Array data in traditional flat format (-1, num_components)

  • sync (deprecated) – Never had an effect; deprecated (one DeprecationWarning if passed).

unpack_raw_data_from_petsc(squeeze=True, sync=None)[source]

Unpack data from PETSc in traditional data shape (-1, num_components). Direct PETSc access without access() context for backward compatibility.

Parameters:
  • squeeze (bool) – Whether to remove singleton dimensions (default True)

  • sync (deprecated) – Never had an effect; deprecated (one DeprecationWarning if passed).

Returns:

Array data in traditional flat format (-1, num_components)

Return type:

numpy.ndarray

rbf_interpolate(new_coords, verbose=False, nnn=None, order=1, monotone=False)[source]

Radial basis function interpolation of particle data to arbitrary points.

By default this reproduces constant and linear fields exactly (order=1): a polyharmonic kernel with an affine tail over the nnn nearest particles. Inverse-distance weighting (order=0) reproduces only constants, so any field with a gradient is smeared by an error that does not vanish as the particles crowd together.

Parameters:
  • new_coords (numpy.ndarray) – Target coordinates of shape (N, dim) to interpolate to.

  • verbose (bool, default=False) – Print diagnostic information during interpolation.

  • nnn (int, optional) – Number of nearest neighbours to use. Defaults to 2 * (mesh.dim + 1) — comfortably above the dim + 2 that the affine tail needs, so that near-degenerate particle neighbourhoods do not have to fall back.

  • order (int, default=1) – Polynomial reproduction order: 1 (constants and linears exact) or 0 (constants only, inverse distance). Drops to 0 automatically on a rank holding too few particles to determine the affine tail.

  • monotone (bool or str, default=False) – Limit the non-affine part of the interpolant to the non-affine variation present in the particle stencil. The local linear trend is preserved, so this does not cost linear exactness.

Returns:

Interpolated values at the target coordinates.

Return type:

numpy.ndarray

property swarm

The swarm this variable belongs to (accessed via weak reference). Raises RuntimeError if the swarm has been garbage collected.

property old_data

Original data property implementation.

Type:

TESTING

property data

Canonical data storage in flat format for internal operations.

Returns particle data in shape (-1, num_components) regardless of variable type. Values are always non-dimensional (no unit conversion).

This property handles PETSc synchronization. The .array property is a view of this with shape conversion.

When to Use

  • Variable-to-variable transfers: Copying data between swarm variables avoids redundant unit conversions

  • Low-level operations: Direct access to particle data

  • Backward compatibility: Existing code using flat format

For general user access, prefer .array which provides a structured shape.

returns:

Array with shape (-1, num_components) with automatic PETSc sync.

rtype:

NDArray_With_Callback

Examples

>>> # Efficient variable-to-variable copy
>>> new_material.data[...] = old_material.data[...]
>>> # Check data shape
>>> scalar_var.data.shape  # (n_particles, 1)
>>> vector_var.data.shape  # (n_particles, dim)

See also

array

Structured format (N, a, b) for general access.

sym

Symbolic representation for equations.

property array

Primary interface for reading and writing particle data.

Returns a structured array view with shape (N, a, b) where N is the number of particles and (a, b) depends on the variable type:

  • Scalar: (N, 1, 1)

  • Vector: (N, 1, dim)

  • Tensor: (N, dim, dim)

Returns:

Array view that delegates changes back to canonical storage.

Return type:

NDArray

Examples

>>> # Scalar field initialization
>>> material_property.array[:, 0, 0] = 1e21  # Set viscosity
>>> # Vector field
>>> velocity.array[:, 0, 0] = vx_values  # x-component
>>> velocity.array[:, 0, 1] = vy_values  # y-component
>>> # Reading values
>>> max_visc = material_property.array[:, 0, 0].max()

Notes

This property is a view of the canonical .data property with automatic shape conversion. All modifications trigger proxy variable updates for mesh interpolation.

See also

data

Flat format (-1, components) for variable-to-variable transfers.

sym

Symbolic representation for use in equations.

property sym

Symbolic representation for use in equations.

Returns the symbolic expression from the proxy mesh variable, which can be used in SymPy expressions for constitutive models, boundary conditions, etc.

Returns:

Symbolic matrix expression.

Return type:

sympy.Matrix

Notes

The proxy is automatically updated if particle data has changed.

property sym_1d

Flattened symbolic representation.

Returns the symbolic expression as a 1D (column) vector form, useful for Voigt notation in tensor calculations.

Returns:

Flattened symbolic expression.

Return type:

sympy.Matrix

global_max(**kwargs)

Maximum value across all MPI ranks.

Finds the maximum value of the particle property across all processors. Useful for finding extreme values in particle swarm data.

Parameters:
  • axis (None, int, or tuple of ints, optional) – Axis or axes along which to operate. By default, flattened input is used.

  • out (None, optional) – Alternative output array (not supported, kept for API compatibility).

  • keepdims (bool, optional) – If True, reduced axes are left as dimensions with size one.

Returns:

Maximum value with units preserved (if variable has units).

Return type:

UWQuantity or scalar

Examples

>>> max_temp = temperature_swarm.global_max()
>>> print(f"Maximum temperature: {max_temp}")

Notes

This is a collective operation - all ranks must call it. The result is identical on all ranks.

global_min(**kwargs)

Minimum value across all MPI ranks.

Finds the minimum value of the particle property across all processors. Useful for finding extreme values in particle swarm data.

Parameters:
  • axis (None, int, or tuple of ints, optional) – Axis or axes along which to operate. By default, flattened input is used.

  • out (None, optional) – Alternative output array (not supported, kept for API compatibility).

  • keepdims (bool, optional) – If True, reduced axes are left as dimensions with size one.

Returns:

Minimum value with units preserved (if variable has units).

Return type:

UWQuantity or scalar

Examples

>>> min_pressure = pressure_swarm.global_min()
>>> print(f"Minimum pressure: {min_pressure}")

Notes

This is a collective operation - all ranks must call it. The result is identical on all ranks.

global_sum(**kwargs)

Sum of values across all MPI ranks.

Computes the sum of particle property values across all processors.

Parameters:
  • axis (None, int, or tuple of ints, optional) – Axis or axes along which to operate. By default, flattened input is used.

  • out (None, optional) – Alternative output array (not supported, kept for API compatibility).

  • keepdims (bool, optional) – If True, reduced axes are left as dimensions with size one.

Returns:

Sum with units preserved (if variable has units).

Return type:

UWQuantity or scalar

Notes

This is a collective operation - all ranks must call it. The result is identical on all ranks.

Warning: This sum is NOT a physical domain-integrated quantity because particles are non-uniformly distributed. For domain integration, use the proxy mesh variable with uw.maths.Integral().

global_norm(**kwargs)

L2 norm (Frobenius norm) across all MPI ranks.

Computes the L2 norm of particle property values: sqrt(sum(x**2)) across all processors.

Parameters:

ord ({non-zero int, inf, -inf, 'fro', 'nuc'}, optional) – Order of the norm (default: None = 2-norm)

Returns:

L2 norm with units preserved (if variable has units).

Return type:

UWQuantity or scalar

Notes

This is a collective operation - all ranks must call it. The result is identical on all ranks.

For vectors, computes the Frobenius norm treating the array as flattened.

Warning: This norm is NOT a physical domain-integrated quantity because particles are non-uniformly distributed.

global_size(**kwargs)

Total particle count across all MPI ranks.

Returns the total number of particles across all processors. Useful for population monitoring and load balancing diagnostics.

Returns:

Total number of particles across all ranks.

Return type:

int

Examples

>>> total_particles = swarm_var.global_size()
>>> local_particles = swarm_var.data.shape[0]
>>> print(f"Rank has {local_particles} of {total_particles} particles")

Notes

This is a collective operation - all ranks must call it. The result is identical on all ranks.

save(filename, compression=False, compressionType='gzip', force_sequential=False)[source]

Save the swarm variable to a h5 file.

Parameters:
  • filename (int) – The filename of the swarm variable to save to disk.

  • compression (bool | None) – Add compression to the h5 files (saves space but increases write times with increasing no. of processors)

  • compressionType (str | None) – Type of compression to use, ‘gzip’ and ‘lzf’ supported. ‘gzip’ is default. Compression also needs to be set to ‘True’.

  • force_sequential (activate the serial version of hdf5)

write_proxy(filename)[source]

Write this variable’s proxy mesh variable to an HDF5 file.

The proxy is the RBF-interpolated mesh-variable image of the particle data (built when the variable was created with proxy_degree > 0); this writes that mesh field via MeshVariable.write, which is often the most convenient checkpoint/visualisation form of particle data.

Parameters:

filename (str) – Output file path, passed directly to MeshVariable.write (conventionally *.h5).

Notes

If the variable has no proxy (proxy_degree=0 / _proxy=False), nothing is written; a message is printed and the call returns.

read_timestep(data_filename, swarmID, data_name, index, outputPath='')[source]

Restore this variable’s values from a saved timestep.

Reads the checkpoint files written by Swarm.write_timestep and maps the saved (coordinate, value) pairs onto the current particles by nearest-neighbour interpolation — the live swarm need not have the same particle count or positions as the saved one.

Filename convention (matching Swarm.write_timestep( data_filename, swarmID, swarmVars=[...], outputPath=..., index=...)):

  • swarm coordinates: {outputPath}/{data_filename}.{swarmID}.{index:05}.h5

  • this variable’s data: {outputPath}/{data_filename}.{swarmID}.{data_name}.{index:05}.h5

Both files must exist (RuntimeError otherwise).

Parameters:
  • data_filename (str) – Base name used when the checkpoint was written. (Note: the corresponding parameter on Swarm.read_timestep is spelled base_filename — the two signatures predate a common convention.)

  • swarmID (str) – Swarm identifier used when the checkpoint was written (swarm_id on Swarm.read_timestep).

  • data_name (str) – The saved variable’s name (Swarm.write_timestep embeds each variable’s .name in its data filename).

  • index (int) – Timestep index (zero-padded to five digits in the filename).

  • outputPath (str, optional) – Directory holding the checkpoint files (default: current directory).

Notes

MPI-collective. Rank 0 reads the saved data in one shot; the saved points are then routed to their owning ranks with the same migration rule the live swarm uses, so the rank-local nearest-neighbour lookup always sees the correct neighbours.

Index Swarm Variable

class underworld3.swarm.IndexSwarmVariable[source]

Bases: SwarmVariable

Integer-valued swarm variable for material tracking.

IndexSwarmVariable stores integer indices at particle locations, typically used for tracking distinct material types. It automatically generates symbolic mask expressions for each material index, enabling material- dependent properties in constitutive models.

Parameters:
  • name (str) – Variable name for identification and I/O.

  • swarm (Swarm) – Parent swarm object.

  • indices (int) – Number of distinct material indices (default 1).

  • proxy_degree (int) – Polynomial degree for mesh projection (default 1).

  • proxy_continuous (bool) – Whether mesh proxy is continuous (default True).

Examples

>>> material = IndexSwarmVariable("M", swarm, indices=3)
>>> material.data[:] = 0  # Set all particles to material 0
>>> # Use sym[i] as multiplier for material i properties
>>> viscosity = material.sym[0] * 1e20 + material.sym[1] * 1e21

See also

SwarmVariable

Base class for particle-supported variables.

__init__(name, swarm, indices=1, proxy_degree=1, proxy_continuous=True, update_type=0, npoints=5, radius=0.5, npoints_bc=2, ind_bc=None, varsymbol=None)[source]

Initialize dimensionality tracking attributes

property sym

Lazy evaluation of symbolic mask array.

Only updates proxy variables when they’re actually needed (when sym is accessed) and only if the proxy variables are marked as stale due to data changes. This avoids expensive RBF interpolation during data assignment operations.

property sym_1d

One-dimensional symbolic mask array (alias for sym).

Returns the same symbolic mask array as sym, provided for API compatibility with other variable types that distinguish between multi-dimensional and flattened representations.

Returns:

Symbolic mask array of shape (indices, 1).

Return type:

sympy.Matrix

See also

sym

Primary symbolic mask array access.

createMask(funcsList)[source]

Create a material-weighted symbolic expression from per-material values.

This method creates a SymPy expression that combines multiple material properties using the index variable’s symbolic masks. The result can be used directly in Underworld’s solver equations.

Parameters:

funcsList (list or tuple) – List of values or symbolic expressions, one per material index. Length must equal indices.

Returns:

Symbolic expression: sum(funcsList[i] * mask[i] for i in indices).

Return type:

sympy.Basic

Raises:

RuntimeError – If funcsList is not a list/tuple or has wrong length.

Examples

>>> # Define viscosity per material
>>> viscosity = material.createMask([1e21, 1e20, 1e22])  # 3 materials
>>> # Use in solver
>>> stokes.constitutive_model.viscosity = viscosity

See also

visMask

Create visualization mask showing material indices.

viewMask(expr)[source]

Decompose a masked expression into per-material components.

Note

This method is not yet implemented. Currently returns None.

Takes a symbolic expression created by createMask() and extracts the individual material-specific components.

Parameters:

expr (sympy.Basic) – A masked symbolic expression created by createMask().

Returns:

List of symbolic expressions, one per material index. Currently returns None (not implemented).

Return type:

list or None

See also

createMask

Create a masked expression from per-material values.

visMask()[source]

Create a visualization mask showing material indices.

Returns a symbolic expression where each material region shows its index value (0, 1, 2, …). Useful for visualization and debugging of material distributions.

Returns:

Symbolic expression evaluating to material index at each point.

Return type:

sympy.Basic

Examples

>>> vis_field = material.visMask()
>>> values = uw.function.evaluate(vis_field, swarm.data)
>>> # values[i] gives material index at particle i

See also

createMask

Create arbitrary material-weighted expressions.

view()[source]

Show information on IndexSwarmVariable

Nodal Point Swarm

A swarm type where particles are located at mesh nodes.

class underworld3.swarm.NodalPointSwarm[source]

Bases: Swarm

BASIC_Swarm with particles located at the coordinate points of a meshVariable

Deprecated since version 2026-07: NodalPointSwarm is deprecated and will be removed in the next release cycle. The semi-Lagrangian history managers in uw.systems.ddt no longer use it and there are no remaining internal callers.

The swarmVariable X0 is defined so that the particles can “snap back” to their original locations after they have been moved.

The purpose of this Swarm is to manage sample points for advection schemes based on upstream sampling (method of characteristics etc)

__init__(trackedVariable, verbose=False)[source]
Parameters:

trackedVariable (EnhancedMeshVariable)

advection(V_fn, delta_t, order=2, corrector=False, restore_points_to_domain_func=None, evalf=False, step_limit=True)[source]

Advect the nodal-point swarm one timestep (semi-Lagrangian sweep).

Records each particle’s launch point (its home mesh node) and origin rank, then delegates to Swarm.advection(). The recorded launch data is what lets semi-Lagrangian schemes return sampled values to the node the particle departed from after the (possibly off-rank) trajectory.

Parameters are those of Swarm.advection(), with one difference: step_limit defaults to True — trajectories are substepped to at most ~one element crossing per substep (see Swarm.estimate_dt()), which keeps the node-return bookkeeping robust for large delta_t.