Skip to content

Bathtub

A bathtub, one stock of water with an inflow from the faucet and an outflow through the drain.

  • Name: bathtub
  • Field: engineering/processes
  • Formalism: ordinary differential equations
  • References: Meadows 2008

Factory

@register_model(
    field='engineering/processes',
    tags=('stocks and flows', 'system dynamics', 'ode'),
    references=('Meadows 2008',),
    defaults={'until': 10.0, 'dt': 0.5},
)
def bathtub(water: float = 50.0, *, inflow: float = 5.0, outflow: float = 5.0) -> System:
    """Return a bathtub, one stock of water with an inflow from the faucet and an outflow through the drain.

    The drain empties the tub at `outflow` gallons per minute while there is water, and the faucet opens to `inflow`
    gallons per minute after five minutes. With equal flows the level falls for five minutes and then stays constant.

    Args:
        water:
            The initial water in the tub, in gallons.

        inflow:
            The inflow after five minutes, in gallons per minute.

        outflow:
            The outflow, in gallons per minute.

    Returns:
        system:
            The model, with the water `tub.water` in gallons and the flows `tub.faucet` and `tub.drain` in gallons
            per minute, over minutes.

    Examples:
        >>> from skcomplex.models.engineering.processes import bathtub
        >>> bathtub(water=20.0).elements[0].water
        20.0
    """
    return System(elements=[TubElement('tub', water=water, inflow=inflow, outflow=outflow)])

Components

class TubElement(Element):
    """Tub of water, a stock filled by a faucet that opens after five minutes and emptied by a drain."""

    def __init__(
        self,
        label: str,
        visual: VisualSpec | None = None,
        *,
        water: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo), quantity='water')] = 50.0,
        inflow: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 5.0,
        outflow: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 5.0,
    ) -> None:
        super().__init__(label, visual=visual)
        self.water = water
        self.inflow = inflow
        self.outflow = outflow

    def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
        """Return the faucet and drain flows of the tub."""
        return [
            TransferLaw(Source(), s.water, s.inflow * step(s.t, INFLOW_START), label='faucet'),
            TransferLaw(s.water, Sink(), where(s.water > 0, s.outflow, 0), label='drain'),
        ]

Equations

tub.water: diff(water_tub(t), t) == inflow_tub*where(t >= 5.0, 1.0, 0) - where(water_tub(t) > 0, outflow_tub, 0)