Skip to content
Open
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
87 changes: 81 additions & 6 deletions SWEET_python/city_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,64 @@
import time


def _build_oxidation_series(default_value, canonical_row, time_series_rows, years_range):
"""Per-year oxidation factor for one modeled landfill, preferring per-site input
oxidation over the type/gas-capture default.

A site can have several input rows from independent sources. Oxidation carries no
year of its own in the source data -- the value that varies row-to-row is the
*emissions* year (``reported_emissions_year``), not an oxidation year -- so we use
the site-wide mean of the available input oxidation values as a constant baseline
across all model years, then overwrite individual years with that year's value
(mean of any conflicting same-year rows) where an emissions year is present.

When the site has no usable input oxidation, fall back to ``default_value`` (the
type/gas-capture default) broadcast across all years -- i.e. the prior behaviour.
Input values are used as-is (no clamping); e.g. a measured 0.35 passes through.

``time_series_rows`` is a DataFrame for multi-row sites and a Series for single-row
sites; ``canonical_row`` is the single deduped row. Either may carry ``oxidation``.
"""
years_index = pd.Index(years_range)
series = pd.Series(float(default_value), index=years_index)

# Assemble the site's input rows as a frame so single-row (Series) and multi-row
# (DataFrame) sites are handled uniformly. Prefer the multi-row frame -- it holds
# every source record -- and fall back to the single canonical row.
if isinstance(time_series_rows, pd.DataFrame):
frame = time_series_rows
elif isinstance(canonical_row, pd.DataFrame):
frame = canonical_row
elif isinstance(canonical_row, pd.Series):
frame = canonical_row.to_frame().T
else:
frame = None

if frame is None or 'oxidation' not in frame.columns:
return series

ox = pd.to_numeric(frame['oxidation'], errors='coerce')
if not ox.notna().any():
return series # no input oxidation -> keep the type/gas-capture default

# 1) Site-wide mean as the full-series baseline.
series[:] = float(ox[ox.notna()].mean())

# 2) Overwrite individual years where an emissions year ties a value to a year.
if 'reported_emissions_year' in frame.columns:
yr = pd.to_numeric(frame['reported_emissions_year'], errors='coerce')
mask = ox.notna() & yr.notna()
if mask.any():
per_year = pd.Series(ox[mask].to_numpy(), index=yr[mask].astype(int).to_numpy())
per_year = per_year.groupby(level=0).mean()
lo, hi = int(years_index.min()), int(years_index.max())
per_year = per_year[(per_year.index >= lo) & (per_year.index <= hi)]
if not per_year.empty:
series.loc[per_year.index] = per_year.to_numpy()

return series


# The way this model is set up is based on the unit of a City, corresponding to the City class.
# Cities can have multiple sets of CityParameters, one for each scenario.
# Sets of CityParameters can have one or more landfills, dumpsites, waste to energy, etc.
Expand Down Expand Up @@ -2486,6 +2544,15 @@ def site_only_estimate_trace(self, canonical_row=None, time_series_rows=None, po
)
fraction_of_waste_vector.loc[open_date:close_date-1] = 1.0
id = int(canonical_row['asset_identifier'])
oxidation_series = _build_oxidation_series(
oxidation_value, canonical_row, time_series_rows, self.years_range
)
# Baseline flaring destruction efficiency, set explicitly to the canonical default
# (was unset -> fell to model_v2's internal default). No per-site source exists;
# mitigation raises it to 0.98/0.99 via _gccs_flaring (max/clip). Local import
# avoids the dst_common <-> city_params import cycle.
from SWEET_python.dst_common import DEFAULT_FLARE_EFFICIENCY
flaring_series = pd.Series(DEFAULT_FLARE_EFFICIENCY, index=self.years_range)
new_landfill = Landfill(
open_date=open_date,
close_date=close_date,
Expand All @@ -2500,13 +2567,13 @@ def site_only_estimate_trace(self, canonical_row=None, time_series_rows=None, po
scenario=0,
new_baseline=True,
gas_capture_efficiency=gas_capture_efficiency,
# flaring=pd.Series(flaring, index=year_range),
flaring=flaring_series,
# leachate_circulate=leachate_circulate[i],
fraction_of_waste_vector=fraction_of_waste_vector,
advanced=True,
latlon=(self.latitude, self.longitude),
ks=baseline.ks,
oxidation_factor=pd.Series(oxidation_value, index=self.years_range),
oxidation_factor=oxidation_series,
rmi_id=id,
)
baseline.landfills.append(new_landfill)
Expand Down Expand Up @@ -2755,6 +2822,14 @@ def citysite_estimate_trace(self, canonical_row=None, time_series_rows=None, cit
close_date = int(close_date)

id = int(canonical_row['asset_identifier'])
oxidation_series = _build_oxidation_series(
oxidation_value, canonical_row, time_series_rows, self.years_range
)
# Baseline flaring destruction efficiency, set explicitly to the canonical default
# (see site_only_estimate_trace). Applies to both the single- and multi-city
# landfill constructors below. Local import avoids the dst_common import cycle.
from SWEET_python.dst_common import DEFAULT_FLARE_EFFICIENCY
flaring_series = pd.Series(DEFAULT_FLARE_EFFICIENCY, index=self.years_range)
baseline._singapore_k(advanced_baseline=True)
if (citysite_rows is None) or (isinstance(citysite_rows, pd.Series)):
fraction_of_waste_vector = pd.Series(
Expand All @@ -2775,13 +2850,13 @@ def citysite_estimate_trace(self, canonical_row=None, time_series_rows=None, cit
scenario=0,
new_baseline=True,
gas_capture_efficiency=gas_capture_efficiency,
# flaring=pd.Series(flaring, index=year_range),
flaring=flaring_series,
# leachate_circulate=leachate_circulate[i],
fraction_of_waste_vector=fraction_of_waste_vector,
advanced=True,
latlon=(self.latitude, self.longitude),
ks=baseline.ks,
oxidation_factor=pd.Series(oxidation_value, index=self.years_range),
oxidation_factor=oxidation_series,
rmi_id=id,
city_id=citysite_rows['city_id']
)
Expand Down Expand Up @@ -2870,13 +2945,13 @@ def citysite_estimate_trace(self, canonical_row=None, time_series_rows=None, cit
scenario=0,
new_baseline=True,
gas_capture_efficiency=gas_capture_efficiency,
# flaring=pd.Series(flaring, index=year_range),
flaring=flaring_series,
# leachate_circulate=leachate_circulate[i],
fraction_of_waste_vector=fraction_of_waste_df[city_id],
advanced=True,
latlon=(self.latitude, self.longitude),
ks=baseline.ks,
oxidation_factor=pd.Series(oxidation_value, index=self.years_range),
oxidation_factor=oxidation_series,
rmi_id=id,
city_id=city_id,
)
Expand Down