Committable extendable
Key Updates
- Extended the unit-commitment formulation to cover committable + extendable generators/links via a big‑M approach
- Added a post-processing warning that flags solutions breaching the inferred big‑M bound
Checklist
- [x] Code changes are sufficiently documented; i.e. new functions contain docstrings and further explanations may be given in
doc. - [x] Unit tests for new features were added (if applicable).
- [x] A note for the release notes
doc/release_notes.rstof the upcoming release is included. - [x] I consent to the release of this PR's code under the MIT license.
Cool, is 1e6 a good bound or too large?
that's the question. Too low would be bad though... I have a function in the pipeline to infer a big m value from the network, and returns 1e6 only if no reasonable value can be extracted. I'm pushing it and you could have a look?
I dient take the time to look I to the formulations, but if you are having troubles maybe you can have a look into our formulation (using linopy, so should be easy to understand).
Implementation
@staticmethod
def scaled_bounds_with_state(
model: Submodel,
variable: linopy.Variable,
scaling_variable: linopy.Variable,
relative_bounds: tuple[TemporalData, TemporalData],
scaling_bounds: tuple[TemporalData, TemporalData],
variable_state: linopy.Variable,
name: str = None,
) -> list[linopy.Constraint]:
"""Constraint a variable by scaling bounds with binary state control.
variable ∈ {0, [max(ε, lower_relative_bound) * scaling_variable, upper_relative_bound * scaling_variable]}
Mathematical Formulation (Big-M):
(variable_state - 1) * M_misc + scaling_variable * rel_lower ≤ variable ≤ scaling_variable * rel_upper
variable_state * big_m_lower ≤ variable ≤ variable_state * big_m_upper
Where:
M_misc = scaling_max * rel_lower
big_m_upper = scaling_max * rel_upper
big_m_lower = max(ε, scaling_min * rel_lower)
Args:
model: The optimization model instance
variable: Variable to be bounded
scaling_variable: Variable that scales the bound factors
relative_bounds: Tuple of (lower_factor, upper_factor) relative to scaling variable
scaling_bounds: Tuple of (scaling_min, scaling_max) bounds of the scaling variable
variable_state: Binary variable for on/off control
name: Optional name prefix for constraints
Returns:
List[linopy.Constraint]: List of constraint objects
"""
if not isinstance(model, Submodel):
raise ValueError('BoundingPatterns.scaled_bounds_with_state() can only be used with a Submodel')
rel_lower, rel_upper = relative_bounds
scaling_min, scaling_max = scaling_bounds
name = name or f'{variable.name}'
big_m_misc = scaling_max * rel_lower
scaling_lower = model.add_constraints(
variable >= (variable_state - 1) * big_m_misc + scaling_variable * rel_lower, name=f'{name}|lb2'
)
scaling_upper = model.add_constraints(variable <= scaling_variable * rel_upper, name=f'{name}|ub2')
big_m_upper = rel_upper * scaling_max
big_m_lower = np.maximum(CONFIG.Modeling.epsilon, rel_lower * scaling_min)
binary_upper = model.add_constraints(variable_state * big_m_upper >= variable, name=f'{name}|ub1')
binary_lower = model.add_constraints(variable_state * big_m_lower <= variable, name=f'{name}|lb1')
return [scaling_lower, scaling_upper, binary_lower, binary_upper]
Usage
BoundingPatterns.scaled_bounds_with_state(
model=self,
variable=self.flow_rate,
scaling_variable=self._investment.size,
relative_bounds=self.relative_flow_rate_bounds,
scaling_bounds=(self.element.size.minimum_or_fixed_size, self.element.size.maximum_or_fixed_size),
variable_state=self.on_off.on,
)
with
| Attribute / Variable | Meaning / Description |
|---|---|
self.flow_rate |
Power |
self._investment.size |
Installed capacity |
self.relative_flow_rate_bounds |
Availability in percent → flow rate bounds = size × relative_bound |
self.element.size.minimum_or_fixed_size, self.element.size.maximum_or_fixed_size |
Minimum and maximum capacity or size |
variable_state |
Binary indicating the state of the flow rate (on/off) |
See flixopt
As discussed in #1007: This goes in master, but not before #1007 is ready to merge, in which it goes as well, to resolve all conflicts and mainly merge back all the new-opt changes.
Also, better docs are missing, but we can move that in 1007.
@coroa Could you have a final look here?