Repository: Genesis-Embodied-AI/Genesis
File: genesis/options/solvers.py (Python)

Code before:
class MPMOptions(Options):
    """
    Options configuring the MPMSolver.

    Note
    ----
    MPM is a hybrid lagrangian-eulerian method for simulating soft materials. In the eulerian phase, it uses a grid representation. The `upper_bound` and `lower_bound` specify the simulation domain, but a safety padding will be added to the actual grid boundary. Therefore, the actual boundary could be slightly tighter than the specified one. Note that the size of the domain affects the performance of the simulation, hence you should set it as tight as possible.

    Parameters
    ----------
    dt : float, optional
        Time duration for each simulation step in seconds. If none, it will inherit from `SimOptions`. Defaults to None.
    gravity : tuple, optional
        Gravity force in N/kg. If none, it will inherit from `SimOptions`. Defaults to None.
    particle_size : float, optional
        Particle diameter in meters. If not given, we will compute `particle_size` based on `grid_density`, where `particle_size` will be linearly proportional to the grid cell size. A reference value is `particle_size = 0.01` for `grid_density = 64`. Defaults to None.
    grid_density : float, optional
        Number of grid cells per meter. Defaults to 64.
    enable_CPIC : bool, optional
        Whether to enable CPIC (Compatible Particle-in-Cell) to support coupling with thin objects. Defaults to False.
    lower_bound : tuple, shape (3,), optional
        Lower bound of the simulation domain. Defaults to (-1.0, -1.0, 0.0).
    upper_bound : tuple, shape (3,), optional
        Upper bound of the simulation domain. Defaults to (1.0, 1.0, 1.0).
    enable_particle_constraints : bool, optional
        Whether to enable particle constraints for attaching MPM particles to rigid links. Defaults to False.
    use_sparse_grid : bool, optional
        This option is deprecated.
    leaf_block_size : int, optional
        This option is deprecated.
    """

    dt: Optional[float] = None
    gravity: Optional[tuple] = None
    particle_size: Optional[float] = None  # in meters. Will be computed automatically if it's None.
    grid_density: float = 64
    enable_CPIC: bool = False
    enable_particle_constraints: bool = False

    # These will later be converted to discrete grid bound. The actual grid boundary could be slightly tighter.
    lower_bound: tuple = (-1.0, -1.0, 0.0)
    upper_bound: tuple = (1.0, 1.0, 1.0)

    # Deprecated sparse computation parameter.
    use_sparse_grid: bool = False
    leaf_block_size: int = 8

    def __init__(self, **data):
        super().__init__(**data)
        if not np.all(np.asarray(self.upper_bound) > np.asarray(self.lower_bound)):
            gs.raise_exception("Invalid pair of upper_bound and lower_bound.")

Review comment (suggestion):
I'm not convinced it is relevant to have a flag to disable this feature. What is supposed to be the drawback of enabling this systematically? Flags complexifying the control flow should only be added if necessary because it makes the codebase harder to maintain and comprehend.

Code after:
class MPMOptions(Options):
    """
    Options configuring the MPMSolver.

    Note
    ----
    MPM is a hybrid lagrangian-eulerian method for simulating soft materials. In the eulerian phase, it uses a grid representation. The `upper_bound` and `lower_bound` specify the simulation domain, but a safety padding will be added to the actual grid boundary. Therefore, the actual boundary could be slightly tighter than the specified one. Note that the size of the domain affects the performance of the simulation, hence you should set it as tight as possible.

    Parameters
    ----------
    dt : float, optional
        Time duration for each simulation step in seconds. If none, it will inherit from `SimOptions`. Defaults to None.
    gravity : tuple, optional
        Gravity force in N/kg. If none, it will inherit from `SimOptions`. Defaults to None.
    particle_size : float, optional
        Particle diameter in meters. If not given, we will compute `particle_size` based on `grid_density`, where `particle_size` will be linearly proportional to the grid cell size. A reference value is `particle_size = 0.01` for `grid_density = 64`. Defaults to None.
    grid_density : float, optional
        Number of grid cells per meter. Defaults to 64.
    enable_CPIC : bool, optional
        Whether to enable CPIC (Compatible Particle-in-Cell) to support coupling with thin objects. Defaults to False.
    lower_bound : tuple, shape (3,), optional
        Lower bound of the simulation domain. Defaults to (-1.0, -1.0, 0.0).
    upper_bound : tuple, shape (3,), optional
        Upper bound of the simulation domain. Defaults to (1.0, 1.0, 1.0).
    use_sparse_grid : bool, optional
        This option is deprecated.
    leaf_block_size : int, optional
        This option is deprecated.
    """

    dt: Optional[float] = None
    gravity: Optional[tuple] = None
    particle_size: Optional[float] = None  # in meters. Will be computed automatically if it's None.
    grid_density: float = 64
    enable_CPIC: bool = False

    # These will later be converted to discrete grid bound. The actual grid boundary could be slightly tighter.
    lower_bound: tuple = (-1.0, -1.0, 0.0)
    upper_bound: tuple = (1.0, 1.0, 1.0)

    # Deprecated sparse computation parameter.
    use_sparse_grid: bool = False
    leaf_block_size: int = 8

    def __init__(self, **data):
        super().__init__(**data)
        if not np.all(np.asarray(self.upper_bound) > np.asarray(self.lower_bound)):
            gs.raise_exception("Invalid pair of upper_bound and lower_bound.")

        if self.particle_size is None:
            self.particle_size = 0.01 * 64.0 / self.grid_density