Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 61 additions & 11 deletions SWEET_python/city_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,54 @@ def __init__(self, code: str, message: str):
super().__init__(self.message)


def _move_to_existing_gas_capture(
split_fractions: SplitFractions, existing_gas_pct: float
) -> SplitFractions:
"""The city's disposal split with ``existing_gas_pct`` of it going to the
landfills it already has with gas capture: the City DST's "move waste to
existing gas capture" lever.

The landfill and dumpsite shares without capture take the rest,
``1 - existing_gas_pct``, in the proportions they had to each other. A city
sending 34% of its waste to capture and 66% to a landfill without it, moved to
90%, sends the other 10% to that landfill.

Raises CustomError("INVALID_PARAMETERS") when the share is outside [0, 1], when
the city has no landfill with gas capture to move waste to, and when it has no
site without capture to move waste away to.
"""
if not 0.0 <= existing_gas_pct <= 1.0:
raise CustomError(
"INVALID_PARAMETERS",
"The share of waste to existing gas capture must be between 0 and 1, "
f"not {existing_gas_pct}",
)
with_capture = split_fractions.landfill_w_capture
without_capture = split_fractions.landfill_wo_capture + split_fractions.dumpsite
if with_capture <= 0:
if existing_gas_pct > 0:
raise CustomError(
"INVALID_PARAMETERS",
"This city has no landfill with gas capture to move waste to",
)
return split_fractions.model_copy()
if without_capture <= 0:
if existing_gas_pct < 1:
raise CustomError(
"INVALID_PARAMETERS",
"This city sends all of its waste to landfills with gas capture, "
"so there is no site without capture to move waste to",
)
return split_fractions.model_copy()
scale = (1 - existing_gas_pct) / without_capture
return SplitFractions(
landfill_w_capture=existing_gas_pct,
landfill_wo_capture=split_fractions.landfill_wo_capture * scale,
dumpsite=split_fractions.dumpsite * scale,
new_w_capture=split_fractions.new_w_capture,
)


