Skip to content
Merged
15 changes: 14 additions & 1 deletion SWEET_python/city_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -4936,7 +4936,12 @@ def _calculate_diverted_masses(self, scenario: int = 0) -> None:
parameters.divs = divs

def dst_baseline_blank(
self, country: str, population: int, precipitation: float, temperature: float
self,
country: str,
population: int,
precipitation: float,
temperature: float,
population_series: Optional[pd.Series] = None,
) -> None:
"""
Initializes the baseline scenario with given parameters for a blank/custom city.
Expand All @@ -4945,6 +4950,10 @@ def dst_baseline_blank(
country (str): The country name.
population (int): Population of the city.
precipitation (float): Average annual precipitation in mm/year.
population_series (pd.Series): the country's population by year, worked
out once by country_population_trajectory and stored. The city's waste
grows along it instead of along SWEET's own table. Only its shape
counts: the city's waste is set by ``population``.

Returns:
None
Expand Down Expand Up @@ -5119,9 +5128,13 @@ def calculate_component_fractions(
waste_masses_df = waste_fractions_df * waste_mass
# The country's population, year by year. The constants above are one rate for
# every country; they stand only for a country SWEET has no series for.
stored_series = population_series
population_series, growth_rate_historic, growth_rate_future = _country_growth(
iso3, year_of_data_pop, growth_rate_historic, growth_rate_future
)
if stored_series is not None:
# The two rates stay the ones a person reads; the stored series is the growth.
population_series = stored_series
waste_generated_df = WasteGeneratedDF.create(
waste_masses_df,
MODEL_START_YEAR,
Expand Down
11 changes: 11 additions & 0 deletions changelog/2026-10.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ take to other landfills, so a caller no longer has to make the shares add up to
No other number moves. ([#91](https://github.com/RMI/SWEET_python/pull/91),
[RMI/WasteMAP#888](https://github.com/RMI/WasteMAP/pull/888))

- **A Custom Location can grow along a stored country trajectory.**
`City.dst_baseline_blank` takes `population_series`, as `load_csv_new` does: the
country's population by year from `country_population_trajectory`, stored with
WasteMAP's map build. The Custom Location's waste, and its scenario's, grow along
it instead of along SWEET's own table; the city's tonnage still comes from the
population it is given, and the two growth rates a person reads are still the
country's. Without it nothing changes. Handed the country's own trajectory, a
Custom Location grows as before, to within 5.9e-16 in every country, and the 12
with fallback rates exactly. ([#95](https://github.com/RMI/SWEET_python/pull/95),
[RMI/WasteMAP#891](https://github.com/RMI/WasteMAP/pull/891))

- **The city-level advanced DST can send the rest of a city's waste to other
landfills.** `AdvancedDSTCityRequest` gains `other_landfills`, a landfill spec
(`OtherLandfillsSpec`) with no open or close years and no gate total. When it
Expand Down
61 changes: 61 additions & 0 deletions tests/test_population_trajectory.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,64 @@ def test_load_csv_new_grows_a_city_along_the_trajectory_it_is_handed():
worked_out.growth_rate_historic,
worked_out.growth_rate_future,
)


def _custom_location(country, population_series=None):
"""A Custom Location, as WasteMAP's city DST builds one."""
city = City("custom")
city.dst_baseline_blank(country, 100_000, 800.0, 15.0, population_series=population_series)
return city


def test_a_custom_location_grows_along_the_country_trajectory_it_is_handed():
stored, _ = country_population_trajectory("USA")
flat = pd.Series(331_000_000.0, index=YEARS)

worked_out, along_stored, along_flat = (
_custom_location("United States", series).baseline_parameters
for series in (None, stored, flat)
)

waste = lambda p: p.waste_generated_df.sum(axis=1) # noqa: E731
np.testing.assert_allclose(waste(along_stored), waste(worked_out), rtol=1e-14)
np.testing.assert_allclose(
(waste(along_stored) / waste(along_stored).loc[2022]).loc[YEARS],
stored / stored.loc[city_params.CUSTOM_LOCATION_POPULATION_YEAR],
rtol=1e-14,
)
# Only the shape counts: the waste is the caller's population's, in every case.
assert waste(along_flat).loc[2022] == waste(worked_out).loc[2022]
assert waste(along_flat).loc[2050] == pytest.approx(waste(along_flat).loc[2022], rel=1e-12)
assert waste(along_flat).loc[2050] != pytest.approx(waste(worked_out).loc[2050], rel=1e-3)
# The two rates a person reads are still the country's.
assert (along_flat.growth_rate_historic, along_flat.growth_rate_future) == (
worked_out.growth_rate_historic,
worked_out.growth_rate_future,
)


def test_a_custom_location_with_its_fallback_trajectory_is_unchanged():
stored, source = country_population_trajectory("ATA")
assert source == city_params.COUNTRY_FALLBACK_RATES

worked_out = _custom_location("Antarctica").baseline_parameters
along_stored = _custom_location("Antarctica", stored).baseline_parameters

pd.testing.assert_frame_equal(along_stored.waste_generated_df, worked_out.waste_generated_df)
assert (along_stored.growth_rate_historic, along_stored.growth_rate_future) == (
city_params.FALLBACK_GROWTH_RATE_HISTORIC,
city_params.FALLBACK_GROWTH_RATE_FUTURE,
)


def test_a_custom_location_scenario_grows_along_the_trajectory_it_is_handed():
flat = pd.Series(331_000_000.0, index=YEARS)
city = _custom_location("United States", flat)
for landfill in city.baseline_parameters.landfills:
landfill.estimate_emissions()

divs = city_params.DiversionFractions(compost=0.1, anaerobic=0.0, combustion=0.0, recycling=0.1)
city.implement_dst_changes_simple_v1_5(divs, False, False, 0.0, 0.0, 2030, 1, 0.0)
waste = city.scenario_parameters[0].waste_generated_df.df.sum(axis=1)

assert waste.loc[2050] == pytest.approx(waste.loc[2030], rel=1e-12)
Loading