Skip to content

Solid waste system

A solid waste system, the waste of a growing city collected, recycled, composted, and landfilled.

  • Name: solid_waste
  • Field: engineering/processes
  • Formalism: ordinary differential equations
  • References: Bala et al. 2017

Factory

@register_model(
    field='engineering/processes',
    tags=('waste management', 'urban', 'stocks and flows', 'system dynamics', 'ode'),
    references=('Bala et al. 2017',),
    defaults={'until': 40.0, 'dt': 0.1},
    observables=lambda system: [
        ObservableSpec('generation', WasteSystemElement._generation, element='city'),
        ObservableSpec('collection', WasteSystemElement._collection, element='city'),
        ObservableSpec('acceptance', WasteSystemElement._acceptance, element='city'),
    ],
)
def solid_waste(
    population: float = 1e6,
    *,
    growth_rate: float = 0.02,
    per_capita: float = 0.2,
    recycling_share: float = 0.2,
    composting_share: float = 0.3,
    landfill_capacity: float = 2e6,
) -> System:
    """Return a solid waste system, the waste of a growing city collected, recycled, composted, and landfilled.

    The population grows exponentially and generates waste in proportion to its size. The waste waits uncollected
    until the collection, limited by a collection capacity that the city expands towards the generation with a delay,
    removes it. The collected waste is recycled, composted, or sent to the landfill in fixed shares. Recycled material
    and compost leave for industry and farms. The landfill accepts waste until it nears its capacity, and a table
    function then turns the residual waste to open dumping. Waste is conserved, so the landfill fills up to its
    capacity and dumping takes over. The structure follows the solid waste management case of Bala, Arshad and Noh
    (2017), with illustrative parameters in tonnes and years for a city of a million people.

    Args:
        population:
            The initial population.

        growth_rate:
            The yearly growth rate of the population.

        per_capita:
            The waste generated per person, in tonnes per year.

        recycling_share:
            The share of the collected waste that is recycled.

        composting_share:
            The share of the collected waste that is composted.

        landfill_capacity:
            The capacity of the landfill in tonnes.

    Returns:
        system:
            The model, with the population `city.population`, the waste stocks `city.uncollected`, `city.recycled`,
            `city.composted`, `city.landfill`, and `city.dumped` in tonnes, the capacity `city.collection_capacity`,
            and the flows `city.population_growth`, `city.waste_generation`, `city.recycling`, `city.composting`,
            `city.landfilling`, `city.dumping`, `city.recycled_sales`, and `city.compost_sales` per year, over years.
            The observables of the model are `city.generation`, `city.collection`, and `city.acceptance`.

    Examples:
        >>> from skcomplex.models.engineering.processes import solid_waste
        >>> solid_waste(landfill_capacity=1e6).elements[0].landfill_capacity
        1000000.0
    """
    city = WasteSystemElement(
        'city',
        population=population,
        collection_capacity=INITIAL_COLLECTION_SHARE * per_capita * population,
        growth_rate=growth_rate,
        per_capita=per_capita,
        recycling_share=recycling_share,
        composting_share=composting_share,
        landfill_capacity=landfill_capacity,
    )
    return System(elements=[city])

Components

class WasteSystemElement(Element):
    """Solid waste system of a growing city that collects, recycles, composts, and landfills its waste."""

    def __init__(
        self,
        label: str,
        visual: VisualSpec | None = None,
        *,
        population: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo), quantity='people')] = 1000000.0,
        uncollected: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo), quantity='waste')] = 0.0,
        recycled: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo), quantity='waste')] = 0.0,
        composted: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo), quantity='waste')] = 0.0,
        landfill: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo), quantity='waste')] = 0.0,
        dumped: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo), quantity='waste')] = 0.0,
        collection_capacity: Annotated[object, StateSpec(domain=RealSet.closed_open(0, oo))] = 150000.0,
        growth_rate: Annotated[object, ParameterSpec()] = 0.02,
        per_capita: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.2,
        collection_time: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 0.05,
        recycling_share: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.2,
        composting_share: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.3,
        landfill_capacity: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 2000000.0,
        recycling_time: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 0.25,
        composting_time: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 0.5,
        capacity_time: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 3.0,
    ) -> None:
        super().__init__(label, visual=visual)
        self.population = population
        self.uncollected = uncollected
        self.recycled = recycled
        self.composted = composted
        self.landfill = landfill
        self.dumped = dumped
        self.collection_capacity = collection_capacity
        self.growth_rate = growth_rate
        self.per_capita = per_capita
        self.collection_time = collection_time
        self.recycling_share = recycling_share
        self.composting_share = composting_share
        self.landfill_capacity = landfill_capacity
        self.recycling_time = recycling_time
        self.composting_time = composting_time
        self.capacity_time = capacity_time

    def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
        """Return the flows of waste through the city and the adjustment of its collection capacity."""
        return WasteSystemElement._waste_laws(s)

    @staticmethod
    def _generation(s: SimpleNamespace) -> Expression:
        """Return the generation of waste by the population."""
        return s.per_capita * s.population

    @staticmethod
    def _collection(s: SimpleNamespace) -> Expression:
        """Return the collection of waste, limited by the collection capacity."""
        return minimum(s.uncollected / s.collection_time, s.collection_capacity)

    @staticmethod
    def _acceptance(s: SimpleNamespace) -> Expression:
        """Return the share of the residual waste that the landfill accepts as it fills."""
        return lookup(s.landfill / s.landfill_capacity, LANDFILL_ACCEPTANCE)

    @staticmethod
    def _waste_laws(s: SimpleNamespace) -> list[BaseLaw]:
        """Return the generation, collection, treatment, and disposal of the solid waste of a growing city."""
        generation, collection, acceptance = (
            WasteSystemElement._generation(s),
            WasteSystemElement._collection(s),
            WasteSystemElement._acceptance(s),
        )
        residual = (1 - s.recycling_share - s.composting_share) * collection
        return [
            TransferLaw(Source('births'), s.population, s.growth_rate * s.population, label='population_growth'),
            TransferLaw(Source('households'), s.uncollected, generation, label='waste_generation'),
            TransferLaw(s.uncollected, s.recycled, s.recycling_share * collection, label='recycling'),
            TransferLaw(s.uncollected, s.composted, s.composting_share * collection, label='composting'),
            TransferLaw(s.uncollected, s.landfill, acceptance * residual, label='landfilling'),
            TransferLaw(s.uncollected, s.dumped, (1 - acceptance) * residual, label='dumping'),
            TransferLaw(s.recycled, Sink('industry'), s.recycled / s.recycling_time, label='recycled_sales'),
            TransferLaw(s.composted, Sink('farms'), s.composted / s.composting_time, label='compost_sales'),
            RateLaw(s.collection_capacity, (generation - s.collection_capacity) / s.capacity_time),
        ]

