Sensitivity Analysis - Nouman090/ThermoSim GitHub Wiki
The SensitivityAnalyzer lets you study how cycle performance changes
when you vary one or more design parameters. It also supports analyzing
existing CSV data files.
Key Concept: Build Function
You write a build function that takes a dictionary of parameters and returns a fully-solved model. The analyzer calls this function many times with different parameter values.
def build_my_cycle(params):
"""Build and solve a cycle from parameters."""
M = ThermodynamicModel()
M.set_dead_state()
T1 = params['T1']
P1 = params['P1']
eta_t = params['eta_t']
# ... define state points and components ...
M.Solve()
return M # return the solved model
Setup
from ThermoSim.analysis import SensitivityAnalyzer
import numpy as np
# Base parameter values
base_params = {
'T1': 753.15,
'P1': 8e6,
'P_cond': 0.008e6,
'eta_t': 0.85,
'eta_p': 0.85,
}
# For parametric sweeps
sa = SensitivityAnalyzer(build_my_cycle, base_params)
# For CSV-only analysis (no build function needed)
sa_csv = SensitivityAnalyzer()
1. Single-Parameter Sweep
Vary one parameter while keeping everything else at base values.
df = sa.single_sweep(
param_name='T1', # which parameter
values=np.arange(623.15, 873.15, 25), # values to try
outputs={ # what to measure
'Efficiency (%)': lambda m: m.Efficiency,
'Net Power (kW)': lambda m: m.Net_power / 1e3,
},
x_label='Turbine Inlet Temperature [K]',
save_to='sweep_T1.csv' # optional: save results
)
print(df) # results as a DataFrame
2. Multi-Output Sweep
Plot multiple outputs on one chart with separate y-axes.
df = sa.multi_output_sweep(
param_name='P1',
values=np.arange(4e6, 16e6, 1e6),
outputs={
'Efficiency (%)': lambda m: m.Efficiency,
'Net Power (kW)': lambda m: m.Net_power / 1e3,
'Turbine Work (kW)': lambda m: m.Component['Turbine'].work / 1e3,
},
x_label='Boiler Pressure [Pa]',
save_to='sweep_P1_multi.csv' # optional: save results
)
3. Two-Parameter Contour Plot
Vary two parameters simultaneously and create a contour/heatmap.
df = sa.double_sweep(
param1_name='T1',
param1_values=np.arange(623.15, 873.15, 50),
param2_name='P1',
param2_values=np.arange(4e6, 14e6, 2e6),
output_name='Efficiency (%)',
output_func=lambda m: m.Efficiency,
save_to='sweep_2D.csv' # optional: save results
)
4. Analyzing Existing CSV Data
Single-Parameter Analysis from CSV
# Analyze previously saved results
df = sa_csv.analyze_csv(
filename='sweep_T1.csv',
param_name='T1',
output_names=['Efficiency (%)', 'Net Power (kW)'],
x_label='Turbine Inlet Temperature [K]'
)
Two-Parameter Analysis from CSV
# Create contour plot from CSV data
pivot = sa_csv.analyze_csv_double(
filename='sweep_2D.csv',
param1_name='T1',
param2_name='P1',
output_name='Efficiency (%)'
)
Compare Two CSV Files
from ThermoSim.analysis import compare_csv_files
# Compare baseline vs. optimized design
compare_csv_files(
file1='baseline_sweep.csv',
file2='optimized_sweep.csv',
param_name='T1',
output_names=['Efficiency (%)', 'Net Power (kW)'],
labels=['Baseline', 'Optimized']
)
5. Sensitivity Indices
Compute statistical sensitivity metrics from sweep results.
# After running a sweep
df = sa.single_sweep('T1', np.arange(623, 873, 25),
{'Efficiency (%)': lambda m: m.Efficiency})
# Compute sensitivity metrics
metrics = sa.compute_sensitivity_indices(df, 'T1', 'Efficiency (%)')
print(f"Sensitivity Index: {metrics['sensitivity_index']:.3f}")
print(f"Correlation: {metrics['correlation']:.3f}")
print(f"Output Range: {metrics['output_range']:.2f} %")
print(f"Min/Max Output: {metrics['output_min']:.2f} / {metrics['output_max']:.2f} %")
6. Save and Load Results
# Manual save
sa.save_results(df, 'my_results.csv', overwrite=False)
# Manual load
df_loaded = sa.load_results('my_results.csv')
Complete Example
import numpy as np
from ThermoSim import (
ThermodynamicModel, Turbine, Pump, HeatExchanger,
)
from ThermoSim.analysis import SensitivityAnalyzer
def build_rankine(params):
M = ThermodynamicModel()
M.set_dead_state()
M.add_point('water', '1', P=params['P1'],
T=params['T1'], Mass_flowrate=1)
M.add_point('water', '2', P=params['P_cond'])
M.add_point('water', '3', P=params['P_cond'], Q=0)
M.add_point('water', '4', P=params['P1'])
Turbine(M, 'Turbine', '1', '2',
n_isen=params['eta_t'])
HeatExchanger(M, 'Condenser', PPT=5, HEX_type='SimpleHEX',
HeatAdded=False,
Hot_In_state='2', Hot_Out_state='3',
Cold_In_state=None, Cold_Out_state=None)
Pump(M, 'Pump', '3', '4',
n_isen=params['eta_p'])
HeatExchanger(M, 'Boiler', PPT=5, HEX_type='SimpleHEX',
HeatAdded=True,
Hot_In_state=None, Hot_Out_state=None,
Cold_In_state='4', Cold_Out_state='1')
M.Solve(verbose=0)
return M
base = {
'T1': 753.15,
'P1': 8e6,
'P_cond': 0.008e6,
'eta_t': 0.85,
'eta_p': 0.85,
}
sa = SensitivityAnalyzer(build_rankine, base)
# Effect of turbine inlet temperature
df_T1 = sa.single_sweep(
'T1',
np.arange(623, 873, 25),
{'η (%)': lambda m: m.Efficiency},
x_label='T_turbine_in [K]',
save_to='T1_sweep.csv'
)
# Effect of condenser pressure
df_Pc = sa.single_sweep(
'P_cond',
np.arange(0.004e6, 0.02e6, 0.002e6),
{'η (%)': lambda m: m.Efficiency},
x_label='P_condenser [Pa]',
save_to='Pcond_sweep.csv'
)
# Two-parameter study
df_2D = sa.double_sweep(
'T1', np.arange(623, 873, 50),
'P1', np.arange(4e6, 14e6, 2e6),
'Efficiency (%)',
lambda m: m.Efficiency,
save_to='2D_sweep.csv'
)
#Multiple Outputs in single graph
sw_m = sa.multi_output_sweep(
'T1',
np.arange(623, 873, 25),
{'η (%)': lambda m: m.Efficiency,
'Turbine Power (kW)': lambda m: m.Component['Turbine'].work / 1e3,
'Net Power (kW)': lambda m: m.Net_power / 1e3},
x_label='T_turbine_in [K]',
save_to='T1_sweep.csv'
)
# Compute sensitivity
metrics_T1 = sa.compute_sensitivity_indices(df_T1, 'T1', 'η (%)')
metrics_Pc = sa.compute_sensitivity_indices(df_Pc, 'P_cond', 'η (%)')
print(f"\nT1 Sensitivity: {metrics_T1['sensitivity_index']:.4f}")
print(f"P_cond Sensitivity: {metrics_Pc['sensitivity_index']:.4f}")
Analyzing Saved Data (No Re-computation)
# Later, analyze without re-running simulations
sa_analysis = SensitivityAnalyzer()
# Load and plot
df = sa_analysis.analyze_csv(
'T1_sweep.csv',
param_name='T1',
output_names=['η (%)']
)
# Compare baseline vs. modified design
from ThermoSim.analysis import compare_csv_files
compare_csv_files(
'baseline_T1_sweep.csv',
'modified_T1_sweep.csv',
param_name='T1',
output_names=['η (%)', 'Net Power (kW)'],
labels=['Baseline', 'Modified']
)
Output Functions
The outputs dictionary maps display names to functions that extract
values from a solved model:
# Cycle-level outputs
lambda m: m.Efficiency # thermal efficiency (%)
lambda m: m.Net_power # net power (W)
lambda m: m.Q_in # heat input (W)
lambda m: m.Q_out # heat rejected (W)
lambda m: m.Total_Ex_d # total exergy destruction (W)
# Component-level outputs
lambda m: m.Component['Turbine'].work # turbine work (W)
lambda m: m.Component['Pump'].work # pump work (W)
lambda m: m.Component['Turbine'].Ex_D # turbine Ex_D (W)
# State-point outputs
lambda m: m.Point['2'].T - 273.15 # temperature at state 2 (°C)
lambda m: m.Point['2'].Q # quality at state 2
lambda m: m.Component['HEX1'].UA # UA value of a HEX
Best Practices
1. Error Handling
The analyzer automatically catches errors during sweeps and marks failed points as NaN. Check your results:
df = sa.single_sweep(...)
print(f"Failed runs: {df['Efficiency (%)'].isna().sum()}")
2. Save Your Results
Always save parametric study results for later analysis:
df = sa.single_sweep(..., save_to='results.csv')
3. Large Sweeps
For computationally expensive models, start with coarse sweeps:
# Coarse sweep first
values_coarse = np.arange(600, 900, 100) # 4 points
# Then refine interesting regions
values_fine = np.arange(700, 800, 10) # 11 points
4. Batch Processing
For very large studies, consider running in batches:
# Batch 1
temps_batch1 = np.arange(623, 723, 25)
df1 = sa.single_sweep('T1', temps_batch1, outputs, save_to='batch1.csv')
# Batch 2
temps_batch2 = np.arange(723, 873, 25)
df2 = sa.single_sweep('T1', temps_batch2, outputs, save_to='batch2.csv')
# Combine later
import pandas as pd
df_combined = pd.concat([df1, df2]).sort_values('T1').reset_index(drop=True)
Troubleshooting
NaN Results
If you get NaN values in your results:
# Check which runs failed
failed_mask = df['Efficiency (%)'].isna()
print(df[failed_mask])
# Common causes:
# - Parameters outside valid range (e.g., P > critical pressure)
# - Non-physical states (e.g., superheated liquid)
# - Convergence failures in components
CSV Column Errors
When analyzing CSV files, ensure column names match:
# Check available columns
df = pd.read_csv('results.csv')
print(df.columns.tolist())
# Use exact column names
sa_csv.analyze_csv('results.csv',
param_name='T1', # must match CSV exactly
output_names=['Efficiency (%)'])
Memory Issues
For very large 2D sweeps:
# Instead of storing all in memory, process in chunks
n_chunks = 5
T1_chunks = np.array_split(np.arange(623, 873, 10), n_chunks)
for i, T1_vals in enumerate(T1_chunks):
df_chunk = sa.double_sweep(
'T1', T1_vals,
'P1', P1_vals,
'Efficiency (%)', lambda m: m.Efficiency,
save_to=f'chunk_{i}.csv'
)