From 98470be7dd792e5e851ba2513ccec73b239e10e1 Mon Sep 17 00:00:00 2001 From: Florian Maurer Date: Wed, 13 May 2026 10:25:42 +0200 Subject: [PATCH 1/3] strategies: merge similar neg and pos strategy --- assume/strategies/__init__.py | 15 +++---- assume/strategies/naive_strategies.py | 43 +------------------ docs/source/bidding_strategies.rst | 3 +- .../notebooks/10a_DSU_and_flexibility.ipynb | 14 +++--- tests/test_steam_plant.py | 11 +++-- 5 files changed, 21 insertions(+), 65 deletions(-) diff --git a/assume/strategies/__init__.py b/assume/strategies/__init__.py index 9d9cbd98c..4a7d074ac 100644 --- a/assume/strategies/__init__.py +++ b/assume/strategies/__init__.py @@ -26,8 +26,7 @@ EnergyNaiveStrategy, ExchangeEnergyNaiveStrategy, EnergyHeuristicElasticStrategy, - DsmCapacityHeuristicBalancingPosStrategy, - DsmCapacityHeuristicBalancingNegStrategy, + DsmCapacityHeuristicBalancingStrategy, ) from assume.strategies.interactive_strategies import EnergyInteractiveStrategy from assume.strategies.dmas_powerplant import EnergyOptimizationDmasStrategy @@ -54,8 +53,8 @@ "flexable_eom_linked": EnergyHeuristicFlexableLinkedStrategy, "flexable_neg_crm_storage": StorageCapacityHeuristicBalancingNegStrategy, "flexable_pos_crm_storage": StorageCapacityHeuristicBalancingPosStrategy, - "pos_crm_dsm": DsmCapacityHeuristicBalancingPosStrategy, - "neg_crm_dsm": DsmCapacityHeuristicBalancingNegStrategy, + "pos_crm_dsm": DsmCapacityHeuristicBalancingStrategy, + "neg_crm_dsm": DsmCapacityHeuristicBalancingStrategy, "naive_redispatch": EnergyNaiveRedispatchStrategy, "naive_da_dsm": DsmEnergyOptimizationStrategy, "naive_redispatch_dsm": DsmEnergyNaiveRedispatchStrategy, @@ -78,10 +77,10 @@ "storage_energy_heuristic_flexable": StorageEnergyHeuristicFlexableStrategy, "storage_capacity_heuristic_balancing_neg": StorageCapacityHeuristicBalancingNegStrategy, "storage_capacity_heuristic_balancing_pos": StorageCapacityHeuristicBalancingPosStrategy, - "household_capacity_heuristic_balancing_pos": DsmCapacityHeuristicBalancingPosStrategy, - "industry_capacity_heuristic_balancing_pos": DsmCapacityHeuristicBalancingPosStrategy, - "household_capacity_heuristic_balancing_neg": DsmCapacityHeuristicBalancingNegStrategy, - "industry_capacity_heuristic_balancing_neg": DsmCapacityHeuristicBalancingNegStrategy, + "household_capacity_heuristic_balancing_pos": DsmCapacityHeuristicBalancingStrategy, + "industry_capacity_heuristic_balancing_pos": DsmCapacityHeuristicBalancingStrategy, + "household_capacity_heuristic_balancing_neg": DsmCapacityHeuristicBalancingStrategy, + "industry_capacity_heuristic_balancing_neg": DsmCapacityHeuristicBalancingStrategy, "powerplant_energy_naive_redispatch": EnergyNaiveRedispatchStrategy, "demand_energy_naive_redispatch": EnergyNaiveRedispatchStrategy, "household_energy_optimization": DsmEnergyOptimizationStrategy, diff --git a/assume/strategies/naive_strategies.py b/assume/strategies/naive_strategies.py index e6499a019..33f03a825 100644 --- a/assume/strategies/naive_strategies.py +++ b/assume/strategies/naive_strategies.py @@ -529,9 +529,9 @@ def find_first_block_bid( return volume -class DsmCapacityHeuristicBalancingPosStrategy(MinMaxStrategy): +class DsmCapacityHeuristicBalancingStrategy(MinMaxStrategy): """ - Strategy for Positive CRM Reserve (Demand Side, i.e., up & down, symmetric). + Strategy for Positive/Negative CRM Reserve (Demand Side, i.e., up & down, symmetric). """ def calculate_bids(self, unit, market_config, product_tuples, **kwargs): @@ -561,45 +561,6 @@ def calculate_bids(self, unit, market_config, product_tuples, **kwargs): "price": 0, # or unit.calculate_marginal_cost(...) "volume": symmetric_capacity, "unit_id": unit.id, - "market_id": "CRM_pos", - } - ) - return self.remove_empty_bids(bids) - - -class DsmCapacityHeuristicBalancingNegStrategy(MinMaxStrategy): - """ - Strategy for Negative CRM Reserve (Demand Side, i.e., up & down, symmetric). - """ - - def calculate_bids(self, unit, market_config, product_tuples, **kwargs): - # IDENTICAL LOGIC as POS, since symmetric in Germany (volume is symmetric) - # If you ever want to do *only* neg or pos (asymmetric), just change which cap you use! - bids = [] - max_power = unit.max_plant_capacity - min_power = unit.min_plant_capacity - - for product in product_tuples: - start, end, only_hours = product - block_times = [dt for dt in unit.index.get_date_list() if start <= dt < end] - - up_caps = [] - down_caps = [] - for t in block_times: - flex = unit.flex_power_requirement.at[t] - up_caps.append(max_power - flex) - down_caps.append(flex - min_power) - symmetric_capacity = min(min(up_caps), min(down_caps)) - if symmetric_capacity > 0: - bids.append( - { - "start_time": start, - "end_time": end, - "only_hours": only_hours, - "price": 0, - "volume": symmetric_capacity, - "unit_id": unit.id, - "market_id": "CRM_neg", } ) return self.remove_empty_bids(bids) diff --git a/docs/source/bidding_strategies.rst b/docs/source/bidding_strategies.rst index 1fc0cd018..821dbe619 100644 --- a/docs/source/bidding_strategies.rst +++ b/docs/source/bidding_strategies.rst @@ -180,8 +180,7 @@ storage_energy_optimization_dmas EOM Storage strateg Optimization method API references: - :py:meth:`assume.strategies.naive_strategies.DsmEnergyOptimizationStrategy` -- :py:meth:`assume.strategies.naive_strategies.DsmCapacityHeuristicBalancingPosStrategy` -- :py:meth:`assume.strategies.naive_strategies.DsmCapacityHeuristicBalancingNegStrategy` +- :py:meth:`assume.strategies.naive_strategies.DsmCapacityHeuristicBalancingStrategy` - :py:meth:`assume.strategies.dmas_powerplant.EnergyOptimizationDmasStrategy` - :py:meth:`assume.strategies.dmas_storage.StorageEnergyOptimizationDmasStrategy` diff --git a/examples/notebooks/10a_DSU_and_flexibility.ipynb b/examples/notebooks/10a_DSU_and_flexibility.ipynb index cf8b8df75..562d059c5 100644 --- a/examples/notebooks/10a_DSU_and_flexibility.ipynb +++ b/examples/notebooks/10a_DSU_and_flexibility.ipynb @@ -1516,9 +1516,7 @@ "outputs": [], "source": [ "from assume.strategies.naive_strategies import (\n", - " DsmCapacityHeuristicBalancingNegStrategy,\n", - " # EnergyNaiveStrategy,\n", - " DsmCapacityHeuristicBalancingPosStrategy,\n", + " DsmCapacityHeuristicBalancingStrategy,\n", " # EnergyNaiveProfileStrategy,\n", " DsmEnergyNaiveRedispatchStrategy,\n", " DsmEnergyOptimizationStrategy,\n", @@ -1527,8 +1525,8 @@ "\n", "bidding_strategies: dict[str, BaseStrategy] = {\n", " \"industry_energy_optimization\": DsmEnergyOptimizationStrategy,\n", - " \"industry_capacity_heuristic_balancing_pos\": DsmCapacityHeuristicBalancingPosStrategy,\n", - " \"industry_capacity_heuristic_balancing_neg\": DsmCapacityHeuristicBalancingNegStrategy,\n", + " \"industry_capacity_heuristic_balancing_pos\": DsmCapacityHeuristicBalancingStrategy,\n", + " \"industry_capacity_heuristic_balancing_neg\": DsmCapacityHeuristicBalancingStrategy,\n", " \"industry_energy_naive_redispatch\": DsmEnergyNaiveRedispatchStrategy,\n", "}" ] @@ -1705,7 +1703,7 @@ "paper_plant_1.min_plant_capacity = components[\"boiler\"].min_power\n", "\n", "# 2. Instantiate the positive CRM bidding strategy\n", - "strategy = DsmCapacityHeuristicBalancingPosStrategy()\n", + "strategy = DsmCapacityHeuristicBalancingStrategy()\n", "\n", "# 3. Calculate market bids using the agent's optimized flexibility\n", "bids = strategy.calculate_bids(\n", @@ -2897,7 +2895,7 @@ "provenance": [] }, "kernelspec": { - "display_name": "assume3", + "display_name": "assume", "language": "python", "name": "python3" }, @@ -2911,7 +2909,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.12" + "version": "3.13.7" } }, "nbformat": 4, diff --git a/tests/test_steam_plant.py b/tests/test_steam_plant.py index fe3a1b3a0..c84c6b0e4 100644 --- a/tests/test_steam_plant.py +++ b/tests/test_steam_plant.py @@ -8,8 +8,7 @@ from assume.common.fast_pandas import FastSeries from assume.common.forecaster import SteamgenerationForecaster from assume.strategies.naive_strategies import ( - DsmCapacityHeuristicBalancingNegStrategy, - DsmCapacityHeuristicBalancingPosStrategy, + DsmCapacityHeuristicBalancingStrategy, DsmEnergyOptimizationStrategy, ) from assume.units.steam_generation_plant import SteamPlant @@ -596,8 +595,8 @@ def steam_plant_with_crm_flex(steam_plant_components_with_hp_b): market_prices={"EOM": 0}, ) bidding_strategy = { - "CRM_pos": DsmCapacityHeuristicBalancingPosStrategy(), - "CRM_neg": DsmCapacityHeuristicBalancingNegStrategy(), + "CRM_pos": DsmCapacityHeuristicBalancingStrategy(), + "CRM_neg": DsmCapacityHeuristicBalancingStrategy(), } plant = SteamPlant( id="A360", @@ -633,13 +632,13 @@ def test_crm_block_flexibility_and_bidding(steam_plant_with_crm_flex): product_tuples.append((index[i], index[i + block_length], None)) # --- POSITIVE CRM --- - pos_strategy = DsmCapacityHeuristicBalancingPosStrategy() + pos_strategy = DsmCapacityHeuristicBalancingStrategy() pos_bids = pos_strategy.calculate_bids( steam_plant_with_crm_flex, market_config, product_tuples ) assert isinstance(pos_bids, list) # --- NEGATIVE CRM --- - neg_strategy = DsmCapacityHeuristicBalancingNegStrategy() + neg_strategy = DsmCapacityHeuristicBalancingStrategy() neg_bids = neg_strategy.calculate_bids( steam_plant_with_crm_flex, market_config, product_tuples ) From c2bf0a83eff95d3469662ed6c02cc86cd67dcb28 Mon Sep 17 00:00:00 2001 From: Florian Maurer Date: Tue, 30 Jun 2026 21:44:23 +0200 Subject: [PATCH 2/3] base_market: remove closed auctions from open_auctions --- assume/markets/base_market.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assume/markets/base_market.py b/assume/markets/base_market.py index 760e15c8d..14fd2b67a 100644 --- a/assume/markets/base_market.py +++ b/assume/markets/base_market.py @@ -670,7 +670,7 @@ async def clear_market(self, market_products: list[MarketProduct]): 0, ) - self.open_auctions - set(market_products) + self.open_auctions -= set(market_products) accepted_orderbook = sorted( accepted_orderbook, key=lambda x: str(x["agent_addr"]) From ad711023b386d9ca321fa3b9800cb6ffbca7674d Mon Sep 17 00:00:00 2001 From: Florian Maurer Date: Tue, 30 Jun 2026 22:05:43 +0200 Subject: [PATCH 3/3] fix: remove empty additional_fields from market config --- assume/common/market_objects.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/assume/common/market_objects.py b/assume/common/market_objects.py index 6d29fed2e..af44daa2b 100644 --- a/assume/common/market_objects.py +++ b/assume/common/market_objects.py @@ -156,9 +156,16 @@ def __hash__(self): def __post_init__(self): """ - Post-initialization checks for deprecated key "solver" in param_dict. + Post-initialization checks for checks on failing initializations. """ + if "" in self.additional_fields: + warn( + "Empty string '' found in 'additional_fields' and will be ignored.", + UserWarning, + ) + self.additional_fields = [f for f in self.additional_fields if f != ""] + if "solver" in self.param_dict: warn( "The key 'solver' in 'param_dict' is deprecated and may be removed in future versions. "