Conversation
| class SolverVariableInfo: | ||
| """ | ||
| Helper class for constructing the structure file | ||
| for Bender solver. It keeps track of the corresponding |
There was a problem hiding this comment.
Benders (name of a mathematician)
| """ | ||
|
|
||
| name: str | ||
| column: int |
There was a problem hiding this comment.
Maybe column_id or col_id is more explicit ?
| key = TimestepComponentVariableKey( | ||
| component_id, variable_name, block_timestep, scenario | ||
| ) | ||
| if key not in self._component_variables: |
There was a problem hiding this comment.
Could you use get_or_add defined in utils.py
There was a problem hiding this comment.
Not quite. It checks a first dictionary and, doing so, it checks implicitly the second adding a new key to the second
| expression=instantiated_expression, | ||
| ) | ||
| simulator = 0 | ||
| xpansion_merged = 1 |
There was a problem hiding this comment.
What is the difference between simulator and xpansion_merged ?
There was a problem hiding this comment.
For now, none. I can remove it for better understanding, but the idea was to eventually differentiate both types of problem if needed
src/andromede/simulation/xpansion.py
Outdated
|
|
||
| """ | ||
| The xpansion module extends the optimization module | ||
| with Bender solver related functions |
| root_dir = os.getcwd() | ||
|
|
||
| if not os.path.isfile(root_dir + "/bin/benders"): | ||
| # TODO Maybe a more robust check and/or return value? |
There was a problem hiding this comment.
For now add an error message to explain the expected binary location
|
|
||
| output = OutputValues(problem) | ||
| expected_output = OutputValues() | ||
| expected_output.component("G1").var("generation").value = 200.0 |
There was a problem hiding this comment.
Check equality with float values with pytest.approx (update everywhere, even in previous unchanged tests)
There was a problem hiding this comment.
I'm working on it on another branch with other modifications to the OutputValues class. Stay tuned :)
tests/andromede/test_xpansion.py
Outdated
| status = problem.solver.Solve() | ||
|
|
||
| assert status == problem.solver.OPTIMAL | ||
| assert problem.solver.Objective().Value() == (45 * 200) + (490 * 100 + 10 * 100) + ( |
There was a problem hiding this comment.
Check equality with float values with pytest.approx
tests/andromede/test_xpansion.py
Outdated
| ) -> None: | ||
| """ | ||
| Same test as before but this time we separate master/subproblem and | ||
| export the problems in MPS format to be solved by the Bender solver in Xpansion |
debb29a to
3d6990c
Compare
src/andromede/simulation/xpansion.py
Outdated
| # TODO Maybe a more robust check and/or return value? | ||
| # For now, it won't look anywhere else because a new | ||
| # architecture should be discussed | ||
| print(root_dir + "/bin/benders executable not found. Returning True") |
There was a problem hiding this comment.
In the future you can use f-strings f"{root_dir}/bin/benders executable ..." that is easier to use and more readable when you have more complex strings
There was a problem hiding this comment.
Indeed a f-string would be nicer. Since It was a simple string, I didn't bother using one but you are right :)
| instantiated_constraint, | ||
| ) | ||
| for model_var in model.variables.values(): | ||
| if not _should_keep_in_model(self.problem_type, model_var.context): |
There was a problem hiding this comment.
Much clearer but I still find it even clearer to do if _should_keep_in_model(): even if it makes one more indentation
There was a problem hiding this comment.
It would be clarer, from the design point of view, that the choice of "what should be included in the problem" is not hidden deep in the problem building code.
Instead, we could have a small interface that makes this choice, as an entry of build_problem, like:
class ModelSelectionStrategy(ABC):
@abstractmethod
def get_variables(model: Model) -> List[Variable]:
...
@abstractmethod
def get_objective(model: Model) -> ExpressionNode:
...This would have 2 implementations, one for investment, one for operation.
Then you won't need the problem_type argument to build_problem, just this strategy:
master = build_problem(
network,
database,
block,
scenarios,
problem_name="master",
border_management=border_management,
solver_id=solver_id,
strategy=InvestmentProblemStrategy(),
)
tests/andromede/test_xpansion.py
Outdated
|
|
||
| @pytest.fixture | ||
| def wind_cluster_candidate() -> Model: | ||
| WIND_CLUSTER_CANDIDATE = model( |
There was a problem hiding this comment.
Rename this candidate as DISCRETE_CANDIDATE which is more explicit regarding its behaviour and also because usually wind is considered a continuous candidate (whereas gas plant are not for instance)
sylvlecl
left a comment
There was a problem hiding this comment.
A few technical comment + proposition to isolate the particularities of operational and investment problems outside from the build_problem logic, by moving it to a dedicated interface.
This would isolate this logic better.
src/andromede/model/common.py
Outdated
|
|
||
|
|
||
| class ProblemContext(Enum): | ||
| operational = 0 |
There was a problem hiding this comment.
Upper case for consistency sake with other parts of the code.
| lower_bound: Optional[ExpressionNode] = None, | ||
| upper_bound: Optional[ExpressionNode] = None, | ||
| structural_type: Optional[IndexingStructure] = None, | ||
| structure: IndexingStructure = IndexingStructure(True, True), |
There was a problem hiding this comment.
Note that it's a little dangerous to use such objects as default in python, because this object will be shared by all calls:
it means that if it's modified at some point, it will be modified for all calls.
However here it's OK because IndexingStructure is immutable (frozen).
There was a problem hiding this comment.
I hadn't take it into account, thanks for the heads up!
In this case then you are ok with leaving it like this?
| if model_var.lower_bound: | ||
| instantiated_lb_expr = _instantiate_model_expression( | ||
| model_var.lower_bound, component.id, opt_context | ||
| merged = 0 |
There was a problem hiding this comment.
Actually this enum could be removed by using an interface for selecting what to include in the problem, see other comment.
It will make this part of the code agnostic from xpansion, which will be clearer.
There was a problem hiding this comment.
I am not sure to understand what you meant
| master = 1 | ||
| subproblem = 2 | ||
|
|
||
| name: str |
There was a problem hiding this comment.
If we don't use dataclass annotation, those fields define class attributes, not instance attributes --> they should be removed since they are actually not used
There was a problem hiding this comment.
I created a small snippet to check and it seems to work as I intended
There was a problem hiding this comment.
Indeed thanks for sharing the knowledge :)
| database: DataBase, | ||
| block: TimeBlock, | ||
| scenarios: int, | ||
| *, |
There was a problem hiding this comment.
I didn't know this syntax!
It forces to use following arguments as keyword arguments (as opposed to positional arguments).
https://peps.python.org/pep-3102/#specification
src/andromede/utils.py
Outdated
| return value | ||
|
|
||
|
|
||
| def serialize(filename: str, message: str, path: str = "outputs") -> bool: |
There was a problem hiding this comment.
we can use pathlib.Path for paths, for example it allows to use / operator to have OS-agnostic construction of paths:
file = open(path / filename, "w")There was a problem hiding this comment.
I agree, but since it was just for the PoC, I didn't think we needed it, but I can change it :)
src/andromede/utils.py
Outdated
| os.makedirs(path, exist_ok=True) | ||
| file = open(f"{path}/{filename}", "w") | ||
|
|
||
| except os.error: |
There was a problem hiding this comment.
Why not let the exception pass through ? Do we really want to ignore this error sometimes?
Most of the time it will be better to get an exception instead of ignoring this, otherwise we can get unexpected behaviour afterwards (like xpansion not having one of its input files).
There was a problem hiding this comment.
Initially the idea was to catch the return False, but I am not using it this way anyway. I agree that an exception would be better now
| instantiated_constraint, | ||
| ) | ||
| for model_var in model.variables.values(): | ||
| if not _should_keep_in_model(self.problem_type, model_var.context): |
There was a problem hiding this comment.
It would be clarer, from the design point of view, that the choice of "what should be included in the problem" is not hidden deep in the problem building code.
Instead, we could have a small interface that makes this choice, as an entry of build_problem, like:
class ModelSelectionStrategy(ABC):
@abstractmethod
def get_variables(model: Model) -> List[Variable]:
...
@abstractmethod
def get_objective(model: Model) -> ExpressionNode:
...This would have 2 implementations, one for investment, one for operation.
Then you won't need the problem_type argument to build_problem, just this strategy:
master = build_problem(
network,
database,
block,
scenarios,
problem_name="master",
border_management=border_management,
solver_id=solver_id,
strategy=InvestmentProblemStrategy(),
)| scenarios = 1 | ||
|
|
||
| xpansion = build_xpansion_problem(network, database, TimeBlock(1, [0]), scenarios) | ||
| assert xpansion.run() |
There was a problem hiding this comment.
If I understand correctly, it's a little hacky that this passes the CI :)
It would be great to adapt the CI workflow to download the asset from xpansionn, so that it actually runs:
https://github.com/AntaresSimulatorTeam/antares-xpansion/releases/tag/v1.2.1
For example that's what xpansion does for its CI, it downloads antares-solver with a small script step.
We could do that in another PR if you prefer.
There was a problem hiding this comment.
You understood it perfectly 😅
I prefer doing it in another PR indeed.
When we create such PR, we could allow it to throw an error if the Benders solvers is not found (then again the question will arise for the other developers that doesn't have it installed locally )
There was a problem hiding this comment.
Indeed, maybe we'll need to separate integration tests and unit tests, so that developers don't have to run this one.
src/andromede/simulation/xpansion.py
Outdated
| from andromede.utils import serialize | ||
|
|
||
|
|
||
| class XpansionProblem: |
There was a problem hiding this comment.
What about naming it InvestmentProblem, to make reference to the actual feature instead of the existing tool ?
There was a problem hiding this comment.
I agree that we could rename it to something not tool related, but since we can solve a investment problem doing a frontal LP, I don't think this name would be very clear.
The main idea here is to separate both master and sub-problem to use the Benders solver. In that case, BendersProblem would be better, no ?
What do you think @tbittar ?
There was a problem hiding this comment.
Yes exactly I suggest BendersProblem or DecomposedProblem or BendersDecomposedProblem as we do not want to have any operational knowledge at problem / solver level
There was a problem hiding this comment.
The idea here is really that we provide a decomposed form of the big initial problem (and more specially a decomposition suited for a resolution with a Benders algorithm)
There was a problem hiding this comment.
Done. I went for BendersDecomposedProblem
| solver: lp.Solver | ||
| context: OptimizationContext | ||
| problem_type: Type | ||
| strategy: Type[ModelSelectionStrategy] |
There was a problem hiding this comment.
ModelSelectionStrategy is not enough for typing ?
There was a problem hiding this comment.
Unfortunately no, because here we are not dealing with instances of the class but the class itself so ModelSelectionStrategy wouldn't pass for MyPy
| solver: lp.Solver, | ||
| opt_context: OptimizationContext, | ||
| opt_type: Type = Type.merged, | ||
| opt_strategy: Type[ModelSelectionStrategy] = MergedProblemStrategy, |
|
|
||
| except os.error: | ||
| return False | ||
| Will throw an exception if it fails to create dir or ro open file |
sylvlecl
left a comment
There was a problem hiding this comment.
Thanks, 2 more minor design comments that I'd prefer handled, but not a must-have.
A first working dev for the integration of Benders to the PoC