Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
dbbbe68
Refactor examples to not use deprectaed patterns
FBumann Sep 29, 2025
cf0d4d9
Refactor tests to replace deprecated `sink`/`source` properties with …
FBumann Sep 30, 2025
b018686
Use 'h' instead of deprectaed 'H' in coordinate freq in tests; adjust…
FBumann Sep 30, 2025
78cf524
Refactor plot tests to use non-interactive backends, save plots as fi…
FBumann Sep 30, 2025
ec86acf
Refactor plot tests to use non-interactive Plotly renderer (`json`), …
FBumann Sep 30, 2025
0928b26
Configure pytest filters to treat most warnings as errors, ignore spe…
FBumann Sep 30, 2025
bba0739
Revert "Configure pytest filters to treat most warnings as errors, ig…
FBumann Sep 30, 2025
0a27637
Refactor plotting logic to prevent memory leaks, improve backend hand…
FBumann Sep 30, 2025
0494556
Update pytest filterwarnings to treat most warnings as errors, ignore…
FBumann Sep 30, 2025
9ab18e1
Suppress specific third-party warnings in `__init__.py` to reduce noi…
FBumann Sep 30, 2025
150b8c0
Update pytest warning filters: treat internal warnings as errors, rev…
FBumann Sep 30, 2025
6da7884
Suppress additional third-party warnings in `__init__.py` to minimize…
FBumann Sep 30, 2025
1870afc
Update pytest warning filters: suppress specific third-party warnings…
FBumann Sep 30, 2025
cd9e5a7
Sync and consolidate third-party warning filters in `__init__.py` and…
FBumann Sep 30, 2025
d0648fb
Expand and clarify third-party warning filters in `__init__.py` and `…
FBumann Sep 30, 2025
6ff0a8c
Update deprecated code in tests
FBumann Sep 30, 2025
499ba0a
Merge remote-tracking branch 'origin/feature/v3/main' into feature/v3…
FBumann Sep 30, 2025
ef8d1b6
Refactor backend checks in `plotting.py` and streamline test fixtures…
FBumann Sep 30, 2025
dfe9d80
Refactor plotting logic to handle test environments explicitly, remov…
FBumann Sep 30, 2025
4e2625d
Merge remote-tracking branch 'origin/feature/v3/feature/no-warnings-i…
FBumann Sep 30, 2025
2444087
Add entry to CHANGELOG.md
FBumann Oct 8, 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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ This replaces `specific_share_to_other_effects_*` parameters and inverts the dir
* `fit_to_model_coords()` method for data alignment
* `fit_effects_to_model_coords()` method for effect data processing
* `connect_and_transform()` method replacing several operations
* **Testing improvements**: Eliminated warnings during test execution
* Updated deprecated code patterns in tests and examples (e.g., `sink`/`source` → `inputs`/`outputs`, `'H'` → `'h'` frequency)
* Refactored plotting logic to handle test environments explicitly with non-interactive backends
* Added comprehensive warning filters in `__init__.py` and `pyproject.toml` to suppress third-party library warnings
* Improved test fixtures with proper figure cleanup to prevent memory leaks
* Enhanced backend detection and handling in `plotting.py` for both Matplotlib and Plotly


Until here -->
Expand Down
6 changes: 4 additions & 2 deletions examples/00_Minmal/minimal_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,15 @@
# Heat load component with a fixed thermal demand profile
heat_load = fx.Sink(
'Heat Demand',
sink=fx.Flow(label='Thermal Load', bus='District Heating', size=1, fixed_relative_profile=thermal_load_profile),
inputs=[
fx.Flow(label='Thermal Load', bus='District Heating', size=1, fixed_relative_profile=thermal_load_profile)
],
)

# Gas source component with cost-effect per flow hour
gas_source = fx.Source(
'Natural Gas Tariff',
source=fx.Flow(label='Gas Flow', bus='Natural Gas', size=1000, effects_per_flow_hour=0.04), # 0.04 €/kWh
outputs=[fx.Flow(label='Gas Flow', bus='Natural Gas', size=1000, effects_per_flow_hour=0.04)], # 0.04 €/kWh
)

# --- Build the Flow System ---
Expand Down
10 changes: 6 additions & 4 deletions examples/01_Simple/simple_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
label='CO2',
unit='kg',
description='CO2_e-Emissionen',
maximum_operation_per_hour=1000, # Max CO2 emissions per hour
maximum_per_hour=1000, # Max CO2 emissions per hour
)

# --- Define Flow System Components ---
Expand Down Expand Up @@ -77,18 +77,20 @@
# Heat Demand Sink: Represents a fixed heat demand profile
heat_sink = fx.Sink(
label='Heat Demand',
sink=fx.Flow(label='Q_th_Last', bus='Fernwärme', size=1, fixed_relative_profile=heat_demand_per_h),
inputs=[fx.Flow(label='Q_th_Last', bus='Fernwärme', size=1, fixed_relative_profile=heat_demand_per_h)],
)

# Gas Source: Gas tariff source with associated costs and CO2 emissions
gas_source = fx.Source(
label='Gastarif',
source=fx.Flow(label='Q_Gas', bus='Gas', size=1000, effects_per_flow_hour={costs.label: 0.04, CO2.label: 0.3}),
outputs=[
fx.Flow(label='Q_Gas', bus='Gas', size=1000, effects_per_flow_hour={costs.label: 0.04, CO2.label: 0.3})
],
)

# Power Sink: Represents the export of electricity to the grid
power_sink = fx.Sink(
label='Einspeisung', sink=fx.Flow(label='P_el', bus='Strom', effects_per_flow_hour=-1 * power_prices)
label='Einspeisung', inputs=[fx.Flow(label='P_el', bus='Strom', effects_per_flow_hour=-1 * power_prices)]
)

# --- Build the Flow System ---
Expand Down
40 changes: 23 additions & 17 deletions examples/02_Complex/complex_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,33 +147,39 @@
# 5.a) Heat demand profile
Waermelast = fx.Sink(
'Wärmelast',
sink=fx.Flow(
'Q_th_Last', # Heat sink
bus='Fernwärme', # Linked bus
size=1,
fixed_relative_profile=heat_demand, # Fixed demand profile
),
inputs=[
fx.Flow(
'Q_th_Last', # Heat sink
bus='Fernwärme', # Linked bus
size=1,
fixed_relative_profile=heat_demand, # Fixed demand profile
)
],
)

# 5.b) Gas tariff
Gasbezug = fx.Source(
'Gastarif',
source=fx.Flow(
'Q_Gas',
bus='Gas', # Gas source
size=1000, # Nominal size
effects_per_flow_hour={Costs.label: 0.04, CO2.label: 0.3},
),
outputs=[
fx.Flow(
'Q_Gas',
bus='Gas', # Gas source
size=1000, # Nominal size
effects_per_flow_hour={Costs.label: 0.04, CO2.label: 0.3},
)
],
)

# 5.c) Feed-in of electricity
Stromverkauf = fx.Sink(
'Einspeisung',
sink=fx.Flow(
'P_el',
bus='Strom', # Feed-in tariff for electricity
effects_per_flow_hour=-1 * electricity_price, # Negative price for feed-in
),
inputs=[
fx.Flow(
'P_el',
bus='Strom', # Feed-in tariff for electricity
effects_per_flow_hour=-1 * electricity_price, # Negative price for feed-in
)
],
)

# --- Build FlowSystem ---
Expand Down
26 changes: 15 additions & 11 deletions examples/03_Calculation_types/example_calculation_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,39 +108,43 @@
# 4. Sinks and Sources
# Heat Load Profile
a_waermelast = fx.Sink(
'Wärmelast', sink=fx.Flow('Q_th_Last', bus='Fernwärme', size=1, fixed_relative_profile=TS_heat_demand)
'Wärmelast', inputs=[fx.Flow('Q_th_Last', bus='Fernwärme', size=1, fixed_relative_profile=TS_heat_demand)]
)

# Electricity Feed-in
a_strom_last = fx.Sink(
'Stromlast', sink=fx.Flow('P_el_Last', bus='Strom', size=1, fixed_relative_profile=TS_electricity_demand)
'Stromlast', inputs=[fx.Flow('P_el_Last', bus='Strom', size=1, fixed_relative_profile=TS_electricity_demand)]
)

# Gas Tariff
a_gas_tarif = fx.Source(
'Gastarif',
source=fx.Flow('Q_Gas', bus='Gas', size=1000, effects_per_flow_hour={costs.label: gas_price, CO2.label: 0.3}),
outputs=[
fx.Flow('Q_Gas', bus='Gas', size=1000, effects_per_flow_hour={costs.label: gas_price, CO2.label: 0.3})
],
)

# Coal Tariff
a_kohle_tarif = fx.Source(
'Kohletarif',
source=fx.Flow('Q_Kohle', bus='Kohle', size=1000, effects_per_flow_hour={costs.label: 4.6, CO2.label: 0.3}),
outputs=[fx.Flow('Q_Kohle', bus='Kohle', size=1000, effects_per_flow_hour={costs.label: 4.6, CO2.label: 0.3})],
)

# Electricity Tariff and Feed-in
a_strom_einspeisung = fx.Sink(
'Einspeisung', sink=fx.Flow('P_el', bus='Strom', size=1000, effects_per_flow_hour=TS_electricity_price_sell)
'Einspeisung', inputs=[fx.Flow('P_el', bus='Strom', size=1000, effects_per_flow_hour=TS_electricity_price_sell)]
)

a_strom_tarif = fx.Source(
'Stromtarif',
source=fx.Flow(
'P_el',
bus='Strom',
size=1000,
effects_per_flow_hour={costs.label: TS_electricity_price_buy, CO2.label: 0.3},
),
outputs=[
fx.Flow(
'P_el',
bus='Strom',
size=1000,
effects_per_flow_hour={costs.label: TS_electricity_price_buy, CO2.label: 0.3},
)
],
)

# Flow System Setup
Expand Down
10 changes: 6 additions & 4 deletions examples/04_Scenarios/scenario_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
label='CO2',
unit='kg',
description='CO2_e-Emissionen',
maximum_operation_per_hour=1000, # Max CO2 emissions per hour
maximum_per_hour=1000, # Max CO2 emissions per hour
)

# --- Define Flow System Components ---
Expand Down Expand Up @@ -90,18 +90,20 @@
# Heat Demand Sink: Represents a fixed heat demand profile
heat_sink = fx.Sink(
label='Heat Demand',
sink=fx.Flow(label='Q_th_Last', bus='Fernwärme', size=1, fixed_relative_profile=heat_demand_per_h),
inputs=[fx.Flow(label='Q_th_Last', bus='Fernwärme', size=1, fixed_relative_profile=heat_demand_per_h)],
)

# Gas Source: Gas tariff source with associated costs and CO2 emissions
gas_source = fx.Source(
label='Gastarif',
source=fx.Flow(label='Q_Gas', bus='Gas', size=1000, effects_per_flow_hour={costs.label: 0.04, CO2.label: 0.3}),
outputs=[
fx.Flow(label='Q_Gas', bus='Gas', size=1000, effects_per_flow_hour={costs.label: 0.04, CO2.label: 0.3})
],
)

# Power Sink: Represents the export of electricity to the grid
power_sink = fx.Sink(
label='Einspeisung', sink=fx.Flow(label='P_el', bus='Strom', effects_per_flow_hour=-1 * power_prices)
label='Einspeisung', inputs=[fx.Flow(label='P_el', bus='Strom', effects_per_flow_hour=-1 * power_prices)]
)

# --- Build the Flow System ---
Expand Down
24 changes: 14 additions & 10 deletions examples/05_Two-stage-optimization/two_stage_optimization.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,30 +84,34 @@
charging=fx.Flow('Q_th_load', size=137, bus='Fernwärme'),
discharging=fx.Flow('Q_th_unload', size=158, bus='Fernwärme'),
),
fx.Sink('Wärmelast', sink=fx.Flow('Q_th_Last', bus='Fernwärme', size=1, fixed_relative_profile=heat_demand)),
fx.Sink(
'Wärmelast', inputs=[fx.Flow('Q_th_Last', bus='Fernwärme', size=1, fixed_relative_profile=heat_demand)]
),
fx.Source(
'Gastarif',
source=fx.Flow('Q_Gas', bus='Gas', size=1000, effects_per_flow_hour={'costs': gas_price, 'CO2': 0.3}),
outputs=[fx.Flow('Q_Gas', bus='Gas', size=1000, effects_per_flow_hour={'costs': gas_price, 'CO2': 0.3})],
),
fx.Source(
'Kohletarif',
source=fx.Flow('Q_Kohle', bus='Kohle', size=1000, effects_per_flow_hour={'costs': 4.6, 'CO2': 0.3}),
outputs=[fx.Flow('Q_Kohle', bus='Kohle', size=1000, effects_per_flow_hour={'costs': 4.6, 'CO2': 0.3})],
),
fx.Source(
'Einspeisung',
source=fx.Flow(
'P_el', bus='Strom', size=1000, effects_per_flow_hour={'costs': electricity_price + 0.5, 'CO2': 0.3}
),
outputs=[
fx.Flow(
'P_el', bus='Strom', size=1000, effects_per_flow_hour={'costs': electricity_price + 0.5, 'CO2': 0.3}
)
],
),
Comment on lines 98 to 105
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify 'Einspeisung' (feed-in) semantics are correct.

The API migration syntax is correct. However, verify that modeling 'Einspeisung' (typically meaning electricity feed-in/selling to grid) as a Source with positive effects_per_flow_hour is semantically correct for your system.

In energy system modeling:

  • Sink typically represents consuming from the system (e.g., selling to grid = demand on system)
  • Source typically represents supplying to the system (e.g., buying from grid = supply to system)

The positive price (electricity_price + 0.5) suggests this represents a cost, which would align with buying from the grid (Source). However, the German term "Einspeisung" usually refers to selling/feed-in.


🏁 Script executed:

#!/bin/bash
# Description: Check usage pattern of Einspeisung and other electricity sources/sinks
# Expected: Clarify whether Einspeisung represents buying or selling electricity

# Find all electricity-related Sources and Sinks with their effects
rg -n --type=py -B2 -A5 "(Source|Sink)\(" examples/ | rg -A5 -B2 "Strom|Einspeisung"

Length of output: 6185


fx.Sink(
'Stromlast',
sink=fx.Flow('P_el_Last', bus='Strom', size=1, fixed_relative_profile=electricity_demand),
inputs=[fx.Flow('P_el_Last', bus='Strom', size=1, fixed_relative_profile=electricity_demand)],
),
fx.Source(
'Stromtarif',
source=fx.Flow(
'P_el', bus='Strom', size=1000, effects_per_flow_hour={'costs': electricity_price, 'CO2': 0.3}
),
outputs=[
fx.Flow('P_el', bus='Strom', size=1000, effects_per_flow_hour={'costs': electricity_price, 'CO2': 0.3})
],
),
)

Expand Down
31 changes: 31 additions & 0 deletions flixopt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
This module bundles all common functionality of flixopt and sets up the logging
"""

import warnings
from importlib.metadata import version

__version__ = version('flixopt')
Expand Down Expand Up @@ -37,3 +38,33 @@
)

CONFIG.load_config()


# === Runtime warning suppression for third-party libraries ===
# These warnings are from dependencies and cannot be fixed by end users.
# They are suppressed at runtime to provide a cleaner user experience.
# These filters match the test configuration in pyproject.toml for consistency.

# tsam: Time series aggregation library
# - FutureWarning: Upcoming API changes in tsam (will be fixed in future tsam releases)
warnings.filterwarnings('ignore', category=FutureWarning, module='tsam')
# - UserWarning: Informational message about minimal value constraints
warnings.filterwarnings('ignore', category=UserWarning, message='.*minimal value.*exceeds.*', module='tsam')
# TODO: Might be able to fix it in flixopt?

# linopy: Linear optimization library
# - UserWarning: Coordinate mismatch warnings that don't affect functionality and are expected.
warnings.filterwarnings(
'ignore', category=UserWarning, message='Coordinates across variables not equal', module='linopy'
)
# - FutureWarning: join parameter default will change in future versions
warnings.filterwarnings(
'ignore',
category=FutureWarning,
message="default value for join will change from join='outer' to join='exact'",
module='linopy',
)

# numpy: Core numerical library
# - RuntimeWarning: Binary incompatibility warnings from compiled extensions (safe to ignore). numpy 1->2
warnings.filterwarnings('ignore', category=RuntimeWarning, message='numpy\\.ndarray size changed')
45 changes: 38 additions & 7 deletions flixopt/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@

import itertools
import logging
import os
import pathlib
from typing import TYPE_CHECKING, Any, Literal

import matplotlib
import matplotlib.colors as mcolors
import matplotlib.pyplot as plt
import numpy as np
Expand Down Expand Up @@ -1450,20 +1452,49 @@ def export_figure(
if filename.suffix != '.html':
logger.warning(f'To save a Plotly figure, using .html. Adjusting suffix for {filename}')
filename = filename.with_suffix('.html')
if show and not save:
fig.show()
elif save and show:
plotly.offline.plot(fig, filename=str(filename))
elif save and not show:
fig.write_html(str(filename))

try:
is_test_env = 'PYTEST_CURRENT_TEST' in os.environ

if is_test_env:
# Test environment: never open browser, only save if requested
if save:
fig.write_html(str(filename))
# Ignore show flag in tests
else:
# Production environment: respect show and save flags
if save and show:
# Save and auto-open in browser
plotly.offline.plot(fig, filename=str(filename))
elif save and not show:
# Save without opening
fig.write_html(str(filename))
elif show and not save:
# Show interactively without saving
fig.show()
# If neither save nor show: do nothing
finally:
# Cleanup to prevent socket warnings
if hasattr(fig, '_renderer'):
fig._renderer = None

return figure_like

elif isinstance(figure_like, tuple):
fig, ax = figure_like
if show:
fig.show()
# Only show if using interactive backend and not in test environment
backend = matplotlib.get_backend().lower()
is_interactive = backend not in {'agg', 'pdf', 'ps', 'svg', 'template'}
is_test_env = 'PYTEST_CURRENT_TEST' in os.environ

if is_interactive and not is_test_env:
plt.show()

if save:
fig.savefig(str(filename), dpi=300)
plt.close(fig) # Close figure to free memory

return fig, ax

raise TypeError(f'Figure type not supported: {type(figure_like)}')
2 changes: 1 addition & 1 deletion flixopt/results.py
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,7 @@ def _create_effects_dataset(self, mode: Literal['temporal', 'periodic', 'total']

component_arrays.append(arr.expand_dims(component=[component]))

ds[effect] = xr.concat(component_arrays, dim='component', coords='minimal').rename(effect)
ds[effect] = xr.concat(component_arrays, dim='component', coords='minimal', join='outer').rename(effect)

# For now include a test to ensure correctness
suffix = {
Expand Down
Loading