Using Implicit MPM#

Newton’s implicit Material Point Method (MPM) solver models particle materials such as granular media. MPM support and rigid-MPM coupling are experimental. Start with the compact mpm-granular example; the snowball-smash and teapot-fill demos provide polished coupling and cavity-sampling showcases.

The launcher uses cuda:0 by default. Pass --device cuda:N to select a different GPU.

Explore MPM Scenes#

These examples and demos introduce MPM construction and interaction before any parameter study. They default to Newton GL for interactive viewing:

Granular drop
uv run isaaclab example mpm-granular
Two-way sphere pit
uv run isaaclab example mpm-two-way-coupling
Snowball smash
uv run isaaclab demo snowball-smash
Teapot fill
uv run isaaclab demo teapot-fill --fluid_render_mode particles

Train and Regenerate Franka Pour#

The IsaacContrib-Franka-Pour task restores episodes from a reset artifact containing the connected 14-phase reset distribution. The canonical 20,000-row artifact downloads from the standard Isaac Lab asset root on first use, so training needs no artifact setup:

uv run isaaclab train --rl_library rsl_rl --task IsaacContrib-Franka-Pour \
  --num_envs 2048

The checked-in generator remains the executable reference for reproducing or customizing the distribution. It takes about two minutes on an L40S-class GPU and writes a local artifact that can be selected explicitly:

uv run python scripts/tools/generate_franka_pour_reset_dataset.py
uv run isaaclab train --rl_library rsl_rl --task IsaacContrib-Franka-Pour \
  --num_envs 2048 \
  env.reset_dataset_path=datasets/franka_pour/reset_dataset.pt

The task validates the payload’s stored content digest automatically. Setting ISAACSIM_ASSET_ROOT redirects the canonical artifact to a compatible local or self-hosted asset tree. Digest pinning remains available for custom reproducible experiments.

The source uses a non-colliding analytic fill volume whose height is controlled by env.source_fill_level in (0, 1]. The default 0.70 produces a 735-particle jittered lattice up to roughly 70% of the cup height. env.pour_target_frac independently controls the fraction of that live payload that must reach the receiver.

Play a checkpoint in Kit with the canonical task configuration; no external callback or particle override is required:

uv run --extra isaacsim isaaclab play --rl_library rsl_rl --task IsaacContrib-Franka-Pour \
  --checkpoint /path/to/model.pt --num_envs 1 --visualizer kit

Minimal Setup#

Use the same voxel size for the solver grid and particle generator. Add the generated object to an InteractiveSceneCfg like any other declarative asset.

import isaaclab.sim as sim_utils
from isaaclab_newton.assets import MPMObjectCfg
from isaaclab_newton.physics import MPMSolverCfg, NewtonCfg
from isaaclab_newton.sim.spawners.mpm import MPMGridCfg

voxel_size = 0.02

sim_cfg = sim_utils.SimulationCfg(
    dt=1.0 / 100.0,
    physics=NewtonCfg(
        solver_cfg=MPMSolverCfg(
            voxel_size=voxel_size,
            max_iterations=100,
            tolerance=1.0e-4,
        ),
        num_substeps=2,
    ),
)

media = MPMObjectCfg(
    prim_path="{ENV_REGEX_NS}/Media",
    spawn=MPMGridCfg(
        lower=(-0.1, -0.1, 0.0),
        upper=(0.1, 0.1, 0.2),
        voxel_size=voxel_size,
        particles_per_cell=2.0,
        particle_placement="cell_center",
    ),
)

Tune the particle material separately through MPMParticleMaterialCfg. The implicit solve already resolves collider contact. project_outside_colliders adds a hard post-step correction for particles that remain inside colliders; use it only when that geometric correction is intentional, and not as a substitute for valid initial states, collision geometry, or a stable timestep. Coupled MPM entries do not support this manager-level projection pass.

Both MPMGridCfg and MPMPointsCfg author schema-valid UsdGeom.Points simulation geometry. Grid configurations still generate the lattice in Isaac Lab before authoring explicit points, widths, velocities, and masses. Materials use NewtonMPMMaterialAPI; damping [s] is authored as damping * young_modulus [Pa·s]. critical_fraction remains solver-global on MPMSolverCfg and is authored on the owning NewtonMPMSceneAPI physics scene.

Grid jitter is generated once in asset-local coordinates with a fixed seed, so USD clones share the same local particle distribution. Use reset events or domain randomization when each environment needs an independent distribution.

Controlled material experiments, comparison demos, and practical solver advice are collected in MPM Solver.

Render a Particle Surface#

MPM simulation state remains a set of particles. Surface reconstruction is an optional visualization pass: it does not change particle motion, collisions, or material behavior. Run the teapot demo to compare the available modes:

# Reconstructed surface
uv run isaaclab demo teapot-fill \
  --visualizer newton_gl --fluid_render_mode surface
# Surface and source particles together
uv run isaaclab demo teapot-fill \
  --visualizer newton_gl --fluid_render_mode both
