jaxdem.colliders.naive#

Naive \(O(N^2)\) collider implementation.

Classes

NaiveSimulator(*, overflow, history)

Implementation that computes forces and potential energies using a naive \(O(N^2)\) all-pairs loop.

class jaxdem.colliders.naive.NaiveSimulator(*, overflow: Array = <factory>, history: Array = <factory>)#

Bases: Collider

Implementation that computes forces and potential energies using a naive \(O(N^2)\) all-pairs loop.

This collider evaluates interactions between all particle pairs directly, without any spatial partitioning or binning.

The total force acting on particle \(i\) is the direct sum of its interactions with all other particles \(j\) in the system:

\[\mathbf{F}_i = \sum_{j=0}^{N-1} \mathbf{F}_{ij}(\mathbf{x}_i, \mathbf{x}_j, r_i, r_j) \cdot M_{ij}\]

where \(\mathbf{F}_{ij}\) is the force vector computed by the physical force model, and \(M_{ij}\) is the interaction eligibility mask. The mask accounts for:

  • Clump member exclusions (internal clump particles do not exert forces on each other)

  • Bond connectivity exclusions

  • Contact overlap/cutoff checks

Runtime and Cost Analysis#

This collider always evaluates a fixed number of pair checks:

\[\text{cost} \approx N^2 \cdot C_{interaction}\]

where \(C_{interaction}\) is the cost of a single pairwise force/energy query.

Because the algorithm does not partition space into cells or project coordinates onto axes, its execution time is completely independent of:

  • The spatial distribution or packing fraction \(\phi\) of the system

  • The particle polydispersity \(\alpha\)

  • Performance Trade-off:

    • For small systems (:math:`N le 10^3 - 2 cdot 10^3` depending on the GPU): NaiveSimulator is often the fastest collider because it does no sorting, hashing, or bookkeeping. This gives full GPU thread utilization and short JIT compilation times.

    • For large systems (:math:`N ge 10^4`): The quadratic complexity \(O(N^2)\) becomes a severe performance bottleneck, and spatial partitioning colliders are significantly faster.

Pair History#

The collider stores force-model history densely: history[i, j] is the stored history of the directed pair (i, j), the interaction on i from j. Every pair keeps the same slot for the life of the collider, so no remapping is ever needed. Stateless laws have a zero-width trailing axis, which costs nothing. System.create allocates the buffer.

Complexity#

  • Time: \(O(N^2)\).

  • Memory: \(O(N^2 H)\) for a law with history width \(H\), otherwise \(O(N)\).

history: Array#

Dense directed pair history with force-model-defined trailing dimensions.

history_pair_shape(state_shape: tuple[int, ...]) → tuple[int, ...][source]#

Leading shape of the stored pair history for state_shape.

None means this collider has no persistent pair memory, so it can only run stateless force laws.

refresh(state: State, force_model: Any | None = None, *, reset_history: bool = False) → Self[source]#

Reallocate the dense history when the particle count changes.

reindex_history(state: State, system: System, new_to_old: jax.Array) → System[source]#

Gather the dense history rows and columns of the surviving particles.

property metadata: dict[str, Any][source]#

The naive collider has no construction parameters to persist.

static get_history(state: State, system: System, neighbor_list: jax.Array) → jax.Array[source]#

Gather stored history for an explicit (N, k) pair query.

static compute_potential_energy(state: State, system: System) → tuple[State, System, jax.Array][source]#

Compute the total potential energy of the system with a naive \(O(N^2)\) all-pairs loop.

This method iterates over all particle pairs (i, j) and sums the potential energy contributions of the system.force_model, using the stored pair history.

Parameters:
  • state (State) – The current state of the simulation.

  • system (System) – The configuration of the simulation.

Returns:

Tuple of (state, system, energy).

Return type:

Tuple[State, System, jax.Array]

static create_neighbor_list(state: State, system: System, cutoff: float, max_neighbors: int) → tuple[State, System, jax.Array, jax.Array][source]#

Compute a neighbor list with a naive \(O(N^2)\) all-pairs search.

Parameters:
  • state (State) – The current state of the simulation.

  • system (System) – The configuration of the simulation.

  • cutoff (float) – The interaction radius (force cutoff).

  • max_neighbors (int) – Maximum number of neighbors to store per particle.

Returns:

A tuple containing:

  • state: The simulation state.

  • system: The simulation system.

  • neighbor_list: Array of shape (N, max_neighbors) containing neighbor indices.

  • overflow: Boolean flag. True when any particle has more than max_neighbors neighbors.

Return type:

Tuple[State, System, jax.Array, jax.Array]

static compute_force(state: State, system: System, *, advance_history: bool = True, quasistatic: bool = False) → tuple[State, System][source]#

Compute the total force acting on each particle with a naive \(O(N^2)\) all-pairs loop.

This method sums the force contributions of the system.force_model over all particle pairs (i, j) and updates the particle forces. With advance_history the dense pair history is advanced in place.

Parameters:
  • state (State) – The current state of the simulation.

  • system (System) – The configuration of the simulation.

  • advance_history (bool, optional) – Advance the stored pair history over this step.

  • quasistatic (bool, optional) – Forwarded to the force law. True omits velocity-dependent terms.

Returns:

The updated State with computed forces and the System with the (possibly advanced) pair history.

Return type:

Tuple[State, System]