diff --git a/LICENSE b/LICENSE index 81195cf1f1..de13546deb 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [2021] [RTE] + Copyright © 2007 - 2023 RTE Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/antarest/study/storage/variantstudy/business/utils.py b/antarest/study/storage/variantstudy/business/utils.py index 25985c38bd..79918c56f3 100644 --- a/antarest/study/storage/variantstudy/business/utils.py +++ b/antarest/study/storage/variantstudy/business/utils.py @@ -1,7 +1,8 @@ -from typing import Union, List, Any, Sequence, Optional +from typing import Any, Dict, List, Optional, Sequence, Union from antarest.core.model import JSON from antarest.matrixstore.model import MatrixData +from antarest.matrixstore.service import ISimpleMatrixService from antarest.study.storage.rawstudy.model.filesystem.factory import FileStudy from antarest.study.storage.variantstudy.business.matrix_constants_generator import ( MATRIX_PROTOCOL_PREFIX, @@ -11,24 +12,39 @@ def validate_matrix( - matrix: Union[List[List[MatrixData]], str], values: Any + matrix: Union[List[List[MatrixData]], str], values: Dict[str, Any] ) -> str: - matrix_id: str + """ + Validates the matrix, stores the matrix array in the matrices repository, + and returns a reference to the stored array. + + Args: + matrix: The matrix data or matrix link to validate. + values: Additional values used during the validation process. + It should contain the following key-value pairs: + - "command_context": An object providing access to matrix services. + It should have a "matrix_service" attribute which allows creating + and checking the existence of matrices. + + Returns: + The ID of the validated matrix prefixed by "matrix://". + + Raises: + TypeError: If the provided matrix is neither a matrix nor a link to a matrix. + ValueError: If the matrix ID does not exist. + """ + # fmt: off + matrix_service: ISimpleMatrixService = values["command_context"].matrix_service if isinstance(matrix, list): - matrix_id = MATRIX_PROTOCOL_PREFIX + values[ - "command_context" - ].matrix_service.create(data=matrix) + return MATRIX_PROTOCOL_PREFIX + matrix_service.create(data=matrix) elif isinstance(matrix, str): - if values["command_context"].matrix_service.exists(matrix): - matrix_id = MATRIX_PROTOCOL_PREFIX + matrix + if matrix_service.exists(matrix): + return MATRIX_PROTOCOL_PREFIX + matrix else: raise ValueError(f"Matrix with id {matrix} does not exist") else: - raise ValueError( - f"The data {matrix} is neither a matrix nor a link to a matrix" - ) - - return matrix_id + raise TypeError(f"The data {matrix} is neither a matrix nor a link to a matrix") + # fmt: on def get_or_create_section(json_ini: JSON, section: str) -> JSON: diff --git a/antarest/study/storage/variantstudy/model/command/icommand.py b/antarest/study/storage/variantstudy/model/command/icommand.py index 61f0f92c15..85b1877cd8 100644 --- a/antarest/study/storage/variantstudy/model/command/icommand.py +++ b/antarest/study/storage/variantstudy/model/command/icommand.py @@ -33,21 +33,57 @@ class ICommand(ABC, BaseModel): version: int command_context: CommandContext - @abstractmethod - def _apply(self, study_data: FileStudy) -> CommandOutput: - raise NotImplementedError() - @abstractmethod def _apply_config( self, study_data: FileStudyTreeConfig ) -> Tuple[CommandOutput, Dict[str, Any]]: + """ + Applies configuration changes to the study data. + + Args: + study_data: The study data configuration. + + Returns: + A tuple containing the command output and a dictionary of extra data. + """ raise NotImplementedError() def apply_config(self, study_data: FileStudyTreeConfig) -> CommandOutput: + """ + Applies configuration changes to the study data. + + Args: + study_data: The study data configuration. + + Returns: + The command output. + """ output, _ = self._apply_config(study_data) return output + @abstractmethod + def _apply(self, study_data: FileStudy) -> CommandOutput: + """ + Applies the study data to update storage configurations and saves the changes. + + Args: + study_data: The study data to be applied. + + Returns: + The output of the command execution. + """ + raise NotImplementedError() + def apply(self, study_data: FileStudy) -> CommandOutput: + """ + Applies the study data to update storage configurations and saves the changes. + + Args: + study_data: The study data to be applied. + + Returns: + The output of the command execution. + """ try: return self._apply(study_data) except Exception as e: @@ -65,10 +101,18 @@ def apply(self, study_data: FileStudy) -> CommandOutput: @abstractmethod def to_dto(self) -> CommandDTO: + """ + Converts the current object to a Data Transfer Object (DTO) + which is stored in the `CommandBlock` in the database. + + Returns: + The DTO object representing the current command. + """ raise NotImplementedError() @abstractmethod def match_signature(self) -> str: + """Returns the command signature.""" raise NotImplementedError() @abstractmethod @@ -84,19 +128,50 @@ def match(self, other: "ICommand", equal: bool = False) -> bool: """ raise NotImplementedError() - def create_diff(self, other: "ICommand") -> List["ICommand"]: - assert_this(self.match(other)) - return self._create_diff(other) - @abstractmethod def _create_diff(self, other: "ICommand") -> List["ICommand"]: + """ + Creates a list of commands representing the differences between + the current instance and another `ICommand` object. + + Args: + other: Another ICommand object to compare against. + + Returns: + A list of commands representing the differences between + the two `ICommand` objects. + """ raise NotImplementedError() + def create_diff(self, other: "ICommand") -> List["ICommand"]: + """ + Creates a list of commands representing the differences between + the current instance and another `ICommand` object. + + Args: + other: Another ICommand object to compare against. + + Returns: + A list of commands representing the differences between + the two `ICommand` objects. + """ + assert_this(self.match(other)) + return self._create_diff(other) + @abstractmethod def get_inner_matrices(self) -> List[str]: + """ + Retrieves the list of matrix IDs. + """ raise NotImplementedError() def get_command_extractor(self) -> "CommandExtractor": + """ + Create a new `CommandExtractor` used to revert the command changes. + + Returns: + An instance of `CommandExtractor`. + """ from antarest.study.storage.variantstudy.business.command_extractor import ( CommandExtractor, ) diff --git a/docs/user-guide/2-variant_manager.md b/docs/user-guide/2-variant_manager.md index 20c6ce5263..892a57b524 100644 --- a/docs/user-guide/2-variant_manager.md +++ b/docs/user-guide/2-variant_manager.md @@ -2,78 +2,98 @@ ## Introduction -The variant manager is built on a descriptive edition language. -It is a simple json file that contains an array of `commands`. +The variant manager in Antares Web allows users to easily manage multiple scenarios of electricity consumption and +production in France and Europe, in order to maintain a balance between supply and demand. This feature enables users to +create and configure variants, which are slightly modified versions of the base study used as a reference. -A command is an object which has 2 field : -- `action` which is the name of the edition action -- `args` which is an object containing the parameters of the action +It is important to note that variant management is currently applicable only to "managed" studies available in the +"default" workspace. Any modifications made to a variant are recorded as a list of commands in the variant's history. +The user interface provides options to view and delete these commands, allowing users to undo changes if needed. +Additionally, variant configurations can be modified programmatically using the API. For example, the GET +endpoint `/v1/studies/{uuid}/commands` lists the commands associated with a variant, while the POST +endpoint `/v1/studies/{uuid}/command` adds a command to a variant. + +The following documentation describes the variant manager and provides a list of available commands. + +## Managing Study Variants + +Commands in the variant manager are actions that define modifications to the study's configuration. +Each command consists of an "action" and "args" field. The "action" field specifies the type of modification to be +performed, while the "args" field contains the parameters required for that action. For example, the +command `"action": "create_area"` creates a new area, and the corresponding `"args"` object specifies the name of the +area to be created. Similarly, the command `"action": "create_link"` creates a link between two areas, with the "args" +object providing the names of the two areas to be connected. Users can create multiple commands within a single variant +to define complex configurations. + +The example provided bellow demonstrates the creation of two areas and a link between them: -Example of creation of 2 areas and 1 link: ```json -[{ - "action": "create_area", - "args": { - "area_name": "new area" +[ + { + "action": "create_area", + "args": { + "area_name": "new area" + } + }, + { + "action": "create_area", + "args": { + "area_name": "new area 2" + } + }, + { + "action": "create_link", + "args": { + "area1": "new area", + "area2": "new area 2" + } } -},{ - "action": "create_area", - "args": { - "area_name": "new area 2" - } -},{ - "action": "create_link", - "args": { - "area1": "new area", - "area2": "new area 2" - } -}] +] ``` ## Command list ### Base commands -| Action Name | Arguments | Description | -|-----------------------|------------------------|------------------------------------------| -| update_config |
{
target: <INI_TARGET>
data: <INI_MODEL>
}
| Update arbitrary config | -| replace_matrix |
{
target: <INPUT_SERIES_MATRIX>
matrix: <MATRIX>
}
| Replace arbitrary matrix | -| create_area |
{
area_name: <STRING>
}
| Create a new area | -| remove_area |
{
id: <AREA_ID>
}
| Remove an existing area | -| create_cluster |
{
area_id: <AREA_ID>
cluster_name: <STRING>
prepro?: <STRING>
modulation?: <MATRIX>
parameters?: <INI_MODEL>
}
| Create a new thermal cluster | -| remove_cluster |
{
area_id: <AREA_ID>
cluster_id: <CLUSTER_ID>
}
| Remove an existing thermal cluster | -| create_renewables_cluster |
{
area_id: <AREA_ID>
cluster_name: <STRING>
parameters?: <INI_MODEL>
}
| Create a new renewable cluster | -| remove_renewables_cluster |
{
area_id: <AREA_ID>
cluster_id: <CLUSTER_ID>
}
| Remove an existing renewable cluster | -| create_link |
{
area1: <AREA_ID>
area2: <AREA_ID>
parameters?: <INI_MODEL>
series?: <MATRIX>
}
| Create a new link | -| remove_link |
{
area1: <AREA_ID>
area2: <AREA_ID>
}
| Remove an existing link | -| create_district |
{
name: <STRING>
base_filter?: "add-all" | "remove-all"
filter_items?: <LIST[AREA_ID]>
output?: <BOOLEAN> (default: True)
comments?: <STRING>
}
| Create a new district (set of areas) | -| remove_district |
{
id: <DISTRICT_ID>
}
| Remove an existing district | -| create_binding_constraint |
{
name: <STRING>
enabled?: <BOOLEAN> (default: True)
time_step: "hourly" | "weekly" | "daily"
operator: "equal" | "both" | "greater" | "less"
coeffs: <LIST[CONSTRAINT_COEFF]>
values?: <MATRIX>
comments?: <STRING>
}

CONSTRAINT_COEFF
{
type: <"cluster" | "link" (choosing one or the other imply filling the right corresponding parameter below)>
link: <AREA_ID>%<AREA_ID> (link)
cluster: <AREA_ID>.<CLUSTER_ID>
coeff: <NUMBER>
offset?: <NUMBER>
}
| Create a new binding constraint | -| update_binding_constraint |
{
id: <BINDING_CONSTRAINT_ID>
enabled?: <BOOLEAN> (default: True)
time_step: "hourly" | "weekly" | "daily"
operator: "equal" | "both" | "greater" | "less"
coeffs: <LIST[CONSTRAINT_COEFF]>
values?: <MATRIX>
comments?: <STRING>
}
| Update an existing binding constraint | -| remove_binding_constraint |
{
id: <BINDING_CONSTRAINT_ID>
}
| Remove an existing binding constraint | -| update_playlist |
{
active: <BOOLEAN> (default: True)
reverse: <BOOLEAN> (default: False)
items: <LIST[NUMBER]> (default: None)
}
| Update the playlist with provided active (or inactive) years (starting from year 1) | -| update_scenario_builder |
{
data: <RULESETS_MODEL>
}
| Update scenario builder with partial configuration | -| update_district |
{
id: <STRING>
base_filter?: "add-all" | "remove-all"
filter_items?: <LIST[AREA_ID]>
output?: <BOOLEAN> (default: True)
comments?: <STRING>
}
| Update a district (set of areas) | -| update_raw_file |
{
target: <INPUT_RAW_FILE_TARGET>
b64Data: <STRING>
}
| Replace arbitrary data file (must not be a matrix or ini target) with a base64 encoded data | +| Action Name | Arguments | Description | +|---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------| +| update_config |
{
target: <INI_TARGET>
data: <INI_MODEL>
}
| Update arbitrary config | +| replace_matrix |
{
target: <INPUT_SERIES_MATRIX>
matrix: <MATRIX>
}
| Replace arbitrary matrix | +| create_area |
{
area_name: <STRING>
}
| Create a new area | +| remove_area |
{
id: <AREA_ID>
}
| Remove an existing area | +| create_cluster |
{
area_id: <AREA_ID>
cluster_name: <STRING>
prepro?: <STRING>
modulation?: <MATRIX>
parameters?: <INI_MODEL>
}
| Create a new thermal cluster | +| remove_cluster |
{
area_id: <AREA_ID>
cluster_id: <CLUSTER_ID>
}
| Remove an existing thermal cluster | +| create_renewables_cluster |
{
area_id: <AREA_ID>
cluster_name: <STRING>
parameters?: <INI_MODEL>
}
| Create a new renewable cluster | +| remove_renewables_cluster |
{
area_id: <AREA_ID>
cluster_id: <CLUSTER_ID>
}
| Remove an existing renewable cluster | +| create_link |
{
area1: <AREA_ID>
area2: <AREA_ID>
parameters?: <INI_MODEL>
series?: <MATRIX>
}
| Create a new link | +| remove_link |
{
area1: <AREA_ID>
area2: <AREA_ID>
}
| Remove an existing link | +| create_district |
{
name: <STRING>
base_filter?: "add-all" | "remove-all"
filter_items?: <LIST[AREA_ID]>
output?: <BOOLEAN> (default: True)
comments?: <STRING>
}
| Create a new district (set of areas) | +| remove_district |
{
id: <DISTRICT_ID>
}
| Remove an existing district | +| create_binding_constraint |
{
name: <STRING>
enabled?: <BOOLEAN> (default: True)
time_step: "hourly" | "weekly" | "daily"
operator: "equal" | "both" | "greater" | "less"
coeffs: <LIST[CONSTRAINT_COEFF]>
values?: <MATRIX>
comments?: <STRING>
}

CONSTRAINT_COEFF
{
type: <"cluster" | "link" (choosing one or the other imply filling the right corresponding parameter below)>
link: <AREA_ID>%<AREA_ID> (link)
cluster: <AREA_ID>.<CLUSTER_ID>
coeff: <NUMBER>
offset?: <NUMBER>
}
| Create a new binding constraint | +| update_binding_constraint |
{
id: <BINDING_CONSTRAINT_ID>
enabled?: <BOOLEAN> (default: True)
time_step: "hourly" | "weekly" | "daily"
operator: "equal" | "both" | "greater" | "less"
coeffs: <LIST[CONSTRAINT_COEFF]>
values?: <MATRIX>
comments?: <STRING>
}
| Update an existing binding constraint | +| remove_binding_constraint |
{
id: <BINDING_CONSTRAINT_ID>
}
| Remove an existing binding constraint | +| update_playlist |
{
active: <BOOLEAN> (default: True)
reverse: <BOOLEAN> (default: False)
items: <LIST[NUMBER]> (default: None)
}
| Update the playlist with provided active (or inactive) years (starting from year 1) | +| update_scenario_builder |
{
data: <RULESETS_MODEL>
}
| Update scenario builder with partial configuration | +| update_district |
{
id: <STRING>
base_filter?: "add-all" | "remove-all"
filter_items?: <LIST[AREA_ID]>
output?: <BOOLEAN> (default: True)
comments?: <STRING>
}
| Update a district (set of areas) | +| update_raw_file |
{
target: <INPUT_RAW_FILE_TARGET>
b64Data: <STRING>
}
| Replace arbitrary data file (must not be a matrix or ini target) with a base64 encoded data | #### Base types -| Type | Description | -|------|-------------| -|STRING|any string value| -|NUMBER|any integer or float| -|BOOLEAN|true or false| -|INI_TARGET|a valid antares file relative path (without extension). The path can be found when browsing the study in detailed view| -|INI_MODEL|a json with a valid field corresponding to the ini file targeted| -|RULESETS_MODEL| like `INI_MODEL` with some specifications: an empty string allows to remove a key (ruleset or cell value) and a ruleset "A" with for value the name of an another ruleset "B" allows to clone the content of "B" in "A" | -|INPUT_RAW_FILE_TARGET|a valid antares raw data file relative path (without extension). The path can be found when browsing the study in detailed view| -|INPUT_SERIES_MATRIX_TARGET|a valid antares matrix data file relative path (without extension). The path can be found when browsing the study in detailed view| -|MATRIX|a matrix id or a list of list of values (eg. [[0,1,2],[4,5,6]] where each sub list is a row of the matrix). Matrix id can be found in the Matrix Data manager tab.| -|AREA_ID|the id of an area (same as name, but lower cased and only with the following characters: [a-z],[0-9]_,(,),-,&,",". Other characters will be transformed into a single space.)| -|CLUSTER_ID|the id of a cluster (same as name, but lower cased and only with the following characters: [a-z],[0-9]_,(,),-,&,",". Other characters will be transformed into a single space.)| -|DISTRICT_ID|the id of a district (same as name, but lower cased and only with the following characters: [a-z],[0-9]_,(,),-,&,",". Other characters will be transformed into a single space.)| -|BINDING_CONSTRAINT_ID|the id of a binding constraint (same as name, but lower cased and only with the following characters: [a-z],[0-9]_,(,),-,&,",". Other characters will be transformed into a single space.)| - +| Type | Description | +|----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| STRING | any string value | +| NUMBER | any integer or float | +| BOOLEAN | true or false | +| INI_TARGET | a valid antares file relative path (without extension). The path can be found when browsing the study in detailed view | +| INI_MODEL | a json with a valid field corresponding to the ini file targeted | +| RULESETS_MODEL | like `INI_MODEL` with some specifications: an empty string allows to remove a key (ruleset or cell value) and a ruleset "A" with for value the name of an another ruleset "B" allows to clone the content of "B" in "A" | +| INPUT_RAW_FILE_TARGET | a valid antares raw data file relative path (without extension). The path can be found when browsing the study in detailed view | +| INPUT_SERIES_MATRIX_TARGET | a valid antares matrix data file relative path (without extension). The path can be found when browsing the study in detailed view | +| MATRIX | a matrix id or a list of list of values (eg. [[0,1,2],[4,5,6]] where each sub list is a row of the matrix). Matrix id can be found in the Matrix Data manager tab. | +| AREA_ID | the id of an area (same as name, but lower cased and only with the following characters: [a-z],[0-9]_,(,),-,&,",". Other characters will be transformed into a single space.) | +| CLUSTER_ID | the id of a cluster (same as name, but lower cased and only with the following characters: [a-z],[0-9]_,(,),-,&,",". Other characters will be transformed into a single space.) | +| DISTRICT_ID | the id of a district (same as name, but lower cased and only with the following characters: [a-z],[0-9]_,(,),-,&,",". Other characters will be transformed into a single space.) | +| BINDING_CONSTRAINT_ID | the id of a binding constraint (same as name, but lower cased and only with the following characters: [a-z],[0-9]_,(,),-,&,",". Other characters will be transformed into a single space.) | ### Specialized commands @@ -85,9 +105,13 @@ Comming soon ## CLI Tool -The CLI tool (`AntaresTool`) is bundled within [AntaresWeb releases](https://github.com/AntaresSimulatorTeam/AntaREST/releases). +The CLI tool (`AntaresTool`) is bundled +within [AntaresWeb releases](https://github.com/AntaresSimulatorTeam/AntaREST/releases). It provides 3 commands : -- `apply-script` will modify a study using commands found in a directory that contain a file `commands.json` and an optional folder named `matrices` which contains matrices used in the commands. -- `generate-script` will transform a study into a commands file and matrices directory -- `generate-script-diff` will take two commands file (and associated matrices directory) and will output a new one consisting of the differences between the two variants \ No newline at end of file + +- `apply-script` will modify a study using commands found in a directory that contain a file `commands.json` and an + optional folder named `matrices` which contains matrices used in the commands. +- `generate-script` will transform a study into a commands file and matrices directory +- `generate-script-diff` will take two commands file (and associated matrices directory) and will output a new one + consisting of the differences between the two variants \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index b46a354a8f..fa50daadc6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -42,6 +42,7 @@ nav: - 'Develop': - 'Introduction': 'architecture/0-introduction.md' - 'Database management': 'architecture/1-database.md' + - 'Add a new Study version': 'architecture/2-add-new-antares-version.md' - 'Roadmap': 'architecture/5-roadmap.md' - 'Antares ecosystem': 'https://antares-doc.readthedocs.io' - 'Changelog': 'CHANGELOG.md' @@ -74,4 +75,4 @@ markdown_extensions: - pymdownx.superfences - pymdownx.tabbed -copyright: Copyright © 2007 - 2021 RTE \ No newline at end of file +copyright: Copyright © 2007 - 2023 RTE \ No newline at end of file