Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
097f73f
Overhaul types
FBumann Nov 14, 2025
01d4c2d
Introduce Bool data
FBumann Nov 14, 2025
a5b37a7
Introduce Bool data
FBumann Nov 14, 2025
cd5b72b
Fix typehints
FBumann Nov 14, 2025
f188f04
Add EffectData type
FBumann Nov 14, 2025
d8bf7f2
EffectData Type - Complete Redesign
FBumann Nov 14, 2025
bfc645e
Use NumericData instead of Data
FBumann Nov 14, 2025
2a06dfb
Update type hints
FBumann Nov 14, 2025
9e11da8
Update type hints
FBumann Nov 14, 2025
d772169
Use | instead of Union
FBumann Nov 14, 2025
18d5162
Use | instead of Union
FBumann Nov 14, 2025
242eecd
Direct type hints
FBumann Nov 15, 2025
d82c5d2
Use direct type hints instead of subscripts
FBumann Nov 15, 2025
d4f4df0
Update type hints
FBumann Nov 15, 2025
540f1f8
Update type hints
FBumann Nov 15, 2025
cfceef6
Update type documentation
FBumann Nov 15, 2025
d0fac14
Update type documentation
FBumann Nov 15, 2025
1187a2c
Update type documentation
FBumann Nov 15, 2025
8abf6fb
Add another datatype
FBumann Nov 15, 2025
73dc336
Fix typehints
FBumann Nov 15, 2025
c5afcbd
Fix typehints
FBumann Nov 15, 2025
559a9fe
Fix validation in linear_converter classes
FBumann Nov 15, 2025
94a0ca4
Fix validation in linear_converter classes
FBumann Nov 15, 2025
ef1a2ee
pre commit run
FBumann Nov 15, 2025
4fa6c6b
Fix Effect type
FBumann Nov 15, 2025
12bc0a6
Improve Fix: check_bounds function to allow for more types
FBumann Nov 15, 2025
9b4781d
Type Hints: OnOffParameters attribute
FBumann Nov 15, 2025
ae59f91
ShareAllocationModel None/inf inconsistency
FBumann Nov 15, 2025
2dd882a
4. Documentation: Effect type docstrings (flixopt/types.py:82-92)
FBumann Nov 15, 2025
c80804e
Remove types from init
FBumann Nov 15, 2025
d70958b
Lets type hints define types instead of docstring
FBumann Nov 15, 2025
3dda003
Revert changes adressed in another PR
FBumann Nov 15, 2025
420085e
Update CHANGELOG.md
FBumann Nov 15, 2025
a76090d
Update CHANGELOG.md
FBumann Nov 15, 2025
1fe8f99
Merge branch 'main' into feature/types-direct
FBumann Nov 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,30 +51,48 @@ If upgrading from v2.x, see the [v3.0.0 release notes](https://github.com/flixOp

## [Unreleased] - ????-??-??

**Summary**:
**Summary**: Type system overhaul with comprehensive type hints for better IDE support and code clarity.

If upgrading from v2.x, see the [v3.0.0 release notes](https://github.com/flixOpt/flixOpt/releases/tag/v3.0.0) and [Migration Guide](https://flixopt.github.io/flixopt/latest/user-guide/migration-guide-v3/).

### ✨ Added
- **New type system** (`flixopt/types.py`):
- Introduced dimension-aware type aliases using suffix notation (`_TPS`, `_PS`, `_S`) to clearly indicate which dimensions data can have
- Added `Numeric_TPS`, `Numeric_PS`, `Numeric_S` for numeric data with Time/Period/Scenario dimensions
- Added `Bool_TPS`, `Bool_PS`, `Bool_S` for boolean data with dimension support
- Added `Effect_TPS`, `Effect_PS`, `Effect_S` for effect dictionaries with dimension support
- Added `Scalar` type for scalar-only numeric values
- Added `NumericOrBool` utility type for internal use
- Type system supports scalars, numpy arrays, pandas Series/DataFrames, and xarray DataArrays

### 💥 Breaking Changes

### ♻️ Changed
- **Code structure**: Removed `commons.py` module and moved all imports directly to `__init__.py` for cleaner code organization (no public API changes)
- **Type handling improvements**: Updated internal data handling to work seamlessly with the new type system

### 🗑️ Deprecated

### 🔥 Removed

### 🐛 Fixed
- Fixed `ShareAllocationModel` inconsistency where None/inf conversion happened in `__init__` instead of during modeling, which could cause issues with parameter validation
- Fixed numerous type hint inconsistencies across the codebase

### 🔒 Security

### 📦 Dependencies
- Updated `mkdocs-material` to v9.6.23

### 📝 Docs
- Enhanced documentation in `flixopt/types.py` with comprehensive examples and dimension explanation table
- Clarified Effect type docstrings - Effect types are dicts, but single numeric values work through union types
- Added clarifying comments in `effects.py` explaining parameter handling and transformation
- Improved OnOffParameters attribute documentation


### 👷 Development
- Added test for FlowSystem resampling

### 🚧 Known Issues

Expand Down
4 changes: 2 additions & 2 deletions flixopt/calculation.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from .aggregation import Aggregation, AggregationModel, AggregationParameters
from .components import Storage
from .config import CONFIG
from .core import DataConverter, Scalar, TimeSeriesData, drop_constant_arrays
from .core import DataConverter, TimeSeriesData, drop_constant_arrays
from .features import InvestmentModel
from .flow_system import FlowSystem
from .results import CalculationResults, SegmentedCalculationResults
Expand Down Expand Up @@ -103,7 +103,7 @@ def __init__(
self._modeled = False

@property
def main_results(self) -> dict[str, Scalar | dict]:
def main_results(self) -> dict[str, int | float | dict]:
from flixopt.features import InvestmentModel

main_results = {
Expand Down
43 changes: 22 additions & 21 deletions flixopt/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import xarray as xr

from . import io as fx_io
from .core import PeriodicDataUser, PlausibilityError, TemporalData, TemporalDataUser
from .core import PlausibilityError
from .elements import Component, ComponentModel, Flow
from .features import InvestmentModel, PiecewiseModel
from .interface import InvestParameters, OnOffParameters, PiecewiseConversion
Expand All @@ -23,6 +23,7 @@
import linopy

from .flow_system import FlowSystem
from .types import Numeric_PS, Numeric_TPS

logger = logging.getLogger('flixopt')

Expand Down Expand Up @@ -169,7 +170,7 @@ def __init__(
inputs: list[Flow],
outputs: list[Flow],
on_off_parameters: OnOffParameters | None = None,
conversion_factors: list[dict[str, TemporalDataUser]] | None = None,
conversion_factors: list[dict[str, Numeric_TPS]] | None = None,
piecewise_conversion: PiecewiseConversion | None = None,
meta_data: dict | None = None,
):
Expand Down Expand Up @@ -386,17 +387,17 @@ def __init__(
label: str,
charging: Flow,
discharging: Flow,
capacity_in_flow_hours: PeriodicDataUser | InvestParameters,
relative_minimum_charge_state: TemporalDataUser = 0,
relative_maximum_charge_state: TemporalDataUser = 1,
initial_charge_state: PeriodicDataUser | Literal['lastValueOfSim'] = 0,
minimal_final_charge_state: PeriodicDataUser | None = None,
maximal_final_charge_state: PeriodicDataUser | None = None,
relative_minimum_final_charge_state: PeriodicDataUser | None = None,
relative_maximum_final_charge_state: PeriodicDataUser | None = None,
eta_charge: TemporalDataUser = 1,
eta_discharge: TemporalDataUser = 1,
relative_loss_per_hour: TemporalDataUser = 0,
capacity_in_flow_hours: Numeric_PS | InvestParameters,
relative_minimum_charge_state: Numeric_TPS = 0,
relative_maximum_charge_state: Numeric_TPS = 1,
initial_charge_state: Numeric_PS | Literal['lastValueOfSim'] = 0,
minimal_final_charge_state: Numeric_PS | None = None,
maximal_final_charge_state: Numeric_PS | None = None,
relative_minimum_final_charge_state: Numeric_PS | None = None,
relative_maximum_final_charge_state: Numeric_PS | None = None,
eta_charge: Numeric_TPS = 1,
eta_discharge: Numeric_TPS = 1,
relative_loss_per_hour: Numeric_TPS = 0,
prevent_simultaneous_charge_and_discharge: bool = True,
balanced: bool = False,
meta_data: dict | None = None,
Expand All @@ -413,8 +414,8 @@ def __init__(
self.charging = charging
self.discharging = discharging
self.capacity_in_flow_hours = capacity_in_flow_hours
self.relative_minimum_charge_state: TemporalDataUser = relative_minimum_charge_state
self.relative_maximum_charge_state: TemporalDataUser = relative_maximum_charge_state
self.relative_minimum_charge_state: Numeric_TPS = relative_minimum_charge_state
self.relative_maximum_charge_state: Numeric_TPS = relative_maximum_charge_state

self.relative_minimum_final_charge_state = relative_minimum_final_charge_state
self.relative_maximum_final_charge_state = relative_maximum_final_charge_state
Expand All @@ -423,9 +424,9 @@ def __init__(
self.minimal_final_charge_state = minimal_final_charge_state
self.maximal_final_charge_state = maximal_final_charge_state

self.eta_charge: TemporalDataUser = eta_charge
self.eta_discharge: TemporalDataUser = eta_discharge
self.relative_loss_per_hour: TemporalDataUser = relative_loss_per_hour
self.eta_charge: Numeric_TPS = eta_charge
self.eta_discharge: Numeric_TPS = eta_discharge
self.relative_loss_per_hour: Numeric_TPS = relative_loss_per_hour
self.prevent_simultaneous_charge_and_discharge = prevent_simultaneous_charge_and_discharge
self.balanced = balanced

Expand Down Expand Up @@ -663,8 +664,8 @@ def __init__(
out1: Flow,
in2: Flow | None = None,
out2: Flow | None = None,
relative_losses: TemporalDataUser | None = None,
absolute_losses: TemporalDataUser | None = None,
relative_losses: Numeric_TPS | None = None,
absolute_losses: Numeric_TPS | None = None,
on_off_parameters: OnOffParameters = None,
prevent_simultaneous_flows_in_both_directions: bool = True,
balanced: bool = False,
Expand Down Expand Up @@ -916,7 +917,7 @@ def _initial_and_final_charge_state(self):
)

@property
def _absolute_charge_state_bounds(self) -> tuple[TemporalData, TemporalData]:
def _absolute_charge_state_bounds(self) -> tuple[xr.DataArray, xr.DataArray]:
relative_lower_bound, relative_upper_bound = self._relative_charge_state_bounds
if not isinstance(self.element.capacity_in_flow_hours, InvestParameters):
return (
Expand Down
37 changes: 3 additions & 34 deletions flixopt/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,9 @@
import pandas as pd
import xarray as xr

logger = logging.getLogger('flixopt')

Scalar = int | float
"""A single number, either integer or float."""

PeriodicDataUser = int | float | np.integer | np.floating | np.ndarray | pd.Series | pd.DataFrame | xr.DataArray
"""User data which has no time dimension. Internally converted to a Scalar or an xr.DataArray without a time dimension."""
from .types import NumericOrBool

PeriodicData = xr.DataArray
"""Internally used datatypes for periodic data."""
logger = logging.getLogger('flixopt')

FlowSystemDimensions = Literal['time', 'period', 'scenario']
"""Possible dimensions of a FlowSystem."""
Expand Down Expand Up @@ -150,15 +143,6 @@ def agg_weight(self):
return self.aggregation_weight


TemporalDataUser = (
int | float | np.integer | np.floating | np.ndarray | pd.Series | pd.DataFrame | xr.DataArray | TimeSeriesData
)
"""User data which might have a time dimension. Internally converted to an xr.DataArray with time dimension."""

TemporalData = xr.DataArray | TimeSeriesData
"""Internally used datatypes for temporal data (data with a time dimension)."""


class DataConverter:
"""
Converts various data types into xarray.DataArray with specified target coordinates.
Expand Down Expand Up @@ -405,16 +389,7 @@ def _broadcast_dataarray_to_target_specification(
@classmethod
def to_dataarray(
cls,
data: int
| float
| bool
| np.integer
| np.floating
| np.bool_
| np.ndarray
| pd.Series
| pd.DataFrame
| xr.DataArray,
data: NumericOrBool,
coords: dict[str, pd.Index] | None = None,
) -> xr.DataArray:
"""
Expand Down Expand Up @@ -637,9 +612,3 @@ def drop_constant_arrays(ds: xr.Dataset, dim: str = 'time', drop_arrays_without_
)

return ds.drop_vars(drop_vars)


# Backward compatibility aliases
# TODO: Needed?
NonTemporalDataUser = PeriodicDataUser
NonTemporalData = PeriodicData
54 changes: 18 additions & 36 deletions flixopt/effects.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,14 @@
import numpy as np
import xarray as xr

from . import io as fx_io
from .core import PeriodicDataUser, Scalar, TemporalData, TemporalDataUser
from .features import ShareAllocationModel
from .structure import Element, ElementContainer, ElementModel, FlowSystemModel, Submodel, register_class_for_io

if TYPE_CHECKING:
from collections.abc import Iterator

from .flow_system import FlowSystem
from .types import Effect_PS, Effect_TPS, Numeric_PS, Numeric_TPS, Scalar

Comment on lines 22 to 27
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Import the new public type aliases under TYPE_CHECKING

This module uses Effect_TPS, Effect_PS, Numeric_TPS, and Numeric_PS in annotations (e.g., Effect.__init__, EffectCollection.create_effect_values_dict) but the TYPE_CHECKING import only brings in EffectData, NumericData, Period, Scalar, Scenario, Time. For static type checking tools, these aliases will appear undefined.

Extend the .types import here to include the new aliases that are referenced in this file.

🤖 Prompt for AI Agents
In flixopt/effects.py around lines 22 to 27, the TYPE_CHECKING import from
.types is missing the public type aliases Effect_TPS, Effect_PS, Numeric_TPS,
and Numeric_PS that are used elsewhere in this module; update the TYPE_CHECKING
block to import these four aliases from .types so static type checkers see them
and annotations resolve correctly.

logger = logging.getLogger('flixopt')

Expand Down Expand Up @@ -52,7 +51,7 @@ class Effect(Element):
is_objective: If True, this effect serves as the optimization objective function.
Only one effect can be marked as objective per optimization.
share_from_temporal: Temporal cross-effect contributions.
Maps temporal contributions from other effects to this effect
Maps temporal contributions from other effects to this effect.
share_from_periodic: Periodic cross-effect contributions.
Maps periodic contributions from other effects to this effect.
minimum_temporal: Minimum allowed total contribution across all timesteps.
Expand All @@ -62,7 +61,6 @@ class Effect(Element):
minimum_periodic: Minimum allowed total periodic contribution.
maximum_periodic: Maximum allowed total periodic contribution.
minimum_total: Minimum allowed total effect (temporal + periodic combined).
maximum_total: Maximum allowed total effect (temporal + periodic combined).
meta_data: Used to store additional information. Not used internally but saved
in results. Only use Python native types.

Expand Down Expand Up @@ -170,25 +168,28 @@ def __init__(
meta_data: dict | None = None,
is_standard: bool = False,
is_objective: bool = False,
share_from_temporal: TemporalEffectsUser | None = None,
share_from_periodic: PeriodicEffectsUser | None = None,
minimum_temporal: PeriodicEffectsUser | None = None,
maximum_temporal: PeriodicEffectsUser | None = None,
minimum_periodic: PeriodicEffectsUser | None = None,
maximum_periodic: PeriodicEffectsUser | None = None,
minimum_per_hour: TemporalDataUser | None = None,
maximum_per_hour: TemporalDataUser | None = None,
minimum_total: Scalar | None = None,
maximum_total: Scalar | None = None,
share_from_temporal: Effect_TPS | Numeric_TPS | None = None,
share_from_periodic: Effect_PS | Numeric_PS | None = None,
minimum_temporal: Numeric_PS | None = None,
maximum_temporal: Numeric_PS | None = None,
minimum_periodic: Numeric_PS | None = None,
maximum_periodic: Numeric_PS | None = None,
minimum_per_hour: Numeric_TPS | None = None,
maximum_per_hour: Numeric_TPS | None = None,
minimum_total: Numeric_PS | None = None,
maximum_total: Numeric_PS | None = None,
**kwargs,
):
super().__init__(label, meta_data=meta_data)
self.unit = unit
self.description = description
self.is_standard = is_standard
self.is_objective = is_objective
self.share_from_temporal: TemporalEffectsUser = share_from_temporal if share_from_temporal is not None else {}
self.share_from_periodic: PeriodicEffectsUser = share_from_periodic if share_from_periodic is not None else {}
# Share parameters accept Effect_* | Numeric_* unions (dict or single value).
# Store as-is here; transform_data() will normalize via fit_effects_to_model_coords().
# Default to {} when None (no shares defined).
self.share_from_temporal = share_from_temporal if share_from_temporal is not None else {}
self.share_from_periodic = share_from_periodic if share_from_periodic is not None else {}

# Handle backwards compatibility for deprecated parameters using centralized helper
minimum_temporal = self._handle_deprecated_kwarg(
Expand Down Expand Up @@ -436,18 +437,6 @@ def _do_modeling(self):
)


TemporalEffectsUser = TemporalDataUser | dict[str, TemporalDataUser] # User-specified Shares to Effects
""" This datatype is used to define a temporal share to an effect by a certain attribute. """

PeriodicEffectsUser = PeriodicDataUser | dict[str, PeriodicDataUser] # User-specified Shares to Effects
""" This datatype is used to define a scalar share to an effect by a certain attribute. """

TemporalEffects = dict[str, TemporalData] # User-specified Shares to Effects
""" This datatype is used internally to handle temporal shares to an effect. """

PeriodicEffects = dict[str, Scalar]
""" This datatype is used internally to handle scalar shares to an effect. """

EffectExpr = dict[str, linopy.LinearExpression] # Used to create Shares


Expand Down Expand Up @@ -489,9 +478,7 @@ def add_effects(self, *effects: Effect) -> None:
self.add(effect) # Use the inherited add() method from ElementContainer
logger.info(f'Registered new Effect: {effect.label}')

def create_effect_values_dict(
self, effect_values_user: PeriodicEffectsUser | TemporalEffectsUser
) -> dict[str, Scalar | TemporalDataUser] | None:
def create_effect_values_dict(self, effect_values_user: Numeric_TPS | Effect_TPS | None) -> Effect_TPS | None:
"""Converts effect values into a dictionary. If a scalar is provided, it is associated with a default effect type.

Examples:
Expand Down Expand Up @@ -851,8 +838,3 @@ def tuples_to_adjacency_list(edges: list[tuple[str, str]]) -> dict[str, list[str
graph[target] = []

return graph


# Backward compatibility aliases
NonTemporalEffectsUser = PeriodicEffectsUser
NonTemporalEffects = PeriodicEffects
Loading