diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..79f2fc5 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,10 @@ +coverage: + status: + project: + default: + target: auto + patch: + default: + enabled: yes + target: 100% + threshold: 5% diff --git a/docs/how_to_advanced.rst b/docs/how_to_advanced.rst index c89b1d4..78d64b9 100644 --- a/docs/how_to_advanced.rst +++ b/docs/how_to_advanced.rst @@ -135,32 +135,91 @@ the previous consumption during this billing period in conjunction with `consump How to Use `demand_scale_factor` ================================ -By default `demand_scale_factor=1`, meaning that there will be no modifications applied to the demand or energy charges. -The purpose of the scale factor is to modify the demand charges proportional to energy charges when performing moving horizon optimization. +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. -There are various heuristics that could be used to calculate the scale factor (see :ref:`why-scale-demand`), -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. +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. + +When `demand_scale_factor < 1.0`, demand charges are proportionally reduced to reflect the shorter optimization horizon. This is useful for: +- Moving horizon optimization where you solve for sub-periods of the billing cycle +- Sequential optimization where you want to reduce demand charge weighting as time goes on in the month .. code-block:: python - from eeco import costs + from electric_emission_cost import costs - # load necessary data - start_dt = np.datetime64("2024-07-10") - end_dt = np.datetime64("2024-07-11") - charge_dict = costs.get_charge_dict(start_dt, end_dt, tariff_df) - num_timesteps_horizon = 96 - num_timesteps_billing = 96 * 31 + # E.g. solving for 3 days out of a 30-day billing period + demand_scale_factor = 3 / 30 + + result, model = costs.calculate_cost( + charge_dict, + consumption_data, + demand_scale_factor=demand_scale_factor + # ... + ) - # this is just a CVXPY variable, but a user would provide constraints to the optimization problem - consumption_data_dict = {"electric": cp.Variable(num_timesteps), "gas": cp.Variable(num_timesteps)} +For more details on applying the sequential optimization strategy, see: - total_monthly_bill, _ = costs.calculate_costs( + 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) + +In `bibtex` format: + +.. code-block:: bibtex + + @Article{Bolorinos2023, + author={Bolorinos, Jose + and Mauter, Meagan S. + and Rajagopal, Ram}, + title={Integrated Energy Flexibility Management at Wastewater Treatment Facilities}, + journal={Environmental Science & Technology}, + year={2023}, + month={Jun}, + day={16}, + volume={57}, + number={46}, + pages={18362--18371}, + doi={10.1021/acs.est.3c00365}, + url={https://doi.org/10.1021/acs.est.3c00365} + } + + +.. _decompose-exports: + +How to Use `decomposition_type` +=============================== + +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. +Options include: + +- Default `None` +- `"binary_variable"`: To be implemented +- `"absolute_value"` + +.. code-block:: python + + from electric_emission_cost import costs + + # Example with export charges + charge_dict = { + "electric_export_0_2024-07-10_2024-07-10_0": np.ones(96) * 0.025, + } + + consumption_data = { + "electric": np.concatenate([np.ones(48) * 10, -np.ones(48) * 5]), + "gas": np.ones(96), + } + + # Decompose consumption into imports and exports + result, model = costs.calculate_cost( charge_dict, - consumption_data_dict, - demand_scale_factor=num_timesteps_horizon/num_timesteps_billing + consumption_data, + decomposition_type="absolute_value" ) +When decomposition_type is not None the function creates separate variables for positive consumption (imports) and negative consumption (exports) +and applies export charges only to the export component. +For Pyomo models, decomposition_type adds a constraint total_consumption = imports - exports + + .. _varstr-alias: How to Use `varstr_alias_func` diff --git a/docs/how_to_cost.rst b/docs/how_to_cost.rst index 0688971..1100660 100644 --- a/docs/how_to_cost.rst +++ b/docs/how_to_cost.rst @@ -73,6 +73,7 @@ NumPy # one month of 15-min intervals num_timesteps = 24 * 4 * 31 # this is synthetic consumption data, but a user could provide real historical meter data + # Positive values represent imports, negative values represent exports in consumption data consumption_data_dict = {"electric": np.ones(num_timesteps) * 100, "gas": np.ones(num_timesteps))} total_monthly_bill, _ = costs.calculate_cost(charge_dict, consumption_data_dict) diff --git a/eeco/costs.py b/eeco/costs.py index 224bcca..8133c4b 100644 --- a/eeco/costs.py +++ b/eeco/costs.py @@ -50,7 +50,7 @@ OFF_PEAK = "off_peak" -def get_unique_row_name(charge, index=None): +def get_charge_name(charge, index=None): """ Get a unique row name for each row of charge df. @@ -70,15 +70,22 @@ def get_unique_row_name(charge, index=None): name = charge[NAME] except KeyError: name = charge[PERIOD] + warnings.warn( + "Please update billing file to use 'name' column rather than 'period'", + DeprecationWarning, + ) - # if no name was given just use the index to differentiate + # If no name was given, use the index to differentiate if not (isinstance(name, str) and name != ""): name = str(index) if index is not None else "" - # replace underscores with dashes for unique delimiter + + # Replace underscores with dashes for unique delimiter return name.replace("_", "-") -def create_charge_array(charge, datetime, effective_start_date, effective_end_date): +def create_charge_array( + charge, datetime, effective_start_date, effective_end_date, utility="electric" +): """Creates a single charge array based on the given parameters. Parameters @@ -138,9 +145,15 @@ def create_charge_array(charge, datetime, effective_start_date, effective_end_da charge_array = apply_charge * charge[CHARGE_METRIC] except KeyError: warnings.warn( - "Please switch to new 'charge (metric)' and 'charge (imperial)' format", + "Please switch to new 'charge (metric)' and 'charge (imperial)' format. " + "Current behavior assumes units of therms (imperial) " + "and converts to cubic meters (metric) when unspecified.", DeprecationWarning, ) + # if not specified, assume the old format (imperial units) + if utility == GAS: + # convert from therms to default units of cubic meters + charge[CHARGE] /= 2.83168 # therm per cubic meter of CH4 charge_array = apply_charge * charge[CHARGE] return charge_array @@ -282,9 +295,14 @@ def get_charge_dict(start_dt, end_dt, rate_data, resolution="15m"): charge_limits = effective_charges[BASIC_CHARGE_LIMIT] warnings.warn( "Please switch to new 'basic_charge_limit (metric)' " - "and 'basic_charge_limit (imperial)' format", + "and 'basic_charge_limit (imperial)' format. " + "Current behavior assumes units of therms (imperial) " + "and converts to cubic meters (metric) when unspecified.", DeprecationWarning, ) + if utility == GAS: + # convert from therms to default units of cubic meters + charge_limits *= 2.83168 # cubic meters per therm of CH4 for limit in np.unique(charge_limits): if np.isnan(limit): limit_charges = effective_charges.loc[ @@ -295,7 +313,7 @@ def get_charge_dict(start_dt, end_dt, rate_data, resolution="15m"): limit_charges = effective_charges.loc[charge_limits == limit, :] for i, idx in enumerate(limit_charges.index): charge = limit_charges.loc[idx, :] - name = get_unique_row_name(charge, i) + name = get_charge_name(charge, i) try: assessed = charge[ASSESSED] @@ -309,18 +327,18 @@ def get_charge_dict(start_dt, end_dt, rate_data, resolution="15m"): charge_array = np.array([charge[CHARGE]]) warnings.warn( "Please switch to new 'charge (metric)' " - "and 'charge (imperial)' format", + "and 'charge (imperial)' format. Current behavior " + "assumes units of therms (imperial) and converts " + "to cubic meters (metric) when unspecified.", DeprecationWarning, ) - key_str = "_".join( - ( - utility, - charge_type, - name, - start.strftime("%Y%m%d"), - end.strftime("%Y%m%d"), - str(int(limit)), - ) + key_str = default_varstr_alias_func( + utility, + charge_type, + name, + start.strftime("%Y%m%d"), + end.strftime("%Y%m%d"), + str(int(limit)), ) add_to_charge_array(charge_dict, key_str, charge_array) elif charge_type == DEMAND and assessed == "daily": @@ -328,32 +346,32 @@ def get_charge_dict(start_dt, end_dt, rate_data, resolution="15m"): new_start = start + dt.timedelta(days=day) new_end = new_start + dt.timedelta(days=1) charge_array = create_charge_array( - charge, datetime, new_start, new_end + charge, + datetime, + new_start, + new_end, + utility=utility, ) - key_str = "_".join( - ( - utility, - charge_type, - name, - new_start.strftime("%Y%m%d"), - new_start.strftime("%Y%m%d"), - str(limit), - ) + key_str = default_varstr_alias_func( + utility, + charge_type, + name, + new_start.strftime("%Y%m%d"), + new_start.strftime("%Y%m%d"), + str(limit), ) add_to_charge_array(charge_dict, key_str, charge_array) else: charge_array = create_charge_array( - charge, datetime, start, new_end + charge, datetime, start, new_end, utility=utility ) - key_str = "_".join( - ( - utility, - charge_type, - name, - start.strftime("%Y%m%d"), - end.strftime("%Y%m%d"), - str(int(limit)), - ) + key_str = default_varstr_alias_func( + utility, + charge_type, + name, + start.strftime("%Y%m%d"), + end.strftime("%Y%m%d"), + str(int(limit)), ) add_to_charge_array(charge_dict, key_str, charge_array) return charge_dict @@ -628,6 +646,12 @@ def calculate_demand_cost( consumption_max = max(max(consumption_estimate), prev_demand) if isinstance(consumption_data, np.ndarray): + if np.any(consumption_data < 0): + warnings.warn( + "Demand calculation includes negative values. Pass in " + "positive values or run calculate_cost with a decomposition_type", + UserWarning, + ) if (np.max(consumption_data) >= limit) or ( (prev_demand >= limit) and (prev_demand <= next_limit) ): @@ -644,7 +668,7 @@ def calculate_demand_cost( if consumption_max <= next_limit: model.add_component( varstr + "_limit", - pyo.Var(model.t, initialize=0, bounds=(0, None)), + pyo.Var(model.t, initialize=0, bounds=(None, None)), ) var = model.find_component(varstr + "_limit") @@ -692,6 +716,7 @@ def const_rule(model, t): "consumption_data must be of type numpy.ndarray, " "cvxpy.Expression, or pyomo.environ.Var" ) + if model is None: max_var, _ = ut.max(demand_charged) max_pos_val, max_pos_model = ut.max_pos(max_var - prev_demand_cost) @@ -707,7 +732,7 @@ def const_rule(model, t): def calculate_energy_cost( charge_array, consumption_data, - divisor, + n_per_hour, limit=0, next_limit=float("inf"), prev_consumption=0, @@ -726,8 +751,9 @@ def calculate_energy_cost( consumption_data : numpy.ndarray cvxpy.Expression, or pyomo.environ.Var Baseline electrical or gas usage data as an optimization variable object - divisor : int - Divisor for the energy charges, based on the timeseries resolution + n_per_hour : int + Divisor for the indexed consumption data elements, + based on the timeseries resolution limit : float The total consumption, or limit, that this charge came into effect. @@ -744,7 +770,7 @@ def calculate_energy_cost( consumption_estimate : float, array Estimate of the total monthly energy consumption from baseline data - in kWh, therms, or cubic meters. + in kWh, therms OR cubic meters. Only used when `consumption_data` is cvxpy.Expression or pyomo.environ.Var for convex relaxation of tiered charges, while numpy.ndarray `consumption_data` will use actual consumption and ignore the estimate. @@ -777,12 +803,19 @@ def calculate_energy_cost( n_steps = len(consumption_data) if isinstance(consumption_data, np.ndarray): + if np.any(consumption_data < 0): + warnings.warn( + "Energy calculation includes negative values. Pass in " + "positive values or run calculate_cost with a decomposition_type", + UserWarning, + ) + energy = prev_consumption # set the flag if we are starting with previous consumption that lands us # within the current tier of charge limits within_limit_flag = energy >= float(limit) and energy < float(next_limit) for i in range(n_steps): - energy += consumption_data[i] / divisor + energy += consumption_data[i] / n_per_hour # only add to charges if already within correct charge limits if within_limit_flag: # went over next charge limit on this iteration @@ -790,42 +823,51 @@ def calculate_energy_cost( if energy >= float(next_limit): within_limit_flag = False cost += ( - float(next_limit) + consumption_data[i] / divisor - energy - ) * charge_array[i] + max( + float(next_limit) + + consumption_data[i] / n_per_hour + - energy, + 0, + ) + * charge_array[i] + ) else: - cost += consumption_data[i] / divisor * charge_array[i] + cost += consumption_data[i] / n_per_hour * charge_array[i] # went over existing charge limit on this iteration elif energy >= float(limit) and energy < float(next_limit): within_limit_flag = True - cost += (energy - float(limit)) * charge_array[i] + cost += max(energy - float(limit), 0) * charge_array[i] + elif isinstance(consumption_data, (cp.Expression, pyo.Var, pyo.Param)): - # assume consumption is split evenly as an approximation + # For tiered charges, approximate extimated consumption being split evenly + # Only necessary if we have a finite next_limit OR if current limit > 0 # NOTE: this convex approximation breaks global optimality guarantees - if isinstance(consumption_estimate, (float, int)): - consumption_per_timestep = consumption_estimate / n_steps - consumption_estimate = np.ones(n_steps) * consumption_per_timestep + if not np.isinf(next_limit) or (not np.isinf(limit) and limit > 0): + if isinstance(consumption_estimate, (float, int)): + consumption_per_timestep = consumption_estimate / n_steps + consumption_estimate = np.ones(n_steps) * consumption_per_timestep - cumulative_consumption = np.cumsum(consumption_estimate) + prev_consumption - total_consumption = cumulative_consumption[-1] + cumulative_consumption = np.cumsum(consumption_estimate) + prev_consumption + total_consumption = cumulative_consumption[-1] - start_idx = np.argmax(cumulative_consumption > float(limit)) - # if not found argmax returns 0, but whole charge array should be zeroed - if (start_idx == 0) and (total_consumption <= float(limit)): - charge_array[:] = 0 - else: - charge_array[:start_idx] = 0 # 0 for charge array before the start index - end_idx = np.argmax(cumulative_consumption > float(next_limit)) - # before applying end_idx ensure (1) that next limit exists - # and (2) that it is higher than total consumption - if not (np.isinf(next_limit) or (total_consumption <= float(next_limit))): - charge_array[end_idx:] = 0 # 0 for charge array after the end index + start_idx = np.argmax(cumulative_consumption > float(limit)) + # if not found argmax returns 0, but whole charge array should be zeroed + if (start_idx == 0) and (total_consumption <= float(limit)): + charge_array[:] = 0 + else: + charge_array[:start_idx] = 0 # 0 for charge array before start index + end_idx = np.argmax(cumulative_consumption > float(next_limit)) + # before applying end_idx ensure (1) that next limit exists + # and (2) that it is higher than total consumption + if not (np.isinf(next_limit) or (total_consumption <= float(next_limit))): + charge_array[end_idx:] = 0 # 0 for charge array after end index charge_expr, model = ut.multiply( consumption_data, charge_array, model=model, varstr=varstr + "_multiply" ) sum_result, model = ut.sum(charge_expr, model=model, varstr=varstr + "_sum") cost, model = ut.max_pos( - sum_result / divisor, + sum_result / n_per_hour, model=model, varstr=varstr, ) @@ -834,12 +876,11 @@ def calculate_energy_cost( "consumption_data must be of type numpy.ndarray, " "cvxpy.Expression, or pyomo.environ.Var" ) - return cost, model -def calculate_export_revenues( - charge_array, export_data, divisor, model=None, varstr="" +def calculate_export_revenue( + charge_array, consumption_data, n_per_hour, model=None, varstr="" ): """Calculates the export revenues for the given billing rate structure, utility, and consumption information. @@ -853,10 +894,13 @@ def calculate_export_revenues( array with price per kWh sold back to the grid consumption_data : numpy.ndarray, cvxpy.Expression, or pyomo.environ.Var - Baseline electrical or gas usage data as an optimization variable object + Magnitude of exported electrical or gas usage data + as an optimization variable object. + Should be positive values. - divisor : int - Divisor for the export revenue, based on the timeseries resolution + n_per_hour : int + Divisor for the indexed consumption data elements, + based on the timeseries resolution model : pyomo.Model The model object associated with the problem. @@ -865,21 +909,157 @@ def calculate_export_revenues( varstr : str Name of the variable to be created if using a Pyomo `model` + Raises + ------ + ValueError + When invalid `utility`, `charge_type`, or `assessed` + is provided in `charge_arrays` + Returns ------- (cvxpy.Expression, pyomo.environ.Var, or float), pyomo.Model tuple with the first entry being a float, cvxpy Expression, or pyomo Var representing export revenues - in USD for the given `charge_array` and `consumption_data` + in USD for the given `charge_array` and `export_data` and the second entry being the pyomo model object (or None) """ - varstr_mul = varstr + "_multiply" if varstr is not None else None - varstr_sum = varstr + "_sum" if varstr is not None else None - result, model = ut.multiply( - charge_array, export_data, model=model, varstr=varstr_mul + if isinstance(consumption_data, np.ndarray): + if np.any(consumption_data < 0): + warnings.warn( + "Export revenue calculation includes negative values. Pass in " + "positive values or run calculate_cost with a decomposition_type", + UserWarning, + ) + return np.sum(consumption_data * charge_array) / n_per_hour, model + + elif isinstance(consumption_data, (cp.Expression, pyo.Var, pyo.Param)): + cost_expr, model = ut.multiply( + consumption_data, + charge_array, + model=model, + varstr=varstr + "_multiply", + ) + export_revenue, model = ut.sum(cost_expr, model=model, varstr=varstr + "_sum") + + return export_revenue / n_per_hour, model + else: + raise ValueError( + "consumption_data must be of type numpy.ndarray, " + "cvxpy.Expression, or pyomo.environ.Var" + ) + + +def get_conversion_factors(electric_consumption_units, gas_consumption_units): + """Calculate conversion factors for electric and gas utilities. + + Parameters + ---------- + electric_consumption_units : pint.Unit + Units for the electricity consumption data + gas_consumption_units : pint.Unit + Units for the gas consumption data + + Returns + ------- + dict + Dictionary with conversion factors for each utility type + """ + conversion_factors = {} + conversion_factors[ELECTRIC] = (1 * electric_consumption_units).to(u.kW).magnitude + conversion_factors[GAS] = ( + (1 * gas_consumption_units).to(u.meter**3 / u.hour).magnitude ) - revenues, model = ut.sum(result, model=model, varstr=varstr_sum) - return revenues / divisor, model + return conversion_factors + + +def get_converted_consumption_data( + consumption_data_dict, conversion_factors, decomposition_type, model=None +): + """Ensure consumption_data_dict has imports/exports structure + with proper unit conversion. + + Parameters + ---------- + consumption_data_dict : dict + Dictionary with utility keys and consumption data values + conversion_factors : dict + Dictionary with conversion factors for each utility type + decomposition_type : str or None + Type of decomposition to apply (e.g., "absolute_value") + model : pyomo.Model, optional + Pyomo model object for optimization variables + + Returns + ------- + dict + Updated consumption_data_dict with imports/exports structure + """ + for utility in consumption_data_dict.keys(): + conversion_factor = conversion_factors[utility] + + # Check if this utility already has imports/exports structure + if ( + isinstance(consumption_data_dict[utility], dict) + and "imports" in consumption_data_dict[utility] + and "exports" in consumption_data_dict[utility] + ): + # Apply conversion if conversion factor is nonzero + if conversion_factor != 1.0: + # Apply conversion if not already done + imports_varstr = utility + "_imports_converted" + if model is None or not hasattr(model, imports_varstr): + consumption_data_dict[utility]["imports"], model = ut.multiply( + consumption_data_dict[utility]["imports"], + conversion_factor, + model=model, + varstr=imports_varstr, + ) + consumption_data_dict[utility]["exports"], model = ut.multiply( + consumption_data_dict[utility]["exports"], + conversion_factor, + model=model, + varstr=utility + "_exports_converted", + ) + continue + else: # create imports/exports + # Convert if not already done + converted_varstr = utility + "_converted" + if model is None or not hasattr(model, converted_varstr): + converted_consumption, model = ut.multiply( + consumption_data_dict[utility], + conversion_factor, + model=model, + varstr=converted_varstr, + ) + + if decomposition_type == "absolute_value": + # Decompose consumption data into positive and negative components + # with constraint that total = positive - negative + # (where negative is stored as positive magnitude) + pos_name, neg_name = ut.get_decomposed_var_names(utility) + + # Decompose if not already done + if model is None or not hasattr(model, pos_name): + imports, exports, model = ut.decompose_consumption( + converted_consumption, + model=model, + varstr=utility, + decomposition_type="absolute_value", + ) + + consumption_data_dict[utility] = { + "imports": imports, + "exports": exports, + } + elif decomposition_type is None: + consumption_data_dict[utility] = { + "imports": converted_consumption, + "exports": converted_consumption, + } + else: + raise NotImplementedError + + return consumption_data_dict def get_charge_array_duration(key): @@ -938,6 +1118,7 @@ def calculate_cost( desired_charge_type=None, demand_scale_factor=1, model=None, + decomposition_type=None, varstr_alias_func=default_varstr_alias_func, ): """Calculates the cost of given charges (demand or energy) for the given @@ -955,8 +1136,17 @@ def calculate_cost( consumption_data_dict : dict Baseline electrical and gas usage data as an optimization variable object - with keys "electric" and "gas". Values of the dictionary must be of type - numpy.ndarray, cvxpy.Expression, or pyomo.environ.Var + with keys "electric" and "gas". Supports two formats: + + Default format: + Values are cumulative consumption data + Example: {"electric": np.array([]), "gas": np.array([])} + + Extended format for imports/exports: + Values are dictionaries with decomposed "imports" and "exports" keys + Example: {"electric": {"imports": np.array([]), "exports": np.array([])}} + + Values must be of type numpy.ndarray, cvxpy.Expression, or pyomo.environ.Var. electric_consumption_units : pint.Unit Units for the electricity consumption data. Default is kW @@ -1011,10 +1201,11 @@ def calculate_cost( The model object associated with the problem. Only used in the case of Pyomo, so `None` by default. - additional_objective_terms : pyomo.Expression, list - Additional terms to be added to the objective function. - Can be a single pyomo Expression or a list of pyomo Expressions. - 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 varstr_alias_func: function Function to generate variable name for pyomo, @@ -1055,6 +1246,14 @@ def calculate_cost( if consumption_estimate is None: consumption_estimate = 0 + conversion_factors = get_conversion_factors( + electric_consumption_units, gas_consumption_units + ) + + consumption_data_dict = get_converted_consumption_data( + consumption_data_dict, conversion_factors, decomposition_type, model + ) + for key, charge_array in charge_dict.items(): utility, charge_type, name, eff_start, eff_end, limit_str = key.split("_") varstr = ut.sanitize_varstr( @@ -1069,27 +1268,9 @@ def calculate_cost( ): continue - if utility == ELECTRIC: - conversion_factor = (1 * electric_consumption_units).to(u.kW).magnitude - divisor = n_per_hour - elif utility == GAS: - conversion_factor = ( - (1 * gas_consumption_units).to(u.meter**3 / u.hour).magnitude - ) - divisor = n_per_hour - else: - raise ValueError("Invalid utility: " + utility) - charge_limit = int(limit_str) key_substr = "_".join([utility, charge_type, name, eff_start, eff_end]) next_limit = get_next_limit(key_substr, charge_limit, charge_dict.keys()) - varstr_converted = varstr + "_converted" if varstr is not None else None - converted_data, model = ut.multiply( - consumption_data_dict[utility], - conversion_factor, - model=model, - varstr=varstr_converted, - ) # Only apply demand_scale_factor if charge spans more than one day charge_duration_days = get_charge_array_duration(key) @@ -1106,20 +1287,20 @@ def calculate_cost( if isinstance(consumption_estimate, (float, int)): # convert single kWh to the equivalent kW per timestep demand_consumption_estimate = ( - consumption_estimate * divisor / len(charge_array) + consumption_estimate * n_per_hour / len(charge_array) ) elif isinstance(consumption_estimate, (dict)): demand_consumption_estimate = consumption_estimate[utility] if isinstance(demand_consumption_estimate, (float, int)): demand_consumption_estimate = ( - demand_consumption_estimate * divisor / len(charge_array) + demand_consumption_estimate * n_per_hour / len(charge_array) ) else: demand_consumption_estimate = consumption_estimate new_cost, model = calculate_demand_cost( charge_array, - converted_data, + consumption_data_dict[utility]["imports"], limit=charge_limit, next_limit=next_limit, prev_demand=prev_demand, @@ -1144,14 +1325,16 @@ def calculate_cost( if not isinstance( energy_consumption_estimate, (float, int) ): # array-like - energy_consumption_estimate = energy_consumption_estimate / divisor + energy_consumption_estimate = ( + energy_consumption_estimate / n_per_hour + ) else: - energy_consumption_estimate = consumption_estimate / divisor + energy_consumption_estimate = consumption_estimate / n_per_hour new_cost, model = calculate_energy_cost( charge_array, - converted_data, - divisor, + consumption_data_dict[utility]["imports"], + n_per_hour, limit=charge_limit, next_limit=next_limit, prev_consumption=prev_consumption, @@ -1161,8 +1344,12 @@ def calculate_cost( ) cost += new_cost elif charge_type == EXPORT: - new_cost, model = calculate_export_revenues( - charge_array, converted_data, divisor, model=model, varstr=varstr + new_cost, model = calculate_export_revenue( + charge_array, + consumption_data_dict[utility]["exports"], + n_per_hour, + model=model, + varstr=varstr, ) cost -= new_cost elif charge_type == CUSTOMER: @@ -1187,6 +1374,7 @@ def build_pyomo_costing( desired_charge_type=None, demand_scale_factor=1, additional_objective_terms=None, + decomposition_type=None, varstr_alias_func=default_varstr_alias_func, ): """ @@ -1256,9 +1444,16 @@ def build_pyomo_costing( Applied to monthly charges where end_date - start_date > 1 day. Default is 1 - additional_objective_terms : list + additional_objective_terms : pyomo.Expression, list Additional terms to be added to the objective function. - Must be a list of pyomo Expressions. + Can be a single pyomo Expression or a list of pyomo Expressions. + 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 varstr_alias_func: function Function to generate variable name for pyomo, @@ -1303,11 +1498,11 @@ def build_pyomo_costing( desired_charge_type=desired_charge_type, demand_scale_factor=demand_scale_factor, model=model, + decomposition_type=decomposition_type, varstr_alias_func=varstr_alias_func, ) model.objective = pyo.Objective(expr=model.electricity_cost, sense=pyo.minimize) - if additional_objective_terms is not None: for term in additional_objective_terms: model.objective.expr += term @@ -1326,6 +1521,7 @@ def calculate_itemized_cost( desired_utility=None, demand_scale_factor=1, model=None, + decomposition_type=None, varstr_alias_func=default_varstr_alias_func, ): """Calculates itemized costs as a nested dictionary @@ -1343,12 +1539,14 @@ def calculate_itemized_cost( Baseline electrical and gas usage data as an optimization variable object with keys "electric" and "gas". Values of the dictionary must be of type numpy.ndarray, cvxpy.Expression, or pyomo.environ.Var + Positive values represent energy imports (consumption from the grid) + Negative values represent energy exports (generation sent to the grid) electric_consumption_units : pint.Unit Units for the electricity consumption data. Default is kW gas_consumption_units : pint.Unit - Units for the natura gas consumption data. Default is cubic meters / hour + Units for the natural gas consumption data. Default is cubic meters / hour resolution : str String of the form `[int][str]` giving the temporal resolution @@ -1422,6 +1620,24 @@ def calculate_itemized_cost( } """ + # Check if decomposition_type is used with CVXPY objects + # (not yet supported because imports - exports creates non-DCP issues) + if decomposition_type is not None: + for utility in consumption_data_dict.keys(): + if isinstance(consumption_data_dict[utility], cp.Variable): + raise NotImplementedError( + "Decomposition types are not supported with CVXPY objects. " + "Use Pyomo instead for problems requiring decomposition_type." + ) + + conversion_factors = get_conversion_factors( + electric_consumption_units, gas_consumption_units + ) + + consumption_data_dict = get_converted_consumption_data( + consumption_data_dict, conversion_factors, decomposition_type, model + ) + total_cost = 0 results_dict = {} @@ -1433,8 +1649,6 @@ def calculate_itemized_cost( cost, model = calculate_cost( charge_dict, consumption_data_dict, - electric_consumption_units=electric_consumption_units, - gas_consumption_units=gas_consumption_units, resolution=resolution, prev_demand_dict=prev_demand_dict, consumption_estimate=consumption_estimate, @@ -1442,6 +1656,7 @@ def calculate_itemized_cost( desired_charge_type=charge_type, demand_scale_factor=demand_scale_factor, model=model, + decomposition_type=decomposition_type, varstr_alias_func=varstr_alias_func, ) @@ -1457,8 +1672,6 @@ def calculate_itemized_cost( cost, model = calculate_cost( charge_dict, consumption_data_dict, - electric_consumption_units=electric_consumption_units, - gas_consumption_units=gas_consumption_units, resolution=resolution, prev_demand_dict=prev_demand_dict, prev_consumption_dict=prev_consumption_dict, @@ -1467,6 +1680,7 @@ def calculate_itemized_cost( desired_charge_type=charge_type, demand_scale_factor=demand_scale_factor, model=model, + decomposition_type=decomposition_type, varstr_alias_func=varstr_alias_func, ) @@ -1611,7 +1825,7 @@ def detect_charge_periods( def parametrize_rate_data( rate_data, - scale_ratios={}, + percent_change_dict={}, shift_peak_hours_before=0, shift_peak_hours_after=0, variant_name=None, @@ -1625,8 +1839,7 @@ def parametrize_rate_data( ---------- rate_data : pandas.DataFrame Tariff data with required columns - - scale_ratios : dict, optional + percent_change_dict : dict, optional Dictionary for charge scaling. Can be one of three formats: Format 1 - Nested dictionary with structure for charge scaling: @@ -1697,10 +1910,10 @@ def parametrize_rate_data( Raises ------ ValueError - If scale_ratios contains both period-based scaling and individual charge + If percent_change_dict contains both period-based scaling and individual charge scaling for the same charge type UserWarning - If scale_ratios contains exact charge keys that are not found in the data + If percent_change_dict contains exact charge keys that are not found in the data """ variant_data = rate_data.copy(deep=True) # deep copy required for variants variant_data[HOUR_START] = variant_data[HOUR_START].astype(float) @@ -1712,25 +1925,26 @@ def parametrize_rate_data( else [CHARGE] ) - # Determine which format scale_ratios was passed in - has_exact_keys = len(scale_ratios) > 0 and any( + # Determine which format percent_change_dict was passed in + has_exact_keys = len(percent_change_dict) > 0 and any( isinstance(k, str) and ("electric_" in k or "gas_" in k) - for k in scale_ratios.keys() + for k in percent_change_dict.keys() ) - has_global_scaling = len(scale_ratios) > 0 and any( + has_global_scaling = len(percent_change_dict) > 0 and any( k in [DEMAND, ENERGY] and isinstance(v, (int, float)) - for k, v in scale_ratios.items() + for k, v in percent_change_dict.items() ) - has_period_scaling = len(scale_ratios) > 0 and any( - isinstance(v, dict) and k in [DEMAND, ENERGY] for k, v in scale_ratios.items() + has_period_scaling = len(percent_change_dict) > 0 and any( + isinstance(v, dict) and k in [DEMAND, ENERGY] + for k, v in percent_change_dict.items() ) # Check for conflicts between period/global scaling and exact keys if has_exact_keys and (has_period_scaling or has_global_scaling): raise ValueError( - "scale_ratios cannot contain both exact charge keys" + "percent_change_dict cannot contain both exact charge keys" " and global/period-based scaling" ) @@ -1748,11 +1962,11 @@ def parametrize_rate_data( for charge_type in [ENERGY, DEMAND]: if ( has_global_scaling - and charge_type in scale_ratios - and isinstance(scale_ratios[charge_type], (int, float)) + and charge_type in percent_change_dict + and isinstance(percent_change_dict[charge_type], (int, float)) ): # Format 3: Global scaling for all charges of this type - scale_factor = scale_ratios[charge_type] + scale_factor = percent_change_dict[charge_type] charge_ratios = { PEAK: scale_factor, HALF_PEAK: scale_factor, @@ -1761,10 +1975,10 @@ def parametrize_rate_data( } # Format 2: Exact charge keys elif has_exact_keys: - charge_ratios = scale_ratios + charge_ratios = percent_change_dict # Format 1: Period-based scaling - elif has_period_scaling and charge_type in scale_ratios: - charge_ratios = scale_ratios[charge_type] + elif has_period_scaling and charge_type in percent_change_dict: + charge_ratios = percent_change_dict[charge_type] else: # No scaling - window shifting only charge_ratios = { PEAK: 1.0, @@ -1790,13 +2004,16 @@ def parametrize_rate_data( continue row = variant_data.loc[row_idx] + if has_exact_keys: # Format 2: Exact charge key matching - row_name = get_unique_row_name(row, row_idx) + row_name = get_charge_name(row, row_idx) for key_prefix, key_ratio in charge_ratios.items(): if key_prefix.startswith( f"{row[UTILITY]}_{row[TYPE]}_{row_name}" ): ratio = key_ratio + # Remove dashes in the df when key is found + variant_data.loc[row_idx, NAME] = row_name break else: missing_keys.add(f"{row[UTILITY]}_{row[TYPE]}_{row_name}") @@ -1933,7 +2150,7 @@ def parametrize_rate_data( if missing_keys and has_exact_keys: warnings.warn( - f"The following charge keys were not found in scale_ratios and " + f"The following charge keys were not found in percent_change_dict and " f"will use default ratio of 1.0: {sorted(list(missing_keys))}", UserWarning, ) @@ -1957,7 +2174,7 @@ def parametrize_charge_dict(start_dt, end_dt, rate_data, variants=None): tariff data with required columns variants : list[dict] List of dictionaries containing variation parameters with keys: - - scale_ratios: dict for charge scaling (see parametrize_rate_data for options) + - percent_change_dict: dict for charge scaling (see parametrize_rate_data) - shift_peak_hours_before: float to shift peak start, in hours - shift_peak_hours_after: float to shift peak end, in hours - variant_name: str (optional) variant name diff --git a/eeco/tests/data/input/negative_purchases_within_tol.csv b/eeco/tests/data/input/negative_purchases_within_tol.csv new file mode 100644 index 0000000..62c5729 --- /dev/null +++ b/eeco/tests/data/input/negative_purchases_within_tol.csv @@ -0,0 +1,2977 @@ +wrrf_natural_gas_combust +719.7256219 +716.2498391 +760.3649102 +709.5657033 +710.6351951 +705.8226419 +693.2565335 +672.9367874 +689.5133315 +649.14137 +662.7769435 +658.2316958 +636.5752134 +634.4362423 +636.8424906 +607.1650621 +636.040411 +599.9462397 +608.2345022 +569.7340812 +601.2830518 +564.119377 +602.0851324 +611.9775767 +459.8372826 +699.0090106 +652.8842814 +650.4781553 +628.286908 +611.9776917 +644.5962026 +641.120511 +653.6865603 +633.6342673 +649.4087954 +637.6447919 +742.1842519 +673.7389575 +600.7484837 +552.622872 +548.3450405 +539.7893797 +538.4525662 +523.480156 +552.0881684 +553.6923585 +509.0424732 +502.3583629 +482.5733831 +491.663785 +501.0215475 +490.8616955 +477.4934588 +471.611443 +492.7332548 +467.0662584 +455.5695923 +490.5944091 +303.9740745 +388.2132392 +425.6248004 +461.9864215 +451.8265324 +444.6076735 +443.5548901 +435.5339478 +428.0477349 +439.2770541 +434.9992183 +435.5339478 +413.610039 +410.6690269 +439.8117836 +420.2941575 +441.4159721 +407.7280149 +401.5786259 +390.6166714 +386.3388357 +422.7027937 +473.7671056 +497.0278381 +493.5520966 +477.510212 +501.2888813 +501.2888851 +569.1994969 +562.7827436 +583.1025193 +594.8665562 +629.8913166 +589.2518829 +601.8180044 +559.3070246 +569.7342234 +554.2270658 +572.9425769 +554.7617718 +544.3345531 +564.119531 +545.9387364 +553.6923148 +537.6504447 +557.4354185 +538.7198968 +540.8588012 +536.5809687 +553.6922817 +557.7027602 +552.0880848 +570.5362332 +559.0395342 +561.980532 +561.4458022 +574.8140171 +578.022398 +586.0432777 +576.4181681 +601.2830728 +599.1441203 +631.4952425 +648.0717649 +662.5094988 +694.5930107 +692.72164 +688.4438615 +687.1070819 +695.9300876 +709.0309713 +691.1175702 +723.4686585 +712.7741287 +790.309657 +814.3723198 +768.6532372 +732.5591325 +728.2812826 +734.4306997 +723.2014015 +719.993049 +758.4935313 +719.4583207 +747.7989787 +750.2052368 +746.7295289 +767.5839329 +763.3061121 +762.2366368 +813.8379216 +875.5986779 +873.4598955 +903.6717433 +898.5397424 +864.1021853 +838.435452 +836.5638517 +828.0082275 +811.9664217 +818.9178731 +815.9768811 +810.6466779 +810.6466778 +814.9245136 +811.1814074 +763.8578489 +706.9091596 +749.1527884 +778.8302743 +833.37268 +801.5562768 +818.1328903 +787.3859457 +795.1395232 +794.3374289 +787.1185809 +794.3374293 +780.7018273 +783.3754747 +786.3164868 +783.3754749 +793.2508953 +751.5420927 +785.497334 +788.9730754 +792.4488809 +861.6960639 +896.4530398 +846.1889802 +844.8521521 +871.3210644 +841.3763727 +797.2613065 +780.6846396 +802.6083278 +806.618628 +822.1254718 +781.2191678 +732.5590047 +784.1601111 +781.4865509 +807.420598 +782.0210669 +720.8996898 +722.1315855 +721.5967902 +716.7842798 +707.4265719 +691.3848038 +686.3049119 +681.4923061 +663.0442114 +659.8358083 +635.2382827 +649.408535 +650.2107168 +611.7102157 +632.2973096 +615.988103 +639.7835479 +610.9082378 +632.5647218 +636.3078371 +644.3288048 +621.8702135 +619.998688 +609.3041006 +608.5020242 +598.3421908 +589.2518169 +621.6029385 +666.5201897 +722.1319414 +694.0587 +644.3289271 +635.5058983 +621.6029549 +626.6828874 +644.863679 +643.5268643 +666.7875948 +688.7114739 +714.9131731 +705.0206918 +714.3784378 +653.686714 +651.5478011 +652.3499273 +692.1872405 +841.6438061 +779.0806481 +738.441254 +689.2462389 +684.4336682 +692.4546127 +668.3918066 +656.6277564 +679.6379561 +659.3182358 +658.2487771 +656.9119532 +662.7939775 +632.5817618 +644.8805398 +658.5161416 +680.1726855 +687.9262629 +661.9918832 +661.189789 +651.5646583 +657.7140475 +709.8501717 +688.7283586 +661.189789 +656.6445885 +663.0613423 +653.4362117 +667.3222916 +649.4088686 +660.3708121 +652.8846089 +678.2843008 +691.6525163 +686.037856 +695.3956097 +695.3956065 +691.3851319 +684.700998 +687.1072465 +709.5985383 +714.1509485 +704.5100436 +713.347414 +705.0453842 +711.4726405 +706.6521535 +713.8827767 +729.6832688 +732.6288601 +727.2729199 +720.576981 +712.5434821 +719.2384508 +710.4002662 +708.2575916 +476.791798 +554.64816 +538.1783982 +542.2464137 +545.6786712 +294.3510851 +491.3220563 +158.597366 +259.1748433 +370.7872896 +597.3988039 +536.5437167 +615.0166721 +675.58196 +735.8413713 +707.1872813 +698.6179507 +716.2928424 +737.9853747 +728.8798551 +703.7066079 +724.5955303 +695.9403805 +702.6354538 +695.4048216 +658.1799157 +681.4791337 +673.9805657 +683.8894167 +666.7498791 +680.1403139 +686.032019 +679.0692008 +700.7615138 +699.6904388 +701.8329387 +705.0466543 +705.8500015 +721.9182112 +727.2745843 +692.7275256 +638.6308168 +686.0327022 +783.2457917 +746.8242417 +729.6846808 +772.801411 +754.3227196 +759.2186968 +745.0249138 +743.418076 +756.0050062 +768.3241309 +760.0221077 +742.0790388 +747.702983 +750.3810504 +743.6858687 +754.9337682 +740.2043763 +743.9536719 +735.6516521 +744.4892839 +723.8681438 +759.7542907 +745.560513 +733.5091949 +733.5091975 +753.2506938 +724.0600073 +731.0228744 +724.0599472 +735.5758765 +744.6812952 +697.8153451 +858.7650469 +769.5867563 +787.7963141 +765.3004491 +757.2659393 +664.4136717 +736.1094649 +424.7959583 +563.6066128 +728.8790444 +652.843747 +722.718909 +577.9717045 +733.9668737 +706.5440801 +718.2095668 +715.2205405 +625.0433572 +716.0237395 +679.6020937 +657.549434 +661.9271138 +643.765365 +649.6080234 +645.8586041 +558.7641849 +648.0019404 +651.489867 +635.4142113 +639.4316822 +622.5597631 +636.7539274 +593.5088124 +631.1302675 +563.9117721 +622.2927162 +591.2276279 +600.3331649 +594.7093571 +597.3875847 +631.1310359 +627.1141359 +585.6042931 +608.3678461 +592.8352114 +621.4904523 +609.4392266 +600.0659802 +593.906487 +600.8695166 +603.0119498 +618.0091702 +596.852503 +613.9921625 +610.2429375 +616.9381829 +603.0123208 +605.1547358 +624.9724846 +618.5450329 +616.9384082 +638.8985426 +630.328695 +628.9897123 +615.5992591 +632.2032689 +626.3115841 +878.3156357 +709.3310825 +695.2127943 +715.298328 +745.8283286 +714.494901 +709.6743785 +677.5375309 +673.7882378 +666.2896376 +663.0759538 +656.1129689 +659.8622721 +643.7938454 +664.6827963 +642.1869995 +719.0476323 +672.9848153 +684.232711 +672.1813829 +656.9162234 +663.3541583 +660.590281 +643.7184982 +659.5190682 +665.6785959 +681.7484325 +666.4835311 +663.5373297 +671.8390227 +666.7505401 +676.6590756 +679.604568 +704.5103061 +699.4218369 +679.0686202 +700.7607848 +681.4787517 +701.8317658 +698.0820778 +704.7771016 +689.2445338 +706.9197457 +687.1021014 +698.0824619 +696.2076398 +730.4864488 +687.6379264 +735.8421111 +703.7045222 +707.4541552 +643.4005953 +718.1668229 +704.7764305 +695.9469686 +644.7161151 +701.0722172 +711.0389839 +557.5086031 +667.8984916 +736.6520284 +509.3346328 +573.3150327 +715.2201999 +719.5049745 +717.6313994 +698.3496628 +671.0339687 +668.6237379 +667.8205555 +666.7493416 +677.7294088 +684.6925142 +687.9062858 +687.638509 +665.9462377 +692.9947629 +651.7526047 +671.3025415 +658.1800393 +666.7498772 +658.180174 +671.3027245 +683.889751 +657.377178 +657.1094557 +675.8556515 +666.4826347 +683.622432 +668.6252323 +683.8902917 +654.6991978 +691.9244375 +701.0296474 +611.5822091 +585.6074289 +690.8536559 +811.9009469 +753.294102 +700.5361551 +695.1800281 +685.0033638 +697.5902904 +739.368152 +748.4735874 +771.5049624 +725.4421939 +639.4761939 +642.154269 +668.3993463 +674.826709 +636.7981263 +629.2995361 +611.3564712 +661.9719739 +623.6755901 +683.1287261 +636.7981314 +670.5872188 +691.1232692 +732.8991262 +710.40346 +716.8308731 +712.0101212 +734.5054602 +788.0662521 +861.1765649 +861.9799014 +820.202516 +777.0856394 +765.1041903 +747.9103117 +768.3279655 +783.909723 +773.4321727 +782.5662693 +766.71591 +752.2087257 +750.8654331 +726.1493234 +731.5223458 +752.4770808 +746.2980709 +718.6268053 +714.5970047 +686.6569646 +712.1789573 +708.9864388 +535.0178402 +551.0932519 +658.9854456 +638.2991605 +626.4782956 +631.0454111 +625.6744826 +619.2246796 +623.5233619 +622.7173826 +635.6127761 +654.6871628 +596.3893504 +580.5388014 +600.1506407 +564.1509477 +564.9569432 +549.9122792 +556.8973176 +542.1212951 +541.5840212 +545.0765345 +562.5390922 +568.4494859 +548.3004286 +571.6733509 +568.180866 +562.8077854 +575.7032441 +561.4645657 +574.628633 +574.0913575 +573.5540942 +560.1214058 +603.3747696 +589.9420815 +611.9717073 +575.9720287 +603.9121898 +593.1659263 +800.029638 +679.672521 +647.7027284 +612.7777606 +599.6136945 +571.9652739 +597.7440998 +611.1768291 +601.5052629 +602.5798806 +597.7440981 +596.1321707 +620.5797378 +596.4008249 +621.3857028 +595.3262066 +613.5947195 +601.2366078 +595.3262073 +604.1918081 +604.1918076 +613.3260631 +605.8037341 +606.6096976 +628.3707179 +610.3708609 +600.6883663 +631.8522402 +618.4194132 +609.8224953 +626.7478769 +604.9868538 +602.3003424 +610.6284654 +617.0761558 +610.8969716 +616.8073132 +606.8670265 +633.1951192 +623.5235481 +636.6875758 +630.7772409 +635.344337 +635.6129563 +626.2100507 +618.687712 +622.9862031 +620.2996532 +639.6426597 +623.2547547 +635.3441763 +631.8517102 +660.0602325 +645.8216368 +632.6574741 +643.1348758 +640.1797728 +623.7917669 +638.2991724 +631.8514727 +643.4035706 +624.8666419 +633.732247 +610.628018 +607.672875 +578.3895934 +570.3299754 +562.807667 +627.0160301 +645.0158394 +723.1939877 +706.5374712 +736.3580826 +692.5676565 +650.3890023 +639.105537 +696.5975084 +699.8214055 +635.34445 +626.7475147 +634.0011971 +621.3744429 +626.7475306 +626.7475188 +605.7925113 +609.8223522 +613.852151 +593.9717456 +599.0761713 +595.8523218 +602.0313668 +605.7925526 +627.0162616 +646.3594641 +626.2103552 +629.9717311 +582.9576854 +513.6444832 +550.9872505 +654.1503247 +709.2244517 +710.0303742 +659.5433225 +625.6928386 +647.7225204 +640.2001895 +658.7373588 +657.6627402 +653.6329203 +646.9165547 +690.4386043 +653.095611 +680.2297287 +650.6777188 +666.2596873 +665.7223789 +669.2148889 +659.0060135 +736.9158564 +794.1392927 +796.0198763 +770.2290325 +710.5676542 +693.3737883 +731.5226845 +695.7916752 +697.4038786 +717.2842153 +709.7618766 +680.2099517 +686.6576885 +694.4486166 +678.0606582 +688.8067061 +709.2242988 +691.2243832 +706.8063564 +679.940928 +682.6274684 +698.2093911 +664.6276376 +697.940673 +714.0596968 +673.7617157 +683.7018512 +670.8065238 +661.1350295 +645.2844029 +635.6128463 +606.0609137 +634.8068636 +635.0754707 +596.6580244 +576.5089542 +594.777449 +577.5835672 +577.5836144 +645.2842997 +665.4333048 +604.1804055 +556.6286272 +537.016875 +529.2259067 +528.6886096 +544.8078696 +560.1211659 +581.344854 +578.3896881 +543.7332685 +552.5988598 +592.8970124 +594.2402963 +575.9718134 +558.2406477 +571.9420302 +564.1510624 +580.5389875 +558.7779819 +554.4795297 +611.1656158 +650.9264756 +588.5986555 +593.1658188 +582.9569521 +588.8673786 +582.6883999 +578.9272914 +585.1063537 +601.4941294 +684.2396705 +633.7326544 +585.1062523 +599.613592 +602.0315174 +604.4494108 +616.5387699 +668.1203881 +644.7474795 +602.3198896 +587.8125368 +789.0348533 +787.6915801 +711.9309754 +645.5732827 +605.812396 +611.7227982 +605.8123956 +600.1706478 +596.6781383 +599.9019947 +603.12585 +598.0214138 +600.4393055 +571.424604 +593.991595 +570.0811796 +580.5615924 +567.1261142 +567.3748897 +616.0013305 +583.2255021 +592.6284022 +604.4493548 +567.9124098 +624.8670152 +567.1063037 +578.3897364 +563.3450569 +569.5240966 +581.6135144 +572.0570744 +588.6242945 +600.3816954 +584.3489084 +595.0374551 +603.5882808 +632.9817925 +628.9734943 +604.6571513 +601.4505372 +623.8962775 +618.2847132 +629.5076364 +626.033609 +642.333608 +646.3417129 +643.936961 +649.2810352 +658.6334267 +555.6550798 +618.3077156 +592.8005892 +667.1843759 +666.1144482 +594.6054739 +661.3055357 +672.5291135 +537.3829427 +727.8421093 +676.5372275 +635.1193842 +606.2603479 +638.5930881 +648.7471664 +654.0910036 +659.4354808 +673.3307904 +644.7391814 +653.5572114 +656.2293615 +645.0064105 +640.1965888 +642.3343058 +626.5687027 +640.998223 +625.7670539 +647.6785609 +626.8359079 +634.3178836 +648.4802312 +634.3179293 +637.5245037 +645.5409126 +635.1195736 +639.9294361 +594.5031231 +585.9522988 +538.1210497 +623.0949979 +670.1246062 +656.7639234 +655.1606428 +661.8409767 +666.1163952 +651.4600616 +645.0469246 +658.4076237 +676.8453916 +677.914246 +684.3273837 +673.6388236 +654.9338429 +674.4404643 +674.7076779 +698.2225125 +675.2421067 +704.6356487 +701.9635063 +689.6716636 +692.8782309 +673.9060374 +709.4454992 +712.9192804 +876.4542433 +761.2443224 +784.4918534 +824.0392799 +729.4459542 +755.0984994 +790.9050476 +747.8837457 +773.5361889 +731.3164649 +705.6638977 +739.3326226 +709.4046852 +717.9554326 +706.7323214 +699.7849031 +693.9062863 +714.7487683 +727.307686 +702.7242955 +711.2750881 +698.4485932 +727.3075027 +724.6354242 +685.622383 +670.1241613 +658.9012452 +661.3062259 +662.6421533 +653.0225545 +641.5323892 +616.681547 +602.5191797 +621.7586297 +612.9405992 +641.5323013 +584.6160372 +597.1750387 +584.0815929 +615.880004 +586.4865784 +581.943976 +581.1423321 +594.5030164 +578.7374296 +602.78663 +631.3785138 +591.5637092 +602.5194658 +640.4637858 +630.8441135 +627.9047836 +648.2130387 +654.8933749 +636.455634 +635.3867803 +640.1966131 +686.4245651 +745.2114647 +709.1377182 +654.8934076 +665.5819501 +646.8769961 +648.4802876 +676.8049261 +659.1688523 +665.0475428 +663.4442743 +654.3590034 +643.1360422 +655.1606402 +882.8257416 +693.9066042 +644.4720978 +609.7343334 +628.4392787 +606.2605456 +604.4303953 +605.2320374 +652.796129 +671.2338966 +614.8517406 +588.1303396 +567.0204318 +560.0728668 +582.2516311 +596.1467594 +600.4221828 +597.2156166 +597.7500429 +634.8927905 +629.2812956 +713.9881361 +707.842215 +609.7746755 +590.8080495 +585.725414 +596.6408322 +581.676864 +562.7046913 +577.4014371 +652.7557471 +649.2819607 +669.0557721 +662.6426251 +669.3229336 +700.0524228 +668.2539864 +690.4326464 +686.1572026 +696.5784632 +701.9226368 +713.1456051 +693.6391071 +701.9226736 +720.0932079 +728.9110967 +749.486503 +727.0404719 +621.7643423 +692.7094279 +606.4848773 +687.6096121 +532.5370966 +550.3024995 +592.5553568 +322.1816879 +263.3738016 +267.8016967 +215.4910463 +221.6316118 +232.3167647 +391.7391142 +467.0786465 +628.9026599 +743.8737143 +763.1146137 +720.8943296 +710.4734065 +715.5505475 +727.3076215 +758.3335272 +833.9248497 +757.2358448 +845.9499675 +828.5815375 +724.6360552 +719.5589901 +765.5197132 +653.5573291 +648.2130508 +663.4442416 +649.2819251 +652.75571 +647.6786592 +670.3918213 +667.185245 +667.9868998 +670.3918374 +666.3836467 +657.5656007 +667.4525033 +667.9869138 +684.0197402 +681.8820154 +682.6836482 +588.891667 +620.155663 +685.3557645 +747.6165083 +724.9033851 +715.0164862 +708.6033482 +705.7045054 +722.004559 +730.020982 +708.1094326 +736.1669043 +713.7209258 +736.1669022 +726.0127709 +739.1062561 +708.9110744 +713.4537133 +713.9881413 +730.020981 +732.1586908 +754.3374571 +724.6767018 +746.3210367 +723.6078456 +744.7177525 +687.8011553 +720.0935257 +717.1541765 +707.0000707 +711.8099032 +733.9886431 +729.7132487 +728.6443768 +761.5115778 +794.3787001 +773.0015032 +746.8146105 +720.8948457 +751.6004456 +743.1947877 +744.0082218 +729.366111 +736.144866 +742.9236233 +725.298845 +748.3466214 +725.2988296 +753.4984755 +720.4181325 +716.079712 +721.2315781 +713.3682528 +710.9278688 +689.5069663 +688.6935314 +662.3919106 +673.5090835 +631.8727874 +664.5611165 +664.5611141 +673.5091894 +628.6215716 +546.2868741 +669.1706839 +636.3614707 +620.9058878 +624.4308495 +607.8906622 +622.8039552 +592.4350871 +591.3504865 +605.9926267 +639.0730032 +627.9558293 +608.4329889 +579.9621735 +558.8124256 +572.0988056 +586.4697909 +625.7866312 +573.4545605 +621.4482242 +578.0641242 +565.5911958 +591.3505091 +614.940616 +580.7756365 +597.8581283 +595.6889242 +605.4503471 +612.7714142 +619.0078793 +645.309491 +653.71516 +603.0099904 +712.8259797 +821.0150615 +772.4791243 +723.1297176 +664.0188836 +629.0404525 +657.7824213 +629.8677629 +675.9633799 +718.5340381 +682.4709967 +636.1042286 +642.8829958 +629.5966118 +-1.50E-11 +-1.52E-11 +-1.24E-11 +-1.74E-11 +-1.46E-11 +-1.51E-11 +-1.42E-11 +-1.46E-11 +-1.39E-11 +-1.47E-11 +-1.46E-11 +-1.47E-11 +-1.46E-11 +-1.48E-11 +-1.47E-11 +-1.49E-11 +-1.50E-11 +-1.51E-11 +-1.52E-11 +-1.53E-11 +-1.53E-11 +-1.52E-11 +-1.51E-11 +-1.50E-11 +-1.50E-11 +-1.48E-11 +-1.47E-11 +-1.47E-11 +-1.47E-11 +-1.47E-11 +-1.46E-11 +-1.46E-11 +-1.46E-11 +-1.46E-11 +-1.46E-11 +-1.45E-11 +-1.45E-11 +-1.45E-11 +-1.44E-11 +-1.44E-11 +-1.44E-11 +-1.43E-11 +-1.43E-11 +-1.43E-11 +-1.43E-11 +-1.43E-11 +-1.43E-11 +-1.43E-11 +-1.42E-11 +-1.44E-11 +-1.45E-11 +-1.45E-11 +-1.46E-11 +-1.47E-11 +-1.48E-11 +-1.46E-11 +776.2754711 +693.0319197 +477.8240134 +764.886787 +685.1685793 +683.8128373 +674.5937253 +675.1360231 +699.26843 +679.7455932 +698.4549931 +672.9668361 +645.0383273 +674.8648978 +686.2532279 +654.5286053 +670.2553429 +674.3226042 +675.9495101 +666.4592376 +671.0687989 +668.086142 +744.0083136 +843.791697 +823.7265724 +789.2904583 +749.4313266 +716.3509559 +594.3331716 +607.6195529 +365.2108667 +503.4977016 +617.9232756 +638.2734338 +669.7269133 +682.1998452 +699.0111874 +699.0111873 +701.4515436 +685.4536534 +695.7573793 +720.1609407 +705.2476527 +694.943926 +704.4341999 +700.0957891 +691.9612687 +712.2975705 +689.7920637 +688.1651601 +689.7920644 +699.8246398 +714.7379275 +672.6957033 +685.9820873 +697.3704145 +698.183867 +711.7413986 +684.8974862 +720.6893677 +685.9820868 +671.3399518 +702.793421 +667.8149817 +715.2663162 +720.9604728 +692.4896611 +713.0971061 +675.4071749 +694.9300087 +717.1643478 +694.1165546 +678.932119 +694.3876832 +701.979894 +695.7434357 +711.7413046 +679.4744066 +680.830169 +662.9342332 +662.9342229 +657.511204 +672.1533995 +653.986265 +640.9712164 +627.9557926 +614.3982616 +624.7019852 +625.244283 +598.6715408 +599.4849967 +598.6715493 +575.8949045 +591.6216434 +583.2159734 +578.8775645 +585.1140282 +565.5911846 +562.879681 +565.8623387 +569.6584486 +577.5218205 +566.6757942 +572.0988073 +600.8407768 +589.723602 +623.6174314 +584.0294395 +600.5696288 +598.6715761 +589.4524548 +591.0793594 +605.9926462 +590.2659104 +608.7041552 +596.5023766 +579.1487343 +604.636895 +587.2832549 +606.5349509 +627.9558526 +616.5675244 +738.3141498 +826.9803763 +682.7282791 +666.1880877 +655.8843627 +632.2942576 +632.5654082 +618.7646112 +610.0843778 +660.5184346 +625.8111357 +621.2015818 +640.1821315 +611.7113055 +656.7223243 +632.047606 +630.1495418 +625.539987 +651.0281598 +627.9803415 +623.9130654 +628.7937973 +680.5835869 +651.8416024 +650.2146841 +715.562028 +782.807403 +727.1969683 +702.2511129 +728.5527204 +788.4769983 +722.8585719 +744.0083161 +779.5290417 +774.1060333 +715.2663533 +709.3010395 +731.5353754 +733.704553 +706.4100443 +700.4758947 +707.2192492 +734.462374 +723.4032626 +725.2913985 +726.9098106 +735.2715609 +707.4889516 +727.718975 +734.4623126 +691.3760753 +685.475207 +528.901438 +465.6975138 +512.9014512 +321.3927536 +334.5450609 +428.7056911 +306.1354332 +415.833314 +429.2755018 +445.9752422 +280.9062043 +522.5743506 +534.7627659 +467.9825084 +414.6960938 +554.0866775 +675.9304314 +695.8903504 +691.5746326 +691.0351968 +699.6666941 +676.7393121 +679.9761242 +671.0749032 +677.009055 +657.8579364 +700.4759186 +664.871027 +672.6933171 +706.949542 +774.3830557 +689.9563006 +667.0289072 +664.6013028 +676.739367 +671.3446634 +693.4628488 +619.8254448 +697.239141 +666.2197306 +692.3839179 +759.5476948 +715.5810495 +692.1141862 +702.3640861 +647.3383175 +636.8186962 +623.6017157 +684.5616184 +696.9693855 +728.798011 +719.9165842 +726.659937 +724.23233 +720.4560524 +720.1863183 +740.9558454 +722.8836594 +754.4425512 +754.7122856 +745.8110596 +716.6797747 +741.765048 +707.7785488 +717.7587113 +706.9693464 +690.245831 +697.7983864 +701.3049299 +695.9102475 +717.7587112 +734.7321549 +724.4822649 +684.8313638 +686.4497719 +748.7583388 +745.2518014 +755.771419 +741.475514 +743.0939205 +782.205351 +754.1529963 +799.7380055 +763.8633939 +761.7055154 +764.133101 +726.1006112 +761.9751855 +706.6797638 +743.0938471 +755.2318666 +732.3044664 +802.7050002 +728.5906317 +754.4226265 +776.001551 +728.2584335 +727.9887025 +725.2914372 +698.8574274 +715.5809612 +667.3636074 +678.3576446 +459.997165 +632.0644456 +580.3461921 +653.0483639 +693.1930419 +653.0026653 +637.088368 +705.3310507 +723.6729855 +669.7261962 +635.20025 +627.9174335 +640.8646726 +699.3969563 +756.3108506 +814.3036531 +739.317619 +661.3644764 +656.2395315 +615.2399537 +615.5096885 +632.2332038 +636.8186862 +623.0622522 +625.2201338 +626.8385497 +611.7334455 +616.0492043 +619.0162786 +628.4569778 +611.1939887 +601.7532914 +646.0127821 +616.5886686 +623.6008797 +621.1741167 +821.3168039 +707.2193055 +678.0880206 +671.0749338 +710.9955634 +733.11376 +698.5877985 +687.2589762 +636.8384766 +649.2462456 +646.8186386 +651.9435874 +653.5619913 +650.5949156 +652.7527885 +658.9566735 +646.5489042 +658.9566733 +649.5159791 +641.1542214 +653.292257 +662.193483 +662.7329513 +669.7460384 +650.0554484 +659.2264081 +634.4108696 +648.7535394 +756.3108969 +696.6996735 +675.9301521 +664.3315811 +670.2657394 +675.120944 +735.5413629 +787.060564 +748.7583271 +700.7456561 +691.5746849 +693.7325501 +691.5746721 +695.0812138 +692.1141406 +690.7654669 +681.0550379 +688.068127 +689.4168002 +736.0807751 +683.4826451 +701.5548252 +698.3180101 +669.9959295 +687.5286279 +723.1340042 +698.0482323 +570.25321 +662.927168 +698.5876915 +690.2259709 +677.0998614 +694.2720896 +634.2484944 +307.5490753 +458.7386723 +698.6120531 +704.7915997 +679.7063497 +667.5683365 +669.4564618 +637.8975957 +628.9963692 +619.8254148 +612.0031307 +446.6561494 +429.1234374 +615.5096854 +671.0749068 +696.1601742 +664.6012921 +653.0027286 +641.6738956 +631.1542732 +643.2923058 +669.7262454 +645.1804448 +647.3383214 +643.2923087 +618.7464998 +631.1542719 +643.5620374 +641.1344293 +644.3712412 +633.0424075 +632.7726792 +635.2002866 +645.4501818 +667.2986401 +580.7140113 +538.6354841 +576.1285183 +663.5223581 +684.0221501 +667.8381089 +668.9170464 +651.9435863 +643.3120945 +641.1542215 +659.4961418 +652.4830547 +667.3184315 +659.2264075 +649.7857133 +654.6409274 +648.9765109 +641.6936895 +647.3581061 +644.3910309 +784.9225072 +690.5155656 +670.8249754 +642.505661 +654.3711945 +641.4239556 +647.3581075 +651.1146381 +642.752882 +651.6540985 +623.0622757 +640.8647326 +631.4240337 +647.8777907 +658.3974143 +656.2395384 +687.7984205 +681.3247983 +669.1867624 +689.3832423 +688.5638348 +685.5593447 +716.6968076 +689.1101118 +709.3221467 +718.3356238 +696.2116386 +707.1370685 +728.7147969 +723.2520766 +736.0894544 +742.9179098 +736.3625967 +735.5432086 +741.2791361 +734.9988995 +728.9880126 +374.3092928 +476.5775041 +687.19887 +633.8123202 +635.3025356 +636.9413993 +637.7611193 +579.3377191 +669.7179206 +639.6728402 +627.9278118 +637.4875048 +635.302409 +652.7830901 +633.6635863 +600.6141673 +612.905267 +634.4829784 +618.6411108 +642.1307732 +614.2709364 +613.7246647 +702.2206169 +803.2807997 +695.9384925 +643.4964418 +626.2888938 +628.7471103 +643.2233026 +643.2232981 +608.5350661 +647.3203312 +626.0157373 +644.8621097 +612.0858145 +627.9276933 +620.2798976 +619.1873576 +612.6321174 +867.4676748 +753.8432491 +747.0148572 +691.2951858 +660.4308563 +653.8755995 +637.48746 +623.2937519 +615.3728187 +682.5641871 +692.6702058 +657.708843 +641.0475676 +651.4267188 +663.9909628 +683.3835938 +649.5147741 +648.1490959 +649.2416382 +648.1490954 +644.5983319 +668.361133 +654.7043511 +630.1221412 +647.3296887 +633.3997701 +664.8116817 +646.2278087 +666.4398472 +642.9501809 +635.0292468 +645.6815248 +669.1711942 +670.5368731 +697.5773051 +677.0921352 +689.6563779 +674.0876506 +682.2817257 +679.0041016 +696.4847775 +700.3086853 +684.1936786 +686.925045 +666.7130076 +681.4623286 +688.5638638 +689.3832773 +688.2907273 +697.0310538 +699.2161639 +697.3042063 +710.960995 +705.2253293 +718.6089797 +711.7827104 +572.3571058 +424.3855118 +370.3945959 +496.1280196 +191.7000091 +532.8875983 +413.7130378 +369.718604 +410.3144928 +677.9118155 +607.3608439 +526.7080783 +680.6472053 +644.5891082 +654.9682046 +648.6860677 +621.0993527 +641.5845183 +645.1352744 +619.7336552 +648.1397621 +615.9097529 +608.5350893 +608.5350882 +619.1873773 +620.2799183 +623.2844104 +636.3949211 +616.456017 +619.7336418 +626.0157608 +612.6321057 +610.9932895 +634.2098219 +618.0948171 +618.3679479 +609.081334 +634.7560766 +643.7695472 +638.0336937 +628.7470828 +624.3769374 +625.4694682 +628.7470924 +631.2053186 +627.3814243 +642.6770267 +625.474786 +520.8638333 +527.9653603 +548.9968053 +669.1764901 +714.2438718 +665.352591 +623.8359724 +631.210635 +633.9419917 +637.7658907 +641.3166543 +653.6077585 +654.9734371 +651.1495378 +624.9285157 +649.2375879 +663.4406414 +646.5062313 +681.4675942 +676.5458599 +649.778562 +664.5278852 +687.1981451 +650.871102 +691.2951839 +720.2475648 +694.0265458 +681.7354457 +695.6653708 +696.7579154 +722.432677 +707.6833497 +705.7714204 +710.687873 +726.2565706 +723.2520808 +710.4147506 +716.4237217 +710.1424256 +709.6592369 +636.8062706 +623.1039489 +541.4845118 +454.1244641 +594.6343346 +434.3608791 +367.4553323 +348.9329012 +367.6236876 +521.0415046 +412.0459072 +552.8928612 +381.6700423 +510.6766385 +518.4194296 +482.1990128 +650.408326 +621.0994319 +635.8488729 +611.53965 +623.0113215 +596.2440107 +593.2395063 +580.6752692 +581.4946849 +709.8684245 +636.6680688 +603.6186547 +589.9618682 +583.1334766 +590.5081389 +597.6096638 +591.0544051 +584.2260137 +580.6752484 +577.9438902 +569.2035478 +557.7318486 +591.3275332 +571.9348988 +610.4470189 +654.1487281 +649.2322839 +636.6680171 +611.2663823 +614.54404 +615.3634468 +626.2889005 +845.6168286 +704.9519602 +680.9160253 +666.7129645 +660.4308483 +623.5575353 +630.659063 +620.2852091 +617.553853 +632.0300423 +616.1881747 +631.2106352 +648.6913164 +640.497247 +647.8719096 +644.0480108 +644.3211464 +628.2061433 +640.770383 +638.5852979 +654.7003008 +642.136061 +667.8108116 +645.4136886 +644.5942818 +655.5197074 +662.6212341 +657.6994653 +685.0130517 +664.5278698 +651.4283063 +658.7917877 +673.8144516 +663.4353256 +669.9905897 +662.0696611 +665.3472988 +673.8145118 +671.083163 +678.2616681 +690.5898071 +671.1387256 +662.0980976 +675.2480623 +671.6865801 +667.5771757 +701.5479856 +675.2479364 +676.0698642 +694.1509813 +680.1791079 +671.1383189 +684.8364662 +683.7401093 +687.3033657 +694.9709909 +666.5982268 +466.5420656 +587.4519901 +600.2658255 +468.0887186 +632.6274821 +158.7708507 +155.9763004 +458.4506119 +612.8369603 +706.7531509 +665.1114989 +670.5905999 +659.0846198 +642.099141 +639.6334929 +650.5918497 +631.140831 +634.4283424 +638.811702 +658.8106971 +631.9627609 +640.1815269 +637.1679934 +648.4003011 +648.9482258 +654.7013658 +639.907597 +651.4138649 +637.1680384 +637.1680393 +633.0586648 +656.0712013 +655.2493548 +677.9879374 +661.8243733 +640.7295535 +666.4816518 +659.6326844 +676.0702132 +704.0140638 +574.7056033 +596.3482599 +676.3441243 +708.6712363 +701.5483202 +674.9743687 +666.5322594 +689.5448018 +680.2301995 +674.2031037 +678.3124875 +692.2843899 +700.5031569 +691.4625146 +688.4489672 +678.3124893 +683.2437471 +688.9968835 +694.4760602 +692.2843894 +705.1604582 +671.4635166 +692.558351 +698.0375293 +710.0917172 +801.5939819 +750.860889 +688.6723095 +703.4660778 +717.4379635 +717.4379625 +723.1911212 +705.109818 +800.7213211 +757.4358791 +794.4201832 +779.6263803 +781.2699462 +789.7626226 +783.1874824 +780.4479983 +767.8458737 +765.6541642 +742.3677009 +737.1626048 +752.2300313 +764.2843492 +784.2827092 +768.3926062 +755.788724 +717.9845211 +728.6678475 +657.4435571 +658.4212583 +622.1356225 +511.1358315 +661.7981103 +696.6150706 +607.1221486 +647.0286211 +384.3361302 +232.036721 +580.6464642 +692.5071313 +650.3176391 +681.2751475 +672.5084737 +672.2346081 +653.0575186 +623.470003 +659.9065215 +667.5773474 +688.9461353 +693.8773892 +641.2773661 +621.5523492 +611.4158953 +599.0877652 +613.333628 +629.7711475 +648.4003385 +649.2222286 +642.9211676 +648.4003681 +640.1816108 +665.1118372 +645.9347433 +655.2493441 +639.3597369 +647.0305995 +675.7962587 +695.7952315 +692.2337682 +667.0295727 +669.4951993 +761.8191637 +849.7597922 +790.3109252 +748.3952798 +722.3691983 +708.1233319 +707.5754181 +701.8729512 +706.5302527 +717.762566 +700.2291975 +693.1062674 +701.3250363 +695.0239806 +689.5448018 +679.6822825 +686.5312541 +698.5854435 +680.5041573 +693.3802264 +703.2427468 +701.3250342 +700.2292007 +692.8323139 +688.722929 +684.3395924 +703.5167147 +698.2609054 +723.1911059 +811.9536203 +835.5140039 +742.6421611 +736.3411345 +734.4234264 +730.5879752 +740.1765132 +746.4774859 +709.7669779 +723.1908602 +748.3949404 +775.2427874 +753.600173 +759.6271056 +751.1343787 +737.4364728 +750.3123472 +768.3934037 +754.9697449 +760.9949593 +568.9189397 +726.5228109 +614.4340124 +513.5754553 +534.5029695 +582.0188127 +612.5841622 +734.4645927 +564.4896095 +669.0566839 +507.4942032 +262.292199 +452.0896392 +610.7527933 +649.6832777 +668.9660344 +609.153943 +740.1752064 +723.4641452 +745.9787344 +765.6542432 +725.3823571 +778.8041007 +785.6533567 +759.0794588 +704.561726 +700.1783969 +571.6918732 +691.1378318 +740.9982849 +726.2045291 +687.85037 +676.6180598 +662.3722381 +678.5358118 +681.8233415 +668.9472778 +650.8660519 +651.1400005 +636.8941543 +638.8118921 +649.2224463 +629.497288 +654.4275057 +658.2629361 +654.7014666 +667.0296151 +511.1474244 +495.0425921 +586.7597353 +680.4535061 +770.5859073 +721.273382 +710.3150508 +716.1188247 +699.9552408 +725.1594569 +722.6938256 +721.0500721 +736.1178114 +730.6386335 +730.9125933 +731.7344701 +716.6667308 +737.2136457 +759.130357 +733.9261389 +732.2823854 +739.4053181 +725.1594555 +739.1313625 +736.3917845 +731.7415419 +735.5698998 +752.2306589 +731.4098711 +747.0255281 +741.2723816 +731.1359268 +803.460908 +798.529653 +868.9368125 +880.9908713 +910.0300122 +932.7762192 +917.7050265 +753.4885494 +475.5339722 +318.5536936 +670.8822871 +547.4847017 +443.3795003 +556.7655883 +650.47231 +455.0955903 +487.2353483 +769.9579251 +774.1862874 +413.0346117 +768.3012905 +615.8563316 +703.5895282 +443.114994 +399.4562855 +396.3336811 +482.2366981 +572.5482623 +430.9938363 +646.8605205 +682.4870702 +695.1605466 +718.7382915 +710.808857 +701.7461837 +704.0122303 +711.3760585 +704.5790464 +709.3936319 +751.3096626 +692.6838959 +715.0580527 +675.4077675 +668.6106283 +669.4603621 +666.9114494 +651.0513718 +666.9114752 +673.1422983 +674.5584034 +666.9115788 +685.3206796 +683.9047992 +688.4360721 +690.1353622 +688.4360833 +705.4290607 +695.7997522 +706.2788156 +727.2367274 +756.4083459 +724.6877457 +727.2366949 +746.212085 +841.8761109 +809.0861442 +807.6700491 +801.7224117 +773.4008162 +798.6070529 +782.4638025 +756.7454909 +772.6056393 +770.6231225 +757.0287161 +760.1440959 +751.9308044 +759.8608839 +763.825919 +760.4273108 +762.1266139 +743.4342902 +751.647583 +761.5601784 +802.6266362 +857.5707273 +817.0707012 +806.3084599 +811.1231519 +786.4832744 +792.1476193 +827.4950426 +800.5895234 +778.4986703 +753.575638 +762.0729828 +752.443997 +763.4885846 +777.6494675 +784.7296682 +763.7719782 +751.3102178 +812.2014496 +885.8374557 +809.3689598 +805.1206193 +820.9805317 +816.4490754 +842.5047396 +828.0604811 +832.0254647 +836.2738895 +828.9092397 +854.6817678 +834.2907229 +752.8913197 +702.6028853 +641.4992232 +558.0744136 +468.2203213 +532.0984439 +295.3401971 +464.4990845 +537.7279929 +513.2679274 +611.7309635 +641.7627605 +778.5851742 +671.6353307 +792.09129 +772.550599 +796.6234859 +793.7917874 +767.4528108 +748.1942223 +762.3548931 +736.5825276 +735.166482 +736.5825687 +782.1803803 +732.9007927 +732.051212 +720.1561445 +726.1036915 +717.3240047 +726.9533948 +726.387006 +713.9255603 +710.2436727 +743.9464478 +728.9359848 +759.2400902 +720.1562669 +737.1492977 +735.7332128 +723.2717659 +737.1493543 +715.3417934 +741.1143344 +779.6318597 +730.3522114 +770.5688574 +800.5897338 +795.2085811 +767.1701717 +764.0547974 +764.3379853 +783.0846768 +782.5182401 +778.8364191 +767.507746 +790.4557006 +791.2979492 +815.9373854 +810.5567241 +844.5427542 +886.4588584 +892.1231983 +848.7910033 +842.843448 +847.6581354 +829.2490339 +809.1406313 +813.3888866 +841.1441515 +833.4972989 +846.8084936 +867.7116567 +811.634946 +813.6174819 +832.5929419 +825.2295678 +850.1525759 +839.107158 +832.0266944 +839.6734963 +828.9111218 +836.2746885 +854.4001944 +818.4318455 +794.3584034 +840.239231 +828.9103791 +823.5290131 +802.2880987 +813.6168645 +812.7671972 +816.7309749 +793.2242545 +797.755905 +797.1914437 +722.5448266 +721.6103381 +726.3434639 +641.4710378 +710.1159354 +731.7664826 +728.9341062 +726.3855466 +710.3634727 +719.3052399 +690.5810088 +686.7355887 +720.4379943 +680.2219414 +713.6414164 +720.1549565 +705.9944074 +725.2533652 +697.4982889 +686.7361538 +707.410828 +662.3796582 +685.3202137 +709.9599724 +696.0825234 +716.4741297 +679.3728729 +645.3869529 +673.4254141 +662.6632422 +674.8415644 +711.3765598 +661.8137576 +728.6527763 +679.6563935 +681.6389384 +682.2053044 +703.7297506 +668.8941725 +711.0933582 +684.4712952 +689.0027482 +707.6951665 +700.0481411 +734.8836746 +858.6488388 +839.9568184 +770.2855953 +737.1493345 +718.1737848 +751.8765561 +716.4744967 +719.6206128 +744.5436997 +722.1695671 +738.8793627 +748.5087372 +722.1695678 +736.896846 +673.7394779 +704.326901 +717.9213068 +721.8863483 +717.6380901 +731.7989351 +707.4422833 +751.057685 +724.4352982 +691.8653557 +769.749996 +789.2919634 +773.4318203 +767.4535468 +764.3382305 +738.2824606 +759.8067246 +770.5689929 +773.1179034 +802.2890969 +783.596782 +785.8624959 +793.5092994 +780.7644082 +783.8797197 +698.8505958 +673.4818213 +697.1094727 +669.5023932 +681.1918351 +691.8864018 +691.8864692 +679.9483045 +692.8813688 +675.2225804 +688.6531036 +684.9225069 +688.9019191 +688.404313 +667.7609945 +538.010141 +579.8860065 +382.3319599 +566.0231154 +486.2132962 +668.2586106 +608.2131254 +656.5694319 +622.4952431 +643.6361453 +645.8742094 +622.992812 +595.6343934 +579.9655087 +577.9758328 +607.5726976 +588.1731196 +583.1989005 +601.3549468 +622.4955499 +604.3395541 +599.3653203 +572.2555811 +581.7067227 +562.5558045 +590.162993 +575.4889207 +578.97091 +593.6450134 +584.9400546 +595.1373174 +603.8422783 +595.6347486 +597.8731628 +616.7753826 +610.3088443 +601.8525966 +613.293418 +615.0344233 +656.0721171 +632.1956541 +634.4340752 +656.0721259 +637.1699131 +625.7290984 +654.3311059 +656.8182442 +644.1338478 +662.7873446 +658.3268387 +632.2119179 +693.1467344 +535.2136396 +584.7076344 +661.3114036 +674.7419338 +666.5343875 +666.7831002 +630.9683509 +637.186188 +594.1587462 +648.8757236 +612.3148353 +626.9889335 +622.0146618 +639.6733249 +599.1330189 +600.3765864 +610.5738415 +609.3140404 +600.8578803 +605.0859867 +589.6657965 +590.4119866 +594.6400873 +634.1853968 +596.3809951 +621.5010088 +640.1545021 +624.485569 +575.2402672 +634.6827017 +613.2933273 +650.3515513 +620.5059352 +629.4596413 +642.6414168 +647.1181914 +628.2159607 +667.7613151 +631.200553 +645.6258531 +635.428623 +634.1850795 +621.500701 +624.7339818 +602.5984628 +622.4953979 +592.89854 +593.1472763 +566.28629 +575.4887241 +545.394441 +533.4562119 +524.0051313 +531.7152122 +525.4974291 +524.2538757 +544.1509343 +502.6158197 +494.9057222 +535.6947107 +483.4649402 +511.8182407 +509.5798377 +490.1802063 +507.0927333 +502.6159012 +497.6416434 +546.6381777 +502.1185119 +517.7874727 +522.5130291 +506.3466634 +525.4976447 +531.9641972 +516.0469987 +555.8407124 +516.295389 +530.969776 +567.7789676 +526.2437979 +528.4822143 +563.3020363 +528.9795945 +533.705192 +512.8133511 +539.6743313 +581.4581348 +789.6310784 +632.9417973 +615.0344501 +575.2403861 +580.7120516 +569.5199105 +550.1489466 +566.5640419 +570.0460317 +576.015157 +621.281028 +582.4817097 +594.171248 +636.4525553 +606.109499 +605.860786 +606.358214 +613.3221935 +617.0528972 +609.0940626 +618.2964647 +649.136948 +631.478284 +608.0992078 +643.4165361 +633.9654198 +649.854185 +629.9571251 +648.8593429 +660.5488712 +667.7616473 +630.70337 +636.4237713 +631.449511 +667.0155114 +661.0463951 +625.2316117 +621.2521663 +657.5642434 +629.2109079 +639.159451 +627.9673054 +619.7597657 +620.2571727 +638.1644885 +600.8575353 +642.8900605 +600.8575527 +606.5779214 +656.3203854 +637.4182317 +604.0907082 +595.6344541 +591.1576037 +632.4437175 +589.4166059 +598.1213803 +573.9964021 +581.2090065 +564.7940389 +576.2346814 +553.8506443 +556.8352512 +570.265791 +560.3173133 +536.9382871 +586.9296457 +601.1063049 +598.8678766 +642.3926815 +619.5111194 +595.137218 +579.7169866 +567.5300447 +578.2247271 +568.0274959 +564.0480906 +576.9812051 +567.0326742 +573.0018063 +582.4529244 +580.7119368 +597.127033 +582.7016556 +591.1578984 +605.3345607 +622.2470602 +694.3739247 +740.6345715 +707.5557431 +670.4975139 +641.6467763 +651.3465945 +659.305415 +649.854327 +650.1030392 +653.3362944 +683.6793335 +538.1820261 +581.2093563 +655.8233605 +663.0360559 +651.8727981 +633.2192794 +643.6652493 +608.8453487 +610.5863437 +620.5348878 +602.3787968 +597.6532383 +612.0786251 +594.6686747 +613.3221932 +602.3787959 +566.564043 +582.4817116 +569.5486045 +555.6206437 +590.1918327 +548.6566681 +584.2227075 +562.0871989 +547.8817176 +578.722183 +569.7685031 +566.0378028 +588.6711858 +584.6918342 +654.5798716 +648.8594689 +599.1168375 +618.7651369 +714.2709786 +573.4992353 +1192.152425 +1111.109688 +1143.901363 +1149.990931 +1237.122917 +1191.214242 +1174.350362 +1232.904955 +1250.702354 +1190.275907 +1223.21386 +1049.04627 +1001.716994 +1134.751834 +1008.205317 +847.668354 +800.8485335 +730.3241609 +731.0436179 +616.5250702 +805.5578291 +799.083314 +680.9412309 +632.3334293 +299.2432696 +967.0254569 +974.3940882 +1081.594813 +1080.190386 +1046.930579 +1059.110879 +1053.021119 +1093.776223 +1322.847739 +1340.749536 +1367.349658 +1254.925075 +1175.288096 +1075.038945 +1110.173102 +1076.912894 +1067.543846 +1061.453934 +1048.33734 +1103.146554 +1211.3596 +1153.271475 +1157.488009 +1032.410892 +1051.618361 +1034.28549 +1034.296171 +1060.051379 +1030.133519 +1009.92542 +1131.255508 +1131.724447 +1113.921547 +1050.680123 +989.7812034 +1026.322209 +1023.985571 +988.8452017 +1017.428189 +966.4218739 +972.5037705 +1008.106426 +1048.806202 +1276.063262 +1154.264707 +1061.510415 +1034.339966 +1038.55607 +1011.854077 +1002.484955 +1009.511796 +958.4500894 +1007.169516 +980.4675227 +972.0353141 +999.674219 +986.0889943 +972.035314 +1005.764148 +945.745262 +978.5371137 +979.0055613 +970.5733872 +1006.647389 +995.4027909 +1006.645355 +1053.490884 +994.4664242 +977.6008666 +1031.941309 +963.5464204 \ No newline at end of file diff --git a/eeco/tests/data/output/billing_scaled.csv b/eeco/tests/data/output/billing_scaled.csv index f1f0e26..ecf10bd 100644 --- a/eeco/tests/data/output/billing_scaled.csv +++ b/eeco/tests/data/output/billing_scaled.csv @@ -1,193 +1,193 @@ DateTime,gas_customer_0_20230409_20230410_0,gas_energy_0_20230409_20230410_0,electric_customer_0_20230409_20230410_0,electric_energy_0_20230409_20230410_0,electric_demand_maximum_20230409_20230410_0 -2023-04-09 00:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 00:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 00:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 00:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 01:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 01:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 01:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 01:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 02:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 02:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 02:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 02:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 03:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 03:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 03:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 03:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 04:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 04:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 04:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 04:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 05:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 05:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 05:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 05:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 06:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 06:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 06:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 06:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 07:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 07:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 07:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 07:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 08:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 08:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 08:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 08:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 09:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 09:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 09:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 09:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 10:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 10:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 10:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 10:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 11:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 11:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 11:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 11:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 12:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 12:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 12:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 12:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 13:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 13:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 13:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 13:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 14:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 14:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 14:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 14:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 15:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 15:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 15:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 15:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 16:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 16:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 16:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 16:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 17:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 17:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 17:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 17:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 18:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 18:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 18:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 18:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 19:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 19:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 19:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 19:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 20:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 20:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 20:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 20:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 21:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 21:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 21:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 21:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 22:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 22:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 22:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 22:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 23:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 23:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 23:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-09 23:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 00:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 00:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 00:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 00:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 01:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 01:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 01:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 01:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 02:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 02:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 02:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 02:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 03:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 03:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 03:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 03:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 04:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 04:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 04:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 04:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 05:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 05:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 05:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 05:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 06:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 06:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 06:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 06:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 07:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 07:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 07:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 07:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 08:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 08:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 08:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 08:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 09:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 09:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 09:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 09:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 10:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 10:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 10:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 10:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 11:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 11:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 11:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 11:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 12:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 12:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 12:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 12:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 13:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 13:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 13:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 13:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 14:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 14:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 14:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 14:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 15:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 15:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 15:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 15:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 16:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 16:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 16:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 16:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 17:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 17:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 17:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 17:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 18:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 18:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 18:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 18:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 19:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 19:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 19:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 19:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 20:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 20:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 20:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 20:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 21:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 21:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 21:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 21:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 22:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 22:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 22:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 22:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 23:00:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 23:15:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 23:30:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 -2023-04-10 23:45:00,0.03234027777,0.2837,0.104166666666667,0.019934,0.4752 +2023-04-09 00:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 00:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 00:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 00:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 01:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 01:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 01:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 01:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 02:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 02:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 02:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 02:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 03:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 03:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 03:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 03:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 04:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 04:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 04:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 04:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 05:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 05:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 05:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 05:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 06:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 06:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 06:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 06:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 07:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 07:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 07:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 07:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 08:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 08:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 08:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 08:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 09:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 09:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 09:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 09:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 10:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 10:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 10:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 10:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 11:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 11:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 11:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 11:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 12:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 12:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 12:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 12:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 13:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 13:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 13:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 13:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 14:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 14:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 14:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 14:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 15:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 15:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 15:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 15:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 16:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 16:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 16:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 16:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 17:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 17:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 17:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 17:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 18:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 18:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 18:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 18:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 19:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 19:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 19:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 19:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 20:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 20:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 20:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 20:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 21:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 21:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 21:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 21:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 22:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 22:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 22:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 22:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 23:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 23:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 23:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-09 23:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 00:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 00:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 00:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 00:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 01:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 01:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 01:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 01:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 02:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 02:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 02:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 02:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 03:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 03:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 03:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 03:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 04:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 04:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 04:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 04:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 05:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 05:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 05:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 05:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 06:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 06:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 06:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 06:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 07:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 07:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 07:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 07:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 08:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 08:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 08:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 08:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 09:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 09:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 09:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 09:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 10:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 10:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 10:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 10:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 11:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 11:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 11:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 11:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 12:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 12:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 12:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 12:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 13:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 13:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 13:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 13:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 14:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 14:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 14:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 14:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 15:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 15:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 15:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 15:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 16:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 16:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 16:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 16:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 17:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 17:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 17:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 17:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 18:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 18:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 18:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 18:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 19:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 19:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 19:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 19:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 20:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 20:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 20:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 20:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 21:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 21:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 21:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 21:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 22:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 22:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 22:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 22:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 23:00:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 23:15:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 23:30:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 +2023-04-10 23:45:00,0.03234027777,0.100187874336083,0.104166666666667,0.019934,0.4752 diff --git a/eeco/tests/data/output/billing_unscaled.csv b/eeco/tests/data/output/billing_unscaled.csv index 6fbcf69..4b8bd8b 100644 --- a/eeco/tests/data/output/billing_unscaled.csv +++ b/eeco/tests/data/output/billing_unscaled.csv @@ -1,193 +1,193 @@ DateTime,gas_customer_0_20230409_20230410_0,gas_energy_0_20230409_20230410_0,electric_customer_0_20230409_20230410_0,electric_energy_0_20230409_20230410_0,electric_demand_maximum_20230409_20230410_0 -2023-04-09 00:00:00,93.14,0.2837,300.0,0.019934,7.128 -2023-04-09 00:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 00:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 00:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 01:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 01:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 01:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 01:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 02:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 02:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 02:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 02:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 03:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 03:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 03:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 03:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 04:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 04:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 04:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 04:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 05:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 05:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 05:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 05:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 06:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 06:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 06:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 06:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 07:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 07:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 07:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 07:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 08:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 08:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 08:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 08:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 09:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 09:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 09:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 09:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 10:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 10:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 10:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 10:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 11:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 11:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 11:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 11:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 12:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 12:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 12:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 12:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 13:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 13:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 13:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 13:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 14:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 14:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 14:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 14:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 15:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 15:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 15:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 15:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 16:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 16:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 16:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 16:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 17:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 17:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 17:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 17:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 18:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 18:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 18:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 18:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 19:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 19:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 19:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 19:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 20:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 20:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 20:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 20:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 21:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 21:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 21:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 21:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 22:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 22:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 22:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 22:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 23:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 23:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 23:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-09 23:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 00:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 00:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 00:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 00:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 01:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 01:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 01:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 01:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 02:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 02:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 02:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 02:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 03:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 03:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 03:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 03:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 04:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 04:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 04:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 04:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 05:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 05:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 05:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 05:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 06:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 06:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 06:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 06:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 07:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 07:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 07:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 07:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 08:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 08:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 08:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 08:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 09:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 09:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 09:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 09:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 10:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 10:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 10:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 10:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 11:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 11:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 11:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 11:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 12:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 12:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 12:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 12:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 13:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 13:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 13:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 13:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 14:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 14:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 14:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 14:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 15:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 15:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 15:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 15:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 16:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 16:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 16:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 16:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 17:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 17:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 17:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 17:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 18:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 18:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 18:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 18:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 19:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 19:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 19:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 19:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 20:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 20:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 20:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 20:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 21:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 21:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 21:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 21:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 22:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 22:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 22:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 22:45:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 23:00:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 23:15:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 23:30:00,0.0,0.2837,0.0,0.019934,7.128 -2023-04-10 23:45:00,0.0,0.2837,0.0,0.019934,7.128 +2023-04-09 00:00:00,93.14,0.100187874336083,300.0,0.019934,7.128 +2023-04-09 00:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 00:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 00:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 01:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 01:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 01:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 01:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 02:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 02:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 02:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 02:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 03:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 03:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 03:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 03:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 04:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 04:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 04:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 04:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 05:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 05:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 05:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 05:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 06:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 06:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 06:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 06:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 07:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 07:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 07:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 07:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 08:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 08:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 08:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 08:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 09:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 09:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 09:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 09:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 10:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 10:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 10:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 10:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 11:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 11:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 11:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 11:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 12:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 12:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 12:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 12:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 13:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 13:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 13:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 13:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 14:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 14:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 14:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 14:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 15:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 15:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 15:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 15:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 16:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 16:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 16:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 16:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 17:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 17:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 17:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 17:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 18:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 18:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 18:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 18:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 19:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 19:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 19:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 19:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 20:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 20:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 20:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 20:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 21:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 21:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 21:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 21:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 22:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 22:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 22:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 22:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 23:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 23:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 23:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-09 23:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 00:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 00:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 00:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 00:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 01:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 01:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 01:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 01:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 02:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 02:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 02:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 02:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 03:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 03:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 03:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 03:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 04:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 04:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 04:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 04:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 05:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 05:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 05:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 05:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 06:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 06:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 06:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 06:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 07:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 07:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 07:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 07:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 08:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 08:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 08:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 08:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 09:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 09:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 09:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 09:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 10:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 10:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 10:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 10:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 11:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 11:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 11:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 11:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 12:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 12:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 12:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 12:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 13:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 13:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 13:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 13:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 14:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 14:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 14:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 14:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 15:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 15:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 15:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 15:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 16:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 16:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 16:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 16:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 17:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 17:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 17:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 17:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 18:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 18:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 18:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 18:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 19:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 19:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 19:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 19:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 20:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 20:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 20:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 20:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 21:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 21:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 21:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 21:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 22:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 22:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 22:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 22:45:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 23:00:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 23:15:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 23:30:00,0,0.100187874336083,0,0.019934,7.128 +2023-04-10 23:45:00,0,0.100187874336083,0,0.019934,7.128 diff --git a/eeco/tests/test_costs.py b/eeco/tests/test_costs.py index a0ec68a..3c4e03c 100644 --- a/eeco/tests/test_costs.py +++ b/eeco/tests/test_costs.py @@ -7,6 +7,7 @@ import datetime from eeco import costs +from eeco.units import u from eeco.costs import ( CHARGE, TYPE, @@ -33,6 +34,89 @@ output_dir = "tests/data/output/" +def obtain_data_array(csv_filename, colname=""): + """Helper to load data to dict from CSV file""" + csv_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "data", "input", csv_filename + ) + df = pd.read_csv(csv_path) + if colname: + return df[colname].to_numpy() + else: + return df.to_numpy() + + +def setup_cvx_vars_constraints(consumption_data_dict): + """Helper to set up CVXPY variables and constraints.""" + cvx_vars = {} + constraints = [] + for key, val in consumption_data_dict.items(): + cvx_vars[key] = cp.Variable(len(val)) + constraints.append(cvx_vars[key] == val) + return cvx_vars, constraints + + +def solve_cvx_problem(objective, constraints): + """Helper to solve CVXPY optimization problem.""" + prob = cp.Problem(cp.Minimize(objective), constraints) + prob.solve() + return prob + + +def setup_pyo_vars_constraints(consumption_data_dict): + """Helper function to set up Pyomo model, variables and constraints.""" + model = pyo.ConcreteModel() + + if isinstance(consumption_data_dict[ELECTRIC], dict): + # Extended format + electric_data = consumption_data_dict[ELECTRIC]["imports"] + gas_data = consumption_data_dict[GAS]["imports"] + else: + electric_data = consumption_data_dict[ELECTRIC] + gas_data = consumption_data_dict[GAS] + + model.T = len(electric_data) + model.t = pyo.RangeSet(0, model.T - 1) + model.electric_consumption = pyo.Var(model.t, bounds=(None, None)) + model.gas_consumption = pyo.Var(model.t, bounds=(None, None)) + + # Constrain variables to initialized values + def electric_constraint_rule(model, t): + return model.electric_consumption[t] == electric_data[t - 1] + + def gas_constraint_rule(model, t): + return model.gas_consumption[t] == gas_data[t - 1] + + model.electric_constraint = pyo.Constraint(model.t, rule=electric_constraint_rule) + model.gas_constraint = pyo.Constraint(model.t, rule=gas_constraint_rule) + + pyo_vars = { + "electric": model.electric_consumption, + "gas": model.gas_consumption, + } + + return model, pyo_vars + + +def solve_pyo_problem( + model, + objective, + decomposition_type=None, + charge_dict=None, + consumption_data_dict=None, +): + """Helper function to solve Pyomo optimization problem.""" + model.obj = pyo.Objective(expr=objective) + + if decomposition_type is not None: # Nonlinear when decomposition_type used + solver = pyo.SolverFactory("ipopt") + else: # Gurobi otherwise + solver = pyo.SolverFactory("gurobi") + + solver.solve(model) + return solver + + @pytest.mark.skipif(skip_all_tests, reason="Exclude all tests") @pytest.mark.parametrize( "charge, start_dt, end_dt, n_per_hour, effective_start_date, " @@ -385,7 +469,8 @@ def test_create_charge_array( "electric_energy_6_20240531_20240601_0": np.zeros(48), "electric_demand_maximum_20240531_20240601_0": np.ones(48) * 7.128, "gas_customer_0_20240531_20240601_0": np.array([93.14]), - "gas_energy_0_20240531_20240601_0": np.ones(48) * 0.2837, + # converted from 0.2837 therms + "gas_energy_0_20240531_20240601_0": np.ones(48) * 0.10018787433608317, "gas_energy_1_20240531_20240601_0": np.zeros(48), }, ), @@ -416,12 +501,14 @@ def test_create_charge_array( "gas_energy_0_20231231_20240101_0": np.concatenate( [ np.zeros(24), - np.ones(24) * 0.2837, + np.ones(24) + * 0.10018787433608317, # converted from 0.2837 therms ] ), "gas_energy_1_20231231_20240101_0": np.concatenate( [ - np.ones(24) * 0.454, + np.ones(24) + * 0.16032885071759523, # converted from 0.454 therms np.zeros(24), ] ), @@ -529,13 +616,17 @@ def test_get_charge_dict(start_dt, end_dt, billing_path, resolution, expected): result = costs.get_charge_dict(start_dt, end_dt, tariff_df, resolution=resolution) assert result.keys() == expected.keys() for key, val in result.items(): + print(key) + print(result[key][0]) + print(expected[key][0]) assert (result[key] == expected[key]).all() @pytest.mark.skipif(skip_all_tests, reason="Exclude all tests") @pytest.mark.parametrize( "charge_dict, consumption_data_dict, resolution, prev_demand_dict, " - "consumption_estimate, desired_utility, desired_charge_type, expected_cost", + "consumption_estimate, desired_utility, desired_charge_type, expected_cost, " + "expect_warning, expect_error", [ # single energy charge with flat consumption ( @@ -547,6 +638,8 @@ def test_get_charge_dict(start_dt, end_dt, billing_path, resolution, expected): None, None, pytest.approx(1.2), + False, + False, ), # single energy charge with increasing consumption ( @@ -558,6 +651,8 @@ def test_get_charge_dict(start_dt, end_dt, billing_path, resolution, expected): None, None, np.sum(np.arange(96)) * 0.05 / 4, + False, + False, ), # energy charge with charge limit ( @@ -584,6 +679,101 @@ def test_get_charge_dict(start_dt, end_dt, billing_path, resolution, expected): None, None, 260, + False, + False, + ), + # single energy charge with negative consumption values - should warn + ( + {"electric_energy_0_2024-07-10_2024-07-10_0": np.ones(96) * 0.05}, + { + ELECTRIC: np.concatenate([np.ones(48) * 10, -np.ones(48) * 5]), + GAS: np.ones(96), + }, + "15m", + None, + 0, + None, + None, + pytest.approx( + 3.0 + ), # (48*10 + 48*5) * 0.05 / 4 = 3.0 (negative values treated as magnitude) + True, + False, + ), + # list input instead of numpy array + ( + {"electric_energy_0_2024-07-10_2024-07-10_0": np.ones(4) * 0.05}, + { + ELECTRIC: [1, 2, 3, 4], + GAS: [1, 1, 1, 1], + }, # Lists instead of numpy arrays + "15m", + None, + 0, + None, + None, + None, # No expected cost + False, + True, + ), + # extended format with pre-decomposed variables (imports/exports) + ( + { + "electric_energy_0_2024-07-10_2024-07-10_0": np.ones(96) * 0.05, + "electric_export_0_2024-07-10_2024-07-10_0": np.ones(96) * 0.025, + }, + { + ELECTRIC: { + "imports": np.ones(96) * 10, + "exports": np.ones(96) * 5, + }, + GAS: { + "imports": np.ones(96) * 2, + "exports": np.zeros(96), + }, + }, + "15m", + None, + 0, + None, + None, + pytest.approx(9.0), + False, + False, + ), + # negative values within tolerance (i.e., should be treated as zeros) + ( + # dictionary manually converted from $/therm to $/m3 + { + "electric_energy_0_2024-08-01_2024-08-31_0": np.ones(2976) + * 0.05 + / 2.83168, + "gas_energy_0_2021-08-01_2024-08-31_0": np.ones(2976) + * 0.570905 + / 2.83168, + "gas_energy_0_2021-08-01_2024-08-31_708": np.ones(2976) + * 0.415764 + / 2.83168, + "gas_energy_0_2021-08-01_2024-08-31_11800": np.ones(2976) + * 0.311744 + / 2.83168, + }, + { + ELECTRIC: np.zeros(96), + GAS: obtain_data_array( + "negative_purchases_within_tol.csv", + colname="wrrf_natural_gas_combust", + ) + / 24, # convert from cubic meters / day to cubic meters / hour + }, + "15m", + None, + 0, + None, + None, + pytest.approx(2720.68223669), + False, + False, ), ], ) @@ -596,18 +786,45 @@ def test_calculate_cost_np( desired_utility, desired_charge_type, expected_cost, + expect_warning, + expect_error, ): - result, model = costs.calculate_cost( - charge_dict, - consumption_data_dict, - resolution=resolution, - prev_demand_dict=prev_demand_dict, - consumption_estimate=consumption_estimate, - desired_utility=desired_utility, - desired_charge_type=desired_charge_type, - ) - assert result == expected_cost - assert model is None + if expect_error: + with pytest.raises(Exception): + costs.calculate_cost( + charge_dict, + consumption_data_dict, + resolution=resolution, + prev_demand_dict=prev_demand_dict, + consumption_estimate=consumption_estimate, + desired_utility=desired_utility, + desired_charge_type=desired_charge_type, + ) + elif expect_warning: + with pytest.warns(UserWarning): + result, model = costs.calculate_cost( + charge_dict, + consumption_data_dict, + resolution=resolution, + prev_demand_dict=prev_demand_dict, + consumption_estimate=consumption_estimate, + desired_utility=desired_utility, + desired_charge_type=desired_charge_type, + ) + assert result == expected_cost + assert model is None + else: + result, model = costs.calculate_cost( + charge_dict, + consumption_data_dict, + resolution=resolution, + prev_demand_dict=prev_demand_dict, + consumption_estimate=consumption_estimate, + desired_utility=desired_utility, + desired_charge_type=desired_charge_type, + ) + assert result == expected_cost + assert model is None @pytest.mark.skipif(skip_all_tests, reason="Exclude all tests") @@ -931,6 +1148,17 @@ def test_calculate_cost_np( None, pytest.approx(260), ), + # energy charge without charge limits + ( + {"electric_energy_0_2024-07-10_2024-07-10_0": np.ones(96) * 0.05}, + {ELECTRIC: np.ones(96) * 100, GAS: np.ones(96)}, + "15m", + None, + 0, + None, + None, + pytest.approx(120.0), + ), ], ) def test_calculate_cost_cvx( @@ -943,11 +1171,7 @@ def test_calculate_cost_cvx( desired_charge_type, expected_cost, ): - cvx_vars = {} - constraints = [] - for key, val in consumption_data_dict.items(): - cvx_vars[key] = cp.Variable(len(val)) - constraints.append(cvx_vars[key] == val) + cvx_vars, constraints = setup_cvx_vars_constraints(consumption_data_dict) result, model = costs.calculate_cost( charge_dict, @@ -958,8 +1182,7 @@ def test_calculate_cost_cvx( desired_utility=desired_utility, desired_charge_type=desired_charge_type, ) - prob = cp.Problem(cp.Minimize(result), constraints) - prob.solve() + solve_cvx_problem(result, constraints) assert result.value == expected_cost assert model is None @@ -967,7 +1190,8 @@ def test_calculate_cost_cvx( @pytest.mark.skipif(skip_all_tests, reason="Exclude all tests") @pytest.mark.parametrize( "charge_dict, consumption_data_dict, resolution, prev_demand_dict, " - "consumption_estimate, desired_utility, desired_charge_type, expected_cost", + "consumption_estimate, desired_utility, desired_charge_type, " + "decomposition_type, expected_cost", [ # energy charge with charge limit ( @@ -993,6 +1217,7 @@ def test_calculate_cost_cvx( 2400, None, None, + None, pytest.approx(260), ), # demand charge with previous consumption @@ -1039,7 +1264,8 @@ def test_calculate_cost_cvx( 0, None, None, - pytest.approx(140), + None, + pytest.approx(138), ), # demand charge with no previous consumption ( @@ -1083,7 +1309,43 @@ def test_calculate_cost_cvx( 0, None, None, - pytest.approx(1191), + None, + pytest.approx(1188), + ), + # export charges + ( + { + "electric_export_0_2024-07-10_2024-07-10_0": np.ones(96) * 0.025, + }, + { + ELECTRIC: np.concatenate([np.ones(48), -np.ones(48)]), + GAS: np.ones(96), + }, + "15m", + None, + 0, + None, + None, + "absolute_value", + pytest.approx(-0.3), + ), + # energy and export charges + ( + { + "electric_energy_0_2024-07-10_2024-07-10_0": np.ones(96) * 0.05, + "electric_export_0_2024-07-10_2024-07-10_0": np.ones(96) * 0.025, + }, + { + ELECTRIC: np.concatenate([np.ones(48), -np.ones(48)]), + GAS: np.ones(96), + }, + "15m", + None, + 0, + None, + None, + "absolute_value", + pytest.approx(0.6 - 0.3), # 48*1*0.05/4 - 48*1*0.025/4 = 0.6 - 0.3 = 0.3 ), # energy charge with charge limit and time-varying consumption estimate ( @@ -1109,6 +1371,7 @@ def test_calculate_cost_cvx( np.ones(96) * 100, None, None, + None, 260, ), # energy charge with charge limit and dictionary consumption estimate @@ -1135,6 +1398,7 @@ def test_calculate_cost_cvx( {ELECTRIC: np.ones(96) * 100, GAS: np.ones(96)}, None, None, + None, 260, ), # energy charge with charge limit and dictionary consumption estimate @@ -1161,6 +1425,7 @@ def test_calculate_cost_cvx( {ELECTRIC: 2400, GAS: np.ones(96)}, None, None, + None, 260, ), # energy charge that won't hit charge limit + time-varying consumption estimate @@ -1190,8 +1455,33 @@ def test_calculate_cost_cvx( 2400, None, None, + None, 260, ), + # extended format with pre-decomposed variables (imports/exports) + ( + { + "electric_energy_0_2024-07-10_2024-07-10_0": np.ones(96) * 0.05, + "electric_export_0_2024-07-10_2024-07-10_0": np.ones(96) * 0.025, + }, + { + ELECTRIC: { + "imports": np.ones(96) * 10, + "exports": np.ones(96) * 5, + }, + GAS: { + "imports": np.ones(96) * 2, + "exports": np.zeros(96), + }, + }, + "15m", + None, + 0, + None, + None, + None, + pytest.approx(9.0), + ), ], ) def test_calculate_cost_pyo( @@ -1202,39 +1492,32 @@ def test_calculate_cost_pyo( consumption_estimate, desired_utility, desired_charge_type, + decomposition_type, expected_cost, ): - model = pyo.ConcreteModel() - model.T = len(consumption_data_dict["electric"]) - model.t = range(model.T) - pyo_vars = {} - for key, val in consumption_data_dict.items(): - var = pyo.Var(range(len(val)), initialize=np.zeros(len(val)), bounds=(0, None)) - model.add_component(key, var) - pyo_vars[key] = var + model, pyo_vars = setup_pyo_vars_constraints(consumption_data_dict) - @model.Constraint(model.t) - def electric_constraint(m, t): - return consumption_data_dict["electric"][t] == m.electric[t] - - @model.Constraint(model.t) - def gas_constraint(m, t): - return consumption_data_dict["gas"][t] == m.gas[t] + if isinstance(consumption_data_dict[ELECTRIC], dict): + # Extended format: pass full consumption data + consumption_input = consumption_data_dict + else: + consumption_input = pyo_vars result, model = costs.calculate_cost( charge_dict, - pyo_vars, + consumption_input, resolution=resolution, prev_demand_dict=prev_demand_dict, consumption_estimate=consumption_estimate, desired_utility=desired_utility, desired_charge_type=desired_charge_type, model=model, + decomposition_type=decomposition_type, ) - model.objective = pyo.Objective(expr=result) - solver = pyo.SolverFactory("gurobi") - solver.solve(model) + solve_pyo_problem( + model, result, decomposition_type, charge_dict, consumption_data_dict + ) assert pyo.value(result) == expected_cost assert model is not None @@ -1350,7 +1633,7 @@ def gas_constraint(m, t): @pytest.mark.skipif(skip_all_tests, reason="Exclude all tests") @pytest.mark.parametrize( "start_dt, end_dt, billing_data, utility, consumption_data_dict, " - "prev_demand_dict, consumption_estimate, scale_factor, expected", + "prev_demand_dict, consumption_estimate, scale_factor, expected, expect_warning", [ ( np.datetime64("2024-07-10"), # Summer weekday @@ -1362,6 +1645,7 @@ def gas_constraint(m, t): 0, 1, # default scale factor np.float64(4027.79), + False, ), ( np.datetime64("2024-07-10"), # Summer weekday @@ -1370,9 +1654,22 @@ def gas_constraint(m, t): ELECTRIC, {ELECTRIC: np.arange(96), GAS: np.arange(96)}, None, - 0, + None, # consumption_estimate=None (default to 0) + 1, # default scale factor + np.float64(4027.79), + False, + ), + ( + np.datetime64("2024-07-10"), # Summer weekday + np.datetime64("2024-07-11"), # Summer weekday + input_dir + "billing_pge.csv", + ELECTRIC, + {ELECTRIC: np.arange(96), GAS: np.arange(96)}, + None, + 0, # consumption_estimate=None (same as default) 1.1, # non-default scale factor np.float64(4027.79), # daily demand charge unscaled + False, ), ( np.datetime64("2024-07-13"), # Summer weekend @@ -1384,6 +1681,7 @@ def gas_constraint(m, t): 0, 1, # default scale factor np.float64(2023.5), + False, ), ( np.datetime64("2024-03-07"), # Winter weekday @@ -1395,6 +1693,7 @@ def gas_constraint(m, t): 0, 1, # default scale factor np.float64(2028.6), + False, ), ( np.datetime64("2024-03-09"), # Winter weekend @@ -1427,6 +1726,7 @@ def gas_constraint(m, t): 0, 1, # default scale factor np.float64(2023.5), + False, ), ( np.datetime64("2024-07-10"), # Summer weekday @@ -1459,6 +1759,7 @@ def gas_constraint(m, t): 0, 1, # default scale factor np.float64(2897.79), + False, ), ( np.datetime64("2024-07-10"), # Summer weekday @@ -1470,6 +1771,7 @@ def gas_constraint(m, t): 0, 1, # default scale factor np.float64(0), + False, ), ( np.datetime64("2024-07-10"), # Summer weekday @@ -1484,6 +1786,34 @@ def gas_constraint(m, t): 0, 1.1, # non-default scale factor pytest.approx(14646.313), # 13314.83 * 1.1 + False, + ), + ( + np.datetime64("2024-07-10"), # Summer weekday + np.datetime64("2024-07-11"), # Summer weekday + input_dir + "billing_pge.csv", + ELECTRIC, + { + ELECTRIC: np.concatenate([np.arange(48), -np.arange(48)]), + GAS: np.arange(96), + }, + None, + 0, + 1, # default scale factor + np.float64(1277.46), # based on 47 kW + True, # negative values warning + ), + ( + np.datetime64("2024-07-10"), # Summer weekday + np.datetime64("2024-07-11"), # Summer weekday + input_dir + "billing_pge.csv", + ELECTRIC, + {ELECTRIC: np.arange(96), GAS: np.arange(96)}, + None, + {"electric": 0, "gas": 0}, # dict consumption_estimate + 1, # default scale factor + np.float64(4027.79), + False, ), ], ) @@ -1497,6 +1827,7 @@ def test_calculate_demand_costs( consumption_estimate, scale_factor, expected, + expect_warning, ): billing_data = pd.read_csv(billing_data) charge_dict = costs.get_charge_dict( @@ -1504,15 +1835,27 @@ def test_calculate_demand_costs( end_dt, billing_data, ) - result, model = costs.calculate_cost( - charge_dict, - consumption_data_dict, - prev_demand_dict=prev_demand_dict, - consumption_estimate=consumption_estimate, - desired_utility=utility, - desired_charge_type="demand", - demand_scale_factor=scale_factor, - ) + if expect_warning: + with pytest.warns(UserWarning): + result, model = costs.calculate_cost( + charge_dict, + consumption_data_dict, + prev_demand_dict=prev_demand_dict, + consumption_estimate=consumption_estimate, + desired_utility=utility, + desired_charge_type="demand", + demand_scale_factor=scale_factor, + ) + else: + result, model = costs.calculate_cost( + charge_dict, + consumption_data_dict, + prev_demand_dict=prev_demand_dict, + consumption_estimate=consumption_estimate, + desired_utility=utility, + desired_charge_type="demand", + demand_scale_factor=scale_factor, + ) assert result == expected assert model is None @@ -1530,7 +1873,7 @@ def test_calculate_demand_costs( {ELECTRIC: np.arange(96), GAS: np.arange(96)}, { "gas_energy_0_20240710_20240710_0": 0, - "gas_energy_0_20240710_20240710_5000": 0, + "gas_energy_0_20240710_20240710_14158": 0, "electric_customer_0_20240710_20240710_0": 0, "electric_energy_0_20240710_20240710_0": 0, "electric_energy_1_20240710_20240710_0": 0, @@ -1558,7 +1901,7 @@ def test_calculate_demand_costs( {ELECTRIC: np.arange(96), GAS: np.arange(96)}, { "gas_energy_0_20240710_20240710_0": 0, - "gas_energy_0_20240710_20240710_5000": 0, + "gas_energy_0_20240710_20240710_14158": 0, "electric_customer_0_20240710_20240710_0": 0, "electric_energy_0_20240710_20240710_0": 0, "electric_energy_1_20240710_20240710_0": 0, @@ -1583,10 +1926,13 @@ def test_calculate_demand_costs( np.datetime64("2024-07-11"), # Summer weekday input_dir + "billing_pge.csv", [ELECTRIC, GAS], - {ELECTRIC: np.arange(96), GAS: np.repeat(np.array([5100 / 24]), 96)}, + { + ELECTRIC: np.arange(96), + GAS: np.repeat(np.array([5100 * 2.83168 / 24]), 96), + }, { "gas_energy_0_20240710_20240710_0": 0, - "gas_energy_0_20240710_20240710_5000": 0, + "gas_energy_0_20240710_20240710_14158": 0, "electric_customer_0_20240710_20240710_0": 0, "electric_energy_0_20240710_20240710_0": 0, "electric_energy_1_20240710_20240710_0": 0, @@ -1604,7 +1950,7 @@ def test_calculate_demand_costs( "electric_energy_13_20240710_20240710_0": 0, }, 0, - pytest.approx(200.016195), + pytest.approx(200.0996), ), ( np.datetime64("2024-07-13"), # Summer weekend @@ -1651,10 +1997,14 @@ def test_calculate_demand_costs( np.datetime64("2024-03-10"), # Winter weekend input_dir + "billing_pge.csv", GAS, - {ELECTRIC: np.arange(96), GAS: np.repeat(np.array([5100 / 24]), 96)}, + # converted from therms to cubic meters + { + ELECTRIC: np.arange(96), + GAS: np.repeat(np.array([5100 * 2.83168 / 24]), 96), + }, None, 0, - pytest.approx(59.1), + pytest.approx(59.18348), # converted from therms to cubic meters ), ( np.datetime64("2024-03-09"), # Winter weekend @@ -1663,7 +2013,7 @@ def test_calculate_demand_costs( GAS, {ELECTRIC: np.arange(96), GAS: np.ones(96)}, None, - 5100, + 5100 * 2.83168, # converted from therms to cubic meters pytest.approx(0), ), ], @@ -1698,15 +2048,53 @@ def test_calculate_energy_costs( @pytest.mark.skipif(skip_all_tests, reason="Exclude all tests") @pytest.mark.parametrize( - "charge_array, export_data, divisor, expected", + "charge_array, export_data, divisor, expected, expect_warning, expect_error", [ - (np.ones(96), np.arange(96), 4, 1140), + ( + np.ones(96), + np.arange(96), + 4, + 1140, + False, + False, + ), # positive values (export magnitude) + ( + np.ones(96), + np.concatenate([np.ones(48), -np.ones(48)]), + 4, + 0, # values treated as magnitude so expectation is 0 + True, + False, + ), # negative values (export magnitude) - should warn + ( + np.ones(96), + [1, 2, 3], # invalid type + 4, + None, + False, + True, # invalid type + ), ], ) -def test_calculate_export_revenues(charge_array, export_data, divisor, expected): - result, model = costs.calculate_export_revenues(charge_array, export_data, divisor) - assert result == expected - assert model is None +def test_calculate_export_revenue( + charge_array, export_data, divisor, expected, expect_warning, expect_error +): + if expect_error: + with pytest.raises(ValueError): + costs.calculate_export_revenue(charge_array, export_data, divisor) + elif expect_warning: + with pytest.warns(UserWarning): + result, model = costs.calculate_export_revenue( + charge_array, export_data, divisor + ) + assert result == expected + assert model is None + else: + result, model = costs.calculate_export_revenue( + charge_array, export_data, divisor + ) + assert result == expected + assert model is None @pytest.mark.skipif(skip_all_tests, reason="Exclude all tests") @@ -1835,7 +2223,7 @@ def test_detect_charge_periods( ( "billing_pge.csv", { - "scale_ratios": { + "percent_change_dict": { DEMAND: { PEAK: 2.0, HALF_PEAK: 2.0, @@ -1865,7 +2253,7 @@ def test_detect_charge_periods( ( "billing_pge.csv", { - "scale_ratios": { + "percent_change_dict": { "demand": 1.5, "energy": 1.0, }, @@ -1884,7 +2272,7 @@ def test_detect_charge_periods( ( "billing_demand_2.csv", { - "scale_ratios": { + "percent_change_dict": { DEMAND: { PEAK: 3.0, HALF_PEAK: 1.0, @@ -1910,7 +2298,7 @@ def test_detect_charge_periods( ( "billing_demand_2.csv", { - "scale_ratios": { + "percent_change_dict": { "demand": 1.0, "energy": 2.0, }, @@ -1926,7 +2314,7 @@ def test_detect_charge_periods( ( "billing_pge.csv", { - "scale_ratios": { + "percent_change_dict": { "electric_demand_peak-summer": 2.0, "electric_energy_0": 3.0, "electric_demand_all-day": 1.5, @@ -1943,7 +2331,7 @@ def test_detect_charge_periods( ( "billing_pge.csv", { - "scale_ratios": { + "percent_change_dict": { "demand": 0.0, "energy": -2.0, }, @@ -1958,11 +2346,11 @@ def test_detect_charge_periods( None, False, ), - # Individual zero scale_ratios + # Individual zero percent_change_dict ( "billing_pge.csv", { - "scale_ratios": { + "percent_change_dict": { DEMAND: { PEAK: 0.0, HALF_PEAK: 0.0, @@ -1991,7 +2379,7 @@ def test_detect_charge_periods( ( "billing_pge.csv", { - "scale_ratios": { + "percent_change_dict": { DEMAND: { PEAK: 1.0, HALF_PEAK: 1.0, @@ -2073,7 +2461,7 @@ def test_detect_charge_periods( ( "billing_pge.csv", { - "scale_ratios": { + "percent_change_dict": { "electric_demand_nonexistent1": 2.0, "electric_demand_nonexistent2": 3.0, "electric_energy_nonexistent1": 1.5, @@ -2094,7 +2482,7 @@ def test_detect_charge_periods( ( "billing_pge.csv", { - "scale_ratios": { + "percent_change_dict": { "electric_demand_peak-summer": 2.0, DEMAND: 3.0, # Conflicts with the exact key above }, @@ -2128,7 +2516,7 @@ def test_detect_charge_periods( ( "billing_energy_super_off_peak.csv", { - "scale_ratios": { + "percent_change_dict": { "energy": { "peak": 1.0, "half_peak": 1.0, @@ -2319,7 +2707,7 @@ def test_parametrize_rate_data( # Verify at least one charge is scaled (not all zeros) assert np.any( peak_summer_demand[CHARGE] > 0 - ), "Peak-summer demand charges should be non-zero" + ), "peak-summer demand charges should be non-zero" # For energy 0: find any energy charge that might be scaled if "energy_0_charge" in expected: @@ -2354,7 +2742,7 @@ def test_parametrize_rate_data( ( [ { - "scale_ratios": { + "percent_change_dict": { DEMAND: { PEAK: 2.0, HALF_PEAK: 2.0, @@ -2383,7 +2771,7 @@ def test_parametrize_rate_data( ( [ { - "scale_ratios": { + "percent_change_dict": { "demand": 1.5, "energy": 1.0, }, @@ -2402,7 +2790,7 @@ def test_parametrize_rate_data( ( [ { - "scale_ratios": { + "percent_change_dict": { "electric_demand_peak-summer": 2.0, "electric_energy_0": 3.0, "electric_demand_all-day": 1.5, @@ -2430,9 +2818,9 @@ def test_parametrize_rate_data( # Duplicate variant names ( [ - {"scale_ratios": {"demand": 2.0}, "variant_name": "test"}, + {"percent_change_dict": {"demand": 2.0}, "variant_name": "test"}, { - "scale_ratios": {"energy": 3.0}, + "percent_change_dict": {"energy": 3.0}, "variant_name": "test", }, # Duplicate name ], @@ -2447,8 +2835,8 @@ def test_parametrize_rate_data( # Variants without names ( [ - {"scale_ratios": {"demand": 2.0}}, # No variant_name - {"scale_ratios": {"energy": 3.0}}, # No variant_name + {"percent_change_dict": {"demand": 2.0}}, # No variant_name + {"percent_change_dict": {"energy": 3.0}}, # No variant_name ], "variant_0", { @@ -2462,7 +2850,7 @@ def test_parametrize_rate_data( ( [ { - "scale_ratios": {"demand": 2.0}, + "percent_change_dict": {"demand": 2.0}, "variant_name": "double_demand", } ], @@ -2478,7 +2866,7 @@ def test_parametrize_rate_data( ( [ { - "scale_ratios": {"energy": 3.0}, + "percent_change_dict": {"energy": 3.0}, "variant_name": "triple_energy", } ], @@ -2494,15 +2882,15 @@ def test_parametrize_rate_data( ( [ { - "scale_ratios": {"demand": 2.0}, + "percent_change_dict": {"demand": 2.0}, "variant_name": "double_demand", }, { - "scale_ratios": {"energy": 3.0}, + "percent_change_dict": {"energy": 3.0}, "variant_name": "triple_energy", }, { - "scale_ratios": { + "percent_change_dict": { DEMAND: { PEAK: 1.5, HALF_PEAK: 1.0, @@ -2649,12 +3037,12 @@ def test_parametrize_charge_dict(variant_params, key_subset, expected): "billing_file, variant_params", [ # Test with different billing files - ("billing_energy_1.csv", {"scale_ratios": {"energy": 2.0}}), - ("billing_demand_2.csv", {"scale_ratios": {"demand": 2.0}}), - ("billing_export.csv", {"scale_ratios": {"energy": 1.5}}), - ("billing_customer.csv", {"scale_ratios": {"energy": 1.0}}), + ("billing_energy_1.csv", {"percent_change_dict": {"energy": 2.0}}), + ("billing_demand_2.csv", {"percent_change_dict": {"demand": 2.0}}), + ("billing_export.csv", {"percent_change_dict": {"energy": 1.5}}), + ("billing_customer.csv", {"percent_change_dict": {"energy": 1.0}}), # Test with complex rate structures - ("billing.csv", {"scale_ratios": {"demand": 2.0, "energy": 1.5}}), + ("billing.csv", {"percent_change_dict": {"demand": 2.0, "energy": 1.5}}), ], ) def test_parametrize_rate_data_different_files(billing_file, variant_params): @@ -2673,10 +3061,10 @@ def test_parametrize_rate_data_different_files(billing_file, variant_params): # Check that at least some charges were modified if scaling was applied if ( - "scale_ratios" in variant_params - and "demand" in variant_params["scale_ratios"] - and isinstance(variant_params["scale_ratios"]["demand"], (int, float)) - and variant_params["scale_ratios"]["demand"] != 1.0 + "percent_change_dict" in variant_params + and "demand" in variant_params["percent_change_dict"] + and isinstance(variant_params["percent_change_dict"]["demand"], (int, float)) + and variant_params["percent_change_dict"]["demand"] != 1.0 ): demand_charges = variant_data[variant_data[TYPE] == costs.DEMAND] if not demand_charges.empty: @@ -2686,10 +3074,10 @@ def test_parametrize_rate_data_different_files(billing_file, variant_params): ), "Demand charges should be modified" if ( - "scale_ratios" in variant_params - and "energy" in variant_params["scale_ratios"] - and isinstance(variant_params["scale_ratios"]["energy"], (int, float)) - and variant_params["scale_ratios"]["energy"] != 1.0 + "percent_change_dict" in variant_params + and "energy" in variant_params["percent_change_dict"] + and isinstance(variant_params["percent_change_dict"]["energy"], (int, float)) + and variant_params["percent_change_dict"]["energy"] != 1.0 ): energy_charges = variant_data[variant_data[TYPE] == costs.ENERGY] if not energy_charges.empty: @@ -2699,7 +3087,441 @@ def test_parametrize_rate_data_different_files(billing_file, variant_params): ), "Energy charges should be modified" -# TODO: write test_calculate_itemized_cost +@pytest.mark.skipif(skip_all_tests, reason="Exclude all tests") +@pytest.mark.parametrize( + "charge_dict, " + "consumption_data_dict, " + "resolution, " + "decomposition_type, " + "expected_cost, " + "expected_itemized", + [ + # single energy charge + ( + {"electric_energy_0_2024-07-10_2024-07-10_0": np.ones(96) * 0.05}, + {ELECTRIC: np.ones(96), GAS: np.ones(96)}, + "15m", + None, + pytest.approx(1.2), + { + "electric": { + "energy": pytest.approx(1.2), + "export": 0.0, + "customer": 0.0, + "demand": 0.0, + }, + "gas": { + "energy": 0.0, + "export": 0.0, + "customer": 0.0, + "demand": 0.0, + }, + }, + ), + # energy and export charges with decomposition_type "absolute_value" + ( + { + "electric_export_0_2024-07-10_2024-07-10_0": np.ones(96) * 0.025, + }, + { + ELECTRIC: np.concatenate([np.ones(48) * 10, -np.ones(48) * 5]), + GAS: np.ones(96), + }, + "15m", + "absolute_value", + pytest.approx(-1.5), + { + "electric": { + "energy": 0.0, + "export": pytest.approx(-1.5), + "customer": 0.0, + "demand": 0.0, + }, + "gas": { + "energy": 0.0, + "export": 0.0, + "customer": 0.0, + "demand": 0.0, + }, + }, + ), + # negative values within tolerance (i.e., should be treated as zeros) + ( + # manually converted from therms to cubic meters to + # mimic automated conversion in `create_charge_array` + { + "electric_energy_0_2024-08-01_2024-08-31_0": np.ones(2976) + * 0.05 + / 2.83168, + "gas_energy_0_2021-08-01_2024-08-31_0": np.ones(2976) + * 0.570905 + / 2.83168, + "gas_energy_0_2021-08-01_2024-08-31_708": np.ones(2976) + * 0.415764 + / 2.83168, + "gas_energy_0_2021-08-01_2024-08-31_11800": np.ones(2976) + * 0.311744 + / 2.83168, + }, + { + ELECTRIC: np.zeros(96), + GAS: obtain_data_array( + "negative_purchases_within_tol.csv", + colname="wrrf_natural_gas_combust", + ), + }, + "15m", + None, + pytest.approx(2720.68223669), + { + "electric": { + "energy": 0.0, + "export": 0, + "customer": 0.0, + "demand": 0.0, + }, + "gas": { + "energy": 2720.6840707162232, + "export": 0.0, + "customer": 0.0, + "demand": 0.0, + }, + }, + ), + ], +) +def test_calculate_itemized_cost_np( + charge_dict, + consumption_data_dict, + resolution, + decomposition_type, + expected_cost, + expected_itemized, +): + """Test calculate_itemized_cost with and without decomposition_type.""" + result, model = costs.calculate_itemized_cost( + charge_dict, + consumption_data_dict, + resolution=resolution, + decomposition_type=decomposition_type, + electric_consumption_units=u.kW, + gas_consumption_units=u.meter**3 / u.day, + ) + + assert result["total"] == expected_cost + for utility in expected_itemized: + for charge_type in expected_itemized[utility]: + print(f"utility: {utility} & charge_type: {charge_type}") + expected_value = expected_itemized[utility][charge_type] + actual_value = result[utility][charge_type] + assert actual_value == expected_value + + +@pytest.mark.skipif(skip_all_tests, reason="Exclude all tests") +@pytest.mark.parametrize( + "charge_dict, " + "consumption_data_dict, " + "resolution, " + "decomposition_type, " + "consumption_estimate, " + "expected_cost, " + "expected_itemized", + [ + # simple energy charge without charge limits + ( + {"electric_energy_0_2024-07-10_2024-07-10_0": np.ones(96) * 0.05}, + {ELECTRIC: np.ones(96) * 100, GAS: np.ones(96)}, + "15m", + None, + 0, + pytest.approx(120.0), + { + "electric": { + "energy": pytest.approx(120.0), + "export": 0.0, + "customer": 0.0, + "demand": 0.0, + }, + "gas": { + "energy": 0.0, + "export": 0.0, + "customer": 0.0, + "demand": 0.0, + }, + }, + ), + # energy and export charges with decomposition_type="absolute_value" (non-DCP) + ( + { + "electric_energy_0_2024-07-10_2024-07-10_0": np.ones(96) * 0.05, + "electric_export_0_2024-07-10_2024-07-10_0": np.ones(96) * 0.025, + }, + { + ELECTRIC: np.concatenate([np.ones(48) * 10, -np.ones(48) * 5]), + GAS: np.ones(96), + }, + "15m", + "absolute_value", + 240, + None, # No expected cost - should raise NotImplementedError + None, # No expected itemized - should raise NotImplementedError + ), + ], +) +def test_calculate_itemized_cost_cvx( + charge_dict, + consumption_data_dict, + resolution, + decomposition_type, + consumption_estimate, + expected_cost, + expected_itemized, +): + """Test calculate_itemized_cost with CVXPY variables.""" + cvx_vars, constraints = setup_cvx_vars_constraints(consumption_data_dict) + + if decomposition_type: + with pytest.raises(NotImplementedError): + costs.calculate_itemized_cost( + charge_dict, + cvx_vars, + resolution=resolution, + decomposition_type=decomposition_type, + consumption_estimate=consumption_estimate, + ) + else: + result, model = costs.calculate_itemized_cost( + charge_dict, + cvx_vars, + resolution=resolution, + decomposition_type=decomposition_type, + consumption_estimate=consumption_estimate, + ) + solve_cvx_problem(result["total"], constraints) + + assert result["total"].value == expected_cost + for utility in expected_itemized: + for charge_type in expected_itemized[utility]: + expected_value = expected_itemized[utility][charge_type] + actual_value = result[utility][charge_type] + if hasattr(actual_value, "value"): + actual_value = actual_value.value + assert actual_value == expected_value + + +@pytest.mark.skipif(skip_all_tests, reason="Exclude all tests") +@pytest.mark.parametrize( + "charge_dict, " + "consumption_data_dict, " + "resolution, " + "decomposition_type, " + "consumption_estimate, " + "electric_consumption_units, " + "gas_consumption_units, " + "expected_cost, " + "expected_itemized", + [ + # energy charge with charge limit + ( + { + "electric_energy_all-day_2024-07-10_2024-07-10_0": np.concatenate( + [ + np.ones(64) * 0.05, + np.ones(20) * 0.1, + np.ones(12) * 0.05, + ] + ), + "electric_energy_all-day_2024-07-10_2024-07-10_100": np.concatenate( + [ + np.ones(64) * 0.1, + np.ones(20) * 0.15, + np.ones(12) * 0.1, + ] + ), + }, + {ELECTRIC: np.ones(96) * 100, GAS: np.ones(96)}, + "15m", + None, + 2400, + None, + None, + pytest.approx(260), + { + "electric": { + "energy": pytest.approx(260), + "export": 0.0, + "customer": 0.0, + "demand": 0.0, + }, + "gas": { + "energy": 0.0, + "export": 0.0, + "customer": 0.0, + "demand": 0.0, + }, + }, + ), + # energy and export charges + ( + { + "electric_energy_0_2024-07-10_2024-07-10_0": np.ones(96) * 0.05, + "electric_export_0_2024-07-10_2024-07-10_0": np.ones(96) * 0.025, + }, + { + ELECTRIC: np.concatenate([np.ones(48) * 10, -np.ones(48) * 5]), + GAS: np.ones(96), + }, + "15m", + "absolute_value", + 240, + None, + None, + pytest.approx(6.0 - 1.5), # 48*10*0.05/4 - 48*5*0.025/4 = 6.0 - 1.5 = 4.5 + { + "electric": { + "energy": pytest.approx(6.0), # 48*10*0.05/4 = 6.0 + "export": pytest.approx(-1.5), # -48*5*0.025/4 = 1.5 + "customer": 0.0, + "demand": 0.0, + }, + "gas": { + "energy": 0.0, + "export": 0.0, + "customer": 0.0, + "demand": 0.0, + }, + }, + ), + # energy and export charges with MW units and MW timeseries + ( + { + "electric_energy_0_2024-07-10_2024-07-10_0": np.ones(96) * 0.05, + "electric_export_0_2024-07-10_2024-07-10_0": np.ones(96) * 0.025, + }, + { + ELECTRIC: np.concatenate( + [np.ones(48) * 0.01, -np.ones(48) * 0.005] + ), # 0.01 MW = 10 kW + GAS: np.ones(96), + }, + "15m", + "absolute_value", + 240, + u.MW, + u.meters**3 / u.hour, + pytest.approx(4.5), + { + "electric": { + "energy": pytest.approx(6.0), # 48*0.01 MW*1000*0.05/4 + "export": pytest.approx(-1.5), # -48*0.005 MW*1000*0.025/4 + "customer": 0.0, + "demand": 0.0, + }, + "gas": { + "energy": 0.0, + "export": 0.0, + "customer": 0.0, + "demand": 0.0, + }, + }, + ), + # energy and export charges with MW instead but kW timeseries + ( + { + "electric_energy_0_2024-07-10_2024-07-10_0": np.ones(96) * 0.05, + "electric_export_0_2024-07-10_2024-07-10_0": np.ones(96) * 0.025, + }, + { + ELECTRIC: np.concatenate([np.ones(48) * 10, -np.ones(48) * 5]), + GAS: np.ones(96), + }, + "15m", + "absolute_value", + 240, + u.MW, + u.meters**3 / u.hour, + pytest.approx(4500), + { + "electric": { + "energy": pytest.approx(6000), # 48*0.01 MW*1000*0.05/4 + "export": pytest.approx(-1500), # -48*0.005 MW*1000*0.025/4 + "customer": 0.0, + "demand": 0.0, + }, + "gas": { + "energy": 0.0, + "export": 0.0, + "customer": 0.0, + "demand": 0.0, + }, + }, + ), + # `binary_variable` for `decomposition_type` should raise `NotImplementedError` + ( + { + "electric_energy_0_2024-07-10_2024-07-10_0": np.ones(96) * 0.05, + "electric_export_0_2024-07-10_2024-07-10_0": np.ones(96) * 0.025, + }, + { + ELECTRIC: np.concatenate([np.ones(48) * 10, -np.ones(48) * 5]), + GAS: np.ones(96), + }, + "15m", + "binary_variable", + 240, + None, + None, + None, # No expected cost - should raise NotImplementedError + None, # No expected cost - should raise NotImplementedError + ), + ], +) +def test_calculate_itemized_cost_pyo( + charge_dict, + consumption_data_dict, + resolution, + decomposition_type, + consumption_estimate, + electric_consumption_units, + gas_consumption_units, + expected_cost, + expected_itemized, +): + """Test calculate_itemized_cost with Pyomo variables.""" + model, pyo_vars = setup_pyo_vars_constraints(consumption_data_dict) + + kwargs = { + "resolution": resolution, + "decomposition_type": decomposition_type, + "model": model, + "consumption_estimate": consumption_estimate, + } + if electric_consumption_units is not None: + kwargs["electric_consumption_units"] = electric_consumption_units + if gas_consumption_units is not None: + kwargs["gas_consumption_units"] = gas_consumption_units + + if decomposition_type == "binary_variable": + with pytest.raises(NotImplementedError): + result, model = costs.calculate_itemized_cost( + charge_dict, pyo_vars, **kwargs + ) + else: + result, model = costs.calculate_itemized_cost(charge_dict, pyo_vars, **kwargs) + solve_pyo_problem( + model, + result["total"], + decomposition_type, + charge_dict, + consumption_data_dict, + ) + + assert pyo.value(result["total"]) == expected_cost + for utility in expected_itemized: + for charge_type in expected_itemized[utility]: + expected_value = expected_itemized[utility][charge_type] + actual_value = pyo.value(result[utility][charge_type]) + assert actual_value == expected_value @pytest.mark.parametrize( diff --git a/eeco/tests/test_emissions.py b/eeco/tests/test_emissions.py index 267378a..3b7206c 100644 --- a/eeco/tests/test_emissions.py +++ b/eeco/tests/test_emissions.py @@ -130,7 +130,7 @@ def test_calculate_grid_emissions_cvx( "VirtualDemand_Electricity_InFlow", u.kg / u.kWh, "15m", - 276375.8735600004, + pytest.approx(276375.8735600004), ), ], ) diff --git a/eeco/tests/test_utils.py b/eeco/tests/test_utils.py index d5ccaf2..c629fbb 100644 --- a/eeco/tests/test_utils.py +++ b/eeco/tests/test_utils.py @@ -2,8 +2,10 @@ import pytest import numpy as np import pyomo.environ as pyo +import cvxpy as cp from eeco import utils as ut +from eeco.tests.test_costs import setup_pyo_vars_constraints os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) skip_all_tests = False @@ -118,6 +120,7 @@ def gas_constraint(m, t): var = getattr(model, varstr) result, model = ut.max(var, model=model, varstr="test") + model.objective = pyo.Objective(expr=0) solver = pyo.SolverFactory("gurobi") solver.solve(model) @@ -127,34 +130,193 @@ def gas_constraint(m, t): @pytest.mark.skipif(skip_all_tests, reason="Exclude all tests") @pytest.mark.parametrize( - "consumption_data, varstr, expected", + "consumption_data, varstr, expected, expect_error", [ - ({"electric": np.ones(96) * 45, "gas": np.ones(96) * -1}, "electric", 45), - ({"electric": np.ones(96) * 100, "gas": np.ones(96) * -1}, "gas", 0), + ( + {"electric": np.ones(96) * 45, "gas": np.ones(96) * -1}, + "electric", + np.ones(96) * 45, + False, + ), + ( + {"electric": np.ones(96) * 100, "gas": np.ones(96) * -1}, + "gas", + np.zeros(96), + False, + ), + ({"electric": 45.0, "gas": -10.0}, "electric", 45.0, False), + ({"electric": 100.0, "gas": -5.0}, "gas", 0.0, False), + ([1, 2, 3], None, None, True), # invalid type ], ) -def test_max_pos_pyo(consumption_data, varstr, expected): +def test_max_pos_pyo(consumption_data, varstr, expected, expect_error): + if expect_error: + with pytest.raises(TypeError): + ut.max_pos(consumption_data) + return + model = pyo.ConcreteModel() - model.T = len(consumption_data["electric"]) - model.t = range(model.T) - pyo_vars = {} - for key, val in consumption_data.items(): - var = pyo.Var(model.t, initialize=np.zeros(len(val))) - model.add_component(key, var) - pyo_vars[key] = var - @model.Constraint(model.t) - def electric_constraint(m, t): - return consumption_data["electric"][t] == m.electric[t] + if isinstance(consumption_data["electric"], (int, float)): + # LinearExpression case + pyo_vars = {} + for key, val in consumption_data.items(): + var = pyo.Var(initialize=0) + model.add_component(key, var) + pyo_vars[key] = var - @model.Constraint(model.t) - def gas_constraint(m, t): - return consumption_data["gas"][t] == m.gas[t] + @model.Constraint() + def electric_constraint(m): + return consumption_data["electric"] == m.electric + + @model.Constraint() + def gas_constraint(m): + return consumption_data["gas"] == m.gas + + var = getattr(model, varstr) + expr = var - 0 # like max_var - prev_demand_cost + result, model = ut.max_pos(expr, model=model, varstr="test") + model.objective = pyo.Objective(expr=0) + solver = pyo.SolverFactory("gurobi") + solver.solve(model) + + assert pyo.value(result) == expected + else: + # Vector case + model.T = len(consumption_data["electric"]) + model.t = range(model.T) + pyo_vars = {} + for key, val in consumption_data.items(): + var = pyo.Var(model.t, initialize=np.zeros(len(val))) + model.add_component(key, var) + pyo_vars[key] = var + + @model.Constraint(model.t) + def electric_constraint(m, t): + return consumption_data["electric"][t] == m.electric[t] + + @model.Constraint(model.t) + def gas_constraint(m, t): + return consumption_data["gas"][t] == m.gas[t] + + var = getattr(model, varstr) + result, model = ut.max_pos(var, model=model, varstr="test") + model.objective = pyo.Objective(expr=0) + solver = pyo.SolverFactory("gurobi") + solver.solve(model) + + # Check each element in returned vector + for t in result.index_set(): + expected_element = expected[t] + assert pyo.value(result[t]) == expected_element - var = getattr(model, varstr) - result, model = ut.max_pos(var, model=model, varstr="test") - model.objective = pyo.Objective(expr=0) - solver = pyo.SolverFactory("gurobi") - solver.solve(model) - assert pyo.value(result) == expected assert model is not None + + +@pytest.mark.skipif(skip_all_tests, reason="Exclude all tests") +@pytest.mark.parametrize( + "consumption_data, expected_positive, expected_negative, expect_error", + [ + ( + np.array([1, -2, 3, -4, 0]), + np.array([1, 0, 3, 0, 0]), + np.array([0, 2, 0, 4, 0]), + False, + ), + ( + np.array([5, 0, -3, 7, -1]), + np.array([5, 0, 0, 7, 0]), + np.array([0, 0, 3, 0, 1]), + False, + ), + (np.array([0, 0, 0]), np.array([0, 0, 0]), np.array([0, 0, 0]), False), + (np.array([-10, -5, -1]), np.array([0, 0, 0]), np.array([10, 5, 1]), False), + (np.array([10, 5, 1]), np.array([10, 5, 1]), np.array([0, 0, 0]), False), + ([1, 2, 3], None, None, True), # invalid type + ], +) +def test_decompose_consumption_np( + consumption_data, expected_positive, expected_negative, expect_error +): + """Test decompose_consumption with numpy arrays.""" + if expect_error: + with pytest.raises(TypeError): + ut.decompose_consumption(consumption_data) + else: + positive_values, negative_values, model = ut.decompose_consumption( + consumption_data + ) + + assert np.array_equal(positive_values, expected_positive) + assert np.array_equal(negative_values, expected_negative) + assert model is None + assert np.array_equal(consumption_data, positive_values - negative_values) + + +@pytest.mark.skipif(skip_all_tests, reason="Exclude all tests") +def test_decompose_consumption_cvx(): + """Test decompose_consumption with cvxpy expressions.""" + x = cp.Variable(5) + positive_values, negative_values, model = ut.decompose_consumption(x) + assert isinstance(positive_values, cp.Expression) + assert isinstance(negative_values, cp.Expression) + + # Test warning for unimplemented decomposition_type + with pytest.warns(UserWarning): + positive_values, negative_values, model = ut.decompose_consumption( + x, decomposition_type="unimplemented" + ) + assert positive_values is None + assert negative_values is None + + +@pytest.mark.skipif(skip_all_tests, reason="Exclude all tests") +@pytest.mark.parametrize( + "consumption_data, expected_positive_sum, expected_negative_sum, " + "decomposition_type, expect_warning", + [ + (np.array([1, -2, 3, -4, 0]), 4, 6, "absolute_value", False), + (np.array([0, 0, 0]), 0, 0, "absolute_value", False), + (np.array([-10, -5, -1]), 0, 16, "absolute_value", False), + (np.array([10, 5, 1]), 16, 0, "absolute_value", False), + (np.array([1, -2, 3]), 4, 2, "binary_variable", True), + ], +) +def test_decompose_consumption_pyo( + consumption_data, + expected_positive_sum, + expected_negative_sum, + decomposition_type, + expect_warning, +): + consumption_data_dict = { + "electric": consumption_data, + "gas": np.zeros_like(consumption_data), + } + model, pyo_vars = setup_pyo_vars_constraints(consumption_data_dict) + + if expect_warning: + with pytest.warns(UserWarning): + positive_var, negative_var, model = ut.decompose_consumption( + pyo_vars["electric"], + model=model, + varstr="electric", + decomposition_type=decomposition_type, + ) + assert positive_var is None + assert negative_var is None + else: + positive_var, negative_var, model = ut.decompose_consumption( + pyo_vars["electric"], + model=model, + varstr="electric", + decomposition_type=decomposition_type, + ) + # Check that variables exist and have the correct length + assert hasattr(model, "electric_positive") + assert hasattr(model, "electric_negative") + assert hasattr(model, "electric_decomposition_constraint") + assert hasattr(model, "electric_magnitude_constraint") + assert len(positive_var) == len(consumption_data) + assert len(negative_var) == len(consumption_data) + # Testing of values handled after solving problem in test_costs.py diff --git a/eeco/utils.py b/eeco/utils.py index 79daaed..b9c0182 100644 --- a/eeco/utils.py +++ b/eeco/utils.py @@ -1,6 +1,7 @@ import re import pytz import datetime +import warnings import numpy as np import cvxpy as cp import pyomo.environ as pyo @@ -240,10 +241,7 @@ def sum(expression, axis=0, model=None, varstr=None): var = model.find_component(varstr) def const_rule(model): - total = 0 - for i in range(len(expression)): - total += expression[i] - return var == total + return var == pyo.summation(expression) constraint = pyo.Constraint(rule=const_rule) model.add_component(varstr + "_constraint", constraint) @@ -261,8 +259,8 @@ def const_rule(model): def max_pos(expression, model=None, varstr=None): - """Returns the maximum positive scalar value of an expression. - I.e., max([x, 0]) where x is any element of the expression (if a matrix) + """Returns the element-wise maximum positive value of an expression. + Returns scalar for scalar input, indexed for indexed input. Parameters ---------- @@ -297,7 +295,8 @@ def max_pos(expression, model=None, varstr=None): [numpy.float, numpy.int, numpy.Array, cvxpy.Expression, or pyomo.environ.Var], pyomo.environ.Model ) - Expression representing maximum positive scalar value of `expression` + Expression representing element-wise maximum positive value of `expression`. + Scalar input returns scalar output, indexed input returns indexed output. """ if isinstance( expression, (LinearExpression, SumExpression, MonomialTermExpression, ScalarVar) @@ -312,13 +311,14 @@ def const_rule(model): model.add_component(varstr + "_constraint", constraint) return (var, model) elif isinstance(expression, (IndexedExpression, pyo.Param, pyo.Var)): - model.add_component(varstr, pyo.Var(bounds=(0, None))) + # Create indexed max_pos variable + model.add_component(varstr, pyo.Var(expression.index_set(), bounds=(0, None))) var = model.find_component(varstr) - def const_rule(model, t): - return var >= expression[t] + def const_rule(model, *indices): + return var[indices] >= expression[indices] - constraint = pyo.Constraint(model.t, rule=const_rule) + constraint = pyo.Constraint(expression.index_set(), rule=const_rule) model.add_component(varstr + "_constraint", constraint) return (var, model) elif isinstance( @@ -326,7 +326,7 @@ def const_rule(model, t): ): return (np.max(expression), model) if np.max(expression) > 0 else (0, model) elif isinstance(expression, cp.Expression): - return cp.max(cp.vstack([expression, 0])), None + return cp.maximum(expression, 0), None # Works for scalar or vector else: raise TypeError( "Only CVXPY or Pyomo variables and NumPy arrays are currently supported." @@ -443,6 +443,117 @@ def const_rule(model, t): ) +def get_decomposed_var_names(utility): + """Get consistent variable names for decomposed consumption variables.""" + return f"{utility}_positive", f"{utility}_negative" + + +def decompose_consumption( + expression, model=None, varstr=None, decomposition_type="absolute_value" +): + """Decomposes consumption data into positive and negative components + And adds constraint such that total consumption equals + positive values minus negative values + (where negative values are stored as positive magnitudes). + + Parameters + ---------- + expression : [ + numpy.Array, + cvxpy.Expression, + pyomo.core.expr.numeric_expr.NumericExpression, + pyomo.core.expr.numeric_expr.NumericNDArray, + pyomo.environ.Param, + pyomo.environ.Var + ] + Expression representing consumption data + + model : pyomo.environ.Model + The model object associated with the problem. + Only used in the case of Pyomo, so `None` by default. + + varstr : str + Name prefix for the variables to be created if using a Pyomo `model` + + decomposition_type : str + Type of decomposition to use. + - "binary_variable": To be implemented + - "absolute_value": Creates nonlinear problem + + Returns + ------- + tuple + (positive_values, negative_values, model) where + positive_values and negative_values are both positive + with the constraint that total = positive - negative + """ + if isinstance(expression, np.ndarray): + positive_values = np.maximum(expression, 0) + negative_values = np.maximum(-expression, 0) # magnitude as positive + return positive_values, negative_values, model + elif isinstance(expression, cp.Expression): + if decomposition_type == "absolute_value": + positive_values, _ = max_pos(expression) + negative_values, _ = max_pos(-expression) # magnitude as positive + else: + warnings.warn( + f"Decomposition type '{decomposition_type}' is not implemented yet. " + "Please use available type. Skipping decomposition.", + UserWarning, + ) + positive_values = None + negative_values = None + return positive_values, negative_values, model + elif isinstance(expression, (pyo.Var, pyo.Param)): + if decomposition_type == "absolute_value": + + # Use max_pos to create positive_var and negative_var + pos_name, neg_name = get_decomposed_var_names(varstr) + positive_var, model = max_pos(expression, model, pos_name) + + # Create negative expression since pyomo won't take -expression directly + def negative_rule(model, t): + return -expression[t] + + negative_expr = pyo.Expression(model.t, rule=negative_rule) + model.add_component(f"{varstr}_negative_expr", negative_expr) + negative_var, model = max_pos(negative_expr, model, neg_name) + + # Add constraint to balance import and export decomposed values + def decomposition_rule(model, t): + return expression[t] == positive_var[t] - negative_var[t] + + model.add_component( + f"{varstr}_decomposition_constraint", + pyo.Constraint(model.t, rule=decomposition_rule), + ) + + # Add constraint to ensure positive_var + negative_var = |expression| + # This both variables becoming larger due to artificial arbitrage + def magnitude_rule(model, t): + return positive_var[t] + negative_var[t] == abs(expression[t]) + + model.add_component( + f"{varstr}_magnitude_constraint", + pyo.Constraint(model.t, rule=magnitude_rule), + ) + + return positive_var, negative_var, model + else: + warnings.warn( + f"Decomposition type '{decomposition_type}' is not implemented yet. " + "Please use available type. Skipping decomposition.", + UserWarning, + ) + positive_var = None + negative_var = None + return positive_var, negative_var, model + else: + raise TypeError( + "Only CVXPY or Pyomo variables and NumPy arrays are currently supported." + ) + + def parse_freq(freq): """Parses a time frequency code string, returning its type and its freq_binsize diff --git a/examples/example_parametrize_charges.ipynb b/examples/example_parametrize_charges.ipynb index f58aae2..638952f 100644 --- a/examples/example_parametrize_charges.ipynb +++ b/examples/example_parametrize_charges.ipynb @@ -69,7 +69,7 @@ "source": [ "## Specify charge variants as a list of dictionaries\n", "### Each with entries for:\n", - "- 'scale_ratios': nested dictionary for charge scaling by period type\n", + "- 'percent_change_dict': nested dictionary for charge scaling by period type\n", "- 'scale_all_demand': global scaling for all demand charges\n", "- 'scale_all_energy': global scaling for all energy charges \n", "- 'shift_peak_hours_before': hours to shift peak window start (must be multiple of 0.25)\n", @@ -88,7 +88,7 @@ "variants = [\n", " # Double peak charges\n", " {\n", - " 'scale_ratios': {\n", + " 'percent_change_dict': {\n", " DEMAND: {PEAK: 2.0, HALF_PEAK: 2.0, OFF_PEAK: 1.0, SUPER_OFF_PEAK: 1.0},\n", " ENERGY: {PEAK: 2.0, HALF_PEAK: 2.0, OFF_PEAK: 1.0, SUPER_OFF_PEAK: 1.0}\n", " },\n", @@ -96,7 +96,7 @@ " },\n", " # Increse peak charges from specific keys\n", " {\n", - " \"scale_ratios\": {\n", + " \"percent_change_dict\": {\n", " \"electric_demand_peak-summer\": 2.2,\n", " \"electric_demand_half_peak-summer\": 2.2,\n", " },\n", @@ -104,7 +104,7 @@ " },\n", " # Halve peak charges\n", " {\n", - " 'scale_ratios': {\n", + " 'percent_change_dict': {\n", " DEMAND: {PEAK: 0.5, HALF_PEAK: 0.5, OFF_PEAK: 1.0, SUPER_OFF_PEAK: 1.0},\n", " ENERGY: {PEAK: 0.5, HALF_PEAK: 0.5, OFF_PEAK: 1.0, SUPER_OFF_PEAK: 1.0}\n", " },\n", @@ -112,7 +112,7 @@ " },\n", " # Increase all demand/energy charges\n", " {\n", - " 'scale_ratios': {\n", + " 'percent_change_dict': {\n", " DEMAND: 1.15,\n", " ENERGY: 1.15\n", " },\n", diff --git a/setup.py b/setup.py index d17e8fd..7320879 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ "pandas>=2.2.1", "numpy>=1.26.4", "cvxpy>=1.3.0", - "pyomo>=6.7", + "pyomo>=6.8", "gurobipy>=11.0", "pint>=0.19.2", ]