Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
be6572d
Try to add to_dataset to Elements
FBumann Jun 23, 2025
f63db8b
Remove TimeSeries
FBumann Jun 23, 2025
167fb2c
Remove TimeSeries
FBumann Jun 23, 2025
fc76adf
Rename conversion method to pattern: to_...
FBumann Jun 23, 2025
cc7b155
Move methods to FlowSystem
FBumann Jun 23, 2025
ec6e792
Drop nan values across time dimension if present
FBumann Jun 23, 2025
b42aad2
Allow lists of values to create DataArray
FBumann Jun 24, 2025
b55af45
Update resolving of FlowSystem
FBumann Jun 24, 2025
d5ace96
Simplify TimeSeriesData
FBumann Jun 24, 2025
4187f30
Move TImeSeriesData to Structure and simplyfy to inherrit from xarray…
FBumann Jun 24, 2025
617600f
Adjust IO
FBumann Jun 24, 2025
e80bba0
Move TimeSeriesData back to core.py and fix Conversion
FBumann Jun 24, 2025
387cac6
Adjust IO to account for attrs of DataArrays in a Dataset
FBumann Jun 24, 2025
27734cf
Rename transforming and connection methods in FlowSystem
FBumann Jun 24, 2025
4915b81
Compacted IO methods
FBumann Jun 24, 2025
fc5549a
Remove infos()
FBumann Jun 24, 2025
299ff43
remove from_dict() and to_dict()
FBumann Jun 24, 2025
abc22b1
Update __str__ of Interface
FBumann Jun 24, 2025
9b4c44c
Improve str and repr
FBumann Jun 24, 2025
0ab7ea6
Improve str and repr
FBumann Jun 24, 2025
1dcbbb0
Add docstring
FBumann Jun 24, 2025
9aec990
Unify IO stuff in Interface class
FBumann Jun 24, 2025
e370311
Improve test tu utilize __eq__ method
FBumann Jun 24, 2025
793e820
Make Interface class more robust and improve exceptions
FBumann Jun 24, 2025
b87d979
Add option to copy Interfaces (And the FlowSystem)
FBumann Jun 24, 2025
8ec265e
Make a copy of a FLowSytsem that gets reused in a second Calculation
FBumann Jun 25, 2025
a46fe64
Remove test_timeseries.py
FBumann Jun 25, 2025
201d066
Reorganizing Datatypes
FBumann Jun 25, 2025
10d2925
Remove TImeSeries and TimeSeriesCollection entirely
FBumann Jun 25, 2025
cf9d17f
Remove old method
FBumann Jun 25, 2025
bd52e05
Add option to get structure with stats of dataarrays
FBumann Jun 25, 2025
aa36689
Change __str__ method
FBumann Jun 25, 2025
63b1c92
Remove old methods
FBumann Jun 25, 2025
29062fa
remove old imports
FBumann Jun 25, 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
5 changes: 5 additions & 0 deletions examples/01_Simple/simple_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,14 @@
calculation = fx.FullCalculation(name='Sim1', flow_system=flow_system)
calculation.do_modeling() # Translate the model to a solvable form, creating equations and Variables

calculation2 = fx.FullCalculation(name='Sim2', flow_system=flow_system)
calculation2.do_modeling() # Translate the model to a solvable form, creating equations and Variables

# --- Solve the Calculation and Save Results ---
calculation.solve(fx.solvers.HighsSolver(mip_gap=0, time_limit_seconds=30))

calculation2.solve(fx.solvers.HighsSolver(mip_gap=0, time_limit_seconds=30))

# --- Analyze Results ---
calculation.results['Fernwärme'].plot_node_balance_pie()
calculation.results['Fernwärme'].plot_node_balance()
Expand Down
38 changes: 19 additions & 19 deletions flixopt/calculation.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from .flow_system import FlowSystem
from .results import CalculationResults, SegmentedCalculationResults
from .solvers import _Solver
from .structure import SystemModel, copy_and_convert_datatypes, get_compact_representation
from .structure import SystemModel

