Source code for carbatpy.models.coupled.cb_comp

# -*- coding: utf-8 -*-
"""
Calculation of a Carnot Battery (PTES) with two energy storages.


Created on Tue Aug  5 12:33:52 2025

@author: atakan
Universität Duisburg-Essen, Germany

In the framework of the Priority Programme: "Carnot Batteries: Inverse Design from
Markets to Molecules" (SPP 2403)
https://www.uni-due.de/spp2403/
https://git.uni-due.de/spp-2403/residuals_weather_storage

"""
import copy
import carbatpy as cb


[docs] def cb_calc(dir_names, **kwargs): """ Runs a Carnot battery model by sequentially simulating a heat pump and an Organic Rankine Cycle (ORC) using the provided configuration files or dictionaries. The function first executes the heat pump cycle based on the configuration in `dir_names["hp"]`. If successful and without warnings, it extracts the relevant thermodynamic conditions and storage states to configure and launch the subsequent ORC calculation (using `dir_names["orc"]`). Both cycles may accept additional configuration dictionaries (useful for parameter optimization). If `plotting` is True, figures for both cycles are generated. The overall Carnot battery round-trip efficiency (`rte`) is computed as the product of the heat pump COP and the ORC thermal efficiency. Parameters ---------- dir_names : dict A dictionary with keys "hp" (for heat pump) and "orc" (for ORC), each containing either the path to a YAML (or similar) configuration file or a configuration dictionary. **kwargs : Additional keyword arguments. Supported keys: config : dict, optional Dictionary with additional or overriding configuration for the heat pump (`"hp"`) and/or ORC (`"orc"`) in the form {"hp": config_hp_dict, "orc": config_orc_dict}. Useful for optimization or parameter studies. verbose : bool, optional If True, enables verbose output. Default is False. plotting : bool, optional If True, generates diagrams for both the heat pump and ORC cycles. Returns ------- rte : float The round-trip efficiency of the Carnot battery (heat pump COP × ORC thermal efficiency); returns negative values if warnings occur. results : dict A dictionary with two entries: - ``"hp"`` : results {COP, outputs_hp, warnings_hp, fig_hp, ax_hp} - ``"orc"`` : results {eta_th, outputs_orc, warnings_orc, fig_orc, ax_orc} Each tuple contains the cycle performance metric, outputs, component warnings, and (optionally) plots. Notes ----- This function calls both the heat pump and ORC functions in sequence, using outputs from the heat pump (such as working fluid composition and storage temperatures) to configure the ORC run. It is suitable for integrating the full Carnot battery workflow, either for direct calculations, sensitivity studies, or optimization routines. If component warnings deviate from zero for either cycle, the function will print diagnostic messages (if ``verbose`` is True) and return a negative code for ``rte``. Examples ------- >>> dir_names = {"hp": "heat_pump_config.yaml", "orc": "orc_config.yaml"} >>> rte, results = cb_calc(dir_names, config={"hp": hp_opt_config, "orc": orc_opt_config}, plotting=True) """ new_config = kwargs.get("config", {"hp": None, "orc": None}) # for optimizations verbose = kwargs.get("verbose", False) plotting = kwargs.get("plotting", False) same_fluid = kwargs.get("same_fluid", True) heat_losses = kwargs.get("heat_losses", 0.0) results = {} hp_src = dir_names["hp"] if isinstance(hp_src, dict): hp_src = copy.deepcopy(hp_src) orc_src = dir_names["orc"] if isinstance(orc_src, dict): orc_src = copy.deepcopy(orc_src) results["hp"] = cb.hp_comp.heat_pump( hp_src, config=copy.deepcopy(new_config["hp"]), verbose=verbose, plotting=plotting, ) if any(ns.value != 0 for ns in results["hp"]["warnings"].values()): if verbose: print( f"Check HP Warnings, at least one deviates from 0!\n {results['hp']['warnings' ]}" ) return -sum(item.value for item in results["hp"]["warnings"].values()), results # Handling of orc inputs q_h = -results["hp"]["output"]["condenser"]["q_dot"] if not (0.0 <= heat_losses <= 1.0): raise ValueError("kwargs['heat_losses'] must be between 0 and 1.") temp_ambient = cb.CB_DEFAULTS["General"]["T_SUR"] temp_hot = copy.deepcopy( results["hp"]["output"]["config"]["hot_storage"]["temp_high"] ) temp_cold = copy.deepcopy( results["hp"]["output"]["config"]["cold_storage"]["temp_low"] ) temp_hot_new = temp_hot - heat_losses * (temp_hot - temp_ambient) temp_cold_new = temp_cold + heat_losses * (temp_ambient - temp_cold) from_hp_config = { "cold_storage": {"temp_low": temp_cold_new}, "hot_storage": {"temp_high": temp_hot_new}, } if same_fluid: from_hp_config["working_fluid"] = { "fractions": copy.deepcopy( results["hp"]["output"]["config"]["working_fluid"]["fractions"] ), "species": copy.deepcopy( results["hp"]["output"]["config"]["working_fluid"]["species"] ), } orc_config = copy.deepcopy(cb.utils.io_utils.read_config(orc_src)) merged_orc = copy.deepcopy(orc_config) for key in from_hp_config: merged_orc.setdefault(key, {}) merged_orc[key].update(copy.deepcopy(from_hp_config[key])) results["orc"] = cb.orc_comp.orc( merged_orc, results["hp"]["COP"], q_h, config=copy.deepcopy(new_config["orc"]), plotting=plotting, verbose=verbose, ) if any(ns.value != 0 for ns in results["orc"]["warnings"].values()): if verbose: print( f'Check ORC Warnings, at least one deviates from 0!\n {results["orc"]["warnings"]}' ) return -sum(item.value for item in results["orc"]["warnings"].values()), results rte = results["orc"]["eta_th"] * results["hp"]["COP"] if verbose: print( f"COP: {results['hp']['COP' ]:.3f}, eta: {results['orc']['eta_th']:.3f}, rte: {rte:.3f}" ) return rte, results
if __name__ == "__main__":
[docs] dir_names_both = { "hp": cb.CB_DEFAULTS["General"]["CB_DATA"] + "\\reference_data\\io-hp-data-ref.yaml", "orc": cb.CB_DEFAULTS["General"]["CB_DATA"] + "\\reference_data\\io-orc-data-ref.yaml", }
rte, res_act = cb_calc(dir_names_both, plotting=True, verbose=True) for i in ["hp", "orc"]: if any(ns.value != 0 for ns in res_act[i]["warnings"].values()): print( f"Check warnings, at least one deviates from 0!\n{res_act[i]['warnings']}" ) import matplotlib.pyplot as plt plt.show()