Advanced Topics#
The examples below demonstrate advanced features beyond the basic usage shown in Getting Started. Each example is self-contained and shows a specific capability of the SunPeek Python API.
Note
These examples assume you have created a plant object using the preconfigured demo
approach, as shown in Using SunPeek with pre-defined demo plant. The complete runnable script
with all examples is available at docs/scripts/script_advanced_usage.py.
Accessing Sensor Data#
Sensor data in SunPeek is stored as pint-pandas Series objects, that is: pandas Series with physical units. They preserve their physical units throughout calculations, allowing for automatic unit conversion and ensuring dimensional consistency.
# Access sensor data - returns pint-pandas Series with units
ambient_temp = plant.te_amb.data
print(f"Data type: {type(ambient_temp)}")
print(f"Unit: {ambient_temp.pint.units}")
print(f"First 3 values:\n{ambient_temp.head(3)}")
Output:
Data type: <class 'pandas.core.series.Series'>
Unit: kelvin
First 3 values:
ds
2017-05-01 00:00:00+01:00 282.101333333333
2017-05-01 00:01:00+01:00 282.011
2017-05-01 00:02:00+01:00 281.834833333333
Name: te_amb, dtype: pint[kelvin][Float64]
The .pint accessor provides unit-aware operations. You can convert to any
compatible unit using .pint.to('target_unit'):
# Convert to different units using .pint.to()
ambient_temp_celsius = plant.te_amb.data.pint.to('degC')
print(f"Values in Celsius:\n{ambient_temp_celsius.head(3)}")
Output:
Values in Celsius:
ds
2017-05-01 00:00:00+01:00 8.951333333333025
2017-05-01 00:01:00+01:00 8.861000000000047
2017-05-01 00:02:00+01:00 8.684833333333017
Name: te_amb, dtype: pint[degree_Celsius][Float64]
By the way, component properties are also stored unit-aware:
# Component properties are also stored unit-aware
print(f"Plant latitude: {plant.latitude:.2f}")
print(f"Array row spacing: {plant.arrays[0].row_spacing:.1f}")
Output:
Plant latitude: 47.05 degree
Array row spacing: 3.1 meter
Accessing Virtual Sensors#
Virtual sensors are automatically calculated after loading measurement data. Examples of virtual sensors include, for instance solar position, angle of incidence, or thermal power calculated from volume flow, fluid properties, and temperature difference. Virtual sensors use the same data access pattern as physical sensors. See Fluids for details on how SunPeek models heat transfer fluids.
Common virtual sensors include:
Plant level:
sun_azimuth,sun_elevation,sun_zenith(solar position)Array level:
aoi(angle of incidence of sun on collectors),iam(incidence angle modifier). The IAM depends on collector type and optical properties, see Collectors for details.
# Virtual sensors are calculated automatically when loading data
# Show values at a specific timestamp
timestamp = pd.Timestamp('2017-05-01 09:15:00', tz='UTC')
# Access plant-level virtual sensors
sun_azimuth = plant.sun_azimuth.data.loc[timestamp] # Solar azimuth [deg]
sun_elevation = plant.sun_elevation.data.loc[timestamp] # Solar elevation [deg]
sun_zenith = plant.sun_zenith.data.loc[timestamp] # Solar zenith [deg]
print(f"At {timestamp}:")
print(f" Solar azimuth: {sun_azimuth:.1f}")
print(f" Solar elevation: {sun_elevation:.1f}")
print(f" Solar zenith: {sun_zenith:.1f}")
# Access array-level virtual sensors
array = plant.arrays[0]
aoi = array.aoi.data.loc[timestamp] # Angle of incidence [deg]
iam = array.iam.data.loc[timestamp] # Incidence Angle Modifier [-]
print(f" Angle of incidence: {aoi:.1f}")
print(f" Incidence angle modifier: {iam:.3f}")
Output:
At 2017-05-01 09:15:00+00:00:
Solar azimuth: 138.4 degree
Solar elevation: 51.9 degree
Solar zenith: 38.1 degree
Angle of incidence: 24.2 degree
Incidence angle modifier: 0.985 dimensionless
Working with Multiple Arrays#
SunPeek plants can contain multiple collector arrays with different orientations, tilt angles, or collector types. Power Check analysis automatically handles all arrays and aggregates results at the plant level.
from sunpeek.components import Array
from sunpeek.common.unit_uncertainty import Q
# Create additional arrays with different configurations
array_1 = Array(
name='Some array',
plant=plant,
collector=some_collector,
area_gr=Q(100, 'm**2'),
azim=Q(160, 'deg'),
tilt=Q(30, 'deg'),
sensor_map=(...)
)
array_2 = Array(
name='Another array',
plant=plant,
collector=another_collector,
area_gr=Q(100, 'm**2'),
azim=Q(210, 'deg'),
tilt=Q(40, 'deg'),
sensor_map=(...)
)
# Add arrays to plant
plant.add_array([array_1, array_2])
# Power Check analyzes all arrays automatically
result = run_power_check(plant)
print(f"Analyzed {len(plant.arrays)} arrays")
Note
This example is conceptual. For a working multi-array example, build a custom plant with multiple arrays, as shown in Using SunPeek with custom plant.
For details on collector types and parameters, see Collectors.
Advanced CSV Loading Options#
The use_csv() function accepts many keyword arguments for handling different
CSV formats and data import requirements:
from datetime import datetime, timezone
response = use_csv(
plant,
csv_files=['path/to/data.csv', 'path/to/another_data.csv'],
# Timezone handling
timezone='Europe/Vienna', # If timestamps lack timezone info
# CSV format
csv_separator=';', # Column separator (default: ';')
csv_decimal=',', # Decimal separator (default: '.')
csv_encoding='utf-8', # File encoding (default: 'utf-8')
index_col=0, # Column index for timestamps (default: 0)
# Datetime parsing
datetime_format='%Y-%m-%d %H:%M:%S', # Custom datetime format
# Time range filtering
eval_start=datetime(2017, 5, 1, tzinfo=timezone.utc),
eval_end=datetime(2017, 5, 2, tzinfo=timezone.utc)
)
The response returned by use_csv() contains helpful information, such as:
# Overall upload statistics
print(f"Uploaded {response.n_uploaded_data_rows} data rows")
print(f"Found {response.n_duplicates_index} duplicate timestamps")
# Per-file information
for file_info in response.response_per_file:
print(f"\nFile: {file_info.name}")
print(f" Rows: {file_info.n_rows}")
print(f" Time range: {file_info.start} to {file_info.end}")
print(f" Missing columns: {file_info.missing_columns}")
if file_info.error_cause:
print(f" Error: {file_info.error_cause}")
For common data import issues and solutions, see Troubleshooting.
Output:
Uploaded 2880 data rows
Found 0 duplicate timestamps
File: data.csv
Rows: 1440
Time range: 2017-05-01 00:00:00+00:00 to 2017-05-01 23:59:00+00:00
Missing columns: []
File: another_data.csv
Rows: 1440
Time range: 2017-05-02 00:00:00+00:00 to 2017-05-02 23:59:00+00:00
Missing columns: ['te_amb']
Loading Data from DataFrames#
In addition to loading CSV files with use_csv(), SunPeek can load data directly
from pandas DataFrames using use_dataframe(). This is useful when data is already
in memory, comes from a database, or requires custom preprocessing.
# Load CSV into plant and apply custom preprocessing
df = pd.read_csv(
sunpeek.demo.DEMO_DATA_PATH_2DAYS,
index_col=0
)
df.index = pd.to_datetime(df.index)
df_resampled = df.resample('2min').mean()
# Load DataFrame into plant
use_dataframe(plant, df=df_resampled)
Configuring Power Check Analysis#
SunPeek’s Power Check analysis offers extensive configuration options to adapt the analysis to different use cases, plant configurations, data quality levels, etc. For detailed background on Power Checkology, see the Guide to ISO 24194:2022 Power Check.
Auto-Mode and dry-run#
All Power Check settings are optional. If not specified, SunPeek automatically selects the best formula, averaging method, and other settings based on your plant configuration and available measurement data.
result = run_power_check(plant)
To inspect the auto-chosen algorithm settings (called a “strategy”) without running the full analysis,
use get_successful_strategy() or dry_run=True.
If no valid strategy is found, you can inspect the attempted strategies to get feedback:
# Inspect auto-chosen configuration without running the full analysis
from sunpeek.core_methods.power_check.wrapper import get_successful_strategy
from sunpeek.components.helpers import AlgoCheckMode
strategy = get_successful_strategy(plant)
if strategy:
print("Auto-chosen settings:")
print(f" Formula: {strategy.power_check.formula.id}")
print(f" Mode: {strategy.power_check.mode.value}")
print(f" Use wind: {strategy.power_check.formula.use_wind}")
else:
result = run_power_check(plant, dry_run=True)
print(f"No valid Power Check strategy found. Tried {len(result.strategies)} strategies.")
# Show feedback from first failed strategy
fb = result.strategies[0].get_feedback(AlgoCheckMode.config_only)
print(f"Example feedback: {fb.parse()}")
Output:
Auto-chosen settings:
Formula: 2
Mode: ISO
Use wind: True
If no valid strategy is found, see Troubleshooting for common sensor mapping and configuration issues.
Formula, Method, and Time Range#
You can explicitly configure the formula (per ISO 24194:2022), averaging method, and an optional time range for the analysis.
result = run_power_check(
plant,
# Power Check formulas 1/2/3, as specified in ISO 24194:2022
formula=1, # Uses global tilted irradiance (GTI)
# formula=2, # Uses beam and diffuse irradiance components separately
# formula=3, # For highly concentrating collectors
# Averaging methods
method='ISO' # Fixed-interval averaging per ISO 24194:2022
# method='Extended' # Moving-average approach with extended interval search
# Limit analysis to specific time range
eval_start=pd.Timestamp('2017-05-01', tz='UTC'),
eval_end=pd.Timestamp('2017-05-02', tz='UTC')
)
Note
This example shows configuration options for Power Check analysis. The result
object is a PowerCheckResult with the same structure as in previous examples,
but using your specified formula, method, and time range.
Fine-Tuning Parameters#
There are more parameters to fine-tune algorithm behavior, including safety factors and numerical parameters.
from datetime import timedelta
result_custom = run_power_check(
plant,
method='Extended',
# Safety factors as per ISO 24194 (between 0 and 1)
safety_uncertainty=0.02, # 2% measurement uncertainty
safety_pipes=0.05, # 5% pipe heat losses
safety_others=0.01, # 1% other safety margin
# Numerical parameters
interval_length=timedelta(minutes=45), # Custom interval, only available with method='Extended'
max_nan_density=0.1, # Max 10% missing data allowed
min_data_in_interval=5, # Minimum data points required in interval
max_gap_in_interval=timedelta(seconds=300) # Max gap between points in interval
)
Creating Individual Plots#
Beyond the comprehensive PDF report, SunPeek provides functions to create individual Power Check plots for custom reporting or interactive analysis. Each plotting function returns a list of matplotlib Figure objects that can be displayed, saved, or further customized.
# Bar chart: show the overall performance result
fig_bars = plot_bars(power_check_output)
# Square plot: measured vs. estimated power per interval
fig_square = plot_square(power_check_output)
# Time series: ratio of measured to estimated over time
fig_time = plot_time(power_check_output)
# Intervals plot: Show detailed measurement data in a Power Check interval
fig_intervals = plot_intervals(power_check_output)
# Optionally save individual plots
# fig_bars.savefig('power_check_bars.png')
Exporting and Importing Plant Configuration#
Plant configurations can be exported to JSON files for documentation or sharing with collaborators.
The exported configuration includes all plant configurations, sensor definitions, fluid and collector parameters.
The exported JSON file is human-readable and can be edited manually if needed.
Import the configuration later using make_plant_from_config_file() to
recreate the exact same plant setup.
import json
from sunpeek.exporter import create_export_config
from sunpeek.common.config_parser import make_plant_from_config_file
# Export plant configuration and save to JSON file
config_dict = create_export_config(plant)
with open('plant_config.json', 'w') as f:
json.dump(config_dict, f, indent=2, default=str)
# Import configuration later
plant_reloaded = make_plant_from_config_file('plant_config.json')
Managing Operational Events#
SunPeek supports “operational events”, a concept useful to track any event that influences plant performance, such as maintenance periods, sensor calibration, or data quality issues. Events can optionally be marked as “ignored” to exclude specific time periods from analysis.
from datetime import datetime, timezone
# Add event that is NOT excluded from analysis
plant.add_operational_event(
start=datetime(2017, 5, 1, 10, 0, tzinfo=timezone.utc),
end=datetime(2017, 5, 1, 14, 0, tzinfo=timezone.utc),
description="Sensor calibration",
ignored_range=False # Include in analysis
)
# Add event that IS excluded from analysis
plant.add_operational_event(
start=datetime(2017, 5, 1, 20, 0, tzinfo=timezone.utc),
end=datetime(2017, 5, 2, 8, 0, tzinfo=timezone.utc),
description="System maintenance",
ignored_range=True # Exclude from analysis
)
Ignored time ranges are automatically excluded from Power Check analysis and
other calculations. Use plant.is_ignored(timestamp) to check if a specific
time is within an ignored range.
from datetime import datetime, timezone
# Check if a specific timestamp is in an ignored range
test_time = datetime(2017, 5, 2, 0, 0, tzinfo=timezone.utc)
print(f"Time {test_time} is ignored: {plant.is_ignored(test_time)}")
# Access all ignored ranges
print(f"Plant has {len(plant.ignored_ranges)} ignored time ranges.")
Output:
Time 2017-05-02 00:00:00+00:00 is ignored: True
Plant has 1 ignored time ranges.