Skip to content

Ideal gas

An ideal gas of non-interacting molecules in a reflecting box.

  • Name: ideal_gas
  • Field: physics/statistical_mechanics
  • Formalism: ordinary differential equations
  • References: Maxwell 1860

Factory

@register_model(
    field='physics/statistical_mechanics',
    tags=('gas', 'ode', 'boundary'),
    references=('Maxwell 1860',),
    defaults={'until': 2.0, 'dt': 0.01},
)
def ideal_gas(
    n_molecules: int = 500,
    *,
    kT: float = 1.0,
    size: float = 1.0,
    n_dim: int = 2,
    mass: float = 1.0,
    random_state: int | np.random.Generator | None = 0,
    label: str = 'gas',
) -> System:
    """Return an ideal gas of non-interacting molecules in a reflecting box.

    Positions are uniform in the box and velocity components are normal with variance `kT / mass`, with the mean
    velocity removed. The molecules are drawn as points colored by speed.

    Args:
        n_molecules:
            The number of molecules.

        kT:
            The temperature in energy units.

        size:
            The side of the box.

        n_dim:
            The number of dimensions.

        mass:
            The mass of every molecule.

        random_state:
            The seed or generator of the initial positions and velocities.

        label:
            The label of the molecule group. The box is labelled `box`.

    Returns:
        system:
            The gas.

    Examples:
        >>> from skcomplex.models.physics.statistical_mechanics import ideal_gas
        >>> group = ideal_gas(1000, random_state=0).elements[0]
        >>> group.label, group.element.__name__, group.size
        ('gas', 'MassPointElement', 1000)
    """
    rng = np.random.default_rng(random_state)
    velocities = rng.normal(0.0, np.sqrt(kT / mass), size=(n_molecules, n_dim))
    velocities -= velocities.mean(axis=0)
    visual = VisualSpec('points', {'position': 'x', 'radius': size / 200, 'color_by': 'x_d1', 'states': ()})
    molecules = GroupElement(
        label,
        element=MassPointElement,
        size=n_molecules,
        values={'x': rng.uniform(0.0, size, size=(n_molecules, n_dim)), 'x_d1': velocities, 'm': mass},
        visual=visual,
    )
    return System(
        elements=[molecules],
        space=ContinuousSpace(extent=[size] * n_dim, boundary='reflecting'),
    )

Components

class MassPointElement(PointElement):
    """Point mass, a point whose position `x` has order 2, with its velocity `x_d1` and a mass `m`.

    The forces on the point divide by the mass through their divisor `'m'`.

    Args:
        label:
            The label of the point.

        x:
            The initial position. A number applies to every component.

        x_d1:
            The initial velocity. A number applies to every component.

        visual:
            How the point is drawn, or `None` for `default_visual_`.

        m:
            The mass.

    Attributes:
        default_visual_:
            Points at the positions.

    Examples:
        >>> from skcomplex.models.physics.common.particles import MassPointElement
        >>> MassPointElement('ball', x=[0.0, 1.0], m=2.0).m
        2.0
    """

    def __init__(
        self,
        label: str,
        x: npt.ArrayLike = 0.0,
        x_d1: npt.ArrayLike = 0.0,
        visual: VisualSpec | None = None,
        *,
        m: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 1.0,
    ) -> None:
        super().__init__(label, x=x, x_d1=x_d1, visual=visual)
        self.m = m

Equations

gas.x[i, 0]: diff(x_gas_0(t), t, t) == 0
gas.x[i, 1]: diff(x_gas_1(t), t, t) == 0