Skip to content

Commit ad00aea

Browse files
fletchapindalyw
andauthored
Rebase/decompose (#49)
* Initializing branch for decomposing positive/negative charges (branched off of feature/parametrize_charges) In utils: - Updating sum() util function to use pyo.summation - Adding decompose_consumption function to split a np array, pyo variable, or cvxpy variable into positive and negative components - Adding initialize_decomposed_pyo_vars to help with initializing the decomposed elements in a pyomo model Modifying existing tests in test_costs.py to include decompositino Adding test_decompose_consumption_np, test_decompose_consumption_cvxpy, test_decompose_consumption_pyo to test_utils.py Adding documentation notes in how_to_advanced Adding test coverage for negative values input to energy / demand calculation Removing ValueError raised in calculate_cost for non-np.array/pyo/cvx object. Since this code is never actually reached because the valueerror will be raised from ut.multiply() first Consolidating consumption_data_dict and consumption_object_dict arguments Adding magnitude constraint so that positive_var[t] + negative_var[t] == abs(expression[t]) to prevent both variables becoming large when export rate is higher than energy rate Reformatted with black Updated test_calculate_cost_pyo to use ipopt rather than gurobi when running decompose_exports, since abs() function is nonlinear / nonconvex * Renaming scale_ratios to percent_change_dict in parametrize_charge_dict and parametrize_rate_data Adding test_calculate_itemized_costs for np, cvx, and pyo with one non-decomposed and one decomposed example Adding a warning test to calculate_export_revenue Adding helper functions for pyo and cvx problems to be used by their versions of both test_calculate_cost and test_calculate_itemized_cost Adding warning that decompose_exports isn't supported with cvxpy due to non-DCP exports-imports issues Reformatted with black * Simplifying test_decompose_consumption_pyo with approx * Changing decompose_exports argument to decomposition_type, which can take None, "absolute_value", "binary", or other types in the future Using max_pos for creating positive and negative pyomo variables (WIP: Index issue failing test in test_utils) * Adding missing arguments in test_costs.py * COSTS Adding calculate_conversion_factors Modifying calculate_energy_cost to only apply convex approximation when there are charge tiers TESTS Adding consumption_estimate to test_calculate_cost_cvx to avoid default 0 issues Adding additional test case for cvx problem without charge limit to test_calculate_cost_cvx Updating test expectations in test_max_pos_pyo so that scalar inputs have scalar expectations, vector inputs have vector expectations * Fixing bug with duplicated conversion_factor in calculate_cost Reformatting with black * Since conversion_factor is applied earlier during import/export decomposition, removing it from divisor in costs.py and renaming divisor to n_per_hour Moving application of conversion factor to helper function get_converted_consumption_data * Consolidating get_unique_row_name and default_varstr_alias_func default_varstr_alias_func takes row index parameter, where index is used if "name" is blank Removing dashes from names in test_costs.py since sanitize_varstr removes dashes and replaces with underscores Cleaning up error expectation in test_calculate_cost_np * Cleaning up calculate_energy_cost * Reverting changes to default_varstr_alias_func Adding back get_unique_row_name (renamed to get_charge_name) Removing initialize_decomposed_pyo_vars. The variables self-initialize during solve. Updating test_decompose_consumption_pyo to just check for the presence of objects, not values. (WIP to add value check in test_calculate_cost_pyo for pre-decomposed inputs) Renaming _get_decomposed_var_names to get_decomposed_var_names Adding test cases for extended consumption data format in test_calculate_cost_np and test_calculate_cost_pyo * Adding test calling max_pos on pyo scalar Updating minimum pyomo version to 6.8 * Reformatted with black * Add comprehensive test coverage for utils and costs - Add scalar LinearExpression tests for max_pos_pyo - Add TypeError tests for invalid types in max_pos and decompose_consumption - Add warning tests for unimplemented decomposition types - Add ValueError test for invalid type in calculate_export_revenue - Add negative values warning test in calculate_demand_costs - Add test for consumption_estimate=None and dict consumption_estimate - Add conversion factor test with MW units in test_calculate_itemized_cost_pyo - Fix idempotency in get_converted_consumption_data to prevent duplicate components - Add warnings for unimplemented decomposition_type in utils.decompose_consumption * Removing uneeded else statements in calculate_cost if conversion / decomposition has already been run on the model * Correcting UserWarning syntax in costs.py * Creating new test to address bug with double unit conversions in itemized costs * Fixed errors in rebase so that only two tests fail now * Fixing flake8 errors * Trying to address double unit conversion * Fixed unit conversion errors and all tests now passing * Autoreformatted with black * Creating codecov.yml file to try to address patch coverage * Fixed flake8 errors * Fixing documentation formatting * Fixing whitespace error --------- Co-authored-by: dalyw <sdwettermark@gmail.com>
1 parent d86ca9c commit ad00aea

13 files changed

Lines changed: 5050 additions & 691 deletions

codecov.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
coverage:
2+
status:
3+
project:
4+
default:
5+
target: auto
6+
patch:
7+
default:
8+
enabled: yes
9+
target: 100%
10+
threshold: 5%

docs/how_to_advanced.rst

Lines changed: 75 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -135,32 +135,91 @@ the previous consumption during this billing period in conjunction with `consump
135135
How to Use `demand_scale_factor`
136136
================================
137137

138-
By default `demand_scale_factor=1`, meaning that there will be no modifications applied to the demand or energy charges.
139-
The purpose of the scale factor is to modify the demand charges proportional to energy charges when performing moving horizon optimization.
138+
The `demand_scale_factor` parameter allows you to scale demand charges to reflect shorter optimization horizons or to prioritize demand differently across sequential optimization horizons.
140139

141-
There are various heuristics that could be used to calculate the scale factor (see :ref:`why-scale-demand`),
142-
but for now let's assume that we just want to scale the demand charge down by the length of the horizon window proportional to billing period.
140+
By default, `demand_scale_factor=1.0`. Use values less than 1.0 when solving for a subset of the billing period, or to adjust demand charge weighting in sequential optimization.
141+
142+
When `demand_scale_factor < 1.0`, demand charges are proportionally reduced to reflect the shorter optimization horizon. This is useful for:
143+
- Moving horizon optimization where you solve for sub-periods of the billing cycle
144+
- Sequential optimization where you want to reduce demand charge weighting as time goes on in the month
143145

144146
.. code-block:: python
145147
146-
from eeco import costs
148+
from electric_emission_cost import costs
147149
148-
# load necessary data
149-
start_dt = np.datetime64("2024-07-10")
150-
end_dt = np.datetime64("2024-07-11")
151-
charge_dict = costs.get_charge_dict(start_dt, end_dt, tariff_df)
152-
num_timesteps_horizon = 96
153-
num_timesteps_billing = 96 * 31
150+
# E.g. solving for 3 days out of a 30-day billing period
151+
demand_scale_factor = 3 / 30
152+
153+
result, model = costs.calculate_cost(
154+
charge_dict,
155+
consumption_data,
156+
demand_scale_factor=demand_scale_factor
157+
# ...
158+
)
154159
155-
# this is just a CVXPY variable, but a user would provide constraints to the optimization problem
156-
consumption_data_dict = {"electric": cp.Variable(num_timesteps), "gas": cp.Variable(num_timesteps)}
160+
For more details on applying the sequential optimization strategy, see:
157161

158-
total_monthly_bill, _ = costs.calculate_costs(
162+
Bolorinos, J., Mauter, M.S. & Rajagopal, R. Integrated Energy Flexibility Management at Wastewater Treatment Facilities. *Environ. Sci. Technol.* **57**, 46, 18362–18371 (2023). DOI: [10.1021/acs.est.3c00365](https://doi.org/10.1021/acs.est.3c00365)
163+
164+
In `bibtex` format:
165+
166+
.. code-block:: bibtex
167+
168+
@Article{Bolorinos2023,
169+
author={Bolorinos, Jose
170+
and Mauter, Meagan S.
171+
and Rajagopal, Ram},
172+
title={Integrated Energy Flexibility Management at Wastewater Treatment Facilities},
173+
journal={Environmental Science & Technology},
174+
year={2023},
175+
month={Jun},
176+
day={16},
177+
volume={57},
178+
number={46},
179+
pages={18362--18371},
180+
doi={10.1021/acs.est.3c00365},
181+
url={https://doi.org/10.1021/acs.est.3c00365}
182+
}
183+
184+
185+
.. _decompose-exports:
186+
187+
How to Use `decomposition_type`
188+
===============================
189+
190+
The `decomposition_type` parameter allows you to decompose consumption data into positive (imports) and negative (exports) components. This is useful when you have export charges or credits in your rate structure.
191+
Options include:
192+
193+
- Default `None`
194+
- `"binary_variable"`: To be implemented
195+
- `"absolute_value"`
196+
197+
.. code-block:: python
198+
199+
from electric_emission_cost import costs
200+
201+
# Example with export charges
202+
charge_dict = {
203+
"electric_export_0_2024-07-10_2024-07-10_0": np.ones(96) * 0.025,
204+
}
205+
206+
consumption_data = {
207+
"electric": np.concatenate([np.ones(48) * 10, -np.ones(48) * 5]),
208+
"gas": np.ones(96),
209+
}
210+
211+
# Decompose consumption into imports and exports
212+
result, model = costs.calculate_cost(
159213
charge_dict,
160-
consumption_data_dict,
161-
demand_scale_factor=num_timesteps_horizon/num_timesteps_billing
214+
consumption_data,
215+
decomposition_type="absolute_value"
162216
)
163217
218+
When decomposition_type is not None the function creates separate variables for positive consumption (imports) and negative consumption (exports)
219+
and applies export charges only to the export component.
220+
For Pyomo models, decomposition_type adds a constraint total_consumption = imports - exports
221+
222+
164223
.. _varstr-alias:
165224

166225
How to Use `varstr_alias_func`

docs/how_to_cost.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ NumPy
7373
# one month of 15-min intervals
7474
num_timesteps = 24 * 4 * 31
7575
# this is synthetic consumption data, but a user could provide real historical meter data
76+
# Positive values represent imports, negative values represent exports in consumption data
7677
consumption_data_dict = {"electric": np.ones(num_timesteps) * 100, "gas": np.ones(num_timesteps))}
7778
total_monthly_bill, _ = costs.calculate_cost(charge_dict, consumption_data_dict)
7879

0 commit comments

Comments
 (0)