193 lines
5.6 KiB
Python
193 lines
5.6 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Evaluation helpers for cycle sensitivity analysis."""
|
|
|
|
from .cycles import BraytonCycle
|
|
|
|
|
|
RC_FIXED_KEYS = ("T_low", "T_high", "p_low", "p_high", "ploss")
|
|
RC_PARAM_KEYS = (
|
|
"x",
|
|
"compressor_eff",
|
|
"recompressor_eff",
|
|
"turbine_eff",
|
|
"recuperator_eff",
|
|
"highT_recuperator_eff",
|
|
)
|
|
RC_DESIGN_VARIABLES = ("T_low", "T_high", "p_low", "p_high", "ploss", "x")
|
|
RC_COMPONENT_PERFORMANCE_VARIABLES = (
|
|
"compressor_eff",
|
|
"recompressor_eff",
|
|
"turbine_eff",
|
|
"recuperator_eff",
|
|
"highT_recuperator_eff",
|
|
)
|
|
|
|
|
|
def _require_keys(data, required_keys, data_name):
|
|
missing = [key for key in required_keys if key not in data]
|
|
if missing:
|
|
missing_text = ", ".join(missing)
|
|
raise ValueError(f"{data_name} missing required keys: {missing_text}")
|
|
|
|
|
|
def evaluate_rc_efficiency(fixed_params, params, refprop_path=None):
|
|
"""Return the recompression Brayton cycle thermal efficiency.
|
|
|
|
The returned efficiency is a decimal value, for example 0.45 means 45%.
|
|
A new cycle instance is created for each evaluation so repeated sensitivity
|
|
runs do not reuse component state from previous cases.
|
|
"""
|
|
fixed = dict(fixed_params)
|
|
cycle_params = dict(params)
|
|
|
|
_require_keys(fixed, RC_FIXED_KEYS, "fixed_params")
|
|
_require_keys(cycle_params, RC_PARAM_KEYS, "params")
|
|
|
|
cycle_kwargs = {"name": "rc efficiency evaluation"}
|
|
if refprop_path is not None:
|
|
cycle_kwargs["refprop_path"] = refprop_path
|
|
|
|
cycle = BraytonCycle(**cycle_kwargs)
|
|
return cycle.RC(
|
|
T_low=fixed["T_low"],
|
|
T_high=fixed["T_high"],
|
|
p_low=fixed["p_low"],
|
|
p_high=fixed["p_high"],
|
|
ploss=fixed["ploss"],
|
|
param=cycle_params,
|
|
)
|
|
|
|
|
|
def _set_rc_variable(fixed_params, params, variable_name, value):
|
|
fixed = dict(fixed_params)
|
|
cycle_params = dict(params)
|
|
|
|
in_fixed = variable_name in fixed
|
|
in_params = variable_name in cycle_params
|
|
if in_fixed and in_params:
|
|
raise ValueError(f"{variable_name!r} exists in both fixed_params and params")
|
|
if not in_fixed and not in_params:
|
|
raise ValueError(f"Unknown RC variable: {variable_name}")
|
|
|
|
if in_fixed:
|
|
fixed[variable_name] = value
|
|
else:
|
|
cycle_params[variable_name] = value
|
|
|
|
return fixed, cycle_params
|
|
|
|
|
|
def local_rc_design_sensitivity(
|
|
fixed_params,
|
|
params,
|
|
variables=None,
|
|
relative_step=0.01,
|
|
absolute_steps=None,
|
|
refprop_path=None,
|
|
):
|
|
"""Run one-at-a-time local sensitivity analysis for RC design variables.
|
|
|
|
The returned rows use decimal efficiency values. For example, 0.46 means
|
|
46%. The normalized sensitivity is:
|
|
|
|
((eff_plus - eff_minus) / eff_base)
|
|
/ ((value_plus - value_minus) / value_base)
|
|
|
|
so variables with different units can be compared directly.
|
|
"""
|
|
if relative_step <= 0:
|
|
raise ValueError("relative_step must be positive")
|
|
|
|
variables = variables or RC_DESIGN_VARIABLES
|
|
absolute_steps = absolute_steps or {}
|
|
|
|
base_fixed = dict(fixed_params)
|
|
base_params = dict(params)
|
|
base_efficiency = evaluate_rc_efficiency(
|
|
base_fixed,
|
|
base_params,
|
|
refprop_path=refprop_path,
|
|
)
|
|
|
|
rows = []
|
|
for variable_name in variables:
|
|
if variable_name in base_fixed:
|
|
base_value = base_fixed[variable_name]
|
|
elif variable_name in base_params:
|
|
base_value = base_params[variable_name]
|
|
else:
|
|
raise ValueError(f"Unknown RC variable: {variable_name}")
|
|
|
|
step = absolute_steps.get(variable_name)
|
|
if step is None:
|
|
step = abs(base_value) * relative_step
|
|
if step <= 0:
|
|
raise ValueError(f"Step for {variable_name!r} must be positive")
|
|
|
|
minus_value = base_value - step
|
|
plus_value = base_value + step
|
|
minus_fixed, minus_params = _set_rc_variable(
|
|
base_fixed,
|
|
base_params,
|
|
variable_name,
|
|
minus_value,
|
|
)
|
|
plus_fixed, plus_params = _set_rc_variable(
|
|
base_fixed,
|
|
base_params,
|
|
variable_name,
|
|
plus_value,
|
|
)
|
|
|
|
eff_minus = evaluate_rc_efficiency(
|
|
minus_fixed,
|
|
minus_params,
|
|
refprop_path=refprop_path,
|
|
)
|
|
eff_plus = evaluate_rc_efficiency(
|
|
plus_fixed,
|
|
plus_params,
|
|
refprop_path=refprop_path,
|
|
)
|
|
|
|
derivative = (eff_plus - eff_minus) / (plus_value - minus_value)
|
|
if base_value == 0 or base_efficiency == 0:
|
|
normalized_sensitivity = None
|
|
else:
|
|
normalized_sensitivity = derivative * base_value / base_efficiency
|
|
|
|
rows.append(
|
|
{
|
|
"variable": variable_name,
|
|
"base_value": base_value,
|
|
"minus_value": minus_value,
|
|
"plus_value": plus_value,
|
|
"base_efficiency": base_efficiency,
|
|
"minus_efficiency": eff_minus,
|
|
"plus_efficiency": eff_plus,
|
|
"derivative": derivative,
|
|
"normalized_sensitivity": normalized_sensitivity,
|
|
}
|
|
)
|
|
|
|
return rows
|
|
|
|
|
|
def local_rc_component_performance_sensitivity(
|
|
fixed_params,
|
|
params,
|
|
variables=None,
|
|
relative_step=0.01,
|
|
absolute_steps=None,
|
|
refprop_path=None,
|
|
):
|
|
"""Run one-at-a-time local sensitivity analysis for RC component performance."""
|
|
return local_rc_design_sensitivity(
|
|
fixed_params=fixed_params,
|
|
params=params,
|
|
variables=variables or RC_COMPONENT_PERFORMANCE_VARIABLES,
|
|
relative_step=relative_step,
|
|
absolute_steps=absolute_steps,
|
|
refprop_path=refprop_path,
|
|
)
|