Skip to content
Merged
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
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ A package for calculating electricity-related emissions and costs for optimizati
Useful Commands
===============

1. ``pip install -e .``
1. ``pip install -e .`` (or ``pip install -e .[test]`` for development)

This will install your package in editable mode.

Expand Down
121 changes: 66 additions & 55 deletions eeco/costs.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,11 @@ def create_charge_array(
# calculate months, days, and hours
weekdays = datetime.dt.weekday.values
months = datetime.dt.month.values
hours = datetime.dt.hour.astype(float).values
# Make sure hours are being incremented by XX-minute increments
minutes = datetime.dt.minute.astype(float).values
hours += minutes / 60
hours = (
datetime.dt.hour.astype(float).values
+ datetime.dt.minute.astype(float).values / 60
)

# create an empty charge array
apply_charge = (
Expand All @@ -153,6 +154,7 @@ def create_charge_array(
# if not specified, assume the old format (imperial units)
if utility == GAS:
# convert from therms to default units of cubic meters
charge = charge.copy()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the goal of copying this dictionary? Was there a bug that you found?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This came up when running with pandas 3.0 from flows-mpc, which had the older billing format in tests.

It was raising a ChainedAssignmentError: https://pandas.pydata.org/docs/user_guide/copy_on_write.html#chained-assignment

Instead of making a copy, we could possibly use .loc (like the linked example) if modifying limit_charges directly instead of charge, but that would probably be more complicated.

I ended up changing the flows-mpc billing csvs to the new format so it's not actually hitting this code anymore. Think it's worth adding a test case for this "except" section?

charge[CHARGE] /= 2.83168 # therm per cubic meter of CH4
charge_array = apply_charge * charge[CHARGE]
return charge_array
Expand Down Expand Up @@ -246,10 +248,6 @@ def get_charge_dict(start_dt, end_dt, rate_data, resolution="15m"):
),
columns=[DATETIME],
)
hours = datetime[DATETIME].dt.hour.astype(float).values
# Make sure hours are being incremented by XX-minute increments
minutes = datetime[DATETIME].dt.minute.astype(float).values
hours += minutes / 60

for utility in [GAS, ELECTRIC]:
for charge_type in [CUSTOMER, ENERGY, DEMAND, EXPORT]:
Expand Down Expand Up @@ -1522,6 +1520,7 @@ def calculate_itemized_cost(
demand_scale_factor=1,
model=None,
decomposition_type=None,
by_charge_key=False,
varstr_alias_func=default_varstr_alias_func,
):
"""Calculates itemized costs as a nested dictionary
Expand Down Expand Up @@ -1582,40 +1581,49 @@ def calculate_itemized_cost(
The model object associated with the problem.
Only used in the case of Pyomo, so `None` by default.

decomposition_type : str or None
Type of decomposition to use for consumption data.
- "absolute_value": Linear problem using absolute value
- "binary_variable": `NotImplementedError`
- None (default): No decomposition, treats all consumption as imports

by_charge_key : bool
Whether to further itemize the results by charge key. Default is False.

Returns
-------
dict
{

"electric": {

"customer": `float`
"customer": `float` or dict (key -> cost) if by_charge_key

"energy": `float`
"energy": `float` or dict (key -> cost) if by_charge_key

"demand": `float`
"demand": `float` or dict (key -> cost) if by_charge_key

"export": `float`
"export": `float` or dict (key -> cost) if by_charge_key

"total": `float`
"total": `float` or dict (key -> cost) if by_charge_key

}

"gas": {

"customer": `float`,
"customer": `float` or dict (key -> cost) if by_charge_key

"energy": `float`
"energy": `float` or dict (key -> cost) if by_charge_key

"demand": `float`
"demand": `float` or dict (key -> cost) if by_charge_key

"export": `float`
"export": `float` or dict (key -> cost) if by_charge_key

"total": `float`
"total": `float` or dict (key -> cost) if by_charge_key

}

"total": `float`
"total": `float` or dict (key -> cost) if by_charge_key

}

Expand All @@ -1638,19 +1646,25 @@ def calculate_itemized_cost(
consumption_data_dict, conversion_factors, decomposition_type, model
)

total_cost = 0
results_dict = {}

if desired_utility is None:
for utility in [ELECTRIC, GAS]:
results_dict[utility] = {}
total_utility_cost = 0
for charge_type in [CUSTOMER, ENERGY, DEMAND, EXPORT]:
utilities = [ELECTRIC, GAS] if desired_utility is None else [desired_utility]
charge_types = [CUSTOMER, ENERGY, DEMAND, EXPORT]
charge_items = list(charge_dict.items()) if by_charge_key else [(None, charge_dict)]

for utility in utilities:
results_dict[utility] = {}
for charge_type in charge_types:
if by_charge_key:
results_dict[utility][charge_type] = {}

for charge_key, charges in charge_items:
charge_input = charges if charge_key is None else {charge_key: charges}
cost, model = calculate_cost(
charge_dict,
charge_input,
consumption_data_dict,
resolution=resolution,
prev_demand_dict=prev_demand_dict,
prev_consumption_dict=prev_consumption_dict,
consumption_estimate=consumption_estimate,
desired_utility=utility,
desired_charge_type=charge_type,
Expand All @@ -1659,38 +1673,35 @@ def calculate_itemized_cost(
decomposition_type=decomposition_type,
varstr_alias_func=varstr_alias_func,
)

results_dict[utility][charge_type] = cost
total_utility_cost += cost

results_dict[utility]["total"] = total_utility_cost
total_cost += total_utility_cost
else:
results_dict[desired_utility] = {}
total_utility_cost = 0
for charge_type in [CUSTOMER, ENERGY, DEMAND, EXPORT]:
cost, model = calculate_cost(
charge_dict,
consumption_data_dict,
resolution=resolution,
prev_demand_dict=prev_demand_dict,
prev_consumption_dict=prev_consumption_dict,
consumption_estimate=consumption_estimate,
desired_utility=desired_utility,
desired_charge_type=charge_type,
demand_scale_factor=demand_scale_factor,
model=model,
decomposition_type=decomposition_type,
varstr_alias_func=varstr_alias_func,
if by_charge_key:
results_dict[utility][charge_type][charge_key] = cost
else:
results_dict[utility][charge_type] = cost

if by_charge_key:
results_dict[utility]["total"] = {}
for charge_type in charge_types:
for charge_key, cost in results_dict[utility][charge_type].items():
results_dict[utility]["total"][charge_key] = (
results_dict[utility]["total"].get(charge_key, 0) + cost
)
else:
results_dict[utility]["total"] = sum(
results_dict[utility][charge_type] for charge_type in charge_types
)

results_dict[desired_utility][charge_type] = cost
total_utility_cost += cost

results_dict[desired_utility]["total"] = total_utility_cost
total_cost += total_utility_cost
if by_charge_key:
results_dict["total"] = {}
for utility in utilities:
for charge_key, cost in results_dict[utility]["total"].items():
results_dict["total"][charge_key] = (
results_dict["total"].get(charge_key, 0) + cost
)
else:
results_dict["total"] = sum(
results_dict[utility]["total"] for utility in utilities
)

results_dict["total"] = total_cost
return results_dict, model


Expand Down
50 changes: 50 additions & 0 deletions eeco/data/emissions_datetime_local.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
datetime_local,co2_eq_kg_per_MWh
2024-07-31 00:00:00,395.532224
2024-07-31 01:00:00,415.03668
2024-07-31 02:00:00,424.10852
2024-07-31 03:00:00,427.737256
2024-07-31 04:00:00,425.015704
2024-07-31 05:00:00,410.50076
2024-07-31 06:00:00,393.264264
2024-07-31 07:00:00,366.955928
2024-07-31 08:00:00,355.616128
2024-07-31 09:00:00,351.987392
2024-07-31 10:00:00,350.626616
2024-07-31 11:00:00,352.440984
2024-07-31 12:00:00,353.348168
2024-07-31 13:00:00,346.99788
2024-07-31 14:00:00,339.740408
2024-07-31 15:00:00,337.018856
2024-07-31 16:00:00,337.92604
2024-07-31 17:00:00,346.99788
2024-07-31 18:00:00,357.430496
2024-07-31 19:00:00,359.698456
2024-07-31 20:00:00,360.152048
2024-07-31 21:00:00,362.420008
2024-07-31 22:00:00,369.223888
2024-07-31 23:00:00,375.574176
2024-08-01 00:00:00,414.583088
2024-08-01 01:00:00,433.18036
2024-08-01 02:00:00,439.98424
2024-08-01 03:00:00,443.612976
2024-08-01 04:00:00,443.159384
2024-08-01 05:00:00,419.119008
2024-08-01 06:00:00,405.96484
2024-08-01 07:00:00,378.295728
2024-08-01 08:00:00,366.955928
2024-08-01 09:00:00,364.234376
2024-08-01 10:00:00,362.420008
2024-08-01 11:00:00,364.234376
2024-08-01 12:00:00,364.234376
2024-08-01 13:00:00,355.162536
2024-08-01 14:00:00,347.451472
2024-08-01 15:00:00,345.637104
2024-08-01 16:00:00,346.99788
2024-08-01 17:00:00,358.791272
2024-08-01 18:00:00,365.595152
2024-08-01 19:00:00,366.048744
2024-08-01 20:00:00,369.223888
2024-08-01 21:00:00,376.934952
2024-08-01 22:00:00,385.099608
2024-08-01 23:00:00,393.264264
2024-08-02 00:00:00,414.583088
34 changes: 23 additions & 11 deletions eeco/emissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,8 @@ def get_carbon_intensity(
rollover = (
end_month != start_month
) # should we extend this for multiple months/years?
old_end_day = end_day
if rollover:
old_end_day = end_day
end_day = calendar.monthrange(start_year, start_month)[1]
old_end_hour = end_hour
end_hour = 24
Expand Down Expand Up @@ -275,17 +275,27 @@ def get_carbon_intensity(
# reset start index to first hour of the month
# can assume start hour is zero since we are rolling over
start_hour = 0
end_month_day = i + 1
if no_day_var:
start_index = emissions_data.loc[
(emissions_data[HOUR_VARNAME] == start_hour)
& (emissions_data[MONTH_VARNAME] == end_month)
].idxmax()[0]
start_index = (
emissions_data.loc[
(emissions_data[HOUR_VARNAME] == start_hour)
& (emissions_data[MONTH_VARNAME] == end_month)
]
.idxmax()
.iloc[0]
)
else:
start_index = emissions_data.loc[
(emissions_data[HOUR_VARNAME] == start_hour)
& (emissions_data[DAY_VARNAME] == start_day)
& (emissions_data[MONTH_VARNAME] == end_month)
].idxmax()[0]
start_index = (
emissions_data.loc[
(emissions_data[HOUR_VARNAME] == start_hour)
# & (emissions_data[DAY_VARNAME] == start_day)
& (emissions_data[DAY_VARNAME] == end_month_day)
& (emissions_data[MONTH_VARNAME] == end_month)
]
.idxmax()
.iloc[0]
)
if i == int(old_end_day - 1):
# don't include last hour as it may not be a full hour
for j in range(0, int(end_hour)):
Expand All @@ -310,6 +320,8 @@ def get_carbon_intensity(
leftover = (ntsteps - num_first_min) % n_per_hour
if leftover == 0:
leftover = n_per_hour
# use last day of range if there is rollover
last_day = old_end_day if rollover else end_day
if no_day_var:
end_index = (
emissions_data.loc[
Expand All @@ -323,7 +335,7 @@ def get_carbon_intensity(
end_index = (
emissions_data.loc[
(emissions_data[HOUR_VARNAME] == end_hour)
& (emissions_data[DAY_VARNAME] == end_day)
& (emissions_data[DAY_VARNAME] == last_day)
& (emissions_data[MONTH_VARNAME] == end_month)
]
.idxmax()
Expand Down
Loading
Loading