battery_optimizer.de.battery_optimization#

Classes

DEBatteryOptimizer(*args, request_id[, ...])

class battery_optimizer.de.battery_optimization.DEBatteryOptimizer(*args, request_id, country=Country.DE, user_id, request_creation_time, elastic_filter=False, already_auctioned_as_constants=True, asset, verbose=False, epsilon=1e-06, commercial_objective=CommercialObjective(commercial_columns=['commodity_revenue', 'commodity_cost']), multiplier_buy_sell_exceed_limit=10.0, upper_bound_risk_increment=None, imbalance=Imbalance(imbalance_cost_per_MWh=1000000.0, min_imbalance_cost_per_MWh=100000.0, imbalance_cost_shaping=True), solve_optimization_problem_max_seconds=90.0, intraday_strategy=IntradayStrategy(name=<IntradayStrategyName.vwap: 'vwap'>, delivery_length=<IntradayDeliveryLength.quarter_hour: 'QuarterHour'>, tradeable_time_window_minutes=NaT), intraday_result_aggregation=IntradayResultAggregation(name=<IntradayResultAggregationName.minmax: 'minmax'>, no_buckets=1), objective=Objective(name=<ObjectiveName.pnl: 'pnl'>), solver_settings=SolverSettings(gapRel=0.01), do_input_for_intraday_bucketing_correct_bucket=True, elastic_soe_constraints=False, global_max_charging_power_kw=None, global_max_discharging_power_kw=None, feature_flags=FeatureFlags(return_pickled_optimizer=False), use_case=UseCase.BESS)#

Bases: DERequest, BatteryOptimizer

Parameters:
add_daily_cycle_limit_constraint(problem, variables, variables_special)#
add_respect_initial_soc_for_flex_products(problem, variables, soc)#
Parameters:

soc (DataFrame)

add_respect_maximal_marketable_power_for_flex_product(flex_product, problem, variables)#
add_soc_during_flex_product_constraint(problem, variables, soc_name, soc, check_adjacent_periods=True)#

When participating in ancillary services we need to reserve both up- and downward energy in the battery for possible activations for ancillary services.

We reserve energy for ancillary services by appropriately lowering the max SoE and raising the min SoE, thus giving the wholesale optimizer a narrower SoE band to operate in, which ensures that the battery can always respect the SoE requirements for ancillary services activations.

In certain optimization runs, we may already assume e.g. full aFRR energy positive activation (and likewise for aFRR energy negative). We do so, e.g. when optimizing aFRR energy positive and assume, within the given aFRR energy positive optimization, full aFRR energy positive activation to understand the maximum possible aFRR energy positive activation and its impact on the battery. In such as scenario, we do not additionally raise the min SoE to account for possible aFRR energy positive activation, since that possible activation is already part of the SoE calculation in such a scenario. The same applies for the max SoE and possible aFRR energy negative activation.

Additional info on FCR PQ limits we reserve here: Formulas 3.7 and 3.8 in

https://www.regelleistung.net/xspproxy/api/StaticFiles/Regelleistung/Infos_f%C3%BCr_Anbieter/ Wie_werde_ich_Regelenergieanbieter_Pr%C3%A4qualifikation/Pr%C3%A4qualifikationsbedingungen_FCR_aFRR_mFRR/PQ-Bedingungen-Delta_2024_07_05.pdf

Args:

problem (BatteryOptimizer): The problem we’re adding the constraints to variables (pd.DataFrame): The decision variables for the optimization problem soc_name (str): The name of the state of charge (SoC) variable soc (pd.DataFrame): The state of charge (SoC) values check_adjacent_periods (bool, optional): Whether to check adjacent periods. Defaults to True.

Parameters:
create_battery_max_power_constraints(problem, variables)#
create_country_specific_constraints(problem, variables)#
create_grid_connection_constraints(problem, variables)#
create_soe_constraints(problem, variables)#
create_wholesale_duration_constraints(problem, variables)#
property custom_lower_bounds: dict[str, dict]#
property custom_upper_bounds: dict#
property custom_variables: dict#
get_max_soe_kwh_with_flex_negative_reservation(variables, timestamp, is_theoretical_afrr_negative_activation_scenario=False)#
get_min_soe_kwh_with_flex_positive_reservation(variables, timestamp, is_theoretical_afrr_positive_activation_scenario=False)#
get_objective_pnl(variables, problem_solved=False, markets=None)#

Computes the objective function for the optimization problem. The objective function is the sum of the commodity revenue from energy products delivery to the grid, minus the commodity cost for charging from the grid, and the penalty for imbalance.

get_reserved_soe_kwh_for_afrr_negative(variables, timestamp, is_theoretical_afrr_negative_activation_scenario=False)#
get_reserved_soe_kwh_for_afrr_positive(variables, timestamp, is_theoretical_afrr_positive_activation_scenario=False)#
get_reserved_soe_kwh_for_fcr(variables, timestamp)#
get_target_soc(variables)#
model_config: ClassVar[ConfigDict] = {'arbitrary_types_allowed': True, 'extra': 'ignore', 'frozen': False, 'json_encoders': {<class 'datetime.datetime'>: <function BaseModel.<lambda>>, <class 'pandas._libs.tslibs.timedeltas.Timedelta'>: <function BaseModel.<lambda>>, <class 'pandas._libs.tslibs.timestamps.Timestamp'>: <function BaseModel.<lambda>>, <class 'pandas.core.frame.DataFrame'>: <function BaseModel.<lambda>>, <class 'pandas.core.series.Series'>: <function BaseModel.<lambda>>}, 'validate_default': True}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(context, /)#

We need to both initialize private attributes and call the user-defined model_post_init method.

Parameters:
Return type:

None