diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 802ab066e..d13c31c25 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1 +1,3 @@ -The developer rules can be found in the chapter [developing TESPy](https://tespy.readthedocs.io/en/latest/developing_tespy.html) of the tespy.documentation. +The developer rules can be found in the chapter +[developing TESPy](https://tespy.readthedocs.io/en/dev/developing_tespy.html) +of the tespy.documentation. diff --git a/docs/api/tespy.tools.rst b/docs/api/tespy.tools.rst index 999837bd5..17543b064 100644 --- a/docs/api/tespy.tools.rst +++ b/docs/api/tespy.tools.rst @@ -7,7 +7,7 @@ tespy.tools module :show-inheritance: tespy.tools.analyses module --------------------------- +--------------------------- .. automodule:: tespy.tools.analyses :members: diff --git a/docs/conf.py b/docs/conf.py index c76ac8396..0c81e8ae7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -153,7 +153,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ['./_build/_static'] html_css_files = ['css/custom.css', ] diff --git a/docs/references.bib b/docs/references.bib index 6f416d20f..f4fd301ea 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -235,10 +235,3 @@ @book{Bejan1996 year={1996}, publisher = {Wiley} } - -@book{ET1, - editor = {George Tsatsaronis}, - title = {Energietechnik - Umdruck zur Vorlesung}, - publisher = {TU Berlin - Institut für Energietechnik - Fachgebiet für Energietechnik und Umweltschutz}, - year = {2018} -} diff --git a/docs/whats_new.rst b/docs/whats_new.rst index 8c2b44b44..9e40c3c6c 100644 --- a/docs/whats_new.rst +++ b/docs/whats_new.rst @@ -7,7 +7,8 @@ Discover noteable new features and improvements in each release :depth: 1 :local: :backlinks: top - + +.. include:: whats_new/v0-4-4.rst .. include:: whats_new/v0-4-3-003.rst .. include:: whats_new/v0-4-3-001.rst .. include:: whats_new/v0-4-3.rst diff --git a/docs/whats_new/v0-0-5.rst b/docs/whats_new/v0-0-5.rst index 37b84be64..fd2735706 100644 --- a/docs/whats_new/v0-0-5.rst +++ b/docs/whats_new/v0-0-5.rst @@ -3,7 +3,8 @@ v0.0.5 (October, 25, 2018) New Features ############ -- added new component: motoric cogeneration unit (`79a1177 `_). An example is provided `here `_. +- added new component: motoric cogeneration unit (`79a1177 `_). + An example is provided `here `_. - improved fluid property checks (`8adc76c `_). - added bus characteristics for modeling variable efficiencys (e.g. for generator, motor, boiler) (`79a1177 `_). - isentropic efficiency characteristic for compressor linked to pressure ratio (`85d317d `_). @@ -11,7 +12,8 @@ New Features Documentation ############# -- adapted documentation and (`example code `_) in regard of new features. +- adapted documentation and (`example code `_) + in regard of new features. - fixed some typos in documentation. Parameter renaming diff --git a/docs/whats_new/v0-4-4.rst b/docs/whats_new/v0-4-4.rst new file mode 100644 index 000000000..dda3f10c6 --- /dev/null +++ b/docs/whats_new/v0-4-4.rst @@ -0,0 +1,32 @@ +v0.4.4 - Reynolds' Reminiscence (July, 14, 2021) +++++++++++++++++++++++++++++++++++++++++++++++++ + +Documentation +############# +- Fix some typos here and there. + +Bug Fixes +######### +- Fix pandas version 1.3.0 dependencies + (`PR #277 `_). +- Add missing results for some components in the results DataFrame of the + network (`PR #277 `_). +- Fix exergy balance equation of class merge + :py:class:`tespy.components.nodes.merge.Merge.exergy_balance` + (`PR #280 `_). +- Fix a lot of DeprecationWarnings in pandas + (`PR #280 `_). + +Other Changes +############# +- Add warning logs for missing fluid composition data in the initialisation + process of the network + (`PR #278 `_). +- Add Dodecane to the available fuels for combustion + (`PR #273 `_). +- Add tests for the solar energy generating system + (`PR #280 `_). + +Contributors +############ +- Francesco Witte (`@fwitte `_) diff --git a/setup.cfg b/setup.cfg index 619e99e0b..fc3581aa2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,7 +6,7 @@ max-line-length = 140 exclude = */migrations/*, docs/conf.py [metadata] -description-file = README.rst +description_file = README.rst [options] # tests_require is a list of dependencies that are *absolutely required* @@ -47,7 +47,7 @@ python_files = tests.py addopts = -ra - --strict + --strict-markers --doctest-modules --doctest-glob=\*.rst --tb=short diff --git a/setup.py b/setup.py index c0de83b20..4b39a70e4 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ def read(*names, **kwargs): setup( name='TESPy', - version='0.4.3-005', + version='0.4.4', license='MIT', description='Thermal Engineering Systems in Python (TESPy)', long_description='%s' % ( @@ -58,7 +58,7 @@ def read(*names, **kwargs): ], project_urls={ 'Documentation': 'https://tespy.readthedocs.io/', - 'Changelog': 'https://tespy.readthedocs.io/en/latest/whats_new.html', + 'Changelog': 'https://tespy.readthedocs.io/en/main/whats_new.html', 'Issue Tracker': 'https://github.com/oemof/tespy/issues', }, python_requires='>=3.7, <3.9', @@ -72,6 +72,6 @@ def read(*names, **kwargs): ], extras_require={ 'dev': ['pytest', 'sphinx', 'sphinx_rtd_theme', - 'sphinxcontrib.bibtex', ], + 'sphinxcontrib.bibtex', 'tox', ], 'dummy': ['tespy']} ) diff --git a/src/tespy/__init__.py b/src/tespy/__init__.py index a3ca641e8..0fda9b323 100644 --- a/src/tespy/__init__.py +++ b/src/tespy/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -__version__ = '0.4.3-005 - Grassmann\'s Graph' +__version__ = '0.4.4 - Reynolds\' Reminiscence' # tespy data and connections import from . import connections # noqa: F401 diff --git a/src/tespy/components/__init__.py b/src/tespy/components/__init__.py index d7adcf19e..c7fd7a105 100644 --- a/src/tespy/components/__init__.py +++ b/src/tespy/components/__init__.py @@ -26,33 +26,3 @@ from .turbomachinery.compressor import Compressor # noqa: F401 from .turbomachinery.pump import Pump # noqa: F401 from .turbomachinery.turbine import Turbine # noqa: F401 - -# ############## deprecated components errors ############## # - -from .deprecated import source # noqa: F401 -from .deprecated import sink # noqa: F401 -from .deprecated import cycle_closer # noqa: F401 -from .deprecated import subsystem_interface # noqa: F401 -from .deprecated import combustion_chamber # noqa: F401 -from .deprecated import combustion_chamber_stoich # noqa: F401 -from .deprecated import combustion_engine # noqa: F401 -from .deprecated import orc_evaporator # noqa: F401 -from .deprecated import condenser # noqa: F401 -from .deprecated import desuperheater # noqa: F401 -from .deprecated import heat_exchanger # noqa: F401 -from .deprecated import heat_exchanger_simple # noqa: F401 -from .deprecated import parabolic_trough # noqa: F401 -from .deprecated import solar_collector # noqa: F401 -from .deprecated import droplet_separator # noqa: F401 -from .deprecated import drum # noqa: F401 -from .deprecated import merge # noqa: F401 -from .deprecated import node # noqa: F401 -from .deprecated import separator # noqa: F401 -from .deprecated import splitter # noqa: F401 -from .deprecated import pipe # noqa: F401 -from .deprecated import valve # noqa: F401 -from .deprecated import water_electrolyzer # noqa: F401 -from .deprecated import compressor # noqa: F401 -from .deprecated import pump # noqa: F401 -from .deprecated import turbine # noqa: F401 -from .deprecated import subsystem # noqa: F401 diff --git a/src/tespy/components/basics/cycle_closer.py b/src/tespy/components/basics/cycle_closer.py index 1c07635b2..d8f653968 100644 --- a/src/tespy/components/basics/cycle_closer.py +++ b/src/tespy/components/basics/cycle_closer.py @@ -99,8 +99,8 @@ def component(): @staticmethod def get_variables(): return { - 'mass_deviation': dc_cp(val=0, max_val=1e-3), - 'fluid_deviation': dc_cp(val=0, max_val=1e-5) + 'mass_deviation': dc_cp(val=0, max_val=1e-3, is_result=True), + 'fluid_deviation': dc_cp(val=0, max_val=1e-5, is_result=True) } def get_mandatory_constraints(self): diff --git a/src/tespy/components/basics/sink.py b/src/tespy/components/basics/sink.py index 0bc62406a..492249978 100644 --- a/src/tespy/components/basics/sink.py +++ b/src/tespy/components/basics/sink.py @@ -101,7 +101,7 @@ def exergy_balance(self, T0): ---- .. math:: - \dot{E}_\mathrm{bus} = E_\mathrm{in}^\mathrm{PH} + \dot{E}_\mathrm{bus} = \dot{E}_\mathrm{in}^\mathrm{PH} """ self.E_P = np.nan self.E_F = np.nan diff --git a/src/tespy/components/basics/source.py b/src/tespy/components/basics/source.py index af35456c3..7dc6e722f 100644 --- a/src/tespy/components/basics/source.py +++ b/src/tespy/components/basics/source.py @@ -101,7 +101,7 @@ def exergy_balance(self, T0): ---- .. math:: - \dot{E}_\mathrm{bus} = E_\mathrm{out}^\mathrm{PH} + \dot{E}_\mathrm{bus} = \dot{E}_\mathrm{out}^\mathrm{PH} """ self.E_P = np.nan self.E_F = np.nan diff --git a/src/tespy/components/combustion/combustion_chamber.py b/src/tespy/components/combustion/combustion_chamber.py index 9ace45db2..bdaf94277 100644 --- a/src/tespy/components/combustion/combustion_chamber.py +++ b/src/tespy/components/combustion/combustion_chamber.py @@ -201,7 +201,8 @@ def comp_init(self, nw): def setup_reaction_parameters(self): r"""Setup parameters for reaction (gas name aliases and LHV).""" self.fuel_list = [] - fuels = ['methane', 'ethane', 'propane', 'butane', 'hydrogen'] + fuels = [ + 'methane', 'ethane', 'propane', 'butane', 'hydrogen', 'nDodecane'] for f in fuels: self.fuel_list += [x for x in self.nw_fluids if x in [ a.replace(' ', '') for a in CP.get_aliases(f)]] @@ -209,14 +210,15 @@ def setup_reaction_parameters(self): if len(self.fuel_list) == 0: msg = ('Your network\'s fluids do not contain any fuels, that are ' 'available for the component ' + self.label + ' of type ' + - self.component() + '. Available fuels are: ' + str(fuels) + - '.') + self.component() + '. Available fuels are: ' + + ', '.join(fuels) + '.') logging.error(msg) raise TESPyComponentError(msg) else: msg = ('The fuels for component ' + self.label + ' of type ' + - self.component() + ' are: ' + str(self.fuel_list) + '.') + self.component() + ' are: ' + ', '.join(self.fuel_list) + + '.') logging.debug(msg) for fluid in ['o2', 'co2', 'h2o', 'n2']: @@ -277,6 +279,7 @@ def calc_lhv(self, f): hf['ethane'] = -84.68 hf['propane'] = -103.8 hf['butane'] = -124.51 + hf['nDodecane'] = -288.1 hf[self.o2] = 0 hf[self.co2] = -393.5 # water (gaseous) diff --git a/src/tespy/components/deprecated.py b/src/tespy/components/deprecated.py deleted file mode 100644 index 46cc2d52e..000000000 --- a/src/tespy/components/deprecated.py +++ /dev/null @@ -1,129 +0,0 @@ -# -*- coding: utf-8 -import logging - -from tespy.tools.helpers import TESPyComponentError - -# %% - - -class PlaceHolderForError: - r"""Throw errors for deprecated classes.""" - - def __init__(self, *args, **kwargs): - msg = ( - 'Version 0.4.x introduced PEP 8 conform class names for all ' - 'classes used in TESPy. Please refer to ' - 'https://tespy.readthedocs.io/en/main/whats_new.html ' - 'learn about the changes necessary to adapt your script to the ' - 'latest API.' - ) - logging.error(msg) - raise TESPyComponentError(msg) - - -class cycle_closer(PlaceHolderForError): - pass - - -class sink(PlaceHolderForError): - pass - - -class source(PlaceHolderForError): - pass - - -class subsystem_interface(PlaceHolderForError): - pass - - -class combustion_chamber(PlaceHolderForError): - pass - - -class combustion_chamber_stoich(PlaceHolderForError): - pass - - -class combustion_engine(PlaceHolderForError): - pass - - -class orc_evaporator(PlaceHolderForError): - pass - - -class condenser(PlaceHolderForError): - pass - - -class desuperheater(PlaceHolderForError): - pass - - -class heat_exchanger(PlaceHolderForError): - pass - - -class heat_exchanger_simple(PlaceHolderForError): - pass - - -class parabolic_trough(PlaceHolderForError): - pass - - -class solar_collector(PlaceHolderForError): - pass - - -class droplet_separator(PlaceHolderForError): - pass - - -class drum(PlaceHolderForError): - pass - - -class merge(PlaceHolderForError): - pass - - -class node(PlaceHolderForError): - pass - - -class separator(PlaceHolderForError): - pass - - -class splitter(PlaceHolderForError): - pass - - -class pipe(PlaceHolderForError): - pass - - -class valve(PlaceHolderForError): - pass - - -class water_electrolyzer(PlaceHolderForError): - pass - - -class compressor(PlaceHolderForError): - pass - - -class pump(PlaceHolderForError): - pass - - -class turbine(PlaceHolderForError): - pass - - -class subsystem(PlaceHolderForError): - pass diff --git a/src/tespy/components/heat_exchangers/heat_exchanger_simple.py b/src/tespy/components/heat_exchangers/heat_exchanger_simple.py index 86ac500ed..8342079f1 100644 --- a/src/tespy/components/heat_exchangers/heat_exchanger_simple.py +++ b/src/tespy/components/heat_exchangers/heat_exchanger_simple.py @@ -868,6 +868,9 @@ def calc_parameters(self): td_log = np.nan self.kA.val = abs(i[0] * (o[2] - i[2]) / td_log) + self.kA.is_result = True + else: + self.kA.is_result = False def entropy_balance(self): r""" diff --git a/src/tespy/components/heat_exchangers/parabolic_trough.py b/src/tespy/components/heat_exchangers/parabolic_trough.py index 1a2d0eeeb..fd5024b3c 100644 --- a/src/tespy/components/heat_exchangers/parabolic_trough.py +++ b/src/tespy/components/heat_exchangers/parabolic_trough.py @@ -366,3 +366,6 @@ def calc_parameters(self): )) if self.energy_group.is_set: self.Q_loss.val = - self.E.val * self.A.val + self.Q.val + self.Q_loss.is_result = True + else: + self.Q_loss.is_result = False diff --git a/src/tespy/components/heat_exchangers/solar_collector.py b/src/tespy/components/heat_exchangers/solar_collector.py index 677ea5c61..c93ea52b2 100644 --- a/src/tespy/components/heat_exchangers/solar_collector.py +++ b/src/tespy/components/heat_exchangers/solar_collector.py @@ -315,3 +315,6 @@ def calc_parameters(self): )) if self.energy_group.is_set: self.Q_loss.val = -(self.E.val * self.A.val - self.Q.val) + self.Q_loss.is_result = True + else: + self.Q_loss.is_result = False diff --git a/src/tespy/components/nodes/drum.py b/src/tespy/components/nodes/drum.py index 2d4d5e4f2..f67984f86 100644 --- a/src/tespy/components/nodes/drum.py +++ b/src/tespy/components/nodes/drum.py @@ -293,8 +293,8 @@ def exergy_balance(self, T0): .. math:: - \dot{E}_\mathrm{P} = E_\mathrm{out}^\mathrm{PH}\\ - \dot{E}_\mathrm{F} = \sum E_{\mathrm{in,}j}^\mathrm{PH} + \dot{E}_\mathrm{P} = \sum \dot{E}_{\mathrm{out,}j}^\mathrm{PH}\\ + \dot{E}_\mathrm{F} = \sum \dot{E}_{\mathrm{in,}i}^\mathrm{PH} """ self.E_P = self.outl[0].Ex_physical + self.outl[1].Ex_physical self.E_F = self.inl[0].Ex_physical + self.inl[1].Ex_physical diff --git a/src/tespy/components/nodes/merge.py b/src/tespy/components/nodes/merge.py index 3b107668e..53e7bd25d 100644 --- a/src/tespy/components/nodes/merge.py +++ b/src/tespy/components/nodes/merge.py @@ -400,15 +400,90 @@ def exergy_balance(self, T0): ---- Please note, that the exergy balance accounts for physical exergy only. - .. math:: - - \dot{E}_\mathrm{P} = E_\mathrm{out}^\mathrm{PH}\\ - \dot{E}_\mathrm{F} = \sum E_{\mathrm{in,}j}^\mathrm{PH} + .. math :: + + \dot{E}_\mathrm{P} = + \begin{cases} + \begin{cases} + \sum_i \dot{m}_i \cdot \left(e_\mathrm{out}^\mathrm{PH} - + e_{\mathrm{in,}i}^\mathrm{PH}\right) + & T_{\mathrm{in,}i} < T_\mathrm{out} \text{ \& } + T_{\mathrm{in,}i} \geq T_0 \\ + \sum_i \dot{m}_i \cdot e_\mathrm{out}^\mathrm{PH} + & T_{\mathrm{in,}i} < T_\mathrm{out} \text{ \& } + T_{\mathrm{in,}i} < T_0 \\ + \end{cases} & T_\mathrm{out} > T_0\\ + + \text{not defined (nan)} & T_\mathrm{out} = T_0\\ + + \begin{cases} + \sum_i \dot{m}_i \cdot e_\mathrm{out}^\mathrm{PH} + & T_{\mathrm{in,}i} > T_\mathrm{out} \text{ \& } + T_{\mathrm{in,}i} \geq T_0 \\ + \sum_i \dot{m}_i \cdot \left(e_\mathrm{out}^\mathrm{PH} - + e_{\mathrm{in,}i}^\mathrm{PH}\right) + & T_{\mathrm{in,}i} > T_\mathrm{out} \text{ \& } + T_{\mathrm{in,}i} < T_0 \\ + \end{cases} & T_\mathrm{out} < T_0\\ + \end{cases} + + \dot{E}_\mathrm{F} = + \begin{cases} + \begin{cases} + \sum_i \dot{m}_i \cdot \left(e_{\mathrm{in,}i}^\mathrm{PH} - + e_\mathrm{out}^\mathrm{PH}\right) + & T_{\mathrm{in,}i} > T_\mathrm{out} \\ + \sum_i \dot{E}_{\mathrm{in,}i}^\mathrm{PH} + & T_{\mathrm{in,}i} < T_\mathrm{out} \text{ \& } + T_{\mathrm{in,}i} < T_0 \\ + \end{cases} & T_\mathrm{out} > T_0\\ + + \sum_i \dot{E}_{\mathrm{in,}i}^\mathrm{PH} & T_\mathrm{out} = T_0\\ + + \begin{cases} + \sum_i \dot{E}_{\mathrm{in,}i}^\mathrm{PH} + & T_{\mathrm{in,}i} > T_\mathrm{out} \text{ \& } + T_{\mathrm{in,}i} \geq T_0 \\ + \sum_i \dot{m}_i \cdot \left(e_{\mathrm{in,}i}^\mathrm{PH} - + e_\mathrm{out}^\mathrm{PH}\right) + & T_{\mathrm{in,}i} < T_\mathrm{out} \\ + \end{cases} & T_\mathrm{out} < T_0\\ + \end{cases} + + \forall i \in \text{merge inlets} + + \dot{E}_\mathrm{bus} = \text{not defined (nan)} """ - self.E_P = self.outl[0].Ex_physical + self.E_P = 0 self.E_F = 0 - for i in self.inl: - self.E_F += i.Ex_physical + if self.outl[0].T.val_SI > T0: + for i in self.inl: + if i.T.val_SI < self.outl[0].T.val_SI: + if i.T.val_SI >= T0: + self.E_P += i.m.val_SI * ( + self.outl[0].ex_physical - i.ex_physical) + else: + self.E_P += i.m.val_SI * self.outl[0].ex_physical + self.E_F += i.Ex_physical + else: + self.E_F += i.m.val_SI * ( + i.ex_physical - self.outl[0].ex_physical) + elif self.outl[0].T.val_SI == T0: + self.E_P = np.nan + for i in self.inl: + self.E_F += i.Ex_physical + else: + for i in self.inl: + if i.T.val_SI > self.outl[0].T.val_SI: + if i.T.val_SI >= T0: + self.E_P += i.m.val_SI * self.outl[0].ex_physical + self.E_F += i.Ex_physical + else: + self.E_P += i.m.val_SI * ( + self.outl[0].ex_physical - i.ex_physical) + else: + self.E_F += i.m.val_SI * ( + i.ex_physical - self.outl[0].ex_physical) self.E_bus = np.nan self.E_D = self.E_F - self.E_P diff --git a/src/tespy/connections/__init__.py b/src/tespy/connections/__init__.py index b2ef3c0e2..62a8389b3 100644 --- a/src/tespy/connections/__init__.py +++ b/src/tespy/connections/__init__.py @@ -3,9 +3,3 @@ from .bus import Bus # noqa: F401 from .connection import Connection # noqa: F401 from .connection import Ref # noqa: F401 - -# ############## deprecated components errors ############## # - -from .deprecated import bus # noqa: F401 -from .deprecated import connection # noqa: F401 -from .deprecated import ref # noqa: F401 diff --git a/src/tespy/connections/bus.py b/src/tespy/connections/bus.py index 6c7c47879..fa31c6373 100644 --- a/src/tespy/connections/bus.py +++ b/src/tespy/connections/bus.py @@ -184,7 +184,8 @@ class Bus: def __init__(self, label, **kwargs): self.comps = pd.DataFrame( - columns=['param', 'P_ref', 'char', 'efficiency', 'base']) + columns=['param', 'P_ref', 'char', 'efficiency', 'base'], + dtype='object') self.label = label self.P = dc_simple(val=np.nan, is_set=False) diff --git a/src/tespy/connections/deprecated.py b/src/tespy/connections/deprecated.py deleted file mode 100644 index 028f93a04..000000000 --- a/src/tespy/connections/deprecated.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -import logging - -from tespy.tools.helpers import TESPyConnectionError - -# %% - - -class PlaceHolderForError: - r"""Throw errors for deprecated classes.""" - - def __init__(self, *args, **kwargs): - msg = ( - 'Version 0.4.x introduced PEP 8 conform class names for all ' - 'classes used in TESPy. Please refer to ' - 'https://tespy.readthedocs.io/en/main/whats_new.html ' - 'learn about the changes necessary to adapt your script to the ' - 'latest API.' - ) - logging.error(msg) - raise TESPyConnectionError(msg) - - -class bus(PlaceHolderForError): - pass - - -class connection(PlaceHolderForError): - pass - - -class ref(PlaceHolderForError): - pass diff --git a/src/tespy/networks/__init__.py b/src/tespy/networks/__init__.py index eda5e7152..a969a3736 100644 --- a/src/tespy/networks/__init__.py +++ b/src/tespy/networks/__init__.py @@ -1,7 +1,3 @@ # -*- coding: utf-8 from .network import Network # noqa: F401 from .network_reader import load_network # noqa: F401 - -# ############## deprecated components errors ############## # - -from .deprecated import network # noqa: F401 diff --git a/src/tespy/networks/deprecated.py b/src/tespy/networks/deprecated.py deleted file mode 100644 index d84c04229..000000000 --- a/src/tespy/networks/deprecated.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -import logging - -from tespy.tools.helpers import TESPyNetworkError - -# %% - - -class network: - r"""Throw errors for deprecated classes.""" - - def __init__(self, *args, **kwargs): - msg = ( - 'Version 0.4.x introduced PEP 8 conform class names for all ' - 'classes used in TESPy. Please refer to ' - 'https://tespy.readthedocs.io/en/main/whats_new.html ' - 'learn about the changes necessary to adapt your script to the ' - 'latest API.' - ) - logging.error(msg) - raise TESPyNetworkError(msg) diff --git a/src/tespy/networks/network.py b/src/tespy/networks/network.py index 195319c81..0d27306be 100644 --- a/src/tespy/networks/network.py +++ b/src/tespy/networks/network.py @@ -174,7 +174,8 @@ def set_defaults(self): """Set default network properties.""" # connection dataframe self.conns = pd.DataFrame( - columns=['object', 'source', 'source_id', 'target', 'target_id']) + columns=['object', 'source', 'source_id', 'target', 'target_id'], + dtype='object') # user defined function dictionary for fast access self.user_defined_eq = {} # bus dictionary @@ -251,6 +252,12 @@ def set_fluid_back_ends(self, memorise_fluid_properties): columns=cols, dtype='bool') self.specifications['Ref'] = pd.DataFrame( columns=cols, dtype='bool') + self.specifications['lookup'] = { + 'properties': 'prop_specifications', + 'chars': 'char_specifications', + 'variables': 'var_specifications', + 'groups': 'group_specifications' + } def set_attr(self, **kwargs): r""" @@ -695,7 +702,7 @@ def init_components(self, comps): connections. Thus it does not hold any additional information, the dataframe is used to simplify the code, only. """ - self.comps = pd.DataFrame() + self.comps = pd.DataFrame(dtype='object') labels = [] for comp in comps: @@ -737,7 +744,7 @@ def init_components(self, comps): chars += [col] self.specifications[comp_type] = { 'groups': pd.DataFrame(columns=groups, dtype='bool'), - 'chars': pd.DataFrame(columns=chars, dtype='bool'), + 'chars': pd.DataFrame(columns=chars, dtype='object'), 'variables': pd.DataFrame(columns=cols, dtype='bool'), 'properties': pd.DataFrame(columns=cols, dtype='bool') } @@ -964,7 +971,7 @@ def init_design(self): for cp in b.comps.index: b.comps.loc[cp, 'P_ref'] = np.nan - series = pd.Series(dtype=np.float64) + series = pd.Series(dtype='float64') for cp in self.comps['object']: # read design point information of components with # local_offdesign activated from their respective design path @@ -1023,29 +1030,10 @@ def init_design(self): # component initialisation cp.comp_init(self) ct = cp.__class__.__name__ - try: - self.specifications[ct]['properties'].loc[cp.label] = ( - cp.prop_specifications) - except ValueError: - pass - - try: - self.specifications[ct]['variables'].loc[cp.label] = ( - cp.var_specifications) - except ValueError: - pass - - try: - self.specifications[ct]['groups'].loc[cp.label] = ( - cp.group_specifications) - except ValueError: - pass - - try: - self.specifications[ct]['chars'].loc[cp.label] = ( - cp.char_specifications) - except ValueError: - pass + for spec in self.specifications[ct].keys(): + if len(cp.get_attr(self.specifications['lookup'][spec])) > 0: + self.specifications[ct][spec].loc[cp.label] = ( + cp.get_attr(self.specifications['lookup'][spec])) # count number of component equations and variables self.num_comp_vars += cp.num_vars @@ -1268,29 +1256,11 @@ def init_offdesign(self): # start component initialisation cp.comp_init(self) ct = cp.__class__.__name__ - try: - self.specifications[ct]['properties'].loc[cp.label] = ( - cp.prop_specifications) - except ValueError: - pass + for spec in self.specifications[ct].keys(): + if len(cp.get_attr(self.specifications['lookup'][spec])) > 0: + self.specifications[ct][spec].loc[cp.label] = ( + cp.get_attr(self.specifications['lookup'][spec])) - try: - self.specifications[ct]['variables'].loc[cp.label] = ( - cp.var_specifications) - except ValueError: - pass - - try: - self.specifications[ct]['groups'].loc[cp.label] = ( - cp.group_specifications) - except ValueError: - pass - - try: - self.specifications[ct]['chars'].loc[cp.label] = ( - cp.char_specifications) - except ValueError: - pass cp.new_design = False self.num_comp_vars += cp.num_vars self.num_comp_eq += cp.num_eq @@ -1379,6 +1349,16 @@ def init_properties(self): 'connections.csv of init_path ' + self.init_path + '.') logging.debug(msg) + if sum(c.fluid.val.values()) == 0: + msg = ( + 'The starting value for the fluid composition of the ' + 'connection ' + c.label + ' is empty. This might lead to ' + 'issues in the initialisation and solving process as ' + 'fluid property functions can not be called. Make sure ' + 'you specified a fluid composition in all parts of the ' + 'network.') + logging.warning(msg) + for key in ['m', 'p', 'h']: if not c.good_starting_values: self.init_val0(c, key) @@ -2533,8 +2513,11 @@ def process_components(self): key = cp.__class__.__name__ for param in self.results[key].columns: p = cp.get_attr(param) - if p.func is not None or (p.func is None and p.is_set): + if (p.func is not None or (p.func is None and p.is_set) or + p.is_result): self.results[key].loc[cp.label, param] = p.val + else: + self.results[key].loc[cp.label, param] = np.nan def process_busses(self): """Process the bus results.""" @@ -2579,24 +2562,33 @@ def print_results(self, colored=True, colors={}): } coloring.update(colors) + if not hasattr(self, 'results'): + msg = ( + 'It is not possible to print the results of a network, that ' + 'has never been solved successfully. Results DataFrames are ' + 'only available after a full simulation run is performed.') + raise hlp.TESPyNetworkError(msg) + for cp in self.comps['comp_type'].unique(): df = self.results[cp].copy() # are there any parameters to print? - cols = df.columns - if len(cols) > 0: - for col in cols: - df[col] = df.apply( - self.print_components, axis=1, - args=(col, colored, coloring)) - - df.dropna(how='all', axis=1, inplace=True) - - if df.size > 0: - # printout with tabulate - print('##### RESULTS (' + cp + ') #####') - print(tabulate( - df, headers='keys', tablefmt='psql', floatfmt='.2e')) + if df.size > 0: + cols = df.columns + if len(cols) > 0: + for col in cols: + df[col] = df.apply( + self.print_components, axis=1, + args=(col, colored, coloring)) + + df.dropna(how='all', inplace=True) + + if len(df) > 0: + # printout with tabulate + print('##### RESULTS (' + cp + ') #####') + print(tabulate( + df, headers='keys', tablefmt='psql', + floatfmt='.2e')) # connection properties df = self.results['Connection'].loc[:, ['m', 'p', 'h', 'T']] @@ -2906,7 +2898,8 @@ def save_busses(self, fn): """ if len(self.busses) > 0: df = pd.DataFrame( - {'id': self.busses.values()}, index=self.busses.values()) + {'id': self.busses.values()}, index=self.busses.values(), + dtype='object') df['label'] = df.apply(Network.get_props, axis=1, args=('label',)) df['P'] = df.apply(Network.get_props, axis=1, args=('P', 'val')) df['P_set'] = df.apply(Network.get_props, axis=1, @@ -2946,7 +2939,8 @@ def save_characteristics(self, path): # characteristic line export if len(char_lines) > 0: # get id and data - df = pd.DataFrame({'id': char_lines}, index=char_lines) + df = pd.DataFrame( + {'id': char_lines}, index=char_lines, dtype='object') df['id'] = df.apply(Network.get_id, axis=1) df['type'] = df.apply(Network.get_class_base, axis=1) @@ -2962,7 +2956,8 @@ def save_characteristics(self, path): if len(char_maps) > 0: # get id and data - df = pd.DataFrame({'id': char_maps}, index=char_maps) + df = pd.DataFrame( + {'id': char_maps}, index=char_maps, dtype='object') df['id'] = df.apply(Network.get_id, axis=1) df['type'] = df.apply(Network.get_class_base, axis=1) diff --git a/src/tespy/networks/network_reader.py b/src/tespy/networks/network_reader.py index dccdc5a51..c83ec739d 100644 --- a/src/tespy/networks/network_reader.py +++ b/src/tespy/networks/network_reader.py @@ -12,13 +12,11 @@ SPDX-License-Identifier: MIT """ - import ast import json import logging import os -import numpy as np import pandas as pd from tespy.components import CombustionChamber @@ -274,7 +272,8 @@ def load_network(path): logging.debug(msg) except FileNotFoundError: - char_lines = pd.DataFrame(columns=['id', 'type', 'x', 'y']) + char_lines = pd.DataFrame( + columns=['id', 'type', 'x', 'y'], dtype='object') # load characteristic maps fn = path_comps + 'char_map.csv' @@ -287,10 +286,11 @@ def load_network(path): 'z': ast.literal_eval}) except FileNotFoundError: - char_maps = pd.DataFrame(columns=['id', 'type', 'x', 'y', 'z']) + char_maps = pd.DataFrame( + columns=['id', 'type', 'x', 'y', 'z'], dtype='object') # load components - comps = pd.DataFrame() + comps = pd.DataFrame(dtype='object') files = os.listdir(path_comps) for f in files: @@ -354,7 +354,7 @@ def load_network(path): logging.debug(msg) except FileNotFoundError: - busses = pd.DataFrame() + busses = pd.DataFrame(dtype='object') msg = 'No bus data found!' logging.debug(msg) @@ -411,10 +411,9 @@ def construct_comps(c, *args): for key in ['design', 'offdesign', 'design_path', 'local_design', 'local_offdesign']: if key in c: - try: - if np.isnan(c[key]): - kwargs[key] = None - except TypeError: + if isinstance(c[key], float): + kwargs[key] = None + else: kwargs[key] = c[key] for key, value in instance.variables.items(): @@ -547,10 +546,9 @@ def construct_conns(c, *args): for key in ['design', 'offdesign', 'design_path', 'local_design', 'local_offdesign', 'label']: if key in c: - try: - if np.isnan(c[key]): - setattr(conn, key, None) - except TypeError: + if isinstance(c[key], float): + setattr(conn, key, None) + else: setattr(conn, key, c[key]) # read fluid properties diff --git a/src/tespy/tools/analyses.py b/src/tespy/tools/analyses.py index c5e0eaf0f..929fb2209 100644 --- a/src/tespy/tools/analyses.py +++ b/src/tespy/tools/analyses.py @@ -90,40 +90,40 @@ def __init__(self, network, E_F, E_P, E_L=[], internal_busses=[]): .. math:: \begin{split} - E_{\mathrm{D},comp} = E_{\mathrm{F},comp} - E_{\mathrm{P},comp} + \dot{E}_{\mathrm{D},comp} = \dot{E}_{\mathrm{F},comp} - \dot{E}_{\mathrm{P},comp} \;& \\ \varepsilon_{\mathrm{comp}} = - \frac{E_{\mathrm{P},comp}}{E_{\mathrm{F},comp}} \;& \\ - E_{\mathrm{D}} = \sum_{comp} E_{\mathrm{D},comp} \;& + \frac{\dot{E}_{\mathrm{P},comp}}{\dot{E}_{\mathrm{F},comp}} \;& \\ + \dot{E}_{\mathrm{D}} = \sum_{comp} \dot{E}_{\mathrm{D},comp} \;& \forall comp \in \text{ network components}\\ - E_{\mathrm{P}} = \sum_{comp} E_{\mathrm{P},comp} \;& + \dot{E}_{\mathrm{P}} = \sum_{comp} \dot{E}_{\mathrm{P},comp} \;& \forall comp \in \text{ components of busses in E\_P if 'base': 'component'}\\ - E_{\mathrm{P}} = E_{\mathrm{P}} - \sum_{comp} E_{\mathrm{F},comp} + \dot{E}_{\mathrm{P}} = \dot{E}_{\mathrm{P}} - \sum_{comp} \dot{E}_{\mathrm{F},comp} \;& \forall comp \in \text{ components of busses in E\_P if 'base': 'bus'}\\ - E_{\mathrm{F}} = \sum_{comp} E_{\mathrm{F},comp} \;& + \dot{E}_{\mathrm{F}} = \sum_{comp} \dot{E}_{\mathrm{F},comp} \;& \forall comp \in \text{ components of busses in E\_F if 'base': 'bus'}\\ - E_{\mathrm{F}} = E_{\mathrm{F}} - \sum_{comp} E_{\mathrm{P},comp} + \dot{E}_{\mathrm{F}} = \dot{E}_{\mathrm{F}} - \sum_{comp} \dot{E}_{\mathrm{P},comp} \;& \forall comp \in \text{ components of busses in E\_F if 'base': 'component'}\\ - E_{\mathrm{L}} = \sum_{comp} E_{\mathrm{D},comp} \;& + \dot{E}_{\mathrm{L}} = \sum_{comp} \dot{E}_{\mathrm{D},comp} \;& \forall comp \in \text{ sinks of network components if parameter exergy='loss'} \end{split} The exergy balance of the network must be closed, meaning fuel exergy minus product exergy, exergy destruction and exergy losses must be - zero (:math:`\Delta E_\text{max}=0.001`). If the balance is violated a + zero (:math:`\Delta \dot{E}_\text{max}=0.001`). If the balance is violated a warning message is prompted. .. math:: - |E_{\text{F}} - E_{\text{P}} - E_{\text{L}} - E_{\text{D}}| \leq - \Delta E_\text{max}\\ + |\dot{E}_{\text{F}} - \dot{E}_{\text{P}} - \dot{E}_{\text{L}} - \dot{E}_{\text{D}}| \leq + \Delta \dot{E}_\text{max}\\ - \varepsilon = \frac{E_{\text{P}}}{E_{\text{F}}} + \varepsilon = \frac{\dot{E}_{\text{P}}}{\dot{E}_{\text{F}}} y_{\text{D},comp} = \frac{\dot{E}_{\text{D},comp}}{\dot{E}_{\text{F}}}\\ @@ -330,7 +330,8 @@ def analyse(self, pamb, Tamb): self.sankey_data = {} for label in self.reserved_fkt_groups: - self.sankey_data[label] = pd.DataFrame(columns=['value', 'cat']) + self.sankey_data[label] = pd.DataFrame( + columns=['value', 'cat'], dtype='object') # exergy balance of components for cp in self.nw.comps['object']: @@ -350,7 +351,7 @@ def analyse(self, pamb, Tamb): raise ValueError(msg) elif cp.fkt_group not in self.sankey_data: self.sankey_data[cp.fkt_group] = pd.DataFrame( - columns=['value', 'cat']) + columns=['value', 'cat'], dtype='object') self.evaluate_busses(cp) diff --git a/src/tespy/tools/data_containers.py b/src/tespy/tools/data_containers.py index 9503d0e2b..4ee86fde0 100644 --- a/src/tespy/tools/data_containers.py +++ b/src/tespy/tools/data_containers.py @@ -278,7 +278,7 @@ def attr(): return { 'val': 1, 'val_SI': 0, 'is_set': False, 'd': 1e-4, 'min_val': -1e12, 'max_val': 1e12, 'is_var': False, - 'val_ref': 1, 'design': np.nan, + 'val_ref': 1, 'design': np.nan, 'is_result': False, 'num_eq': 0, 'func_params': {}, 'func': None, 'deriv': None, 'latex': None} diff --git a/src/tespy/tools/document_models.py b/src/tespy/tools/document_models.py index 2fc9b7dbe..6c77c9552 100644 --- a/src/tespy/tools/document_models.py +++ b/src/tespy/tools/document_models.py @@ -8,9 +8,9 @@ SPDX-License-Identifier: MIT """ -import collections import os import sys +from collections.abc import Mapping from copy import deepcopy from datetime import date @@ -85,7 +85,7 @@ def merge_dicts(dict1, dict2): result = deepcopy(dict1) for key, value in dict2.items(): - if isinstance(value, collections.Mapping): + if isinstance(value, Mapping): result[key] = merge_dicts(result.get(key, {}), value) else: result[key] = deepcopy(dict2[key]) @@ -740,7 +740,7 @@ def get_component_specifications(nw, cp, rpt): # # get parameter groups tables for param, data in data_dict_gcp.items(): - df_data_gcp = pd.DataFrame(data) + df_data_gcp = pd.DataFrame(data, dtype='object') if df_data_gcp.size > 0: for col in df_data_gcp.columns: col_headers[col] = col.replace('_', r'\_') @@ -796,7 +796,8 @@ def document_busses(nw, rpt): df.loc['total', 'bus value'] = ( fmt.format(df.loc['total', 'bus value'])) else: - df = pd.DataFrame(columns=['comp eq', 'bus eq', 'eta ref']) + df = pd.DataFrame( + columns=['comp eq', 'bus eq', 'eta ref'], dtype='object') figures = [] for cp in b.comps.index: @@ -925,7 +926,7 @@ def data_to_df(data): Polished DataFrame. """ if not isinstance(data, pd.DataFrame): - df = pd.DataFrame(data) + df = pd.DataFrame(data, dtype='object') else: df = data to_drop = [n for n in df.columns if n != 'label'] diff --git a/src/tespy/tools/fluid_properties.py b/src/tespy/tools/fluid_properties.py index 72d133c01..0681b35de 100644 --- a/src/tespy/tools/fluid_properties.py +++ b/src/tespy/tools/fluid_properties.py @@ -262,7 +262,7 @@ def save_lookup(self, name, x1, x2, y): y : ndarray Lookup value (enthalpy, entropy, density or viscosity) """ - df = pd.DataFrame(y, columns=x2, index=x1) + df = pd.DataFrame(y, columns=x2, index=x1, dtype='float') alias = self.alias.replace('::', '_') path = './LUT/' + alias + '/' if not os.path.exists(path): @@ -1034,9 +1034,14 @@ def h_mix_pQ(flow, Q): """ fluid = single_fluid(flow[3]) if fluid is None: - msg = 'The function h_mix_pQ can only be used for pure fluids.' - logging.error(msg) - raise ValueError(msg) + if sum(flow[3].values()) == 0: + msg = 'The function h_mix_pQ is called without fluid information.' + logging.error(msg) + raise ValueError(msg) + else: + msg = 'The function h_mix_pQ can only be used for pure fluids.' + logging.error(msg) + raise ValueError(msg) try: Memorise.state[fluid].update(CP.PQ_INPUTS, flow[1], Q) diff --git a/tests/test_components/test_heat_exchangers.py b/tests/test_components/test_heat_exchangers.py index 60d9a688a..751c47c61 100644 --- a/tests/test_components/test_heat_exchangers.py +++ b/tests/test_components/test_heat_exchangers.py @@ -61,7 +61,7 @@ def setup_HeatExchanger_network(self, instance): self.nw.add_conns(self.c1, self.c2, self.c3, self.c4) - def test_HeatExhangerSimple(self): + def test_HeatExchangerSimple(self): """Test component properties of simple heat exchanger.""" instance = HeatExchangerSimple('heat exchanger') self.setup_HeatExchangerSimple_network(instance) @@ -139,6 +139,27 @@ def test_HeatExhangerSimple(self): ', is ' + str(instance.Q.val) + '.') assert Q == round(instance.Q.val, 0), msg + # test kA as network results parameter + instance.set_attr(Q=-5e4, Tamb=None) + b.set_attr(P=None) + self.nw.solve('design') + convergence_check(self.nw.lin_dep) + kA_network = self.nw.results['HeatExchangerSimple'].loc[ + instance.label, 'kA'] + print(kA_network) + msg = 'kA value must not be included in network results.' + expr = not instance.kA.is_result and np.isnan(kA_network) + assert expr, msg + + # test kA as network results parameter + instance.set_attr(Tamb=20) + self.nw.solve('design') + kA_network = self.nw.results['HeatExchangerSimple'].loc[ + instance.label, 'kA'] + kA_comp = instance.kA.val + msg = 'kA value needs to be identical on network and component level.' + assert kA_network == kA_comp, msg + def test_ParabolicTrough(self): """Test component properties of parabolic trough.""" instance = ParabolicTrough('parabolic trough') diff --git a/tests/test_errors.py b/tests/test_errors.py index 2cf066deb..396436131 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -309,7 +309,7 @@ def test_cc_stoich_unset_alias(self): self.setup_CombustionChamberStoich_error_tests() self.instance.set_attr(air_alias='some alias') msg = 'The air_alias has been set, is_set should be True.' - assert self.instance.air_alias.is_set is True, msg + assert self.instance.air_alias.is_set, msg self.instance.set_attr(air_alias=None) msg = 'The air_alias has been unset, is_set should be False.' diff --git a/tests/test_models/test_solar_energy_generating_system.py b/tests/test_models/test_solar_energy_generating_system.py new file mode 100644 index 000000000..f88d13e51 --- /dev/null +++ b/tests/test_models/test_solar_energy_generating_system.py @@ -0,0 +1,416 @@ +# -*- coding: utf-8 + +"""Module for testing a tespy simulation vs results from a different simulator. + +This file is part of project TESPy (github.com/oemof/tespy). It's copyrighted +by the contributors recorded in the version control history of the file, +available from its original location +tests/test_models/test_solar_energy_generating_system.py + +SPDX-License-Identifier: MIT +""" +from tespy.components import Compressor +from tespy.components import Condenser +from tespy.components import CycleCloser +from tespy.components import Drum +from tespy.components import HeatExchanger +from tespy.components import Merge +from tespy.components import ParabolicTrough +from tespy.components import Pump +from tespy.components import Sink +from tespy.components import Source +from tespy.components import Splitter +from tespy.components import Turbine +from tespy.components import Valve +from tespy.connections import Bus +from tespy.connections import Connection +from tespy.connections import Ref +from tespy.networks import Network +from tespy.tools import ExergyAnalysis + + +class TestSEGS: + + def setup(self): + """ + Full model validation of SEGS model in TESPy vs. EBSILON. + + Find original models at https://github.com/fwitte/SEGS_exergy. + """ + # specification of ambient state + self.pamb = 1.013 + self.Tamb = 25 + + # setting up network + self.nw = Network(fluids=['water', 'INCOMP::TVP1', 'air']) + self.nw.set_attr( + T_unit='C', p_unit='bar', h_unit='kJ / kg', m_unit='kg / s', + s_unit="kJ / kgK") + + # components definition + air_in = Source('Ambient air source', fkt_group='CW') + air_out = Sink('Ambient air sink', fkt_group='CW') + + closer_pt = CycleCloser('Cycle closer pt', fkt_group='SF') + pt = ParabolicTrough('Parabolic trough', fkt_group='SF') + ptpump = Pump('HTF pump', fkt_group='SF') + + closer = CycleCloser('Cycle closer power cycle', fkt_group='SG') + + eco = HeatExchanger('Economizer', fkt_group='SG') + eva = HeatExchanger('Evaporator', fkt_group='SG') + sup = HeatExchanger('Superheater', fkt_group='SG') + drum = Drum('Drum', fkt_group='SG') + + reh = HeatExchanger('Reheater', fkt_group='RH') + + hpt1 = Turbine('HP turbine 1', fkt_group='HPT') + hpt2 = Turbine('HP turbine 2', fkt_group='HPT') + lpt1 = Turbine('LP turbine 1', fkt_group='LPT') + lpt2 = Turbine('LP turbine 2', fkt_group='LPT') + lpt3 = Turbine('LP turbine 3', fkt_group='LPT') + lpt4 = Turbine('LP turbine 4', fkt_group='LPT') + lpt5 = Turbine('LP turbine 5', fkt_group='LPT') + + cond = Condenser('Condenser', fkt_group='CW') + condpump = Pump('Condenser pump', fkt_group='CW') + fwt = Merge('Feedwater tank', num_in=3, fkt_group='LPP') + fwp = Pump('Feedwater pump', fkt_group='FWP') + cwp = Pump('Cooling water pump', fkt_group='CW') + closer_cw = CycleCloser('Cycle closer cw', fkt_group='CW') + ct = HeatExchanger('Cooling tower', fkt_group='CW') + fan = Compressor('Cooling tower fan', fkt_group='CW') + + sp1 = Splitter('Splitter 1', fkt_group='HPT') + sp2 = Splitter('Splitter 2', fkt_group='HPT') + sp3 = Splitter('Splitter 3', fkt_group='LPT') + sp4 = Splitter('Splitter 4', fkt_group='LPT') + sp5 = Splitter('Splitter 5', fkt_group='LPT') + sp6 = Splitter('Splitter 6', fkt_group='LPT') + sp7 = Splitter('Splitter 7', fkt_group='SF') + + m1 = Merge('Merge 1', fkt_group='CW') + m2 = Merge('Merge 2', fkt_group='HPP') + m3 = Merge('Merge 3', fkt_group='LPP') + m4 = Merge('Merge 4', fkt_group='LPP') + m5 = Merge('Merge 5', fkt_group='SF') + + v1 = Valve('Valve 1', fkt_group='HPP') + v2 = Valve('Valve 2', fkt_group='HPP') + v3 = Valve('Valve 3', fkt_group='LPP') + v4 = Valve('Valve 4', fkt_group='LPP') + v5 = Valve('Valve 5', fkt_group='LPP') + + hppre1 = Condenser('High pressure preheater 1', fkt_group='HPP') + hppre2 = Condenser('High pressure preheater 2', fkt_group='HPP') + hppre1_sub = HeatExchanger( + 'High pressure preheater 1 subcooling', fkt_group='HPP') + hppre2_sub = HeatExchanger( + 'High pressure preheater 2 subcooling', fkt_group='HPP') + + lppre1 = Condenser('Low pressure preheater 1', fkt_group='LPP') + lppre2 = Condenser('Low pressure preheater 2', fkt_group='LPP') + lppre3 = Condenser('Low pressure preheater 3', fkt_group='LPP') + lppre1_sub = HeatExchanger( + 'Low pressure preheater 1 subcooling', fkt_group='LPP') + lppre2_sub = HeatExchanger( + 'Low pressure preheater 2 subcooling', fkt_group='LPP') + lppre3_sub = HeatExchanger( + 'Low pressure preheater 3 subcooling', fkt_group='LPP') + + # connections definition + # power cycle + c1 = Connection(sup, 'out2', closer, 'in1', label='1') + c2 = Connection(closer, 'out1', hpt1, 'in1', label='2') + c3 = Connection(hpt1, 'out1', sp1, 'in1', label='3') + c4 = Connection(sp1, 'out1', hpt2, 'in1', label='4') + c5 = Connection(hpt2, 'out1', sp2, 'in1', label='5') + c6 = Connection(sp2, 'out1', reh, 'in2', label='6') + c7 = Connection(reh, 'out2', lpt1, 'in1', label='7') + c8 = Connection(lpt1, 'out1', sp3, 'in1', label='8') + c9 = Connection(sp3, 'out1', lpt2, 'in1', label='9') + c10 = Connection(lpt2, 'out1', sp4, 'in1', label='10') + c11 = Connection(sp4, 'out1', lpt3, 'in1', label='11') + c12 = Connection(lpt3, 'out1', sp5, 'in1', label='12') + c13 = Connection(sp5, 'out1', lpt4, 'in1', label='13') + c14 = Connection(lpt4, 'out1', sp6, 'in1', label='14') + c15 = Connection(sp6, 'out1', lpt5, 'in1', label='15') + c16 = Connection(lpt5, 'out1', m1, 'in1', label='16') + c17 = Connection(m1, 'out1', cond, 'in1', label='17') + c18 = Connection(cond, 'out1', condpump, 'in1', label='18') + c19 = Connection(condpump, 'out1', lppre1, 'in2', label='19') + # c19 = Connection(condpump, 'out1', lppre1_sub, 'in2', label='19') + # c20 = Connection(lppre1_sub, 'out2', lppre1, 'in2', label='20') + c21 = Connection(lppre1, 'out2', lppre2, 'in2', label='21') + # c21 = Connection(lppre1, 'out2', lppre2_sub, 'in2', label='21') + # c22 = Connection(lppre2_sub, 'out2', lppre2, 'in2', label='22') + c23 = Connection(lppre2, 'out2', lppre3, 'in2', label='23') + # c23 = Connection(lppre2, 'out2', lppre3_sub, 'in2', label='23') + # c24 = Connection(lppre3_sub, 'out2', lppre3, 'in2', label='24') + c25 = Connection(lppre3, 'out2', fwt, 'in1', label='25') + c26 = Connection(fwt, 'out1', fwp, 'in1', label='26') + c27 = Connection(fwp, 'out1', hppre1, 'in2', label='27') + c29 = Connection(hppre1, 'out2', hppre2, 'in2', label='29') + c31 = Connection(hppre2, 'out2', eco, 'in2', label='31') + + c36 = Connection(sp1, 'out2', hppre2, 'in1', label='36') + c37 = Connection(hppre2, 'out1', v1, 'in1', label='37') + c39 = Connection(v1, 'out1', m2, 'in2', label='39') + c40 = Connection(sp2, 'out2', m2, 'in1', label='40') + c41 = Connection(m2, 'out1', hppre1, 'in1', label='41') + c42 = Connection(hppre1, 'out1', v2, 'in1', label='42') + c44 = Connection(v2, 'out1', fwt, 'in2', label='44') + c45 = Connection(sp3, 'out2', fwt, 'in3', label='45') + c46 = Connection(sp4, 'out2', lppre3, 'in1', label='46') + c47 = Connection(lppre3, 'out1', v3, 'in1', label='47') + # c47 = Connection(lppre3, 'out1', lppre3_sub, 'in1', label='47') + # c48 = Connection(lppre3_sub, 'out1', v3, 'in1', label='48') + c49 = Connection(v3, 'out1', m3, 'in1', label='49') + c50 = Connection(sp5, 'out2', m3, 'in2', label='50') + c51 = Connection(m3, 'out1', lppre2, 'in1', label='51') + c52 = Connection(lppre2, 'out1', v4, 'in1', label='52') + # c52 = Connection(lppre2, 'out1', lppre2_sub, 'in1', label='52') + # c53 = Connection(lppre2_sub, 'out1', v4, 'in1', label='53') + c54 = Connection(v4, 'out1', m4, 'in2', label='54') + c55 = Connection(sp6, 'out2', m4, 'in1', label='55') + c56 = Connection(m4, 'out1', lppre1, 'in1', label='56') + c57 = Connection(lppre1, 'out1', v5, 'in1', label='57') + # c57 = Connection(lppre1, 'out1', lppre1_sub, 'in1', label='57') + # c58 = Connection(lppre1_sub, 'out1', v5, 'in1', label='58') + c59 = Connection(v5, 'out1', m1, 'in2', label='59') + + # components from subsystem + c32 = Connection(eco, 'out2', drum, 'in1', label='32') + c33 = Connection(drum, 'out1', eva, 'in2', label='33') + c34 = Connection(eva, 'out2', drum, 'in2', label='34') + c35 = Connection(drum, 'out2', sup, 'in2', label='35') + c73 = Connection(sup, 'out1', eva, 'in1', label='73') + c74 = Connection(eva, 'out1', eco, 'in1', label='74') + + # cooling water + c60 = Connection(cond, 'out2', closer_cw, 'in1', label='60') + c61 = Connection(closer_cw, 'out1', ct, 'in1', label='61') + c62 = Connection(ct, 'out1', cwp, 'in1', label='62') + c63 = Connection(cwp, 'out1', cond, 'in2', label='63') + + # cooling tower + c64 = Connection(air_in, 'out1', fan, 'in1', label='64') + c65 = Connection(fan, 'out1', ct, 'in2', label='65') + c66 = Connection(ct, 'out2', air_out, 'in1', label='66') + + # parabolic trough cycle + c70 = Connection(pt, 'out1', closer_pt, 'in1', label='67') + c71 = Connection(closer_pt, 'out1', sp7, 'in1', label='71') + c72 = Connection(sp7, 'out1', sup, 'in1', label='72') + c75 = Connection(eco, 'out1', m5, 'in1', label='75') + c76 = Connection(sp7, 'out2', reh, 'in1', label='76') + c77 = Connection(reh, 'out1', m5, 'in2', label='77') + c78 = Connection(m5, 'out1', ptpump, 'in1', label='78') + c79 = Connection(ptpump, 'out1', pt, 'in1', label='79') + + # add connections to network + self.nw.add_conns( + c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11, c12, c13, c14, c15, + c16, c17, c18, c19, c21, c23, c25, c26, c27, c29, c31, c32, c33, + c34, c35, c36, c37, c39, c40, c41, c42, c44, c45, c46, c47, c49, + c50, c51, c52, c54, c55, c56, c57, c59, c60, c61, c62, c63, c64, + c65, c66, c70, c71, c72, c73, c74, c75, c76, c77, c78, c79) + + # power bus + power = Bus('total output power') + power.add_comps( + {'comp': hpt1, 'char': 0.97, 'base': 'component'}, + {'comp': hpt2, 'char': 0.97, 'base': 'component'}, + {'comp': lpt1, 'char': 0.97, 'base': 'component'}, + {'comp': lpt2, 'char': 0.97, 'base': 'component'}, + {'comp': lpt3, 'char': 0.97, 'base': 'component'}, + {'comp': lpt4, 'char': 0.97, 'base': 'component'}, + {'comp': lpt5, 'char': 0.97, 'base': 'component'}, + {'comp': fwp, 'char': 0.95, 'base': 'bus'}, + {'comp': condpump, 'char': 0.95, 'base': 'bus'}, + {'comp': ptpump, 'char': 0.95, 'base': 'bus'}, + {'comp': cwp, 'char': 0.95, 'base': 'bus'}, + {'comp': fan, 'char': 0.95, 'base': 'bus'}) + + heat_input_bus = Bus('heat input') + heat_input_bus.add_comps({'comp': pt, 'base': 'bus'}) + + exergy_loss_bus = Bus('exergy loss') + exergy_loss_bus.add_comps( + {'comp': air_in, 'base': 'bus'}, {'comp': air_out}) + + self.nw.add_busses(power, heat_input_bus, exergy_loss_bus) + + # component parameters + pt.set_attr(doc=0.95, aoi=0, + Tamb=25, A='var', eta_opt=0.73, + c_1=0.00496, c_2=0.000691, E=1000, + iam_1=1, iam_2=1) + + ptpump.set_attr(eta_s=0.6) + + eco.set_attr() + eva.set_attr(ttd_l=5) + sup.set_attr() + + hpt1.set_attr(eta_s=0.8376) + hpt2.set_attr(eta_s=0.8463) + lpt1.set_attr(eta_s=0.8623) + lpt2.set_attr(eta_s=0.917) + lpt3.set_attr(eta_s=0.9352) + lpt4.set_attr(eta_s=0.88) + lpt5.set_attr(eta_s=0.6445) + + cond.set_attr(pr1=1, pr2=0.9, ttd_u=5) + condpump.set_attr(eta_s=0.7) + fwp.set_attr(eta_s=0.7) + cwp.set_attr(eta_s=0.7) + ct.set_attr(pr1=0.95) + fan.set_attr(eta_s=0.6) + + lppre1.set_attr(pr1=1, ttd_u=5) + lppre2.set_attr(pr1=1, ttd_u=5) + lppre3.set_attr(pr1=1, ttd_u=5) + hppre1.set_attr(pr1=1, ttd_u=5) + hppre2.set_attr(pr1=1, ttd_u=5) + + lppre1_sub.set_attr(pr1=1, pr2=1, ttd_l=10) + lppre2_sub.set_attr(pr1=1, pr2=1, ttd_l=10) + lppre3_sub.set_attr(pr1=1, pr2=1, ttd_l=10) + hppre1_sub.set_attr(pr1=1, pr2=1, ttd_l=10) + hppre2_sub.set_attr(pr1=1, pr2=1, ttd_l=10) + + # connection parameters + # parabolic trough cycle + c70.set_attr(fluid={'TVP1': 1, 'water': 0, 'air': 0}, T=390, p=23.304) + c76.set_attr(m=Ref(c70, 0.1284, 0)) + c73.set_attr(p=22.753) + c74.set_attr(p=21.167) + c78.set_attr(p=20.34) + c79.set_attr(p=41.024) + + # cooling water + c62.set_attr( + fluid={'TVP1': 0, 'water': 1, 'air': 0}, T=30, p=self.pamb) + # cooling tower + c64.set_attr( + fluid={'water': 0, 'TVP1': 0, 'air': 1}, p=self.pamb, T=self.Tamb) + c65.set_attr(p=self.pamb + 0.0005) + c66.set_attr(p=self.pamb, T=30) + # power cycle + c32.set_attr(Td_bp=-2) + c34.set_attr(x=0.5) + c1.set_attr(fluid={'water': 1, 'TVP1': 0, 'air': 0}, p=100, T=371) + + # steam generator pressure values + c31.set_attr(p=103.56) + c35.set_attr(p=103.42) + + # turbine pressure values + c3.set_attr(p=33.61, m=38.969) + c5.set_attr(p=18.58) + c7.set_attr(p=17.1, T=371) + c8.set_attr(p=7.98) + c10.set_attr(p=2.73) + c12.set_attr(p=0.96) + c14.set_attr(p=0.29) + + # preheater pressure values + c19.set_attr(p=14.755, state='l') + c21.set_attr(p=9.9975, state='l') + c23.set_attr(p=8.7012, state='l') + c25.set_attr(state='l') + + c27.set_attr(p=125) + c29.set_attr(p=112) + + # condensation + c16.set_attr(p=0.08) + + # feedwater tank + c26.set_attr(x=0) + + # a stable solution is generated for parts of the network + self.nw.solve(mode='design') + + self.nw.del_conns(c19, c21, c23, c27, c29, c37, c42, c47, c52, c57) + + c19 = Connection(condpump, 'out1', lppre1_sub, 'in2', label='19') + c20 = Connection(lppre1_sub, 'out2', lppre1, 'in2', label='20') + c21 = Connection(lppre1, 'out2', lppre2_sub, 'in2', label='21') + c22 = Connection(lppre2_sub, 'out2', lppre2, 'in2', label='22') + c23 = Connection(lppre2, 'out2', lppre3_sub, 'in2', label='23') + c24 = Connection(lppre3_sub, 'out2', lppre3, 'in2', label='24') + + c27 = Connection(fwp, 'out1', hppre1_sub, 'in2', label='27') + c28 = Connection(hppre1_sub, 'out2', hppre1, 'in2', label='28') + c29 = Connection(hppre1, 'out2', hppre2_sub, 'in2', label='29') + c30 = Connection(hppre2_sub, 'out2', hppre2, 'in2', label='30') + + c37 = Connection(hppre2, 'out1', hppre2_sub, 'in1', label='37') + c38 = Connection(hppre2_sub, 'out1', v1, 'in1', label='38') + c42 = Connection(hppre1, 'out1', hppre1_sub, 'in1', label='42') + c43 = Connection(hppre1_sub, 'out1', v2, 'in1', label='43') + + c47 = Connection(lppre3, 'out1', lppre3_sub, 'in1', label='47') + c48 = Connection(lppre3_sub, 'out1', v3, 'in1', label='48') + c52 = Connection(lppre2, 'out1', lppre2_sub, 'in1', label='52') + c53 = Connection(lppre2_sub, 'out1', v4, 'in1', label='53') + c57 = Connection(lppre1, 'out1', lppre1_sub, 'in1', label='57') + c58 = Connection(lppre1_sub, 'out1', v5, 'in1', label='58') + + self.nw.add_conns( + c19, c20, c21, c22, c23, c24, c27, c28, c29, c30, c37, c38, c42, + c43, c47, c48, c52, c53, c57, c58) + + # specification of missing parameters + c19.set_attr(p=14.755) + c21.set_attr(p=9.9975, state='l') + c23.set_attr(p=8.7012, state='l') + c27.set_attr(p=125) + c29.set_attr(p=112) + + # solve final state + self.nw.solve(mode='design') + + def test_model(self): + """Test the thermodynamic model.""" + power_ebsilon = -31.769 + power_tespy = round( + self.nw.busses['total output power'].P.val / 1e6, 3) + msg = ( + 'The total power calculated (' + str(power_tespy) + ') does not ' + 'match the power calculated with the EBSILON model (' + + str(power_ebsilon) + ').') + assert power_tespy == power_ebsilon, msg + + T_c79_ebsilon = 296.254 + T_c79_tespy = round(self.nw.get_conn('79').T.val, 3) + msg = ( + 'The temperature at connection 79 calculated (' + + str(T_c79_tespy) + ') does not match the temperature calculated ' + 'with the EBSILON model (' + str(T_c79_ebsilon) + ').') + assert T_c79_tespy == T_c79_ebsilon, msg + + def test_exergy_analysis(self): + """Test the exergy analysis results.""" + # carry out exergy analysis + ean = ExergyAnalysis( + self.nw, E_P=[self.nw.busses['total output power']], + E_F=[self.nw.busses['heat input']], + E_L=[self.nw.busses['exergy loss']]) + ean.analyse(pamb=self.pamb, Tamb=self.Tamb) + + # generate Grassmann diagram + links, nodes = ean.generate_plotly_sankey_input() + + # check if exergy product value in links is equal to total power + # output + position = links['target'].index(nodes.index('E_P')) + power_links = round(links['value'][position], 0) + power_bus = round(-self.nw.busses['total output power'].P.val, 0) + msg = ( + 'The exergy product value in the links (' + str(power_links) + + ') must be equal to the power on the respective bus (' + + str(power_bus) + ').') + assert power_links == power_bus, msg diff --git a/tests/test_networks/test_network.py b/tests/test_networks/test_network.py index a0401c623..ab856c3dc 100644 --- a/tests/test_networks/test_network.py +++ b/tests/test_networks/test_network.py @@ -57,7 +57,7 @@ def test_Network_linear_dependency(self): self.nw.solve('design') msg = ('This test must result in a linear dependency of the jacobian ' 'matrix.') - assert self.nw.lin_dep is True, msg + assert self.nw.lin_dep, msg def test_Network_no_progress(self): """Test no convergence progress.""" @@ -92,7 +92,7 @@ def test_Network_delete_conns(self): self.nw.add_conns(a) self.nw.check_network() msg = ('After the network check, the .checked-property must be True.') - assert self.nw.checked is True, msg + assert self.nw.checked, msg self.nw.del_conns(a) msg = ('A connection has been deleted, the network consistency check ' @@ -108,7 +108,7 @@ def test_Network_missing_connection_in_init_path(self): self.nw.solve('design', init_only=True) self.nw.save('tmp') msg = ('After the network check, the .checked-property must be True.') - assert self.nw.checked is True, msg + assert self.nw.checked, msg self.nw.del_conns(a) a = Connection(self.source, 'out1', IF, 'in1') @@ -116,7 +116,7 @@ def test_Network_missing_connection_in_init_path(self): self.nw.add_conns(a, b) self.nw.solve('design', init_path='tmp', init_only=True) msg = ('After the network check, the .checked-property must be True.') - assert self.nw.checked is True, msg + assert self.nw.checked, msg shutil.rmtree('./tmp', ignore_errors=True) @@ -152,7 +152,7 @@ def test_Network_reader_no_chars_busses(self): imported_nwk.solve('design', init_only=True) msg = ('If the network import was successful the network check ' 'should have been successful, too, but it is not.') - assert imported_nwk.checked is True, msg + assert imported_nwk.checked, msg shutil.rmtree('./tmp', ignore_errors=True) def test_Network_reader_deleted_chars(self): @@ -174,7 +174,7 @@ def test_Network_reader_deleted_chars(self): imported_nwk.solve('design', init_only=True) msg = ('If the network import was successful the network check ' 'should have been successful, too, but it is not.') - assert imported_nwk.checked is True, msg + assert imported_nwk.checked, msg shutil.rmtree('./tmp', ignore_errors=True) def test_Network_missing_data_in_design_case_files(self): @@ -397,11 +397,11 @@ def test_local_offdesign_on_connections_and_components(self): assert self.sc1_v1.T.design > self.sc1_v1.T.val, msg msg = ('Parameter eta_s_char must be set for pump one.') - assert self.pump1.eta_s_char.is_set is True, msg + assert self.pump1.eta_s_char.is_set, msg msg = ('Parameter v must be set for connection from solar collector1 ' 'to pump1.') - assert self.sc1_v1.v.val_set is True, msg + assert self.sc1_v1.v.val_set, msg shutil.rmtree('./design1', ignore_errors=True) shutil.rmtree('./design2', ignore_errors=True) diff --git a/tests/test_tools/test_characteristics.py b/tests/test_tools/test_characteristics.py index c3537770f..a862eedc6 100644 --- a/tests/test_tools/test_characteristics.py +++ b/tests/test_tools/test_characteristics.py @@ -62,13 +62,13 @@ def test_custom_CharLine_import(): str(char_custom.x) + ' must be identical to the x values from ' 'the default characteristic line ' + str(char_original.x) + ' ' 'as these have been duplicated before load.') - assert x_cond is True, msg + assert x_cond, msg msg = ('The y values from the custom characteristic line ' + str(char_custom.y) + ' must be identical to the y values from ' 'the default characteristic line ' + str(char_original.y) + ' ' 'as these have been duplicated before load.') - assert y_cond is True, msg + assert y_cond, msg def test_custom_CharMap_import(): @@ -111,19 +111,19 @@ def test_custom_CharMap_import(): str(char_custom.x) + ' must be identical to the x values from ' 'the default characteristic line ' + str(char_original.x) + ' ' 'as these have been duplicated before load.') - assert x_cond is True, msg + assert x_cond, msg msg = ('The y values from the custom characteristic line ' + str(char_custom.y) + ' must be identical to the y values from ' 'the default characteristic line ' + str(char_original.y) + ' ' 'as these have been duplicated before load.') - assert y_cond is True, msg + assert y_cond, msg msg = ('The z values from the custom characteristic line ' + str(char_custom.z) + ' must be identical to the z values from ' 'the default characteristic line ' + str(char_original.z) + ' ' 'as these have been duplicated before load.') - assert z_cond is True, msg + assert z_cond, msg def test_CharLine_evaluation():