class City:
def __init__(self, city_name: str):
"""
Expand Down Expand Up @@ -7204,18 +7252,19 @@ def implement_dst_changes_simple_v1_5(
skip_ox = True

if move_gas:
original_gas_pct = (
scenario_parameters.split_fractions.landfill_w_capture
scenario_parameters.split_fractions = _move_to_existing_gas_capture(
scenario_parameters.split_fractions, existing_gas_pct
)
# Each landfill's waste comes from its own fraction_of_waste
# (LandfillWasteMassDF.create below), not from split_fractions.
split = scenario_parameters.split_fractions
scenario_parameters.landfills[0].fraction_of_waste = (
split.landfill_w_capture
)
scenario_parameters.split_fractions.landfill_w_capture = (
existing_gas_pct
scenario_parameters.landfills[1].fraction_of_waste = (
split.landfill_wo_capture
)
if original_gas_pct > 0:
ratio = existing_gas_pct / original_gas_pct
else:
ratio = 0
scenario_parameters.split_fractions.landfill_wo_capture *= ratio
scenario_parameters.split_fractions.dumpsite *= ratio
scenario_parameters.landfills[2].fraction_of_waste = split.dumpsite

if new_gas_pct > 0:
scenario_parameters.landfills.append(
Expand All @@ -7232,7 +7281,8 @@ def implement_dst_changes_simple_v1_5(
total = sum(scenario_parameters.split_fractions.model_dump().values())
if abs(total - 1.0) > 1e-3:
raise CustomError(
f"Invalid split fractions: {scenario_parameters.split_fractions}"
"INVALID_PARAMETERS",
f"Invalid split fractions: {scenario_parameters.split_fractions}",
)
scenario_parameters.landfills[0].fraction_of_waste = (
scenario_parameters.split_fractions.landfill_w_capture
Expand Down
52 changes: 52 additions & 0 deletions changelog/2026-10.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# SWEET_python Changelog — October 2026

**Highlights:** The City DST's "move waste to existing gas capture" lever now
changes a city's emissions. For a city on the generic landfill split, it worked
out a new split and never gave it to the landfills, so every setting returned the
no-change scenario. Moved to 90% gas capture, Lahore now emits 45% less in 2040
(model-output change).

## Fixed

- **Moving waste to a city's existing gas capture changes its emissions**
(model-output change). `City.implement_dst_changes_simple_v1_5` with
`move_gas=1` and `existing_gas_pct` is the City DST's "% of waste diverted to
existing landfill with gas capture" slider. Its generic branch rewrote
`split_fractions`, but each landfill's waste comes from its own
`fraction_of_waste`, and only the "new landfill with gas capture" block copied
the split there. WasteMAP offers this slider only to a city that already has gas
capture, and switches the new-landfill lever off for exactly those cities, so the
two never ran together. On the June 2026 cities table, all 7 cities with a live
slider (Mexico City, Lahore, Santiago, Amman, Tegucigalpa, Cebu City, Tunis)
returned the no-change scenario exactly, at every setting.

The arithmetic was wrong as well. It set the capture share to `existing_gas_pct`
and multiplied the shares without capture by `existing / original`, scaling them
up rather than down: Lahore's landfill without capture came out at 171% of the
city's waste, and Mexico City's at 3,848%. A city with no capture had both shares
zeroed. With a new landfill with gas capture as well, the inflated split failed
its sum check, and that check built `CustomError` with one argument, so the API
returned a 500.

The new `_move_to_existing_gas_capture` gives the capture share
`existing_gas_pct`. The landfill and dumpsite without capture share the rest in
the proportions they had, and the scenario applies the split to the landfills
from the implementation year. In 2040:
- Moved to 90% capture: Lahore −45%, Mexico City −43%, Cebu City −30%.
- Moved to 100%: Lahore −53%, Mexico City −48%.
- Moving waste off capture raises emissions. Santiago (98.8% to capture) moved
to 0% emits 96% more.

Three requests the lever can't carry out now return `INVALID_PARAMETERS`
instead of the no-change scenario:
- a share outside [0, 1];
- a share above 0 for a city with no landfill with gas capture (WasteMAP doesn't
offer the lever there);
- less than 100% for a city that sends all its waste to capture. WasteMAP
disables the slider for the 41 such cities in the June table and sends 100%,
which leaves the split as it is.

Nothing else moves. The no-change scenario, the other levers and the 41 cities at
100% give the same numbers as before. The sum check now raises a proper
`INVALID_PARAMETERS` too.
([#75](https://github.com/RMI/SWEET_python/pull/75), [RMI/WasteMAP#854](https://github.com/RMI/WasteMAP/pull/854))
1 change: 1 addition & 0 deletions changelog/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ The project does not publish semantic version tags, so releases are tracked by

Newest first:

- [2026-10](2026-10.md) — the City DST's "move waste to existing gas capture" slider changes a city's emissions; it computed a new split and never applied it to the landfills, so every setting returned the no-change scenario for the 7 cities with a live slider (its arithmetic also scaled the shares without capture up, not down) (model-output change)
- [2026-09](2026-09.md) — WasteMAP's site tool, city tool and Custom Location grow waste with the country's UN population year by year, as Climate TRACE does; big cities keep their own UN city rates (model-output change); model output frames get one fixed waste-type column order instead of a per-process one (a `set` fed the ordering, and Python randomizes set iteration per process), moving values in the last bit only, where a sum runs across the reordered columns; City DST's diversion sum stops passing pandas a keyword pandas is removing, which would have broken the model path on pandas 4 behind a misleading `AttributeError` (no output change); annual model no longer charges a flare's inefficiency twice (a hardcoded 2% slip stacked on the `flaring` destruction efficiency), so it agrees with the monthly model again and a site with gas capture emits ~3.5% less (model-output change); `gas_capture_efficiency` bounded to [0, 1], where an out-of-range value used to produce negative emissions silently; one methane density constant instead of two 9.1% apart
- [2026-08](2026-08.md) — Single-site adst gains optional `depth` (deep-dump MCF bump) and `k_override` (caller-supplied decomposition rate) inputs, restoring the last two site-DST levers; `/sdst` flaring efficiency reaches the model again after a variable-name bug silently forced flare destruction to 0.98; annual model applies cover oxidation by emission year not deposit year, fixing biocover having no effect on closed landfills (WasteMAP #719); `City.sdst_v1_5` custom-site path holds the scenario equal to the baseline before the implementation year even when composition changes (was back-dating the new composition onto pre-implementation deposits) (model-output change); MCF consolidated into a new `SWEET_python.mcf` module and both dump types moved to the IPCC uncategorised-SWDS 0.6 (open dumps up from 0.4, controlled dumps down from 0.7), with a supplied depth now selecting the deep/shallow category (model-output change)
- [2026-07](2026-07.md) — All ten waste types eligible for combustion (metal/glass/other added); methane-only model treats combustion as landfill diversion (model-output change)
Expand Down
Loading
Loading