From 694ce5e68c219b96c414c9b14943c5751731e76c Mon Sep 17 00:00:00 2001 From: OUSTRY Antoine Date: Thu, 4 Dec 2025 20:20:40 +0100 Subject: [PATCH 1/3] test_nominal_capacity --- tests/antares_historic/test_thermal_model.py | 105 ++++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) diff --git a/tests/antares_historic/test_thermal_model.py b/tests/antares_historic/test_thermal_model.py index 4defad01..075fd2e5 100644 --- a/tests/antares_historic/test_thermal_model.py +++ b/tests/antares_historic/test_thermal_model.py @@ -17,7 +17,7 @@ LOAD_FILES_DIR = Path("tests/antares_historic/data") THERMAL_TEST_REL_ACCURACY = 5 * 1e-5 THERMAL_TEST_SOLVER = "highs" - +MODIFICATION_RATIO = 1.2 def thermal_test_procedure( study_name: str, @@ -113,3 +113,106 @@ def test_general_thermal( LOAD_FILES_DIR / load_time_serie_file, antares_exec_folder, ) + +@pytest.mark.parametrize( + "base_capacity", + [50, 100, 200] +) +@pytest.mark.parametrize( + "load_time_serie_file", + [ + "load_matrix_1.txt", + "load_matrix_2.txt", + "load_matrix_original.txt", + ], +) +def test_nominal_capacity( + base_capacity: float, + load_time_serie_file: str, + auto_generated_studies_path: Path, + antares_exec_folder: Path, +) -> None: + # Setup thermal cluster properties + marg_cluster_properties = ThermalClusterProperties( + nominal_capacity=base_capacity, + marginal_cost=20, + market_bid_cost=20, + fixed_cost=500, + group=ThermalClusterGroup.NUCLEAR, + ) + + # Run base test + study_name_base = f"e2e_capacity_base_{str(int(100*time()))}" + cluster_data_frame_base = pd.DataFrame( + data=marg_cluster_properties.unit_count + * marg_cluster_properties.nominal_capacity + * np.ones((8760, 1)) + ) + createThermalTestAntaresStudy( + study_name_base, + auto_generated_studies_path, + LOAD_FILES_DIR / load_time_serie_file, + marg_cluster_properties, + cluster_data_frame_base, + ) + orig_path_base, conv_path_base = convert_study( + auto_generated_studies_path, study_name_base, ["thermal"] + ) + rel_gap_base = first_optim_relgap( + antares_exec_folder, orig_path_base, conv_path_base, THERMAL_TEST_SOLVER + ) + assert rel_gap_base < THERMAL_TEST_REL_ACCURACY + + # Test +20% capacity + marg_cluster_plus = ThermalClusterProperties( + nominal_capacity=base_capacity * MODIFICATION_RATIO, + marginal_cost=10, + market_bid_cost=10, + fixed_cost=500, + group=ThermalClusterGroup.NUCLEAR, + ) + study_name_plus = f"e2e_capacity_plus_{str(int(100*time()))}" + cluster_data_frame_plus = pd.DataFrame( + data=marg_cluster_plus.unit_count + * marg_cluster_plus.nominal_capacity + * np.ones((8760, 1)) + ) + createThermalTestAntaresStudy( + study_name_plus, + auto_generated_studies_path, + LOAD_FILES_DIR / load_time_serie_file, + marg_cluster_plus, + cluster_data_frame_plus, + ) + orig_path_plus = auto_generated_studies_path / study_name_plus + rel_gap_plus = first_optim_relgap( + antares_exec_folder, orig_path_plus, conv_path_base, THERMAL_TEST_SOLVER + ) + assert rel_gap_plus > THERMAL_TEST_REL_ACCURACY + + # Test -20% capacity + marg_cluster_minus = ThermalClusterProperties( + nominal_capacity=base_capacity /MODIFICATION_RATIO, + marginal_cost=20, + market_bid_cost=20, + fixed_cost=500, + group=ThermalClusterGroup.NUCLEAR, + ) + study_name_minus = f"e2e_capacity_minus_{str(int(100*time()))}" + cluster_data_frame_minus = pd.DataFrame( + data=marg_cluster_minus.unit_count + * marg_cluster_minus.nominal_capacity + * np.ones((8760, 1)) + ) + createThermalTestAntaresStudy( + study_name_minus, + auto_generated_studies_path, + LOAD_FILES_DIR / load_time_serie_file, + marg_cluster_minus, + cluster_data_frame_minus, + ) + orig_path_minus = auto_generated_studies_path / study_name_minus + rel_gap_minus = first_optim_relgap( + antares_exec_folder, orig_path_minus, conv_path_base, THERMAL_TEST_SOLVER + ) + assert rel_gap_minus > THERMAL_TEST_REL_ACCURACY From 05e8a51ca20c2825c40aa4310832850c680149bf Mon Sep 17 00:00:00 2001 From: OUSTRY Antoine Date: Fri, 5 Dec 2025 10:03:04 +0100 Subject: [PATCH 2/3] Test thermal --- tests/antares_historic/test_thermal_model.py | 240 ++++++++++++++++++- 1 file changed, 235 insertions(+), 5 deletions(-) diff --git a/tests/antares_historic/test_thermal_model.py b/tests/antares_historic/test_thermal_model.py index 075fd2e5..4f6283bb 100644 --- a/tests/antares_historic/test_thermal_model.py +++ b/tests/antares_historic/test_thermal_model.py @@ -19,6 +19,7 @@ THERMAL_TEST_SOLVER = "highs" MODIFICATION_RATIO = 1.2 + def thermal_test_procedure( study_name: str, study_path: Path, @@ -114,10 +115,8 @@ def test_general_thermal( antares_exec_folder, ) -@pytest.mark.parametrize( - "base_capacity", - [50, 100, 200] -) + +@pytest.mark.parametrize("base_capacity", [50, 100, 200]) @pytest.mark.parametrize( "load_time_serie_file", [ @@ -192,7 +191,7 @@ def test_nominal_capacity( # Test -20% capacity marg_cluster_minus = ThermalClusterProperties( - nominal_capacity=base_capacity /MODIFICATION_RATIO, + nominal_capacity=base_capacity / MODIFICATION_RATIO, marginal_cost=20, market_bid_cost=20, fixed_cost=500, @@ -216,3 +215,234 @@ def test_nominal_capacity( antares_exec_folder, orig_path_minus, conv_path_base, THERMAL_TEST_SOLVER ) assert rel_gap_minus > THERMAL_TEST_REL_ACCURACY + + +@pytest.mark.parametrize("base_startup_cost", [50, 1000, 5000]) +@pytest.mark.parametrize( + "load_time_serie_file", + [ + "load_matrix_1.txt", + "load_matrix_2.txt", + "load_matrix_original.txt", + ], +) +def test_startup_cost( + base_startup_cost: float, + load_time_serie_file: str, + auto_generated_studies_path: Path, + antares_exec_folder: Path, +) -> None: + # Setup thermal cluster properties with base startup cost + marg_cluster_properties = ThermalClusterProperties( + nominal_capacity=100, + marginal_cost=20, + market_bid_cost=20, + startup_cost=base_startup_cost, + fixed_cost=500, + group=ThermalClusterGroup.NUCLEAR, + ) + + # Run base test + study_name_base = f"e2e_startup_base_{str(int(100*time()))}" + cluster_data_frame_base = pd.DataFrame( + data=marg_cluster_properties.unit_count + * marg_cluster_properties.nominal_capacity + * np.ones((8760, 1)) + ) + createThermalTestAntaresStudy( + study_name_base, + auto_generated_studies_path, + LOAD_FILES_DIR / load_time_serie_file, + marg_cluster_properties, + cluster_data_frame_base, + ) + orig_path_base, conv_path_base = convert_study( + auto_generated_studies_path, study_name_base, ["thermal"] + ) + rel_gap_base = first_optim_relgap( + antares_exec_folder, orig_path_base, conv_path_base, THERMAL_TEST_SOLVER + ) + assert rel_gap_base < THERMAL_TEST_REL_ACCURACY + + for perturbation in [MODIFICATION_RATIO, 1 / MODIFICATION_RATIO]: + # Test +/-20% startup cost + marg_cluster = ThermalClusterProperties( + nominal_capacity=marg_cluster_properties.nominal_capacity, + marginal_cost=marg_cluster_properties.marginal_cost, + market_bid_cost=marg_cluster_properties.market_bid_cost, + startup_cost=base_startup_cost * perturbation, + group=ThermalClusterGroup.NUCLEAR, + ) + study_name = f"e2e_startup_{str(int(100*time()))}" + cluster_data_frame = pd.DataFrame( + data=marg_cluster.unit_count + * marg_cluster.nominal_capacity + * np.ones((8760, 1)) + ) + createThermalTestAntaresStudy( + study_name, + auto_generated_studies_path, + LOAD_FILES_DIR / load_time_serie_file, + marg_cluster, + cluster_data_frame, + ) + orig_path_perturbated = auto_generated_studies_path / study_name + rel_gap = first_optim_relgap( + antares_exec_folder, + orig_path_perturbated, + conv_path_base, + THERMAL_TEST_SOLVER, + ) + assert rel_gap > THERMAL_TEST_REL_ACCURACY + + +@pytest.mark.parametrize("base_fixed_cost", [250, 500, 1000]) +@pytest.mark.parametrize( + "load_time_serie_file", + [ + "load_matrix_1.txt", + "load_matrix_2.txt", + "load_matrix_original.txt", + ], +) +def test_fixed_cost( + base_fixed_cost: float, + load_time_serie_file: str, + auto_generated_studies_path: Path, + antares_exec_folder: Path, +) -> None: + # Setup thermal cluster properties with base fixed cost + marg_cluster_properties = ThermalClusterProperties( + nominal_capacity=100, + marginal_cost=20, + market_bid_cost=20, + fixed_cost=base_fixed_cost, + group=ThermalClusterGroup.NUCLEAR, + ) + + # Run base test + study_name_base = f"e2e_fixedcost_base_{str(int(100*time()))}" + cluster_data_frame_base = pd.DataFrame( + data=marg_cluster_properties.unit_count + * marg_cluster_properties.nominal_capacity + * np.ones((8760, 1)) + ) + createThermalTestAntaresStudy( + study_name_base, + auto_generated_studies_path, + LOAD_FILES_DIR / load_time_serie_file, + marg_cluster_properties, + cluster_data_frame_base, + ) + orig_path_base, conv_path_base = convert_study( + auto_generated_studies_path, study_name_base, ["thermal"] + ) + rel_gap_base = first_optim_relgap( + antares_exec_folder, orig_path_base, conv_path_base, THERMAL_TEST_SOLVER + ) + assert rel_gap_base < THERMAL_TEST_REL_ACCURACY + for perturbation in [MODIFICATION_RATIO, 1 / MODIFICATION_RATIO]: + # Test +/-20% fixed cost + marg_cluster = ThermalClusterProperties( + nominal_capacity=marg_cluster_properties.nominal_capacity, + marginal_cost=marg_cluster_properties.marginal_cost, + market_bid_cost=marg_cluster_properties.market_bid_cost, + fixed_cost=base_fixed_cost * perturbation, + group=ThermalClusterGroup.NUCLEAR, + ) + study_name = f"e2e_fixedcost_{str(int(100*time()))}" + cluster_data_frame = pd.DataFrame( + data=marg_cluster.unit_count + * marg_cluster.nominal_capacity + * np.ones((8760, 1)) + ) + createThermalTestAntaresStudy( + study_name, + auto_generated_studies_path, + LOAD_FILES_DIR / load_time_serie_file, + marg_cluster, + cluster_data_frame, + ) + orig_path_perturbated = auto_generated_studies_path / study_name + rel_gap = first_optim_relgap( + antares_exec_folder, + orig_path_perturbated, + conv_path_base, + THERMAL_TEST_SOLVER, + ) + assert rel_gap > THERMAL_TEST_REL_ACCURACY + + +@pytest.mark.parametrize("base_marginal_cost", [1, 10, 100]) +@pytest.mark.parametrize( + "load_time_serie_file", + [ + "load_matrix_1.txt", + "load_matrix_2.txt", + "load_matrix_original.txt", + ], +) +def test_marginal_cost_marketbid_equals( + base_marginal_cost: float, + load_time_serie_file: str, + auto_generated_studies_path: Path, + antares_exec_folder: Path, +) -> None: + # Setup thermal cluster properties + marg_cluster_properties = ThermalClusterProperties( + nominal_capacity=50, + marginal_cost=base_marginal_cost, + market_bid_cost=base_marginal_cost, + group=ThermalClusterGroup.NUCLEAR, + ) + + # Run base test + study_name_base = f"e2e_marginal_base_{str(int(100*time()))}" + cluster_data_frame_base = pd.DataFrame( + data=marg_cluster_properties.unit_count + * marg_cluster_properties.nominal_capacity + * np.ones((8760, 1)) + ) + createThermalTestAntaresStudy( + study_name_base, + auto_generated_studies_path, + LOAD_FILES_DIR / load_time_serie_file, + marg_cluster_properties, + cluster_data_frame_base, + ) + orig_path_base, conv_path_base = convert_study( + auto_generated_studies_path, study_name_base, ["thermal"] + ) + rel_gap_base = first_optim_relgap( + antares_exec_folder, orig_path_base, conv_path_base, THERMAL_TEST_SOLVER + ) + assert rel_gap_base < THERMAL_TEST_REL_ACCURACY + for perturbation in [MODIFICATION_RATIO, 1 / MODIFICATION_RATIO]: + # Test +/-20% marginal cost + marg_cluster = ThermalClusterProperties( + nominal_capacity=marg_cluster_properties.nominal_capacity, + marginal_cost=base_marginal_cost * perturbation, + market_bid_cost=base_marginal_cost * perturbation, + group=ThermalClusterGroup.NUCLEAR, + ) + study_name = f"e2e_marginal_{str(int(100*time()))}" + cluster_data_frame = pd.DataFrame( + data=marg_cluster.unit_count + * marg_cluster.nominal_capacity + * np.ones((8760, 1)) + ) + createThermalTestAntaresStudy( + study_name, + auto_generated_studies_path, + LOAD_FILES_DIR / load_time_serie_file, + marg_cluster, + cluster_data_frame, + ) + orig_path_perturbated = auto_generated_studies_path / study_name + rel_gap = first_optim_relgap( + antares_exec_folder, + orig_path_perturbated, + conv_path_base, + THERMAL_TEST_SOLVER, + ) + assert rel_gap > THERMAL_TEST_REL_ACCURACY From ab1ae2cceb3f95ba1ac124159f214e9c2eb1c263 Mon Sep 17 00:00:00 2001 From: OUSTRY Antoine Date: Fri, 5 Dec 2025 10:41:00 +0100 Subject: [PATCH 3/3] Test thermal --- tests/antares_historic/test_thermal_model.py | 277 ++++++++++++++++--- 1 file changed, 237 insertions(+), 40 deletions(-) diff --git a/tests/antares_historic/test_thermal_model.py b/tests/antares_historic/test_thermal_model.py index 4f6283bb..bbe3dee2 100644 --- a/tests/antares_historic/test_thermal_model.py +++ b/tests/antares_historic/test_thermal_model.py @@ -18,6 +18,11 @@ THERMAL_TEST_REL_ACCURACY = 5 * 1e-5 THERMAL_TEST_SOLVER = "highs" MODIFICATION_RATIO = 1.2 +LOAD_TIME_SERIE_FILES = [ + "load_matrix_1.txt", + # "load_matrix_2.txt", #uncomment to test with different load profile + # "load_matrix_original.txt", +] def thermal_test_procedure( @@ -91,14 +96,7 @@ def cluster_list_general_test() -> list[ThermalClusterProperties]: ] # , -@pytest.mark.parametrize( - "load_time_serie_file", - [ - "load_matrix_1.txt", - "load_matrix_2.txt", - "load_matrix_original.txt", - ], -) +@pytest.mark.parametrize("load_time_serie_file", LOAD_TIME_SERIE_FILES) def test_general_thermal( cluster_list_general_test: list[ThermalClusterProperties], load_time_serie_file: str, @@ -117,14 +115,7 @@ def test_general_thermal( @pytest.mark.parametrize("base_capacity", [50, 100, 200]) -@pytest.mark.parametrize( - "load_time_serie_file", - [ - "load_matrix_1.txt", - "load_matrix_2.txt", - "load_matrix_original.txt", - ], -) +@pytest.mark.parametrize("load_time_serie_file", LOAD_TIME_SERIE_FILES) def test_nominal_capacity( base_capacity: float, load_time_serie_file: str, @@ -218,14 +209,7 @@ def test_nominal_capacity( @pytest.mark.parametrize("base_startup_cost", [50, 1000, 5000]) -@pytest.mark.parametrize( - "load_time_serie_file", - [ - "load_matrix_1.txt", - "load_matrix_2.txt", - "load_matrix_original.txt", - ], -) +@pytest.mark.parametrize("load_time_serie_file", LOAD_TIME_SERIE_FILES) def test_startup_cost( base_startup_cost: float, load_time_serie_file: str, @@ -297,14 +281,7 @@ def test_startup_cost( @pytest.mark.parametrize("base_fixed_cost", [250, 500, 1000]) -@pytest.mark.parametrize( - "load_time_serie_file", - [ - "load_matrix_1.txt", - "load_matrix_2.txt", - "load_matrix_original.txt", - ], -) +@pytest.mark.parametrize("load_time_serie_file", LOAD_TIME_SERIE_FILES) def test_fixed_cost( base_fixed_cost: float, load_time_serie_file: str, @@ -374,14 +351,7 @@ def test_fixed_cost( @pytest.mark.parametrize("base_marginal_cost", [1, 10, 100]) -@pytest.mark.parametrize( - "load_time_serie_file", - [ - "load_matrix_1.txt", - "load_matrix_2.txt", - "load_matrix_original.txt", - ], -) +@pytest.mark.parametrize("load_time_serie_file", LOAD_TIME_SERIE_FILES) def test_marginal_cost_marketbid_equals( base_marginal_cost: float, load_time_serie_file: str, @@ -446,3 +416,230 @@ def test_marginal_cost_marketbid_equals( THERMAL_TEST_SOLVER, ) assert rel_gap > THERMAL_TEST_REL_ACCURACY + + +@pytest.mark.parametrize("base_min_down_time", [2, 4, 6]) +@pytest.mark.parametrize("load_time_serie_file", LOAD_TIME_SERIE_FILES) +def test_min_down_time( + base_min_down_time: float, + load_time_serie_file: str, + auto_generated_studies_path: Path, + antares_exec_folder: Path, +) -> None: + # Ensure min_down_time is < min_up_time for all tests + base_min_up_time = base_min_down_time + 2 + + marg_cluster_properties = ThermalClusterProperties( + nominal_capacity=100, + marginal_cost=20, + market_bid_cost=20, + fixed_cost=500, + min_down_time=base_min_down_time, + min_up_time=base_min_up_time, + group=ThermalClusterGroup.NUCLEAR, + ) + + study_name_base = f"e2e_mindown_base_{str(int(100*time()))}" + cluster_data_frame_base = pd.DataFrame( + data=marg_cluster_properties.unit_count + * marg_cluster_properties.nominal_capacity + * np.ones((8760, 1)) + ) + createThermalTestAntaresStudy( + study_name_base, + auto_generated_studies_path, + LOAD_FILES_DIR / load_time_serie_file, + marg_cluster_properties, + cluster_data_frame_base, + ) + orig_path_base, conv_path_base = convert_study( + auto_generated_studies_path, study_name_base, ["thermal"] + ) + rel_gap_base = first_optim_relgap( + antares_exec_folder, orig_path_base, conv_path_base, THERMAL_TEST_SOLVER + ) + assert rel_gap_base < THERMAL_TEST_REL_ACCURACY + + for perturbation in [1, -1]: + perturbed_min_down_time = base_min_down_time + perturbation + # Always keep min_up_time > min_down_time + marg_cluster = ThermalClusterProperties( + nominal_capacity=marg_cluster_properties.nominal_capacity, + marginal_cost=marg_cluster_properties.marginal_cost, + market_bid_cost=marg_cluster_properties.market_bid_cost, + fixed_cost=marg_cluster_properties.fixed_cost, + min_down_time=perturbed_min_down_time, + min_up_time=marg_cluster_properties.min_up_time, + group=ThermalClusterGroup.NUCLEAR, + ) + study_name = f"e2e_mindown_{str(int(100*time()))}" + cluster_data_frame = pd.DataFrame( + data=marg_cluster.unit_count + * marg_cluster.nominal_capacity + * np.ones((8760, 1)) + ) + createThermalTestAntaresStudy( + study_name, + auto_generated_studies_path, + LOAD_FILES_DIR / load_time_serie_file, + marg_cluster, + cluster_data_frame, + ) + orig_path_perturbated = auto_generated_studies_path / study_name + rel_gap = first_optim_relgap( + antares_exec_folder, + orig_path_perturbated, + conv_path_base, + THERMAL_TEST_SOLVER, + ) + assert rel_gap > rel_gap_base + + +@pytest.mark.parametrize("base_min_up_time", [3, 4, 6]) +@pytest.mark.parametrize("load_time_serie_file", LOAD_TIME_SERIE_FILES) +def test_min_up_time( + base_min_up_time: float, + load_time_serie_file: str, + auto_generated_studies_path: Path, + antares_exec_folder: Path, +) -> None: + # Ensure min_down_time < min_up_time for all tests + base_min_down_time = max(1, base_min_up_time - 1) + + marg_cluster_properties = ThermalClusterProperties( + nominal_capacity=100, + marginal_cost=20, + market_bid_cost=20, + fixed_cost=500, + min_down_time=base_min_down_time, + min_up_time=base_min_up_time, + group=ThermalClusterGroup.NUCLEAR, + ) + + study_name_base = f"e2e_minup_base_{str(int(100*time()))}" + cluster_data_frame_base = pd.DataFrame( + data=marg_cluster_properties.unit_count + * marg_cluster_properties.nominal_capacity + * np.ones((8760, 1)) + ) + createThermalTestAntaresStudy( + study_name_base, + auto_generated_studies_path, + LOAD_FILES_DIR / load_time_serie_file, + marg_cluster_properties, + cluster_data_frame_base, + ) + orig_path_base, conv_path_base = convert_study( + auto_generated_studies_path, study_name_base, ["thermal"] + ) + rel_gap_base = first_optim_relgap( + antares_exec_folder, orig_path_base, conv_path_base, THERMAL_TEST_SOLVER + ) + assert rel_gap_base < THERMAL_TEST_REL_ACCURACY + + for perturbation in [1, -1]: + perturbed_min_up_time = base_min_up_time + perturbation + marg_cluster = ThermalClusterProperties( + nominal_capacity=marg_cluster_properties.nominal_capacity, + marginal_cost=marg_cluster_properties.marginal_cost, + market_bid_cost=marg_cluster_properties.market_bid_cost, + fixed_cost=marg_cluster_properties.fixed_cost, + min_down_time=marg_cluster_properties.min_down_time, + min_up_time=perturbed_min_up_time, + group=ThermalClusterGroup.NUCLEAR, + ) + study_name = f"e2e_minup_{str(int(100*time()))}" + cluster_data_frame = pd.DataFrame( + data=marg_cluster.unit_count + * marg_cluster.nominal_capacity + * np.ones((8760, 1)) + ) + createThermalTestAntaresStudy( + study_name, + auto_generated_studies_path, + LOAD_FILES_DIR / load_time_serie_file, + marg_cluster, + cluster_data_frame, + ) + orig_path_perturbated = auto_generated_studies_path / study_name + rel_gap = first_optim_relgap( + antares_exec_folder, + orig_path_perturbated, + conv_path_base, + THERMAL_TEST_SOLVER, + ) + assert rel_gap > rel_gap_base + + +@pytest.mark.parametrize("base_min_stable_power", [20, 40, 60]) +@pytest.mark.parametrize("load_time_serie_file", LOAD_TIME_SERIE_FILES) +def test_min_stable_power( + base_min_stable_power: float, + load_time_serie_file: str, + auto_generated_studies_path: Path, + antares_exec_folder: Path, +) -> None: + # Setup thermal cluster properties with base min_stable_power + marg_cluster_properties = ThermalClusterProperties( + nominal_capacity=100, + marginal_cost=20, + market_bid_cost=20, + fixed_cost=500, + startup_cost=1000, + min_stable_power=base_min_stable_power, + group=ThermalClusterGroup.NUCLEAR, + ) + + # Run base test + study_name_base = f"e2e_minstable_base_{str(int(100*time()))}" + cluster_data_frame_base = pd.DataFrame( + data=marg_cluster_properties.unit_count + * marg_cluster_properties.nominal_capacity + * np.ones((8760, 1)) + ) + createThermalTestAntaresStudy( + study_name_base, + auto_generated_studies_path, + LOAD_FILES_DIR / load_time_serie_file, + marg_cluster_properties, + cluster_data_frame_base, + ) + orig_path_base, conv_path_base = convert_study( + auto_generated_studies_path, study_name_base, ["thermal"] + ) + rel_gap_base = first_optim_relgap( + antares_exec_folder, orig_path_base, conv_path_base, THERMAL_TEST_SOLVER + ) + assert rel_gap_base < THERMAL_TEST_REL_ACCURACY + + for perturbation in [MODIFICATION_RATIO, 1 / MODIFICATION_RATIO]: + # Test +/-20% min_stable_power + marg_cluster = ThermalClusterProperties( + nominal_capacity=marg_cluster_properties.nominal_capacity, + marginal_cost=marg_cluster_properties.marginal_cost, + market_bid_cost=marg_cluster_properties.market_bid_cost, + fixed_cost=marg_cluster_properties.fixed_cost, + min_stable_power=base_min_stable_power * perturbation, + group=ThermalClusterGroup.NUCLEAR, + ) + study_name = f"e2e_minstable_{str(int(100*time()))}" + cluster_data_frame = pd.DataFrame( + data=marg_cluster.unit_count + * marg_cluster.nominal_capacity + * np.ones((8760, 1)) + ) + createThermalTestAntaresStudy( + study_name, + auto_generated_studies_path, + LOAD_FILES_DIR / load_time_serie_file, + marg_cluster, + cluster_data_frame, + ) + orig_path_perturbated = auto_generated_studies_path / study_name + rel_gap = first_optim_relgap( + antares_exec_folder, + orig_path_perturbated, + conv_path_base, + THERMAL_TEST_SOLVER, + ) + assert rel_gap > THERMAL_TEST_REL_ACCURACY