Skip to content

Feature/simulation table13082025#125

Merged
tbittar merged 14 commits intomainfrom
feature/simulationTable13082025
Aug 14, 2025
Merged

Feature/simulation table13082025#125
tbittar merged 14 commits intomainfrom
feature/simulationTable13082025

Conversation

@aalzoobi
Copy link
Collaborator

simulation table added,
output value extended to make simultation table based only on output values. Except for objective value (why not)
Also, tests have been updated

@aalzoobi aalzoobi requested a review from tbittar August 14, 2025 07:33
@aalzoobi aalzoobi self-assigned this Aug 14, 2025
_name: str
_value: Dict[TimeScenarioIndex, float] = field(init=False, default_factory=dict)
_size: Tuple[int, int] = field(init=False, default=(0, 0))
_basis_status: Optional[str] = field(default=None, init=False) # NEW Ali
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove useless comment

return string

def _build_components(self) -> None:
def _build_components(self) -> None: # old build
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove comment

_basis_status: Optional[str] = field(default=None, init=False) # NEW Ali
ignore: bool = field(default=False, init=False)

def __init__(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You do not need an __init__ method, it is generated by the dataclass, using the information given in the field(default=..., init= ...)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only difference I see with the previous behaviour is that you want to set _value to value if is given as argument, however this is forbidden as it has init=False so your init method basically is the one automatically generated

if self.problem.solver.IsMip():
return
for key, value in self.problem.context.get_all_component_variables().items():
var_obj = self.component(key.component_id).var(str(key.variable_name))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just write self.component(key.component_id).var(str(key.variable_name)) = value.basis_status() inside the for loop in _build_components ?

  • This avoids going through the dict of component variables a second time (one in build_components, one in _set_basis in your case)
  • The logic is that build_components build the whole component, thereofore setting all properties of the variables, including the basis_status. Therefore it is weird to split the basis setting part from the solution value setting
  • Avoid copying the variable object, just use a one liner to set the basis status

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Obviously, move the mip check in consequence

simulation_id: str
df: pd.DataFrame

def __init__(self, simulation_id: Optional[str] = None, mode: str = "eco") -> None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove mode

self.df = pd.DataFrame(rows)

# Ensure problem is set before accessing solver
if output_values.problem is None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check should be done at the very beginning of the build (you need the problem to access the block length, scenarios, ...)
And it is best practice to raise errors as soon as possible, to avoid losing time

"value": objective_value,
"basis_status": None,
}
self.df.loc[len(self.df)] = [obj_row.get(col, None) for col in self.df.columns]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not not work ?

Suggested change
self.df.loc[len(self.df)] = [obj_row.get(col, None) for col in self.df.columns]
self.df.loc[len(self.df)] = obj_row

@@ -0,0 +1,69 @@
# Standard library imports
from dataclasses import dataclass
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Useless import

from pathlib import Path

# Third-party imports
import pandas as pd
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Useless import


# --- Assertions ---
assert csv_path.exists(), "Simulation table CSV not created"
assert not simu_table.df.empty, "Simulation table dataframe is empty"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok for now, but we may think of a more robust test

  • When we split the class with the builder and writer, the test will be on the content of the dataframe (maybe check its length, its values if possible (may be hard))

@aalzoobi aalzoobi requested a review from tbittar August 14, 2025 15:53
timestep = 0 if timestep is None else timestep
scenario = 0 if scenario is None else scenario
key = TimeScenarioIndex(timestep, scenario)
if key not in self._value:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this has nothing to do here, this is done by the set method. Maybe the simplest is to add self._basis_status[key] = status to the _set method, in the logic that _set is responsible for setting all relevant attribute of the variable (then the isMip check should also be in _set)

BLOCK_TIME_INDEX = "block-time-index"
SCENARIO_INDEX = "scenario-index"
VALUE = "value"
BASIS_STATUS = "basis-status"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The convention is to use _ rather than - in column names

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is writne "in kebab case" in the specs. of the Modeleur. I thought It is wise to keep consistent with it. Up to you

context = output_values.problem.context
block = context._block.id
block_size = context.block_length()
absolute_time_offset = (block - 1) * block_size
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assumes that all blocks are the same size... this may not be the case in the most general case

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is why I think this is ok to have this as argument

+ ts_index.time,
SimulationColumns.BLOCK_TIME_INDEX.value: ts_index.time,
SimulationColumns.SCENARIO_INDEX.value: ts_index.scenario,
SimulationColumns.VALUE.value: value,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need .value everywhere ?

class SimulationTableWriter:
"""Handles writing simulation tables to CSV."""

def __init__(self, simulation_table: pd.DataFrame) -> None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could use a dataclass

@tbittar tbittar merged commit 0b3b0dc into main Aug 14, 2025
2 checks passed
@tbittar tbittar deleted the feature/simulationTable13082025 branch August 14, 2025 16:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants