diff --git a/SWEET_python/city_params.py b/SWEET_python/city_params.py index 133e96f..72a6edb 100644 --- a/SWEET_python/city_params.py +++ b/SWEET_python/city_params.py @@ -4938,7 +4938,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. @@ -4947,6 +4952,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 @@ -5121,9 +5130,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, diff --git a/changelog/2026-10.md b/changelog/2026-10.md index 6e0d930..daf39ba 100644 --- a/changelog/2026-10.md +++ b/changelog/2026-10.md @@ -68,6 +68,17 @@ or 9% above the default (model-output change). 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. ([#94](https://github.com/RMI/SWEET_python/pull/94), + [RMI/WasteMAP#890](https://github.com/RMI/WasteMAP/pull/890)) + - **Seven more Climate TRACE modelling decisions are marked where SWEET applies them**: waste before 1970 counted as zero, a site's last intake in the year before it closes, intake following population, the decay rate from climate, each waste type's methane diff --git a/tests/test_population_trajectory.py b/tests/test_population_trajectory.py index 845bc10..5dad5e3 100644 --- a/tests/test_population_trajectory.py +++ b/tests/test_population_trajectory.py @@ -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)