Equations

city.population: diff(population_city(t), t) == growth_rate_city*population_city(t)
city.uncollected: diff(uncollected_city(t), t) == -(composting_share_city + recycling_share_city - 1)*(where(landfill_city(t)/landfill_capacity_city < 0.0, 1.0, where(landfill_city(t)/landfill_capacity_city < 0.9, 1.0, where(landfill_city(t)/landfill_capacity_city < 1.0, -10.000000000000002*landfill_city(t)/landfill_capacity_city + 10.000000000000002, 0.0))) - 1)*where(collection_capacity_city(t) < uncollected_city(t)/collection_time_city, collection_capacity_city(t), uncollected_city(t)/collection_time_city) + (composting_share_city + recycling_share_city - 1)*where(landfill_city(t)/landfill_capacity_city < 0.0, 1.0, where(landfill_city(t)/landfill_capacity_city < 0.9, 1.0, where(landfill_city(t)/landfill_capacity_city < 1.0, -10.000000000000002*landfill_city(t)/landfill_capacity_city + 10.000000000000002, 0.0)))*where(collection_capacity_city(t) < uncollected_city(t)/collection_time_city, collection_capacity_city(t), uncollected_city(t)/collection_time_city) + per_capita_city*population_city(t) - composting_share_city*where(collection_capacity_city(t) < uncollected_city(t)/collection_time_city, collection_capacity_city(t), uncollected_city(t)/collection_time_city) - recycling_share_city*where(collection_capacity_city(t) < uncollected_city(t)/collection_time_city, collection_capacity_city(t), uncollected_city(t)/collection_time_city)
city.recycled: diff(recycled_city(t), t) == recycling_share_city*where(collection_capacity_city(t) < uncollected_city(t)/collection_time_city, collection_capacity_city(t), uncollected_city(t)/collection_time_city) - recycled_city(t)/recycling_time_city
city.composted: diff(composted_city(t), t) == composting_share_city*where(collection_capacity_city(t) < uncollected_city(t)/collection_time_city, collection_capacity_city(t), uncollected_city(t)/collection_time_city) - composted_city(t)/composting_time_city
city.landfill: diff(landfill_city(t), t) == -(composting_share_city + recycling_share_city - 1)*where(landfill_city(t)/landfill_capacity_city < 0.0, 1.0, where(landfill_city(t)/landfill_capacity_city < 0.9, 1.0, where(landfill_city(t)/landfill_capacity_city < 1.0, -10.000000000000002*landfill_city(t)/landfill_capacity_city + 10.000000000000002, 0.0)))*where(collection_capacity_city(t) < uncollected_city(t)/collection_time_city, collection_capacity_city(t), uncollected_city(t)/collection_time_city)
city.dumped: diff(dumped_city(t), t) == (composting_share_city + recycling_share_city - 1)*(where(landfill_city(t)/landfill_capacity_city < 0.0, 1.0, where(landfill_city(t)/landfill_capacity_city < 0.9, 1.0, where(landfill_city(t)/landfill_capacity_city < 1.0, -10.000000000000002*landfill_city(t)/landfill_capacity_city + 10.000000000000002, 0.0))) - 1)*where(collection_capacity_city(t) < uncollected_city(t)/collection_time_city, collection_capacity_city(t), uncollected_city(t)/collection_time_city)
city.collection_capacity: diff(collection_capacity_city(t), t) == (per_capita_city*population_city(t) - collection_capacity_city(t))/capacity_time_city