Skip to content

Commit

Permalink
feat(api,ui-config): add 'MILP' value option in 'Unit Commitment Mode…
Browse files Browse the repository at this point in the history
…' field for study >= v8.8 (#2056)
  • Loading branch information
skamril authored Jun 17, 2024
2 parents 472883b + 2e64e56 commit 5e674a3
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 45 deletions.
10 changes: 10 additions & 0 deletions antarest/study/business/advanced_parameters_management.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -44,6 +45,7 @@ class ReserveManagement(EnumIgnoreCase):
class UnitCommitmentMode(EnumIgnoreCase):
FAST = "fast"
ACCURATE = "accurate"
MILP = "milp"


class SimulationCore(EnumIgnoreCase):
Expand Down Expand Up @@ -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"],
Expand Down
95 changes: 95 additions & 0 deletions tests/integration/study_data_blueprint/test_advanced_parameters.py
Original file line number Diff line number Diff line change
@@ -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"
Original file line number Diff line number Diff line change
Expand Up @@ -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<AdvancedParamsFormFields>();
const { version } = props;
const { study } = useOutletContext<{ study: StudyMetadata }>();
const studyVersion = Number(study.version);

////////////////////////////////////////////////////////////////
// JSX
Expand Down Expand Up @@ -178,7 +178,11 @@ function Fields(props: Props) {
/>
<SelectFE
label={t("study.configuration.advancedParameters.unitCommitmentMode")}
options={UNIT_COMMITMENT_MODE_OPTIONS}
options={UNIT_COMMITMENT_MODE_OPTIONS.filter(
(v) => v !== UnitCommitmentMode.MILP || studyVersion >= 880,
).map((v) =>
v === UnitCommitmentMode.MILP ? { label: "MILP", value: v } : v,
)}
name="unitCommitmentMode"
control={control}
/>
Expand All @@ -188,7 +192,7 @@ function Fields(props: Props) {
name="numberOfCoresMode"
control={control}
/>
{version >= 810 && (
{studyVersion >= 810 && (
<SelectFE
label={t(
"study.configuration.advancedParameters.renewableGenerationModeling",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,23 @@ function AdvancedParameters() {
// Event Handlers
////////////////////////////////////////////////////////////////

const handleSubmit = async (
data: SubmitHandlerPlus<AdvancedParamsFormFields>,
) => {
const values = { ...data.dirtyValues };
const handleSubmit = ({
dirtyValues,
}: SubmitHandlerPlus<AdvancedParamsFormFields>) => {
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<AdvancedParamsFormFields>) => {
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 },
}),
);
}
});
};

////////////////////////////////////////////////////////////////
Expand All @@ -54,9 +49,10 @@ function AdvancedParameters() {
defaultValues: () => getAdvancedParamsFormFields(study.id),
}}
onSubmit={handleSubmit}
onSubmitSuccessful={handleSubmitSuccessful}
enableUndoRedo
>
<Fields version={Number(study.version)} />
<Fields />
</Form>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { DeepPartial } from "react-hook-form";
import { StudyMetadata } from "../../../../../../common/types";
import client from "../../../../../../services/api/client";

Expand Down Expand Up @@ -41,9 +42,11 @@ enum ReserveManagement {
Global = "global",
}

enum UnitCommitmentMode {
export enum UnitCommitmentMode {
Fast = "fast",
Accurate = "accurate",
// Since v8.8
MILP = "milp",
}

enum SimulationCore {
Expand Down Expand Up @@ -82,7 +85,7 @@ export const RENEWABLE_GENERATION_OPTIONS = Object.values(
////////////////////////////////////////////////////////////////

export interface AdvancedParamsFormFields {
accuracyOnCorrelation: string;
accuracyOnCorrelation: string[];
dayAheadReserveManagement: string;
hydroHeuristicPolicy: string;
hydroPricingMode: string;
Expand All @@ -105,26 +108,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<AdvancedParamsFormFields> {
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<AdvancedParamsFormFields_RAW>(
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<AdvancedParamsFormFields>,
): Promise<void> {
return client.put(makeRequestURL(studyId), values);
values: DeepPartial<AdvancedParamsFormFields>,
) {
const { accuracyOnCorrelation, ...rest } = values;
const newValues: Partial<AdvancedParamsFormFields_RAW> = rest;

if (accuracyOnCorrelation) {
newValues.accuracyOnCorrelation = accuracyOnCorrelation.join(", ");
}

await client.put(makeRequestURL(studyId), newValues);
}
2 changes: 1 addition & 1 deletion webapp/src/components/common/fieldEditors/SelectFE.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type OptionObj<T extends O.Object = O.Object> = {
} & T;

export interface SelectFEProps extends Omit<SelectProps, "labelId"> {
options: string[] | readonly string[] | OptionObj[];
options: Array<string | OptionObj> | readonly string[];
helperText?: React.ReactNode;
emptyValue?: boolean;
startCaseLabel?: boolean;
Expand Down

0 comments on commit 5e674a3

Please sign in to comment.