"""Brayton HP targeting."""
from typing import List, Tuple
import numpy as np
from ....classes.stream_collection import StreamCollection
from ....lib.enums import PT
from ....lib.schemas.hpr import HeatPumpTargetInputs
from ....utils.decorators import timing_decorator
from ..common.shared import (
calc_hpr_obj,
get_process_heat_cascade,
)
from ..unit_models.brayton_heat_pump import SimpleBraytonHeatPumpCycle
__all__ = [
"optimise_brayton_heat_pump_placement",
]
################################################################################
# Public API
################################################################################
[docs]
@timing_decorator
def optimise_brayton_heat_pump_placement(
args: HeatPumpTargetInputs,
) -> None:
"""Optimise a single-stage Brayton Heat Pump placement against the background."""
raise NotImplementedError(
"Brayton HPR targeting is currently unsupported pending contract "
"and solver repair."
)
# args.n_cond = args.n_evap = 1
# args.refrigerant_ls = ["air"]
# x0, bnds = _get_brayton_hp_opt_setup(args)
# opt = minimize(
# fun=lambda x: _compute_brayton_hp_system_obj(x, args)["obj"],
# x0=x0,
# method="SLSQP",
# bounds=bnds,
# options={"disp": False, "maxiter": 1000},
# tol=1e-7,
# )
# if not opt.success:
# raise ValueError(f"Brayton heat pump targeting failed: {opt.message}")
# res = _compute_brayton_hp_system_obj(opt.x, args)
# res["success"] = opt.success
# return HeatPumpTargetOutputs.model_validate(res)
def _get_brayton_hp_opt_setup(
args: HeatPumpTargetInputs,
) -> tuple[list, list]:
return [
0.0,
abs(args.T_cold[0] - args.T_hot[0]) / args.dt_range_max,
abs(args.T_cold[0] - args.T_cold[-1]) / args.dt_range_max,
1.0,
], [
(-0.2, 1.0),
(0.01, 1.5),
(0.01, 1.5),
(0.01, 1.0),
]
################################################################################
# Helper Functions
################################################################################
def _parse_brayton_hp_state_variables(
x: np.ndarray,
args: HeatPumpTargetInputs,
) -> Tuple[np.ndarray]:
T_comp_out = np.array(args.T_cold[0] + x[0] * args.dt_range_max, dtype=np.float64)
dT_comp = np.array(x[1] * args.dt_range_max, dtype=np.float64)
dT_gc = np.array(x[2] * args.dt_range_max, dtype=np.float64)
Q_heat = np.array(x[3] * args.Q_hpr_target, dtype=np.float64)
return [T_comp_out], [dT_comp], [dT_gc], [Q_heat]
def _create_brayton_hp_list(
T_comp_out: np.ndarray,
dT_gc: np.ndarray,
Q_gc: np.ndarray,
dT_comp: np.ndarray,
args: HeatPumpTargetInputs,
) -> List[SimpleBraytonHeatPumpCycle]:
hp_list = []
n_hp = args.n_cond
for i in range(n_hp):
hp = SimpleBraytonHeatPumpCycle()
hp.solve(
T_comp_out=T_comp_out[i],
T_comp_in=T_comp_out[i] - dT_comp[i],
dT_gc=dT_gc[i],
Q_heat=Q_gc[i],
eta_comp=args.eta_comp,
eta_exp=args.eta_exp,
is_recuperated=False,
refrigerant=args.refrigerant_ls[0],
)
hp_list.append(hp)
return hp_list
def _compute_brayton_hp_system_obj(
x: np.ndarray,
args: HeatPumpTargetInputs,
) -> float:
T_comp_out, dT_comp, dT_gc, Q_heat = _parse_brayton_hp_state_variables(x, args)
hp_list = _create_brayton_hp_list(
T_comp_out=T_comp_out,
dT_comp=dT_comp,
dT_gc=dT_gc,
Q_gc=Q_heat,
args=args,
)
hpr_hot_streams = _build_simulated_hpr_streams(hp_list, include_cond=True)
hpr_cold_streams = _build_simulated_hpr_streams(hp_list, include_evap=True)
T_exp_out = hp_list[0].cycle_states[3]["T"]
pt_gas_cooler = get_process_heat_cascade(
hot_streams=hpr_hot_streams,
cold_streams=args.bckgrd_cold_streams,
is_shifted=True,
idx=args.idx,
)
pt_gas_heater = get_process_heat_cascade(
hot_streams=args.bckgrd_hot_streams,
cold_streams=hpr_cold_streams,
is_shifted=True,
idx=args.idx,
)
w_hpr = sum([hp.work_net for hp in hp_list])
c = (pt_gas_cooler[PT.H_NET][-1] + pt_gas_heater[PT.H_NET][0]) * 10
Q_ext = pt_gas_cooler[PT.H_NET][0]
Q_cool = np.array([hp.Q_cool for hp in hp_list])
cop = (args.Q_hpr_target - Q_ext) / (w_hpr + 1e-9)
Q_amb = 0.0
# Q_amb = _calc_Q_amb(Q_cool.sum(), np.abs(args.H_hot[-1]), args.Q_amb_max)
obj = calc_hpr_obj(
work=w_hpr,
Q_ext_heat=Q_ext,
Q_ext_cold=0.0,
Q_hpr_target=args.Q_hpr_target,
heat_to_power_ratio=args.heat_to_power_ratio,
cold_to_power_ratio=args.cold_to_power_ratio,
penalty=c,
)
return {
"obj": obj,
"utility_tot": w_hpr + Q_ext,
"w_net": w_hpr,
"Q_ext": Q_ext,
"T_comp_out": np.array(T_comp_out),
"dT_gc": np.array(dT_gc),
"Q_heat": np.array(Q_heat),
"T_evap": np.array(T_exp_out),
"dT_comp": np.array(dT_comp),
"Q_cool": np.array(Q_cool),
"cop_h": cop,
"Q_amb_hot": Q_amb if args.is_heat_pumping else 0.0,
"Q_amb_cold": 0.0 if args.is_heat_pumping else Q_amb,
"hpr_hot_streams": hpr_hot_streams,
"hpr_cold_streams": hpr_cold_streams,
"model": hp_list,
}
def _build_simulated_hpr_streams(
hp_list,
*,
is_process_stream: bool = False,
include_cond: bool = False,
include_evap: bool = False,
dtcont_hp: float = 0.0,
) -> StreamCollection:
hp_streams = StreamCollection()
for hp in hp_list:
hp_streams.add_many(
hp.build_stream_collection(
include_cond=include_cond,
include_evap=include_evap,
is_process_stream=is_process_stream,
dtcont=dtcont_hp,
)
)
return hp_streams
# def _calc_Q_amb(
# Q_evap_total: float,
# H_hot_limit: float,
# Q_amb_max: float,
# ) -> float:
# return max(Q_evap_total - (H_hot_limit - Q_amb_max), 0.0)