From 8b9dd8643d5157a1318276d2ca0d95d5a5f95786 Mon Sep 17 00:00:00 2001 From: belthlemar Date: Mon, 10 Jun 2024 13:58:44 +0200 Subject: [PATCH 1/4] feat(parameters): add MILP option in unit commitment mode --- .../advanced_parameters_management.py | 10 ++ .../test_advanced_parameters.py | 95 +++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 tests/integration/study_data_blueprint/test_advanced_parameters.py diff --git a/antarest/study/business/advanced_parameters_management.py b/antarest/study/business/advanced_parameters_management.py index f18e47a71f..4801d5487a 100644 --- a/antarest/study/business/advanced_parameters_management.py +++ b/antarest/study/business/advanced_parameters_management.py @@ -4,6 +4,7 @@ from pydantic import validator from pydantic.types import StrictInt, StrictStr +from antarest.core.exceptions import InvalidFieldForVersionError from antarest.study.business.enum_ignore_case import EnumIgnoreCase from antarest.study.business.utils import GENERAL_DATA_PATH, FieldInfo, FormFieldsBaseModel, execute_or_add_commands from antarest.study.model import Study @@ -44,6 +45,7 @@ class ReserveManagement(EnumIgnoreCase): class UnitCommitmentMode(EnumIgnoreCase): FAST = "fast" ACCURATE = "accurate" + MILP = "milp" class SimulationCore(EnumIgnoreCase): @@ -236,6 +238,14 @@ def set_field_values(self, study: Study, field_values: AdvancedParamsFormFields) if value is not None: info = FIELDS_INFO[field_name] + # Handle the specific case of `milp` value that appeared in v8.8 + if ( + field_name == "unit_commitment_mode" + and value == UnitCommitmentMode.MILP + and int(study.version) < 880 + ): + raise InvalidFieldForVersionError("Unit commitment mode `MILP` only exists in v8.8+ studies") + commands.append( UpdateConfig( target=info["path"], diff --git a/tests/integration/study_data_blueprint/test_advanced_parameters.py b/tests/integration/study_data_blueprint/test_advanced_parameters.py new file mode 100644 index 0000000000..b126349335 --- /dev/null +++ b/tests/integration/study_data_blueprint/test_advanced_parameters.py @@ -0,0 +1,95 @@ +from http import HTTPStatus + +import pytest +from starlette.testclient import TestClient + +from antarest.core.tasks.model import TaskStatus +from tests.integration.utils import wait_task_completion + + +class TestAdvancedParametersForm: + """ + Test the end points related to advanced parameters. + + Those tests use the "examples/studies/STA-mini.zip" Study, + which contains the following areas: ["de", "es", "fr", "it"]. + """ + + def test_get_advanced_parameters_values( + self, + client: TestClient, + user_access_token: str, + study_id: str, + ): + """Check `get_advanced_parameters_form_values` end point""" + res = client.get( + f"/v1/studies/{study_id}/config/advancedparameters/form", + headers={"Authorization": f"Bearer {user_access_token}"}, + ) + assert res.status_code == HTTPStatus.OK, res.json() + actual = res.json() + expected = { + "accuracyOnCorrelation": "", + "dayAheadReserveManagement": "global", + "hydroHeuristicPolicy": "accommodate rule curves", + "hydroPricingMode": "fast", + "initialReservoirLevels": "cold start", + "numberOfCoresMode": "maximum", + "powerFluctuations": "free modulations", + "renewableGenerationModelling": "clusters", + "seedHydroCosts": 9005489, + "seedInitialReservoirLevels": 10005489, + "seedSpilledEnergyCosts": 7005489, + "seedThermalCosts": 8005489, + "seedTsgenHydro": 2005489, + "seedTsgenLoad": 1005489, + "seedTsgenSolar": 4005489, + "seedTsgenThermal": 3005489, + "seedTsgenWind": 5489, + "seedTsnumbers": 5005489, + "seedUnsuppliedEnergyCosts": 6005489, + "sheddingPolicy": "shave peaks", + "unitCommitmentMode": "fast", + } + assert actual == expected + + @pytest.mark.parametrize("study_version", [0, 880]) + def test_set_advanced_parameters_values( + self, client: TestClient, user_access_token: str, study_id: str, study_version: int + ): + """Check `set_advanced_parameters_values` end point""" + obj = {"initialReservoirLevels": "hot start"} + res = client.put( + f"/v1/studies/{study_id}/config/advancedparameters/form", + headers={"Authorization": f"Bearer {user_access_token}"}, + json=obj, + ) + assert res.status_code == HTTPStatus.OK, res.json() + actual = res.json() + assert actual is None + + if study_version: + res = client.put( + f"/v1/studies/{study_id}/upgrade", + headers={"Authorization": f"Bearer {user_access_token}"}, + params={"target_version": study_version}, + ) + assert res.status_code == 200, res.json() + + task_id = res.json() + task = wait_task_completion(client, user_access_token, task_id) + assert task.status == TaskStatus.COMPLETED, task + + obj = {"unitCommitmentMode": "milp"} + res = client.put( + f"/v1/studies/{study_id}/config/advancedparameters/form", + headers={"Authorization": f"Bearer {user_access_token}"}, + json=obj, + ) + if study_version: + assert res.status_code == HTTPStatus.OK, res.json() + else: + assert res.status_code == 422 + response = res.json() + assert response["exception"] == "InvalidFieldForVersionError" + assert response["description"] == "Unit commitment mode `MILP` only exists in v8.8+ studies" From 7da2e24f29ed705ed8fbfb930b4efb57bee67b12 Mon Sep 17 00:00:00 2001 From: Samir Kamal <1954121+skamril@users.noreply.github.com> Date: Mon, 17 Jun 2024 16:43:12 +0200 Subject: [PATCH 2/4] refactor(ui-config): enhance submit and API functions for 'Advanced Parameters' --- .../AdvancedParameters/index.tsx | 36 ++++++------- .../Configuration/AdvancedParameters/utils.ts | 50 +++++++++++++------ 2 files changed, 52 insertions(+), 34 deletions(-) diff --git a/webapp/src/components/App/Singlestudy/explore/Configuration/AdvancedParameters/index.tsx b/webapp/src/components/App/Singlestudy/explore/Configuration/AdvancedParameters/index.tsx index 6b696aec84..3182bb1e56 100644 --- a/webapp/src/components/App/Singlestudy/explore/Configuration/AdvancedParameters/index.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Configuration/AdvancedParameters/index.tsx @@ -19,28 +19,23 @@ function AdvancedParameters() { // Event Handlers //////////////////////////////////////////////////////////////// - const handleSubmit = async ( - data: SubmitHandlerPlus, - ) => { - const values = { ...data.dirtyValues }; + const handleSubmit = ({ + dirtyValues, + }: SubmitHandlerPlus) => { + return setAdvancedParamsFormFields(study.id, dirtyValues); + }; - // Get a comma separated string from accuracyOnCorrelation array as expected by the api - if (values.accuracyOnCorrelation) { - values.accuracyOnCorrelation = ( - values.accuracyOnCorrelation as unknown as string[] - ).join(", "); + const handleSubmitSuccessful = ({ + dirtyValues: { renewableGenerationModelling }, + }: SubmitHandlerPlus) => { + if (renewableGenerationModelling) { + dispatch( + updateStudySynthesis({ + id: study.id, + changes: { enr_modelling: renewableGenerationModelling }, + }), + ); } - - return setAdvancedParamsFormFields(study.id, values).then(() => { - if (values.renewableGenerationModelling) { - dispatch( - updateStudySynthesis({ - id: study.id, - changes: { enr_modelling: values.renewableGenerationModelling }, - }), - ); - } - }); }; //////////////////////////////////////////////////////////////// @@ -54,6 +49,7 @@ function AdvancedParameters() { defaultValues: () => getAdvancedParamsFormFields(study.id), }} onSubmit={handleSubmit} + onSubmitSuccessful={handleSubmitSuccessful} enableUndoRedo > diff --git a/webapp/src/components/App/Singlestudy/explore/Configuration/AdvancedParameters/utils.ts b/webapp/src/components/App/Singlestudy/explore/Configuration/AdvancedParameters/utils.ts index 551cb4a083..71789b0c7b 100644 --- a/webapp/src/components/App/Singlestudy/explore/Configuration/AdvancedParameters/utils.ts +++ b/webapp/src/components/App/Singlestudy/explore/Configuration/AdvancedParameters/utils.ts @@ -1,3 +1,4 @@ +import { DeepPartial } from "react-hook-form"; import { StudyMetadata } from "../../../../../../common/types"; import client from "../../../../../../services/api/client"; @@ -82,7 +83,7 @@ export const RENEWABLE_GENERATION_OPTIONS = Object.values( //////////////////////////////////////////////////////////////// export interface AdvancedParamsFormFields { - accuracyOnCorrelation: string; + accuracyOnCorrelation: string[]; dayAheadReserveManagement: string; hydroHeuristicPolicy: string; hydroPricingMode: string; @@ -105,26 +106,47 @@ export interface AdvancedParamsFormFields { unitCommitmentMode: string; } +type AdvancedParamsFormFields_RAW = Omit< + AdvancedParamsFormFields, + "accuracyOnCorrelation" +> & { + accuracyOnCorrelation: string; +}; + +//////////////////////////////////////////////////////////////// +// API +//////////////////////////////////////////////////////////////// + function makeRequestURL(studyId: StudyMetadata["id"]): string { return `v1/studies/${studyId}/config/advancedparameters/form`; } export async function getAdvancedParamsFormFields( studyId: StudyMetadata["id"], -): Promise { - const res = await client.get(makeRequestURL(studyId)); - - // Get array of values from accuracyOnCorrelation string as expected for the SelectFE component - const accuracyOnCorrelation = res.data.accuracyOnCorrelation - .split(/\s*,\s*/) - .filter((v: string) => v.trim()); - - return { ...res.data, accuracyOnCorrelation }; +) { + const { data } = await client.get( + makeRequestURL(studyId), + ); + + return { + ...data, + accuracyOnCorrelation: data.accuracyOnCorrelation + .split(",") + .map((v) => v.trim()) + .filter(Boolean), + } as AdvancedParamsFormFields; } -export function setAdvancedParamsFormFields( +export async function setAdvancedParamsFormFields( studyId: StudyMetadata["id"], - values: Partial, -): Promise { - return client.put(makeRequestURL(studyId), values); + values: DeepPartial, +) { + const { accuracyOnCorrelation, ...rest } = values; + const newValues: Partial = rest; + + if (accuracyOnCorrelation) { + newValues.accuracyOnCorrelation = accuracyOnCorrelation.join(", "); + } + + await client.put(makeRequestURL(studyId), newValues); } From 3c56c0bc39f2d6cc97ffd8a053d9c57931f029ae Mon Sep 17 00:00:00 2001 From: Samir Kamal <1954121+skamril@users.noreply.github.com> Date: Mon, 17 Jun 2024 17:05:38 +0200 Subject: [PATCH 3/4] refactor(ui-commons): update `options` type in SelectFE --- webapp/src/components/common/fieldEditors/SelectFE.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/src/components/common/fieldEditors/SelectFE.tsx b/webapp/src/components/common/fieldEditors/SelectFE.tsx index 0eabb24884..938f94f499 100644 --- a/webapp/src/components/common/fieldEditors/SelectFE.tsx +++ b/webapp/src/components/common/fieldEditors/SelectFE.tsx @@ -19,7 +19,7 @@ type OptionObj = { } & T; export interface SelectFEProps extends Omit { - options: string[] | readonly string[] | OptionObj[]; + options: Array | readonly string[]; helperText?: React.ReactNode; emptyValue?: boolean; startCaseLabel?: boolean; From 2e64e56267d3b1804ad88dec63109f1dd1c39626 Mon Sep 17 00:00:00 2001 From: Samir Kamal <1954121+skamril@users.noreply.github.com> Date: Mon, 17 Jun 2024 17:07:24 +0200 Subject: [PATCH 4/4] feat(ui-config): add 'MILP' value option in 'Unit Commitment Mode' field in 'Advanced Parameters' for studies v8.8 or above' --- .../AdvancedParameters/Fields.tsx | 20 +++++++++++-------- .../AdvancedParameters/index.tsx | 2 +- .../Configuration/AdvancedParameters/utils.ts | 4 +++- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/webapp/src/components/App/Singlestudy/explore/Configuration/AdvancedParameters/Fields.tsx b/webapp/src/components/App/Singlestudy/explore/Configuration/AdvancedParameters/Fields.tsx index 96d313437d..8c2482b7f2 100644 --- a/webapp/src/components/App/Singlestudy/explore/Configuration/AdvancedParameters/Fields.tsx +++ b/webapp/src/components/App/Singlestudy/explore/Configuration/AdvancedParameters/Fields.tsx @@ -16,16 +16,16 @@ import { UNIT_COMMITMENT_MODE_OPTIONS, SIMULATION_CORES_OPTIONS, RENEWABLE_GENERATION_OPTIONS, + UnitCommitmentMode, } from "./utils"; +import { useOutletContext } from "react-router"; +import { StudyMetadata } from "../../../../../../common/types"; -interface Props { - version: number; -} - -function Fields(props: Props) { +function Fields() { const [t] = useTranslation(); const { control } = useFormContextPlus(); - const { version } = props; + const { study } = useOutletContext<{ study: StudyMetadata }>(); + const studyVersion = Number(study.version); //////////////////////////////////////////////////////////////// // JSX @@ -178,7 +178,11 @@ function Fields(props: Props) { /> v !== UnitCommitmentMode.MILP || studyVersion >= 880, + ).map((v) => + v === UnitCommitmentMode.MILP ? { label: "MILP", value: v } : v, + )} name="unitCommitmentMode" control={control} /> @@ -188,7 +192,7 @@ function Fields(props: Props) { name="numberOfCoresMode" control={control} /> - {version >= 810 && ( + {studyVersion >= 810 && ( - + ); } diff --git a/webapp/src/components/App/Singlestudy/explore/Configuration/AdvancedParameters/utils.ts b/webapp/src/components/App/Singlestudy/explore/Configuration/AdvancedParameters/utils.ts index 71789b0c7b..4a8b69e918 100644 --- a/webapp/src/components/App/Singlestudy/explore/Configuration/AdvancedParameters/utils.ts +++ b/webapp/src/components/App/Singlestudy/explore/Configuration/AdvancedParameters/utils.ts @@ -42,9 +42,11 @@ enum ReserveManagement { Global = "global", } -enum UnitCommitmentMode { +export enum UnitCommitmentMode { Fast = "fast", Accurate = "accurate", + // Since v8.8 + MILP = "milp", } enum SimulationCore {