From ff7e856f1b03112c0063812d5000f47f3e357596 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Sat, 8 Feb 2025 10:27:35 +0100 Subject: [PATCH 1/8] Create an exerpy export --- src/tespy/networks/network.py | 88 +++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 5 deletions(-) diff --git a/src/tespy/networks/network.py b/src/tespy/networks/network.py index 123aa709..9c324951 100644 --- a/src/tespy/networks/network.py +++ b/src/tespy/networks/network.py @@ -2665,11 +2665,89 @@ def print_components(self, c, *args): def export(self, path): """Export the network structure and parametrization.""" - path, path_comps = self._create_export_paths(path) - self.export_network(path) - self.export_connections(path) - self.export_components(path_comps) - self.export_busses(path) + export = {} + export["Network"] = self._export_network() + export["Connection"] = self._export_connections() + export["Component"] = self._export_components() + export["Bus"] = self._export_busses() + + if path: + path, path_comps = self._create_export_paths(path) + for key, value in export.items(): + + fn = os.path.join(path, f'{key}.json') + with open(fn, 'w') as f: + json.dump(value, f, indent=4) + logger.debug(f'{key} information saved to {fn}.') + + return export + + def to_exerpy(self, Tamb, pamb, exerpy_mappings): + """Export the network to exerpy + + Parameters + ---------- + Tamb : float + Ambient temperature. + pamb : float + Ambient pressure. + exerpy_mappings : dict + Mappings for tespy components to exerpy components + + Returns + ------- + dict + exerpy compatible input dictionary + """ + component_json = {} + for comp_type in self.comps["comp_type"].unique(): + if comp_type not in exerpy_mappings.keys(): + msg = f"Component class {comp_type} not available in exerpy." + logger.warning(msg) + continue + + key = exerpy_mappings[comp_type] + if key not in component_json: + component_json[key] = {} + + for c in self.comps.loc[self.comps["comp_type"] == comp_type, "object"]: + component_json[key][c.label] = { + "name": c.label, + "type": comp_type, + "type_index": None, + } + + connection_json = {} + for c in self.conns["object"]: + c.get_physical_exergy(pamb, Tamb) + + connection_json[c.label] = { + "source_component": c.source.label, + "source_connector": int(c.source_id.removeprefix("out")) - 1, + "target_component": c.target.label, + "target_connector": int(c.target_id.removeprefix("in")) - 1 + } + connection_json[c.label].update({f"mass_composition": c.fluid.val}) + connection_json[c.label].update({"kind": "material"}) + for param in ["m", "T", "p", "h", "s"]: + connection_json[c.label].update({ + param: c.get_attr(param).val_SI, + f"{param}_unit": c.get_attr(param).unit + }) + connection_json[c.label].update( + {"e_T": c.ex_therm, "e_M": c.ex_mech, "e_PH": c.ex_physical} + ) + + return { + "components": component_json, + "connections": connection_json, + "ambient_conditions": { + "Tamb": Tamb, + "Tamb_unit": "K", + "pamb": pamb, + "pamb_unit": "Pa" + } + } def save(self, path): r""" From 54e72f840a5b06b669e20eda4d3bcc09c6be858f Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Sat, 8 Feb 2025 10:32:26 +0100 Subject: [PATCH 2/8] Make the export method return the export json --- src/tespy/networks/network.py | 96 +++++++++++++++++++++-------------- 1 file changed, 58 insertions(+), 38 deletions(-) diff --git a/src/tespy/networks/network.py b/src/tespy/networks/network.py index 9c324951..a17cc6cf 100644 --- a/src/tespy/networks/network.py +++ b/src/tespy/networks/network.py @@ -2663,8 +2663,20 @@ def print_components(self, c, *args): else: return np.nan - def export(self, path): - """Export the network structure and parametrization.""" + def export(self, path=None): + """Export the parametrization and structure of the Network instance + + Parameters + ---------- + path : str, optional + Path for exporting to filesystem. If path is None, the data are + only returned and not written to the filesystem, by default None + + Returns + ------- + dict + Parametrization and structure of the Network instance. + """ export = {} export["Network"] = self._export_network() export["Connection"] = self._export_connections() @@ -2672,7 +2684,7 @@ def export(self, path): export["Bus"] = self._export_busses() if path: - path, path_comps = self._create_export_paths(path) + path, _ = self._create_export_paths(path) for key, value in export.items(): fn = os.path.join(path, f'{key}.json') @@ -2786,20 +2798,6 @@ def _create_export_paths(self, path): return path, path_comps - def export_network(self, fn): - r""" - Save basic network configuration. - - Parameters - ---------- - fn : str - Path/filename for the network configuration file. - """ - with open(os.path.join(fn, 'network.json'), 'w') as f: - json.dump(self._serialize(), f, indent=4) - - logger.debug('Network information saved to %s.', fn) - def save_connections(self, fn): r""" Save the connection properties. @@ -2846,33 +2844,55 @@ def save_busses(self, fn): json.dump(bus_data, f, indent=4) logger.debug('Bus information saved to %s.', fn) - def export_connections(self, fn): + def _export_network(self): + r"""Export network information + + Returns + ------- + dict + Serialization of network object. + """ + return self._serialize() + + def _export_connections(self): + """Export connection information + + Returns + ------- + dict + Serialization of connection objects. + """ connections = {} for c in self.conns["object"]: connections.update(c._serialize()) + return connections - fn = os.path.join(fn, "connections.json") - with open(fn, "w", encoding="utf-8") as f: - json.dump(connections, f, indent=4) - logger.debug('Connection information exported to %s.', fn) + def _export_components(self): + """Export component information - def export_components(self, fn): + Returns + ------- + dict + Dict of dicts with per class serialization of component objects. + """ + components = {} for c in self.comps["comp_type"].unique(): - components = {} + components[c] = {} for cp in self.comps.loc[self.comps["comp_type"] == c, "object"]: - components.update(cp._serialize()) + components[c].update(cp._serialize()) - fname = os.path.join(fn, f"{c}.json") - with open(fname, "w", encoding="utf-8") as f: - json.dump(components, f, indent=4) - logger.debug('Component information exported to %s.', fname) + return components - def export_busses(self, fn): - if len(self.busses) > 0: - busses = {} - for bus in self.busses.values(): - busses.update(bus._serialize()) - fn = os.path.join(fn, 'busses.json') - with open(fn, "w", encoding="utf-8") as f: - json.dump(busses, f, indent=4) - logger.debug('Bus information exported to %s.', fn) + def _export_busses(self): + """Export bus information + + Returns + ------- + dict + Serialization of bus objects. + """ + busses = {} + for bus in self.busses.values(): + busses.update(bus._serialize()) + + return busses From 1aa2a0f365c0739f44c08023904f03ba19cd93a1 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Sat, 8 Feb 2025 15:37:49 +0100 Subject: [PATCH 3/8] Adapt the network reader to the new export structure --- src/tespy/networks/network_reader.py | 39 ++++++++++------------------ 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/src/tespy/networks/network_reader.py b/src/tespy/networks/network_reader.py index f75ee033..ef3666db 100644 --- a/src/tespy/networks/network_reader.py +++ b/src/tespy/networks/network_reader.py @@ -55,12 +55,10 @@ def load_network(path): The structure of the path must be as follows: - Folder: path (e.g. 'mynetwork') - - Subfolder: components ('mynetwork/components') containing - {component_class_name}.json (e.g. HeatExchanger.json) - - - connections.json - - busses.json - - network.json + - Component.json + - Connection.json + - Bus.json + - Network.json Example ------- @@ -130,7 +128,7 @@ def load_network(path): >>> nw.lin_dep False >>> nw.save('design_state') - >>> nw.export('exported_nwk') + >>> _ = nw.export('exported_nwk') >>> mass_flow = round(nw.get_conn('ambient air').m.val_SI, 1) >>> c.set_attr(igva='var') >>> nw.solve('offdesign', design_path='design_state') @@ -173,8 +171,6 @@ def load_network(path): >>> shutil.rmtree('./exported_nwk', ignore_errors=True) >>> shutil.rmtree('./design_state', ignore_errors=True) """ - path_comps = os.path.join(path, 'components') - msg = f'Reading network data from base path {path}.' logger.info(msg) @@ -184,13 +180,13 @@ def load_network(path): module_name = "tespy.components" _ = importlib.import_module(module_name) - files = os.listdir(path_comps) - for f in files: - if not f.endswith(".json"): - continue - - component = f.replace(".json", "") + fn = os.path.join(path, "Component.json") + with open(fn, "r", encoding="utf-8") as f: + component_data = json.load(f) + msg = f"Reading component data from {fn}." + logger.debug(msg) + for component, data in component_data.items(): if component not in component_registry.items: msg = ( f"A class {component} is not available through the " @@ -201,13 +197,6 @@ def load_network(path): logger.warning(msg) continue - fn = os.path.join(path_comps, f) - msg = f"Reading component data ({component}) from {fn}." - logger.debug(msg) - - with open(fn, "r", encoding="utf-8") as c: - data = json.load(c) - target_class = component_registry.items[component] comps.update(_construct_components(target_class, data)) @@ -218,7 +207,7 @@ def load_network(path): nw = _construct_network(path) # load connections - fn = os.path.join(path, 'connections.json') + fn = os.path.join(path, 'Connection.json') msg = f"Reading connection data from {fn}." logger.debug(msg) @@ -235,7 +224,7 @@ def load_network(path): logger.info(msg) # load busses - fn = os.path.join(path, 'busses.json') + fn = os.path.join(path, 'Bus.json') if os.path.isfile(fn): msg = f"Reading bus data from {fn}." @@ -316,7 +305,7 @@ def _construct_network(path): TESPy network object. """ # read network .json-file - fn = os.path.join(path, 'network.json') + fn = os.path.join(path, 'Network.json') with open(fn, 'r') as f: data = json.load(f) From 7e09f7d9fd5e55a02c5ec2428e7876420055bd16 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Sat, 8 Feb 2025 15:48:35 +0100 Subject: [PATCH 4/8] Update changelog --- docs/whats_new/v0-7-9.rst | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/docs/whats_new/v0-7-9.rst b/docs/whats_new/v0-7-9.rst index b6f6df85..fae8a8f6 100644 --- a/docs/whats_new/v0-7-9.rst +++ b/docs/whats_new/v0-7-9.rst @@ -1,4 +1,4 @@ -v0.7.9 - Under development +v0.8.0 - Under development ++++++++++++++++++++++++++ New Features @@ -7,8 +7,30 @@ New Features i.e. :code:`"l"` for liquid, :code:`"tp"` for two-phase and :code:`"g"` for gaseous. The phase is only reported in subcritical pressure (`PR #592 `__). +- Change the export of a :py:class:`tespy.networks.network.Network` instance to + separate writing to the filesystem from creating the data to export. This + breaks the API of the :py:meth:`tespy.networks.network_reader.load_network` + method, meaning exported network data based on :code:`tespy<0.8` are not + compatible with how :code:`tespy>=0.8` is handling the data + (`PR #605 `__). + + .. code-block:: python + + >>> from tespy.networks import Network + >>> from tespy.connections import Connection + >>> from tespy.components import Source, Sink + >>> nwk = Network() + >>> so = Source("source") + >>> si = Sink("sink") + >>> c1 = Connection(so, "out1", si, "in1") + >>> nwk.add_conns(c1) + >>> data = nwk.export() + >>> list(data.keys()) + ['Network', 'Connection', 'Component', 'Bus'] + >>> list(data["Component"]) + ['Source', 'Sink'] Contributors ############ -- `@tlmerbecks `__ - Francesco Witte (`@fwitte `__) +- `@tlmerbecks `__ From 320b0731fb838b28d9a9a1b4996d6e31c3a7a0e3 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Sat, 8 Feb 2025 15:49:37 +0100 Subject: [PATCH 5/8] Bump version --- docs/whats_new.rst | 2 +- docs/whats_new/{v0-7-9.rst => v0-8-0.rst} | 0 pyproject.toml | 2 +- src/tespy/__init__.py | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename docs/whats_new/{v0-7-9.rst => v0-8-0.rst} (100%) diff --git a/docs/whats_new.rst b/docs/whats_new.rst index 74b68dee..9473e53a 100644 --- a/docs/whats_new.rst +++ b/docs/whats_new.rst @@ -3,7 +3,7 @@ What's New Discover notable new features and improvements in each release -.. include:: whats_new/v0-7-9.rst +.. include:: whats_new/v0-8-0.rst .. include:: whats_new/v0-7-8-002.rst .. include:: whats_new/v0-7-8-001.rst .. include:: whats_new/v0-7-8.rst diff --git a/docs/whats_new/v0-7-9.rst b/docs/whats_new/v0-8-0.rst similarity index 100% rename from docs/whats_new/v0-7-9.rst rename to docs/whats_new/v0-8-0.rst diff --git a/pyproject.toml b/pyproject.toml index 75a57834..45fa1d6a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ exclude = ["docs/_build"] [project] name = "tespy" -version = "0.7.9.dev0" +version = "0.8.0.dev0" description = "Thermal Engineering Systems in Python (TESPy)" readme = "README.rst" authors = [ diff --git a/src/tespy/__init__.py b/src/tespy/__init__.py index 70ade2f6..7481914a 100644 --- a/src/tespy/__init__.py +++ b/src/tespy/__init__.py @@ -3,7 +3,7 @@ import os __datapath__ = os.path.join(importlib.resources.files("tespy"), "data") -__version__ = '0.7.9.dev0 - Newton\'s Nature' +__version__ = '0.8.0.dev0' # tespy data and connections import from . import connections # noqa: F401 From 3720584996512c1a3b337afe288ce1e9d3e56cc4 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Mon, 10 Feb 2025 11:48:01 +0100 Subject: [PATCH 6/8] Implement an intermediate solution to export motors and generators --- src/tespy/networks/network.py | 68 +++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/tespy/networks/network.py b/src/tespy/networks/network.py index a17cc6cf..2af53b49 100644 --- a/src/tespy/networks/network.py +++ b/src/tespy/networks/network.py @@ -2750,6 +2750,74 @@ def to_exerpy(self, Tamb, pamb, exerpy_mappings): {"e_T": c.ex_therm, "e_M": c.ex_mech, "e_PH": c.ex_physical} ) + from tespy.components.turbomachinery.base import Turbomachine + for label, bus in self.busses.items(): + + if "Motor" not in component_json: + component_json["Motor"] = {} + if "Generator" not in component_json: + component_json["Generator"] = {} + + for i, (idx, row) in enumerate(bus.comps.iterrows()): + if isinstance(idx, Turbomachine): + kind = "power" + else: + kind = "heat" + if row["base"] == "component": + label = idx.label + "__" + "generator" + connection_json[label] = { + "source_component": idx.label, + "source_connector": 999, + "target_component": f"generator_of_{idx.label}", + "target_connector": 0, + "mass_composition": None, + "kind": kind, + "rate": idx.bus_func(bus) + } + label = "generator__" + label + connection_json[label] = { + "source_component": f"generator_of_{idx.label}", + "source_connector": 0, + "target_component": label, + "target_connector": i, + "mass_composition": None, + "kind": kind, + "rate": idx.calc_bus_value(bus) + } + component_label = f"generator_of_{idx.label}" + component_json["Generator"][component_label] = { + "name": component_label, + "type": "Generator", + "type_index": None, + } + else: + label = f"{label}__motor" + component_label = f"motor_of_{idx.label}" + connection_json[label] = { + "source_component": label, + "source_connector": i, + "target_component": component_label, + "target_connector": 0, + "mass_composition": None, + "kind": kind, + "rate": idx.calc_bus_value(bus) + } + label = f"motor__{idx.label}" + connection_json[label] = { + "source_component": component_label, + "source_connector": 0, + "target_component": idx.label, + "target_connector": 999, + "mass_composition": None, + "kind": kind, + "rate": idx.bus_func(bus) + } + component_json["Motor"][component_label] = { + "name": component_label, + "type": "Motor", + "type_index": None, + } + return { "components": component_json, "connections": connection_json, From 5b606a27a56aa9c93c93b64d222bdf86b36b1152 Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Tue, 11 Feb 2025 10:53:43 +0100 Subject: [PATCH 7/8] Fix naming schemes for bus connected components --- src/tespy/networks/network.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/tespy/networks/network.py b/src/tespy/networks/network.py index 2af53b49..6376c3ea 100644 --- a/src/tespy/networks/network.py +++ b/src/tespy/networks/network.py @@ -2764,53 +2764,53 @@ def to_exerpy(self, Tamb, pamb, exerpy_mappings): else: kind = "heat" if row["base"] == "component": - label = idx.label + "__" + "generator" - connection_json[label] = { + component_label = f"generator_of_{idx.label}" + connection_label = f"{idx.label}__{component_label}" + connection_json[connection_label] = { "source_component": idx.label, "source_connector": 999, - "target_component": f"generator_of_{idx.label}", + "target_component": component_label, "target_connector": 0, "mass_composition": None, "kind": kind, - "rate": idx.bus_func(bus) + "energy_flow": idx.bus_func(bus) } - label = "generator__" + label - connection_json[label] = { - "source_component": f"generator_of_{idx.label}", + connection_label = f"{component_label}__{label}" + connection_json[connection_label] = { + "source_component": component_label, "source_connector": 0, "target_component": label, "target_connector": i, "mass_composition": None, "kind": kind, - "rate": idx.calc_bus_value(bus) + "energy_flow": idx.calc_bus_value(bus) } - component_label = f"generator_of_{idx.label}" component_json["Generator"][component_label] = { "name": component_label, "type": "Generator", "type_index": None, } else: - label = f"{label}__motor" component_label = f"motor_of_{idx.label}" - connection_json[label] = { + connection_label = f"{label}__{component_label}" + connection_json[connection_label] = { "source_component": label, "source_connector": i, "target_component": component_label, "target_connector": 0, "mass_composition": None, "kind": kind, - "rate": idx.calc_bus_value(bus) + "energy_flow": idx.calc_bus_value(bus) } - label = f"motor__{idx.label}" - connection_json[label] = { + connection_label = f"{component_label}__{idx.label}" + connection_json[connection_label] = { "source_component": component_label, "source_connector": 0, "target_component": idx.label, "target_connector": 999, "mass_composition": None, "kind": kind, - "rate": idx.bus_func(bus) + "energy_flow": idx.bus_func(bus) } component_json["Motor"][component_label] = { "name": component_label, From 25b3b1654991804ae1e13646e0737ce453fababc Mon Sep 17 00:00:00 2001 From: Francesco Witte Date: Wed, 12 Feb 2025 16:12:57 +0100 Subject: [PATCH 8/8] Remove unnecessary fields and use absolute values for generator energy streams --- src/tespy/networks/network.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/tespy/networks/network.py b/src/tespy/networks/network.py index 6376c3ea..0fa516ba 100644 --- a/src/tespy/networks/network.py +++ b/src/tespy/networks/network.py @@ -2725,8 +2725,7 @@ def to_exerpy(self, Tamb, pamb, exerpy_mappings): for c in self.comps.loc[self.comps["comp_type"] == comp_type, "object"]: component_json[key][c.label] = { "name": c.label, - "type": comp_type, - "type_index": None, + "type": comp_type } connection_json = {} @@ -2763,6 +2762,7 @@ def to_exerpy(self, Tamb, pamb, exerpy_mappings): kind = "power" else: kind = "heat" + if row["base"] == "component": component_label = f"generator_of_{idx.label}" connection_label = f"{idx.label}__{component_label}" @@ -2773,7 +2773,7 @@ def to_exerpy(self, Tamb, pamb, exerpy_mappings): "target_connector": 0, "mass_composition": None, "kind": kind, - "energy_flow": idx.bus_func(bus) + "energy_flow": abs(idx.bus_func(bus)) } connection_label = f"{component_label}__{label}" connection_json[connection_label] = { @@ -2783,13 +2783,14 @@ def to_exerpy(self, Tamb, pamb, exerpy_mappings): "target_connector": i, "mass_composition": None, "kind": kind, - "energy_flow": idx.calc_bus_value(bus) + "energy_flow": abs(idx.calc_bus_value(bus)) } component_json["Generator"][component_label] = { "name": component_label, "type": "Generator", "type_index": None, } + else: component_label = f"motor_of_{idx.label}" connection_label = f"{label}__{component_label}"