logger = logging.getLogger('flixopt')

Expand All @@ -54,7 +54,13 @@ def __init__(
folder: folder where results should be saved. If None, then the current working directory is used.
"""
self.name = name
if flow_system.used_in_calculation:
logging.warning(f'FlowSystem {flow_system.name} is already used in a calculation. '
f'Creating a copy for Calculation "{self.name}".')
flow_system = flow_system.copy()

self.flow_system = flow_system
self.flow_system._used_in_calculation = True
self.model: Optional[SystemModel] = None
self.active_timesteps = active_timesteps

Expand Down Expand Up @@ -119,7 +125,7 @@ def main_results(self) -> Dict[str, Union[Scalar, Dict]]:
def summary(self):
return {
'Name': self.name,
'Number of timesteps': len(self.flow_system.time_series_collection.timesteps),
'Number of timesteps': len(self.flow_system.timesteps),
'Calculation Type': self.__class__.__name__,
'Constraints': self.model.constraints.ncons,
'Variables': self.model.variables.nvars,
Expand All @@ -136,7 +142,7 @@ class for defined way of solving a flow_system optimization

def do_modeling(self) -> SystemModel:
t_start = timeit.default_timer()
self._activate_time_series()
self.flow_system.connect_and_transform()

self.model = self.flow_system.create_model()
self.model.do_modeling()
Expand Down Expand Up @@ -181,12 +187,6 @@ def solve(self, solver: _Solver, log_file: Optional[pathlib.Path] = None, log_ma

self.results = CalculationResults.from_calculation(self)

def _activate_time_series(self):
self.flow_system.transform_data()
self.flow_system.time_series_collection.activate_timesteps(
active_timesteps=self.active_timesteps,
)


class AggregatedCalculation(FullCalculation):
"""
Expand Down Expand Up @@ -224,7 +224,7 @@ def __init__(

def do_modeling(self) -> SystemModel:
t_start = timeit.default_timer()
self._activate_time_series()
self.flow_system.connect_and_transform()
self._perform_aggregation()

# Model the System
Expand All @@ -245,8 +245,8 @@ def _perform_aggregation(self):

# Validation
dt_min, dt_max = (
np.min(self.flow_system.time_series_collection.hours_per_timestep),
np.max(self.flow_system.time_series_collection.hours_per_timestep),
np.min(self.flow_system.hours_per_timestep),
np.max(self.flow_system.hours_per_timestep),
)
if not dt_min == dt_max:
raise ValueError(
Expand All @@ -255,11 +255,11 @@ def _perform_aggregation(self):
)
steps_per_period = (
self.aggregation_parameters.hours_per_period
/ self.flow_system.time_series_collection.hours_per_timestep.max()
/ self.flow_system.hours_per_timestep.max()
)
is_integer = (
self.aggregation_parameters.hours_per_period
% self.flow_system.time_series_collection.hours_per_timestep.max()
% self.flow_system.hours_per_timestep.max()
).item() == 0
if not (steps_per_period.size == 1 and is_integer):
raise ValueError(
Expand All @@ -272,21 +272,21 @@ def _perform_aggregation(self):

# Aggregation - creation of aggregated timeseries:
self.aggregation = Aggregation(
original_data=self.flow_system.time_series_collection.to_dataframe(
original_data=self.flow_system.to_dataframe(
include_extra_timestep=False
), # Exclude last row (NaN)
hours_per_time_step=float(dt_min),
hours_per_period=self.aggregation_parameters.hours_per_period,
nr_of_periods=self.aggregation_parameters.nr_of_periods,
weights=self.flow_system.time_series_collection.calculate_aggregation_weights(),
weights=self.flow_system.calculate_aggregation_weights(),
time_series_for_high_peaks=self.aggregation_parameters.labels_for_high_peaks,
time_series_for_low_peaks=self.aggregation_parameters.labels_for_low_peaks,
)

self.aggregation.cluster()
self.aggregation.plot(show=True, save=self.folder / 'aggregation.html')
if self.aggregation_parameters.aggregate_data_and_fix_non_binary_vars:
self.flow_system.time_series_collection.insert_new_data(
self.flow_system.insert_new_data(
self.aggregation.aggregated_data, include_extra_timestep=False
)
self.durations['aggregation'] = round(timeit.default_timer() - t_start_agg, 2)
Expand Down Expand Up @@ -327,8 +327,8 @@ def __init__(
self.nr_of_previous_values = nr_of_previous_values
self.sub_calculations: List[FullCalculation] = []

self.all_timesteps = self.flow_system.time_series_collection.all_timesteps
self.all_timesteps_extra = self.flow_system.time_series_collection.all_timesteps_extra
self.all_timesteps = self.flow_system.all_timesteps
self.all_timesteps_extra = self.flow_system.all_timesteps_extra

self.segment_names = [
f'Segment_{i + 1}' for i in range(math.ceil(len(self.all_timesteps) / self.timesteps_per_segment))
Expand Down
73 changes: 37 additions & 36 deletions flixopt/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@

import linopy
import numpy as np
import xarray as xr

from . import utils
from .core import NumericData, NumericDataTS, PlausibilityError, Scalar, TimeSeries
from .core import NumericDataUser, PlausibilityError, Scalar
from .elements import Component, ComponentModel, Flow
from .features import InvestmentModel, OnOffModel, PiecewiseModel
from .interface import InvestParameters, OnOffParameters, PiecewiseConversion
Expand All @@ -34,7 +35,7 @@ def __init__(
inputs: List[Flow],
outputs: List[Flow],
on_off_parameters: OnOffParameters = None,
conversion_factors: List[Dict[str, NumericDataTS]] = None,
conversion_factors: List[Dict[str, NumericDataUser]] = None,
piecewise_conversion: Optional[PiecewiseConversion] = None,
meta_data: Optional[Dict] = None,
):
Expand Down Expand Up @@ -98,14 +99,14 @@ def transform_data(self, flow_system: 'FlowSystem'):
if self.piecewise_conversion:
self.piecewise_conversion.transform_data(flow_system, f'{self.label_full}|PiecewiseConversion')

def _transform_conversion_factors(self, flow_system: 'FlowSystem') -> List[Dict[str, TimeSeries]]:
"""macht alle Faktoren, die nicht TimeSeries sind, zu TimeSeries"""
def _transform_conversion_factors(self, flow_system: 'FlowSystem') -> List[Dict[str, xr.DataArray]]:
"""Converts all conversion factors to internal datatypes"""
list_of_conversion_factors = []
for idx, conversion_factor in enumerate(self.conversion_factors):
transformed_dict = {}
for flow, values in conversion_factor.items():
# TODO: Might be better to use the label of the component instead of the flow
transformed_dict[flow] = flow_system.create_time_series(
transformed_dict[flow] = flow_system.fit_to_model_coords(
f'{self.flows[flow].label_full}|conversion_factor{idx}', values
)
list_of_conversion_factors.append(transformed_dict)
Expand All @@ -128,14 +129,14 @@ def __init__(
charging: Flow,
discharging: Flow,
capacity_in_flow_hours: Union[Scalar, InvestParameters],
relative_minimum_charge_state: NumericData = 0,
relative_maximum_charge_state: NumericData = 1,
relative_minimum_charge_state: NumericDataUser = 0,
relative_maximum_charge_state: NumericDataUser = 1,
initial_charge_state: Union[Scalar, Literal['lastValueOfSim']] = 0,
minimal_final_charge_state: Optional[Scalar] = None,
maximal_final_charge_state: Optional[Scalar] = None,
eta_charge: NumericData = 1,
eta_discharge: NumericData = 1,
relative_loss_per_hour: NumericData = 0,
eta_charge: NumericDataUser = 1,
eta_discharge: NumericDataUser = 1,
relative_loss_per_hour: NumericDataUser = 0,
prevent_simultaneous_charge_and_discharge: bool = True,
meta_data: Optional[Dict] = None,
):
Expand Down Expand Up @@ -176,16 +177,16 @@ def __init__(
self.charging = charging
self.discharging = discharging
self.capacity_in_flow_hours = capacity_in_flow_hours
self.relative_minimum_charge_state: NumericDataTS = relative_minimum_charge_state
self.relative_maximum_charge_state: NumericDataTS = relative_maximum_charge_state
self.relative_minimum_charge_state: NumericDataUser = relative_minimum_charge_state
self.relative_maximum_charge_state: NumericDataUser = relative_maximum_charge_state

self.initial_charge_state = initial_charge_state
self.minimal_final_charge_state = minimal_final_charge_state
self.maximal_final_charge_state = maximal_final_charge_state

self.eta_charge: NumericDataTS = eta_charge
self.eta_discharge: NumericDataTS = eta_discharge
self.relative_loss_per_hour: NumericDataTS = relative_loss_per_hour
self.eta_charge: NumericDataUser = eta_charge
self.eta_discharge: NumericDataUser = eta_discharge
self.relative_loss_per_hour: NumericDataUser = relative_loss_per_hour
self.prevent_simultaneous_charge_and_discharge = prevent_simultaneous_charge_and_discharge

def create_model(self, model: SystemModel) -> 'StorageModel':
Expand All @@ -195,19 +196,19 @@ def create_model(self, model: SystemModel) -> 'StorageModel':

def transform_data(self, flow_system: 'FlowSystem') -> None:
super().transform_data(flow_system)
self.relative_minimum_charge_state = flow_system.create_time_series(
self.relative_minimum_charge_state = flow_system.fit_to_model_coords(
f'{self.label_full}|relative_minimum_charge_state',
self.relative_minimum_charge_state,
needs_extra_timestep=True,
)
self.relative_maximum_charge_state = flow_system.create_time_series(
self.relative_maximum_charge_state = flow_system.fit_to_model_coords(
f'{self.label_full}|relative_maximum_charge_state',
self.relative_maximum_charge_state,
needs_extra_timestep=True,
)
self.eta_charge = flow_system.create_time_series(f'{self.label_full}|eta_charge', self.eta_charge)
self.eta_discharge = flow_system.create_time_series(f'{self.label_full}|eta_discharge', self.eta_discharge)
self.relative_loss_per_hour = flow_system.create_time_series(
self.eta_charge = flow_system.fit_to_model_coords(f'{self.label_full}|eta_charge', self.eta_charge)
self.eta_discharge = flow_system.fit_to_model_coords(f'{self.label_full}|eta_discharge', self.eta_discharge)
self.relative_loss_per_hour = flow_system.fit_to_model_coords(
f'{self.label_full}|relative_loss_per_hour', self.relative_loss_per_hour
)
if isinstance(self.capacity_in_flow_hours, InvestParameters):
Expand Down Expand Up @@ -264,8 +265,8 @@ def __init__(
out1: Flow,
in2: Optional[Flow] = None,
out2: Optional[Flow] = None,
relative_losses: Optional[NumericDataTS] = None,
absolute_losses: Optional[NumericDataTS] = None,
relative_losses: Optional[NumericDataUser] = None,
absolute_losses: Optional[NumericDataUser] = None,
on_off_parameters: OnOffParameters = None,
prevent_simultaneous_flows_in_both_directions: bool = True,
meta_data: Optional[Dict] = None,
Expand Down Expand Up @@ -331,10 +332,10 @@ def create_model(self, model) -> 'TransmissionModel':

def transform_data(self, flow_system: 'FlowSystem') -> None:
super().transform_data(flow_system)
self.relative_losses = flow_system.create_time_series(
self.relative_losses = flow_system.fit_to_model_coords(
f'{self.label_full}|relative_losses', self.relative_losses
)
self.absolute_losses = flow_system.create_time_series(
self.absolute_losses = flow_system.fit_to_model_coords(
f'{self.label_full}|absolute_losses', self.absolute_losses
)

Expand All @@ -348,7 +349,7 @@ def __init__(self, model: SystemModel, element: Transmission):
def do_modeling(self):
"""Initiates all FlowModels"""
# Force On Variable if absolute losses are present
if (self.element.absolute_losses is not None) and np.any(self.element.absolute_losses.active_data != 0):
if (self.element.absolute_losses is not None) and np.any(self.element.absolute_losses != 0):
for flow in self.element.inputs + self.element.outputs:
if flow.on_off_parameters is None:
flow.on_off_parameters = OnOffParameters()
Expand Down Expand Up @@ -385,14 +386,14 @@ def create_transmission_equation(self, name: str, in_flow: Flow, out_flow: Flow)
# eq: out(t) + on(t)*loss_abs(t) = in(t)*(1 - loss_rel(t))
con_transmission = self.add(
self._model.add_constraints(
out_flow.model.flow_rate == -in_flow.model.flow_rate * (self.element.relative_losses.active_data - 1),
out_flow.model.flow_rate == -in_flow.model.flow_rate * (self.element.relative_losses - 1),
name=f'{self.label_full}|{name}',
),
name,
)

if self.element.absolute_losses is not None:
con_transmission.lhs += in_flow.model.on_off.on * self.element.absolute_losses.active_data
con_transmission.lhs += in_flow.model.on_off.on * self.element.absolute_losses

return con_transmission

Expand Down Expand Up @@ -420,8 +421,8 @@ def do_modeling(self):

self.add(
self._model.add_constraints(
sum([flow.model.flow_rate * conv_factors[flow.label].active_data for flow in used_inputs])
== sum([flow.model.flow_rate * conv_factors[flow.label].active_data for flow in used_outputs]),
sum([flow.model.flow_rate * conv_factors[flow.label] for flow in used_inputs])
== sum([flow.model.flow_rate * conv_factors[flow.label] for flow in used_outputs]),
name=f'{self.label_full}|conversion_{i}',
)
)
Expand Down Expand Up @@ -481,12 +482,12 @@ def do_modeling(self):
)

charge_state = self.charge_state
rel_loss = self.element.relative_loss_per_hour.active_data
rel_loss = self.element.relative_loss_per_hour
hours_per_step = self._model.hours_per_step
charge_rate = self.element.charging.model.flow_rate
discharge_rate = self.element.discharging.model.flow_rate
eff_charge = self.element.eta_charge.active_data
eff_discharge = self.element.eta_discharge.active_data
eff_charge = self.element.eta_charge
eff_discharge = self.element.eta_discharge

self.add(
self._model.add_constraints(
Expand Down Expand Up @@ -556,7 +557,7 @@ def _initial_and_final_charge_state(self):
)

@property
def absolute_charge_state_bounds(self) -> Tuple[NumericData, NumericData]:
def absolute_charge_state_bounds(self) -> Tuple[NumericDataUser, NumericDataUser]:
relative_lower_bound, relative_upper_bound = self.relative_charge_state_bounds
if not isinstance(self.element.capacity_in_flow_hours, InvestParameters):
return (
Expand All @@ -570,10 +571,10 @@ def absolute_charge_state_bounds(self) -> Tuple[NumericData, NumericData]:
)

@property
def relative_charge_state_bounds(self) -> Tuple[NumericData, NumericData]:
def relative_charge_state_bounds(self) -> Tuple[NumericDataUser, NumericDataUser]:
return (
self.element.relative_minimum_charge_state.active_data,
self.element.relative_maximum_charge_state.active_data,
self.element.relative_minimum_charge_state,
self.element.relative_maximum_charge_state,
)


Expand Down
Loading
Loading