# Path-traced translucent surface
uv run --extra ovrtx isaaclab demo teapot-fill \
  --visualizer newton_rtx --fluid_render_mode surface

The teapot demo defaults to the reconstructed surface. Select particles or both to inspect the source MPM state in Newton GL or Newton RTX.

Surface rendering is available in the Newton GL and Newton RTX visualizers. The Kit visualizer continues to render the MPM particles directly.

This recording demonstrates rendering appearance, not a different constitutive model.

The teapot demo configures newton.geometry.ParticleSurface after sim.reset() and updates the mesh before sim.render(). Its renderer captures extraction on CUDA when enabled and stages the dynamic mesh inside the viewer’s frame lifecycle.

Tune reconstruction independently from the simulation:

  • voxel_size controls surface detail and memory use. It can be smaller than the MPM solver voxel size.

  • kernel_radius controls how far each particle contributes to the surface. Start near three times the particle spacing.

  • max_grid_cells provides fixed-capacity storage for CUDA graph capture. Increase it if the reconstructed domain outgrows the reserved grid.

  • Anisotropic kernels preserve sheets and stretched fluid features better, but cost more than isotropic kernels.

The teapot demo handles CUDA graph capture, empty surfaces, inactive particles, and dynamic topology:

Teapot surface renderer implementation
class FluidSurfaceRenderer:
    """Extract and display a dynamic water surface in Newton visualizers."""

    def __init__(self, sim) -> None:
        import warp as wp
        from isaaclab_newton.physics import NewtonManager
        from isaaclab_visualizers.newton import NewtonGLVisualizer, NewtonRTXVisualizer
        from newton.geometry import ParticleSurface

        self._wp = wp
        self._visualizers = tuple(
            visualizer
            for visualizer in sim.visualizers
            if isinstance(visualizer, (NewtonGLVisualizer, NewtonRTXVisualizer))
        )
        if not self._visualizers:
            raise RuntimeError("Particle surface rendering requires a Newton GL or RTX visualizer.")

        self._model = NewtonManager.get_model()
        self._state = NewtonManager.get_state_0()
        self._surface = ParticleSurface(
            voxel_size=SURFACE_VOXEL_SIZE,
            max_grid_cells=SURFACE_MAX_GRID_CELLS,
            world_count=max(self._model.world_count, 1),
            kernel_radius=SURFACE_KERNEL_RADIUS,
            threshold=0.4,
            smooth_lambda=0.0,
            anisotropic=True,
            kernel_scale=0.5,
            anisotropy_ratio=16.0,
            anisotropy_scale=1.0,
            anisotropy_min_neighbors=4,
            anisotropy_binning=True,
            anisotropy_strength=0.95,
            field_smooth_iterations=0,
            mesh_smooth_iterations=1,
            device=self._model.device,
        )
        self._empty_points = wp.empty(0, dtype=wp.vec3, device=self._model.device)
        self._empty_indices = wp.empty(0, dtype=wp.int32, device=self._model.device)
        self._empty_normals = wp.empty(0, dtype=wp.vec3, device=self._model.device)
        self._surface_mesh = None
        self._surface_graph = None
        self._capture_surface_extraction()

    def _extract_surface(self):
        """Extract the water surface from the current Newton particle state."""
        return self._surface.extract(
            self._state.particle_q,
            self._model.particle_radius,
            particle_flags=self._model.particle_flags,
            particle_world=self._model.particle_world if self._surface.world_count > 1 else None,
        )

    def _capture_surface_extraction(self) -> None:
        """Capture reconstruction separately from the MPM physics graph."""
        if not self._model.device.is_cuda or args_cli.disable_cuda_graph:
            return
        self._surface_mesh = self._extract_surface()
        with self._wp.ScopedCapture(device=self._model.device) as capture:
            self._surface_mesh = self._extract_surface()
        self._surface_graph = capture.graph

    def update(self) -> int:
        """Reconstruct and publish the current water surface, returning its triangle count."""
        if self._surface_graph is None:
            self._surface_mesh = self._extract_surface()
        else:
            self._wp.capture_launch(self._surface_graph)

        vertices, indices, normals = self._surface_mesh.to_arrays()
        if vertices is None:
            vertices = self._empty_points
            indices = self._empty_indices
            normals = self._empty_normals
            hidden = True
            triangle_count = 0
        else:
            hidden = False
            triangle_count = indices.shape[0] // 3

        for visualizer in self._visualizers:
            visualizer.log_mesh(
                SURFACE_PATH,
                vertices,
                indices,
                normals=normals,
                hidden=hidden,
                backface_culling=False,
                color=WATER_COLOR,
                roughness=0.1,
                metallic=0.0,
                dynamic=True,
                opacity=WATER_OPACITY,
            )
        return triangle_count

Next Steps#

See MPM Solver for resolution and convergence tuning, controlled material comparisons, and the nearly rigid MPM limit. See Coupled Solvers for rigid–MPM coupling.