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