From 9e8acdd14fbc18a6a79c9bfa87188445da1b5cc8 Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 31 May 2026 13:43:22 +0200 Subject: [PATCH 1/2] fix: use max() for penalty scaling floor (closes #75) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The penalty scaling base was computed with np.min([max_import_price, 0.1e-3]) despite the comment intending a positive floor. min() inverts penalties on negative market prices (turning them into rewards in the maximizing objective) and clamps them to the tiny floor whenever real prices are higher, making penalties too weak to dominate. Switch to np.max() for a proper positive floor that scales with the highest import price, and deduplicate the expression—repeated five times—into a single penalty_base. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/optimizer/optimizer.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/optimizer/optimizer.py b/src/optimizer/optimizer.py index a1e0fab0e..12453de13 100644 --- a/src/optimizer/optimizer.py +++ b/src/optimizer/optimizer.py @@ -81,17 +81,24 @@ def __init__(self, strategy: OptimizationStrategy, grid: GridConfig, batteries: self.min_import_price = np.min(self.time_series.p_N) self.max_import_price = np.max(self.time_series.p_N) - # scaling for penalty parameters. Make sure goal_penalty is always positive - self.prc_e_goal_pen = np.min([self.max_import_price, 0.1e-3]) * 10e1 - self.prc_p_goal_pen = np.min([self.max_import_price, 0.1e-3]) * np.max(self.time_series.dt) / 3600 * 10e1 - self.prc_soc_exc_pen = np.min([self.max_import_price, 0.1e-3]) * 10e2 + # Base scaling for all penalty parameters. Penalties must stay strictly + # positive so that, in the maximizing objective, they actually discourage + # the violations they are subtracted for. Scale with the highest import + # price but never below a small positive floor, otherwise zero or negative + # market prices would weaken or even invert the penalties. + penalty_base = np.max([self.max_import_price, 0.1e-3]) + + # scaling for penalty parameters + self.prc_e_goal_pen = penalty_base * 10e1 + self.prc_p_goal_pen = penalty_base * np.max(self.time_series.dt) / 3600 * 10e1 + self.prc_soc_exc_pen = penalty_base * 10e2 # penalty for exceeding grid import limit. Result shall not become infeasible but report the violation # with helpful information - self.prc_e_grid_imp_pen = np.min([self.max_import_price, 0.1e-3]) * 10e1 + self.prc_e_grid_imp_pen = penalty_base * 10e1 # penalty for exceeding the grid export limit. Result shall not become infeasible but report the 'lost' # solar power - self.prc_e_grid_exp_pen = np.min([self.max_import_price, 0.1e-3]) * 10e1 + self.prc_e_grid_exp_pen = penalty_base * 10e1 # if there is a demand rate given in the input, the grid import limit will be interpreted as the # threshold beyond wich the demand rate is to be applied. Compute a demand rate flag for use in the From c38aaffa65ee88cbf3c5a5b9631d5738c7fa4124 Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 31 May 2026 13:45:27 +0200 Subject: [PATCH 2/2] Apply suggestion from @andig --- src/optimizer/optimizer.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/optimizer/optimizer.py b/src/optimizer/optimizer.py index 12453de13..b878ec8e6 100644 --- a/src/optimizer/optimizer.py +++ b/src/optimizer/optimizer.py @@ -81,11 +81,7 @@ def __init__(self, strategy: OptimizationStrategy, grid: GridConfig, batteries: self.min_import_price = np.min(self.time_series.p_N) self.max_import_price = np.max(self.time_series.p_N) - # Base scaling for all penalty parameters. Penalties must stay strictly - # positive so that, in the maximizing objective, they actually discourage - # the violations they are subtracted for. Scale with the highest import - # price but never below a small positive floor, otherwise zero or negative - # market prices would weaken or even invert the penalties. + # scaling base for penalty parameters. Make sure goal_penalty is always positive. penalty_base = np.max([self.max_import_price, 0.1e-3]) # scaling for penalty parameters