Skip to content

Commit

Permalink
add yaml test
Browse files Browse the repository at this point in the history
modified error message
add variable type entry (integer)
  • Loading branch information
ChouaneLouis committed May 3, 2024
1 parent 1221880 commit 635c75d
Show file tree
Hide file tree
Showing 7 changed files with 735 additions and 30 deletions.
4 changes: 4 additions & 0 deletions src/andromede/expression/parsing/parse_expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,10 @@ def parse_expression(expression: str, identifiers: ModelIdentifiers) -> Expressi

return ExpressionNodeBuilderVisitor(identifiers).visit(parser.fullexpr()) # type: ignore

except ValueError as e:
raise AntaresParseException(
f"An error occurred during parsing: {e}"
) from e
except Exception as e:
raise AntaresParseException(
f"An error occurred during parsing: {type(e).__name__}"
Expand Down
45 changes: 22 additions & 23 deletions src/andromede/model/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,72 +12,77 @@
import typing
from typing import List, Optional

from pydantic import BaseModel, Field
from pydantic import BaseModel, Field, ValidationError
from yaml import safe_load


def parse_yaml_library(input: typing.TextIO) -> "InputLibrary":
tree = safe_load(input)
return InputLibrary.model_validate(tree["library"])
try:
return InputLibrary.model_validate(tree["library"])
except ValidationError as e:
raise ValueError(f"An error occurred during parsing: {e}")


# Design note: actual parsing and validation is delegated to pydantic models
def _to_kebab(snake: str) -> str:
return snake.replace("_", "-")

class ModifiedBaseModel(BaseModel):
description: Optional[str] = None
class Config:
alias_generator = _to_kebab
extra = "forbid"

class InputParameter(BaseModel):

class InputParameter(ModifiedBaseModel):
name: str
time_dependent: bool = False
scenario_dependent: bool = False

class Config:
alias_generator = _to_kebab


class InputVariable(BaseModel):
class InputVariable(ModifiedBaseModel):
name: str
time_dependent: bool = True
scenario_dependent: bool = True
lower_bound: Optional[str] = None
upper_bound: Optional[str] = None
variable_type: str = "float"

class Config:
alias_generator = _to_kebab
coerce_numbers_to_str = True
extra = "forbid"


class InputConstraint(BaseModel):
class InputConstraint(ModifiedBaseModel):
name: str
expression: str
lower_bound: Optional[str] = None
upper_bound: Optional[str] = None

class Config:
alias_generator = _to_kebab


class InputField(BaseModel):
class InputField(ModifiedBaseModel):
name: str


class InputPortType(BaseModel):
class InputPortType(ModifiedBaseModel):
id: str
fields: List[InputField] = Field(default_factory=list)


class InputModelPort(BaseModel):
class InputModelPort(ModifiedBaseModel):
name: str
type: str


class InputPortFieldDefinition(BaseModel):
class InputPortFieldDefinition(ModifiedBaseModel):
port: str
field: str
definition: str


class InputModel(BaseModel):
class InputModel(ModifiedBaseModel):
id: str
parameters: List[InputParameter] = Field(default_factory=list)
variables: List[InputVariable] = Field(default_factory=list)
Expand All @@ -87,14 +92,8 @@ class InputModel(BaseModel):
constraints: List[InputConstraint] = Field(default_factory=list)
objective: Optional[str] = None

class Config:
alias_generator = _to_kebab


class InputLibrary(BaseModel):
class InputLibrary(ModifiedBaseModel):
id: str
port_types: List[InputPortType] = Field(default_factory=list)
models: List[InputModel] = Field(default_factory=list)

class Config:
alias_generator = _to_kebab
2 changes: 1 addition & 1 deletion src/andromede/model/resolve_library.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def _to_expression_if_present(
def _to_variable(var: InputVariable, identifiers: ModelIdentifiers) -> Variable:
return Variable(
name=var.name,
data_type=ValueType.FLOAT,
data_type={"float":ValueType.FLOAT, "integer":ValueType.INTEGER}[var.variable_type],
structure=IndexingStructure(var.time_dependent, var.scenario_dependent),
lower_bound=_to_expression_if_present(var.lower_bound, identifiers),
upper_bound=_to_expression_if_present(var.upper_bound, identifiers),
Expand Down
21 changes: 15 additions & 6 deletions src/andromede/simulation/optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from andromede.expression.time_operator import TimeEvaluation, TimeShift, TimeSum
from andromede.model.constraint import Constraint
from andromede.model.model import PortFieldId
from andromede.model.common import ValueType
from andromede.simulation.linear_expression import LinearExpression, Term
from andromede.simulation.linearize import linearize_expression
from andromede.simulation.strategy import MergedProblemStrategy, ModelSelectionStrategy
Expand Down Expand Up @@ -750,11 +751,19 @@ def _create_variables(self) -> None:
# Externally, for the Solver, this variable will have a full name
# Internally, it will be indexed by a structure that into account
# the component id, variable name, timestep and scenario separately
solver_var = self.solver.NumVar(
lower_bound,
upper_bound,
f"{component.id}_{model_var.name}_t{block_timestep}_s{scenario}",
)
solver_var = None
if model_var.data_type == ValueType.FLOAT:
solver_var = self.solver.NumVar(
lower_bound,
upper_bound,
f"{component.id}_{model_var.name}_t{block_timestep}_s{scenario}",
)
elif model_var.data_type == ValueType.INTEGER:
solver_var = self.solver.IntVar(
lower_bound,
upper_bound,
f"{component.id}_{model_var.name}_t{block_timestep}_s{scenario}",
)
component_context.add_variable(
block_timestep, scenario, model_var.name, solver_var
)
Expand Down Expand Up @@ -814,7 +823,7 @@ def build_problem(
*,
problem_name: str = "optimization_problem",
border_management: BlockBorderManagement = BlockBorderManagement.CYCLE,
solver_id: str = "GLOP",
solver_id: str = "SCIP",
problem_strategy: ModelSelectionStrategy = MergedProblemStrategy(),
) -> OptimizationProblem:
"""
Expand Down
32 changes: 32 additions & 0 deletions tests/functional/tests-yamel/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Copyright (c) 2024, RTE (https://www.rte-france.com)
#
# See AUTHORS.txt
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# SPDX-License-Identifier: MPL-2.0
#
# This file is part of the Antares project.
from pathlib import Path

import pytest

from andromede.model.parsing import parse_yaml_library
from andromede.model.resolve_library import resolve_library


@pytest.fixture(scope="session")
def libs_dir() -> Path:
return Path(__file__).parent / "libs"

@pytest.fixture(scope="session")
def lib(libs_dir: Path):
lib_file = libs_dir / "lib.yml"

with lib_file.open() as f:
input_lib = parse_yaml_library(f)

lib = resolve_library(input_lib)
return lib
188 changes: 188 additions & 0 deletions tests/functional/tests-yamel/libs/lib.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# Copyright (c) 2024, RTE (https://www.rte-france.com)
#
# See AUTHORS.txt
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# SPDX-License-Identifier: MPL-2.0
#
# This file is part of the Antares project.
library:
id: basic
description: Basic library

port-types:
- id: flow
description: A port which transfers power flow
fields:
- name: flow

models:
- id: node
ports:
- name: balance_port
type: flow
binding-constraints:
- name: balance
expression: sum_connections(balance_port.flow) = 0

- id: demand
parameters:
- name: demand
time-dependent: true
scenario-dependent: true
ports:
- name: balance_port
type: flow
port-field-definitions:
- port: balance_port
field: flow
definition: -demand

- id: production
parameters:
- name: cost
time-dependent: false
scenario-dependent: false
- name: p_max
time-dependent: false
scenario-dependent: false
variables:
- name: generation
lower-bound: 0
upper-bound: p_max
ports:
- name: balance_port
type: flow
port-field-definitions:
- port: balance_port
field: flow
definition: generation
objective: expec(sum(cost * generation))

- id: production_with_min
parameters:
- name: cost
time-dependent: false
scenario-dependent: false
- name: p_max
time-dependent: false
scenario-dependent: false
- name: p_min
time-dependent: false
scenario-dependent: false
variables:
- name: generation
lower-bound: p_min
upper-bound: p_max
ports:
- name: balance_port
type: flow
port-field-definitions:
- port: balance_port
field: flow
definition: generation
objective: expec(sum(cost * generation))

- id: link
parameters:
- name: f_max
time-dependent: false
scenario-dependent: false
variables:
- name: input
lower-bound: -f_max
upper-bound: f_max
ports:
- name: out_port
type: flow
- name: in_port
type: flow
port-field-definitions:
- port: out_port
field: flow
definition: input
- port: in_port
field: flow
definition: -input

- id: spillage
parameters:
- name: cost
time-dependent: false
scenario-dependent: false
variables:
- name: input
lower-bound: 0
ports:
- name: balance_port
type: flow
port-field-definitions:
- port: balance_port
field: flow
definition: -input
objective: expec(sum(cost * input))

- id: unsuplied
parameters:
- name: cost
time-dependent: false
scenario-dependent: false
variables:
- name: output
lower-bound: 0
ports:
- name: balance_port
type: flow
port-field-definitions:
- port: balance_port
field: flow
definition: output
objective: expec(sum(cost * output))

- id: thermal_cluster
parameters:
- name: p_max
- name: p_min
- name: cost
- name: d_min_up
- name: d_min_down
- name: nb_units_max
- name: nb_failures
variables:
- name: nb_units_on
lower-bound: 0
upper-bound: nb_units_max
variable-type: integer
- name: nb_starting
lower-bound: 0
upper-bound: nb_units_max
variable-type: integer
- name: nb_stoping
lower-bound: 0
upper-bound: nb_units_max
variable-type: integer
- name: production
lower-bound: 0
upper-bound: nb_units_max * p_max
ports:
- name: balance_port
type: flow
port-field-definitions:
- port: balance_port
field: flow
definition: production
constraints:
- name: max production
expression: production <= nb_units_on * p_max
- name: min production
expression: production >= nb_units_on * p_min
- name: on units variation
expression: nb_units_on = nb_units_on[-1] + nb_starting - nb_stoping
- name: starting time
expression: sum(nb_starting[-d_min_up + 1 .. 0]) <= nb_units_on
- name: stoping time
expression: sum(nb_stoping[-d_min_down + 1 .. 0]) <= nb_units_max - nb_units_on
objective: expec(sum(cost * production))
Loading

0 comments on commit 635c75d

Please sign in to comment.