Skip to content

Commit 563cc7f

Browse files
committed
Add material subtraction vetting
1 parent 8b81a61 commit 563cc7f

File tree

1 file changed

+106
-0
lines changed

1 file changed

+106
-0
lines changed

message_ix_models/model/bmt/utils.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,106 @@
1313
log = logging.getLogger(__name__)
1414

1515

16+
def _generate_vetting_csv(
17+
original_demand: pd.DataFrame,
18+
modified_demand: pd.DataFrame,
19+
output_path: str,
20+
) -> None:
21+
"""Generate a CSV file showing material demand subtraction details.
22+
23+
Parameters
24+
----------
25+
original_demand : pd.DataFrame
26+
Original demand data before subtraction
27+
modified_demand : pd.DataFrame
28+
Modified demand data after subtraction
29+
output_path : str
30+
Path where to save the vetting CSV file
31+
"""
32+
# Reset index to work with columns
33+
orig = original_demand.reset_index()
34+
mod = modified_demand.reset_index()
35+
36+
# Merge original and modified data
37+
vetting_data = orig.merge(
38+
mod,
39+
on=["node", "year", "commodity"],
40+
suffixes=("_original", "_modified"),
41+
how="outer",
42+
).fillna(0)
43+
44+
# Calculate subtraction amounts and percentages
45+
vetting_data["subtracted_amount"] = (
46+
vetting_data["value_original"] - vetting_data["value_modified"]
47+
)
48+
49+
# Calculate percentage subtracted (avoid division by zero)
50+
vetting_data["subtraction_percentage"] = (
51+
vetting_data["subtracted_amount"]
52+
/ vetting_data["value_original"].replace(0, 1)
53+
* 100
54+
)
55+
56+
# Replace infinite values with 0 (when original was 0)
57+
vetting_data["subtraction_percentage"] = vetting_data[
58+
"subtraction_percentage"
59+
].replace([float("inf"), -float("inf")], 0)
60+
61+
# Round to reasonable precision
62+
vetting_data["subtraction_percentage"] = vetting_data[
63+
"subtraction_percentage"
64+
].round(2)
65+
66+
# Select and rename columns for clarity
67+
output_columns = [
68+
"node",
69+
"year",
70+
"commodity",
71+
"value_original",
72+
"value_modified",
73+
"subtracted_amount",
74+
"subtraction_percentage",
75+
]
76+
77+
vetting_data = vetting_data[output_columns].copy()
78+
vetting_data.columns = [
79+
"node",
80+
"year",
81+
"commodity",
82+
"original_demand",
83+
"modified_demand",
84+
"subtracted_amount",
85+
"subtraction_percentage",
86+
]
87+
88+
# # Filter out rows where no subtraction occurred
89+
# vetting_data = vetting_data[vetting_data["subtracted_amount"] > 0]
90+
91+
# Sort by commodity, node, year for better readability
92+
vetting_data = vetting_data.sort_values(["commodity", "node", "year"])
93+
94+
# Save to CSV
95+
vetting_data.to_csv(output_path, index=False)
96+
97+
log.info(f"Vetting CSV saved to: {output_path}")
98+
99+
# Log summary statistics
100+
if len(vetting_data) > 0:
101+
avg_pct = vetting_data["subtraction_percentage"].mean()
102+
max_pct = vetting_data["subtraction_percentage"].max()
103+
log.info(f"Average subtraction percentage: {avg_pct:.2f}%")
104+
log.info(f"Max subtraction percentage: {max_pct:.2f}%")
105+
106+
107+
# Maybe it is better to have one function for each method?
16108
def subtract_material_demand(
17109
scenario: "Scenario",
18110
info: "ScenarioInfo",
19111
sturm_r: pd.DataFrame,
20112
sturm_c: pd.DataFrame,
21113
method: str = "bm_subtraction",
114+
generate_vetting_csv: bool = True,
115+
vetting_output_path: str = "material_demand_subtraction_vetting.csv",
22116
) -> pd.DataFrame:
23117
"""Subtract inter-sector material demand from existing demands in scenario.
24118
@@ -43,6 +137,11 @@ def subtract_material_demand(
43137
- "im_subtraction": substract base year and rerun material demand projection
44138
- "pm_subtraction": to be determined (currently treated as additional demand)
45139
- "tm_subtraction": to be determined
140+
generate_vetting_csv : bool, optional
141+
Whether to generate a CSV file showing subtraction details (default: True)
142+
vetting_output_path : str, optional
143+
Path for the vetting CSV file (default:
144+
"material_demand_subtraction_vetting.csv")
46145
47146
Returns
48147
-------
@@ -54,6 +153,9 @@ def subtract_material_demand(
54153
index_cols = ["node", "year", "commodity"]
55154

56155
if method == "bm_subtraction":
156+
# Store original demand for vetting if requested
157+
original_demand = mat_demand.copy() if generate_vetting_csv else None
158+
57159
# Subtract the building material demand trajectory from existing demands
58160
for rc, base_data, how in (
59161
("resid", sturm_r, "right"),
@@ -99,6 +201,10 @@ def subtract_material_demand(
99201
.drop(columns=["demand_comm_const", "demand_resid_const"])
100202
)
101203

204+
# Generate vetting CSV if requested
205+
if generate_vetting_csv and original_demand is not None:
206+
_generate_vetting_csv(original_demand, mat_demand, vetting_output_path)
207+
102208
elif method == "im_subtraction":
103209
# TODO: to be implemented
104210
log.warning("Method 'im_subtraction' not implemented yet, using bm_subtraction")

0 commit comments

Comments
 (0)