diff --git a/.github/workflows/packaging.yml b/.github/workflows/packaging.yml index 3979d73fb..dd0f9b87e 100644 --- a/.github/workflows/packaging.yml +++ b/.github/workflows/packaging.yml @@ -31,7 +31,7 @@ jobs: run: python -m build . - name: Run twine check run: twine check dist/* - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 with: name: tox-gh-actions-dist path: dist diff --git a/docs/_static/images/examples/heatpumps.png b/docs/_static/images/examples/heatpumps.png new file mode 100644 index 000000000..0e8c208c6 Binary files /dev/null and b/docs/_static/images/examples/heatpumps.png differ diff --git a/docs/_static/images/examples/heatpumps_darkmode.png b/docs/_static/images/examples/heatpumps_darkmode.png new file mode 100644 index 000000000..e4476a002 Binary files /dev/null and b/docs/_static/images/examples/heatpumps_darkmode.png differ diff --git a/docs/examples.rst b/docs/examples.rst index 7c5156fc5..90d75929b 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -10,6 +10,39 @@ of the application should be accessible freely, so other users can learn from your project. +.. card:: + + **Dashboard for the exploration of various heat pump designs** + ^^^ + + .. image:: /_static/images/examples/heatpumps.png + :align: center + :alt: Heatpumps Dashboard + :class: only-light + :target: https://github.com/jfreissmann/heatpumps + + .. image:: /_static/images/examples/heatpumps_darkmode.png + :align: center + :alt: Heatpumps Dashboard + :class: only-dark + :target: https://github.com/jfreissmann/heatpumps + + The streamlit dashboard *heatpumps* provides users with powerful tools for + both design and part load simulation of a comprehensive library of heat + pump topologies. Furthermore, TESPy's integration of CoolProp facilitates + the use of a wide range of refrigerants. The exploration of simulation + results is supported by fluprodia's state diagrams as well as the TESPy + built-in exergy analysis feature. An assessment of the economic + attractiveness of different heat pump setups is enabled through a + component-based cost estimation. + + +++ + Title: heatpumps + + Authors: Jonas Freißmann, Malte Fritz + + Reference: :cite:`Fritz2024` + .. card:: **Coupled Porous Media Storage and Power Plant Simulation** diff --git a/docs/modules/components.rst b/docs/modules/components.rst index fe1116ec2..05be881d0 100644 --- a/docs/modules/components.rst +++ b/docs/modules/components.rst @@ -88,23 +88,27 @@ evaporator and how to unset the parameter again. .. code-block:: python - from tespy.components import HeatExchanger - from tespy.tools import ComponentProperties as dc_cp - import numpy as np - - he = HeatExchanger('evaporator') - - # specify the value - he.set_attr(kA=1e5) - # specify via dictionary - he.set_attr(kA={'val': 1e5, 'is_set': True}) - # set data container parameters - he.kA.set_attr(val=1e5, is_set=True) - - # possibilities to unset a value - he.set_attr(kA=np.nan) - he.set_attr(kA=None) - he.kA.set_attr(is_set=False) + >>> from tespy.components import HeatExchanger + >>> from tespy.tools import ComponentProperties as dc_cp + >>> import numpy as np + + >>> he = HeatExchanger('evaporator') + + >>> # specify the value + >>> he.set_attr(kA=1e5) + >>> # specify via dictionary + >>> he.set_attr(kA={'val': 1e5, 'is_set': True}) + >>> # set data container parameters + >>> he.kA.set_attr(val=1e5, is_set=True) + >>> he.kA.is_set + True + + >>> # possibilities to unset a value + >>> he.set_attr(kA=np.nan) + >>> he.set_attr(kA=None) + >>> he.kA.set_attr(is_set=False) + >>> he.kA.is_set + False Grouped parameters ^^^^^^^^^^^^^^^^^^ @@ -118,17 +122,35 @@ not be implemented by the solver. .. code-block:: python - from tespy.components import Pipe - import numpy as np + >>> from tespy.components import Pipe, Source, Sink + >>> from tespy.networks import Network + >>> from tespy.connections import Connection + + >>> nw = Network(T_unit='C', p_unit='bar') + + >>> so = Source('source') + >>> si = Sink('sink') + >>> my_pipe = Pipe('pipe') - my_pipe = Pipe('pipe') + >>> c1 = Connection(so, 'out1', my_pipe, 'in1') + >>> c2 = Connection(my_pipe, 'out1', si, 'in1') + >>> nw.add_conns(c1, c2) + >>> c1.set_attr(fluid={"CH4": 1}, m=1, p=10, T=25) + >>> c2.set_attr(p0=10, T=25) - # specify grouped parameters - my_pipe.set_attr(D=0.1, L=20, ks=0.00005) + >>> # specify grouped parameters + >>> my_pipe.set_attr(D=0.1, L=20, ks=0.00005) + >>> nw.solve('design', init_only=True) + >>> my_pipe.darcy_group.is_set + True - # the solver will not apply an equation, since the information of the - # pipe's length is missing. - my_pipe.set_attr(D=0.1, ks=0.00005) + >>> # the solver will not apply an equation, since the information of the + >>> # pipe's length is now missing (by removing it as follows). + >>> c2.set_attr(p=10) + >>> my_pipe.set_attr(L=None) + >>> nw.solve('design', init_only=True) + >>> my_pipe.darcy_group.is_set + False There are several components using parameter groups: @@ -168,21 +190,22 @@ diameter the following way. .. code-block:: python - from tespy.components import Pipe - from tespy.tools import ComponentProperties as dc_cp - import numpy as np - - # custom variables - my_pipe = Pipe('my pipe') - - # make diameter variable of system - my_pipe.set_attr(pr=0.98, L=100, ks=0.00002, D='var') - - # a second way of specifying this is similar to the - # way used in the component parameters section - # val will be used as starting value - my_pipe.set_attr(pr=0.98, L=100, ks=0.00002) - my_pipe.set_attr(D={'val': 0.2, 'is_set': True, 'is_var': True}) + >>> # make diameter variable of system + >>> my_pipe.set_attr(pr=0.98, L=100, ks=0.00002, D='var') + >>> c2.set_attr(p=None) + >>> nw.solve("design", init_only=True) + >>> my_pipe.darcy_group.is_set + True + + >>> # a second way of specifying this is similar to the + >>> # way used in the component parameters section + >>> # val will be used as starting value + >>> my_pipe.darcy_group.is_set = False + >>> my_pipe.set_attr(pr=0.98, L=100, ks=0.00002) + >>> my_pipe.set_attr(D={'val': 0.2, 'is_set': True, 'is_var': True}) + >>> nw.solve("design", init_only=True) + >>> my_pipe.darcy_group.is_set + True It is also possible to set value boundaries for you custom variable. You can do this, if you expect the result to be within a specific range. But beware: This @@ -191,12 +214,14 @@ specified range. .. code-block:: python - # data container specification with identical result, - # benefit: specification of bounds will increase stability - my_pipe.set_attr(D={ - 'val': 0.2, 'is_set': True, 'is_var': True, - 'min_val': 0.1, 'max_val': 0.3} - ) + >>> # data container specification with identical result, + >>> # benefit: specification of bounds will increase stability + >>> my_pipe.set_attr(D={ + ... 'val': 0.2, 'is_set': True, 'is_var': True, + ... 'min_val': 0.1, 'max_val': 0.3} + ... ) + >>> round(my_pipe.D.max_val, 1) + 0.3 .. _component_characteristic_specification_label: @@ -233,88 +258,127 @@ For example, :code:`kA_char` specification for heat exchangers: .. code-block:: python - from tespy.components import HeatExchanger - from tespy.tools.characteristics import load_default_char as ldc - from tespy.tools.characteristics import CharLine - import numpy as np - - he = HeatExchanger('evaporator') - - # the characteristic function requires the design value of the property, - # therefore the design value of kA must be set and additionally we set - # the kA_char method. This is performed automatically, if you specify the - # kA_char as offdesign parameter (usual case). - he.set_attr(kA={'design': 1e5}, kA_char={'is_set': True}) - - # use a characteristic line from the defaults: specify the component, the - # parameter and the name of the characteristic function. Also, specify, - # what type of characteristic function you want to use. - kA_char1 = ldc('heat exchanger', 'kA_char1', 'DEFAULT', CharLine) - kA_char2 = ldc('heat exchanger', 'kA_char2', 'EVAPORATING FLUID', CharLine) - he.set_attr(kA_char2=kA_char2) - - # specification of a data container yields the same result. It is - # additionally possible to specify the characteristics parameter, e.g. mass - # flow for kA_char1 and volumetric flow for kA_char2 - he.set_attr( - kA_char1={'char_func': kA_char1, 'param': 'm'}, - kA_char2={'char_func': kA_char2, 'param': 'v'} - ) - - # or use custom values for the characteristic line e.g. kA vs volumetric - # flow - x = np.array([0, 0.5, 1, 2]) - y = np.array([0, 0.8, 1, 1.2]) - kA_char1 = CharLine(x, y) - he.set_attr(kA_char1={'char_func': kA_char1, 'param': 'v'}) + >>> from tespy.components import HeatExchanger + >>> from tespy.tools.characteristics import load_default_char as ldc + >>> from tespy.tools.characteristics import CharLine + + >>> nw = Network(T_unit="C", p_unit="bar", iterinfo=False) + + >>> he = HeatExchanger('evaporator') + >>> cond = Source('condensate') + >>> steam = Sink('steam') + >>> gas_hot = Source('air inlet') + >>> gas_cold = Sink('air outlet') + + >>> c1 = Connection(cond, "out1", he, "in2") + >>> c2 = Connection(he, "out2", steam, "in1") + >>> c3 = Connection(gas_hot, "out1", he, "in1") + >>> c4 = Connection(he, "out1", gas_cold, "in1") + + >>> nw.add_conns(c1, c2, c3, c4) + + >>> c1.set_attr(fluid={'water': 1}, m=10, p=10, x=0) + >>> c2.set_attr(p=10, x=1) + >>> c3.set_attr(fluid={'air': 1}, T=250, p=1) + >>> c4.set_attr(T=200, p=1) + + >>> nw.solve("design") + >>> nw.save("design_case") + >>> round(he.kA.val) + 503013 + + >>> # the characteristic function is made for offdesign calculation. + >>> he.set_attr(kA_char={'is_set': True}) + >>> c4.set_attr(T=None) + >>> nw.solve("offdesign", design_path="design_case") + >>> # since we did not change any property, the offdesign case yields the + >>> # same value as the design kA value + >>> round(he.kA.val) + 503013 + + >>> c1.set_attr(m=9) + >>> # use a characteristic line from the defaults: specify the component, the + >>> # parameter and the name of the characteristic function. Also, specify, + >>> # what type of characteristic function you want to use. + >>> kA_char1 = ldc('heat exchanger', 'kA_char1', 'DEFAULT', CharLine) + >>> kA_char2 = ldc('heat exchanger', 'kA_char2', 'EVAPORATING FLUID', CharLine) + >>> he.set_attr(kA_char2=kA_char2) + >>> nw.solve("offdesign", design_path="design_case") + >>> round(he.kA.val) + 481745 + + >>> # specification of a data container yields the same result. It is + >>> # additionally possible to specify the characteristics parameter, e.g. + >>> # mass flow for kA_char1 (identical to default case) and volumetric + >>> # flow for kA_char2 + >>> he.set_attr( + ... kA_char1={'char_func': kA_char1, 'param': 'm'}, + ... kA_char2={'char_func': kA_char2, 'param': 'v'} + ... ) + >>> nw.solve("offdesign", design_path="design_case") + >>> round(he.kA.val) + 481745 + + >>> # or use custom values for the characteristic line e.g. kA vs volumetric + >>> # flow + >>> x = np.array([0, 0.5, 1, 2]) + >>> y = np.array([0, 0.8, 1, 1.2]) + >>> kA_char2 = CharLine(x, y) + >>> he.set_attr(kA_char2={'char_func': kA_char2, 'param': 'v'}) + >>> nw.solve("offdesign", design_path="design_case") + >>> round(he.kA.val) + 475107 Full working example for :code:`eta_s_char` specification of a turbine. .. code-block:: python - from tespy.components import Sink, Source, Turbine - from tespy.connections import Connection - from tespy.networks import Network - from tespy.tools.characteristics import CharLine - import numpy as np - - nw = Network(p_unit='bar', T_unit='C', h_unit='kJ / kg') - si = Sink('sink') - so = Source('source') - t = Turbine('turbine') - inc = Connection(so, 'out1', t, 'in1') - outg = Connection(t, 'out1', si, 'in1') - nw.add_conns(inc, outg) - - # design value specification, cone law and eta_s characteristic as - # offdesign parameters - eta_s_design = 0.855 - t.set_attr(eta_s=eta_s_design, design=['eta_s'], offdesign=['eta_s_char','cone']) - - # Characteristics x as m/m_design and y as eta_s(m)/eta_s_design - # make sure to cross the 1/1 point (design point) to yield the same - # output in the design state of the system - line = CharLine( - x=[0.1, 0.3, 0.5, 0.7, 0.9, 1, 1.1], - y=np.array([0.6, 0.65, 0.75, 0.82, 0.85, 0.855, 0.79]) / eta_s_design) - - # default parameter for x is m / m_design - t.set_attr(eta_s_char={'char_func': line}) - inc.set_attr(fluid={'water': 1}, m=10, T=550, p=110, design=['p']) - outg.set_attr(p=0.5) - nw.solve('design') - nw.save('tmp') - # change mass flow value, e.g. 3 kg/s and run offdesign calculation - inc.set_attr(m=3) - nw.solve('offdesign', design_path='tmp') - # isentropic efficiency should be at 0.65 - nw.print_results() - - # alternatively, we can specify the volumetric flow v / v_design for - # the x lookup - t.set_attr(eta_s_char={'param': 'v'}) - nw.solve('offdesign', design_path='tmp') - nw.print_results() + >>> from tespy.components import Sink, Source, Turbine + >>> from tespy.connections import Connection + >>> from tespy.networks import Network + >>> from tespy.tools.characteristics import CharLine + >>> import numpy as np + + >>> nw = Network(p_unit='bar', T_unit='C', h_unit='kJ / kg', iterinfo=False) + >>> si = Sink('sink') + >>> so = Source('source') + >>> t = Turbine('turbine') + >>> inc = Connection(so, 'out1', t, 'in1') + >>> outg = Connection(t, 'out1', si, 'in1') + >>> nw.add_conns(inc, outg) + + >>> # design value specification, cone law and eta_s characteristic as + >>> # offdesign parameters + >>> eta_s_design = 0.855 + >>> t.set_attr(eta_s=eta_s_design, design=['eta_s'], offdesign=['eta_s_char','cone']) + + >>> # Characteristics x as m/m_design and y as eta_s(m)/eta_s_design + >>> # make sure to cross the 1/1 point (design point) to yield the same + >>> # output in the design state of the system + >>> line = CharLine( + ... x=[0.1, 0.3, 0.5, 0.7, 0.9, 1, 1.1], + ... y=np.array([0.6, 0.65, 0.75, 0.82, 0.85, 0.855, 0.79]) / eta_s_design + ... ) + + >>> # default parameter for x is m / m_design + >>> t.set_attr(eta_s_char={'char_func': line}) + >>> inc.set_attr(fluid={'water': 1}, m=10, T=550, p=110, design=['p']) + >>> outg.set_attr(p=0.5) + >>> nw.solve('design') + >>> nw.save('tmp') + >>> # change mass flow value, e.g. 3 kg/s and run offdesign calculation + >>> inc.set_attr(m=3) + >>> nw.solve('offdesign', design_path='tmp') + >>> # isentropic efficiency should be at 0.65 + >>> round(t.eta_s.val, 2) + 0.65 + + >>> # alternatively, we can specify the volumetric flow v / v_design for + >>> # the x lookup + >>> t.set_attr(eta_s_char={'param': 'v'}) + >>> nw.solve('offdesign', design_path='tmp') + >>> round(t.eta_s.val, 2) + 0.84 Instead of writing your custom characteristic line information directly into your Python script, TESPy provides a second method of implementation: It is @@ -336,14 +400,16 @@ parameter to :code:`True`. .. code-block:: python # use custom specification parameters - x = np.array([0, 0.5, 1, 2]) - y = np.array([0, 0.8, 1, 1.2]) - kA_char1 = CharLine(x, y, extrapolate=True) - he.set_attr(kA_char1=kA_char1) + >>> x = np.array([0, 0.5, 1, 2]) + >>> y = np.array([0, 0.8, 1, 1.2]) + >>> kA_char1 = CharLine(x, y, extrapolate=True) + >>> kA_char1.extrapolate + True - # set extrapolation to True for existing lines, e.g. - he.kA_char1.func.extrapolate = True - pu.eta_s_char.func.extrapolate = True + >>> # set extrapolation to True for existing lines, e.g. + >>> he.kA_char1.char_func.extrapolate = True + >>> he.kA_char1.char_func.extrapolate + True Characteristics are available for the following components and parameters: @@ -755,115 +821,107 @@ Create a file, e.g. :code:`mysubsystems.py` and add the following lines: .. code-block:: python - from tespy.components import Subsystem, HeatExchanger, Drum - from tespy.connections import Connection - - - class WasteHeatSteamGenerator(Subsystem): - """Class documentation""" - - def create_comps(self): - """Create the subsystem's components.""" - self.comps['eco'] = HeatExchanger('economizer') - self.comps['eva'] = HeatExchanger('evaporator') - self.comps['sup'] = HeatExchanger('superheater') - self.comps['drum'] = Drum('drum') - - def create_conns(self): - """Define the subsystem's connections.""" - self.conns['eco_dr'] = Connection( - self.comps['eco'], 'out2', self.comps['drum'], 'in1') - self.conns['dr_eva'] = Connection( - self.comps['drum'], 'out1', self.comps['eva'], 'in2') - self.conns['eva_dr'] = Connection( - self.comps['eva'], 'out2', self.comps['drum'], 'in2') - self.conns['dr_sup'] = Connection( - self.comps['drum'], 'out2', self.comps['sup'], 'in2') - self.conns['sup_eva'] = Connection( - self.comps['sup'], 'out1', self.comps['eva'], 'in1') - self.conns['eva_eco'] = Connection( - self.comps['eva'], 'out1', self.comps['eco'], 'in1') - -Import your subsystem -^^^^^^^^^^^^^^^^^^^^^ + >>> from tespy.components import Subsystem, HeatExchanger, Drum + >>> from tespy.connections import Connection + + >>> class WasteHeatSteamGenerator(Subsystem): + ... """Class documentation""" + ... + ... def create_comps(self): + ... """Create the subsystem's components.""" + ... self.comps['eco'] = HeatExchanger(f'{self.label}_economizer') + ... self.comps['eva'] = HeatExchanger(f'{self.label}_evaporator') + ... self.comps['sup'] = HeatExchanger(f'{self.label}_superheater') + ... self.comps['drum'] = Drum(f'{self.label}_drum') + ... + ... def create_conns(self): + ... """Define the subsystem's connections.""" + ... self.conns['eco_dr'] = Connection( + ... self.comps['eco'], 'out2', self.comps['drum'], 'in1') + ... self.conns['dr_eva'] = Connection( + ... self.comps['drum'], 'out1', self.comps['eva'], 'in2') + ... self.conns['eva_dr'] = Connection( + ... self.comps['eva'], 'out2', self.comps['drum'], 'in2') + ... self.conns['dr_sup'] = Connection( + ... self.comps['drum'], 'out2', self.comps['sup'], 'in2') + ... self.conns['sup_eva'] = Connection( + ... self.comps['sup'], 'out1', self.comps['eva'], 'in1') + ... self.conns['eva_eco'] = Connection( + ... self.comps['eva'], 'out1', self.comps['eco'], 'in1') -In a different script, we create a network and import the subsystem we just -created along with the different tespy classes required. The location of the -:code:`mysubsystems.py` file must be known by your python installation or lie -within the same folder as your script. - -.. code-block:: python - - from tespy.networks import Network - from tespy.components import Source, Sink - from tespy.connections import Connection - import numpy as np - - from mysubsystems import WasteHeatSteamGenerator as WHSG - - # %% network definition - - nw = Network(p_unit='bar', T_unit='C') - - # %% component definition - - feed_water = Source('feed water inlet') - steam = Sink('live steam outlet') +.. note:: - waste_heat = Source('waste heat inlet') - chimney = Sink('waste heat chimney') + Please note, that you should label your components (and connections) with + unitque names, otherwise you can only use the subsystem once per model. In + this case, it is achieved by adding the subsystem label to all of the + component labels. - sg = WHSG('waste heat steam generator') +Make use of your subsystem +^^^^^^^^^^^^^^^^^^^^^^^^^^ - # %% connection definition +We create a network and use the subsystem we just created along with the +different tespy classes required. - fw_sg = Connection(feed_water, 'out1', sg.comps['eco'], 'in2') - sg_ls = Connection(sg.comps['sup'], 'out2', steam, 'in1') - fg_sg = Connection(waste_heat, 'out1', sg.comps['sup'], 'in1') - sg_ch = Connection(sg.comps['eco'], 'out1', chimney, 'in1') +.. code-block:: python - nw.add_conns(fw_sg, sg_ls, fg_sg, sg_ch) - nw.add_subsys(sg) + >>> from tespy.networks import Network + >>> from tespy.components import Source, Sink + >>> from tespy.connections import Connection + >>> import numpy as np - # %% connection parameters + >>> # %% network definition + >>> nw = Network(p_unit='bar', T_unit='C', iterinfo=False) - fw_sg.set_attr(fluid={'water': 1}, T=25) - fg_sg.set_attr(fluid={'air': 1}, T=650, m=100) + >>> # %% component definition + >>> feed_water = Source('feed water inlet') + >>> steam = Sink('live steam outlet') + >>> waste_heat = Source('waste heat inlet') + >>> chimney = Sink('waste heat chimney') - sg_ls.set_attr(p=130) - sg_ch.set_attr(p=1) + >>> sg = WasteHeatSteamGenerator('waste heat steam generator') - sg.conns['eva_dr'].set_attr(x=0.6) + >>> # %% connection definition + >>> fw_sg = Connection(feed_water, 'out1', sg.comps['eco'], 'in2') + >>> sg_ls = Connection(sg.comps['sup'], 'out2', steam, 'in1') + >>> fg_sg = Connection(waste_heat, 'out1', sg.comps['sup'], 'in1') + >>> sg_ch = Connection(sg.comps['eco'], 'out1', chimney, 'in1') - # %% component parameters + >>> nw.add_conns(fw_sg, sg_ls, fg_sg, sg_ch) + >>> nw.add_subsys(sg) - sg.comps['eco'].set_attr( - pr1=0.999, pr2=0.97, design=['pr1', 'pr2', 'ttd_u'], - offdesign=['zeta1', 'zeta2', 'kA_char'] - ) + >>> # %% connection parameters + >>> fw_sg.set_attr(fluid={'water': 1}, T=25) + >>> fg_sg.set_attr(fluid={'air': 1}, T=650, m=100) + >>> sg_ls.set_attr(p=130) + >>> sg_ch.set_attr(p=1) - sg.comps['eva'].set_attr( - pr1=0.999, ttd_l=20, design=['pr1', 'ttd_l'], - offdesign=['zeta1', 'kA_char'] - ) + >>> sg.conns['eva_dr'].set_attr(x=0.6) - sg.comps['sup'].set_attr( - pr1=0.999, pr2=0.99, ttd_u=50, design=['pr1', 'pr2', 'ttd_u'], - offdesign=['zeta1', 'zeta2', 'kA_char'] - ) + >>> # %% component parameters + >>> sg.comps['eco'].set_attr( + ... pr1=0.999, pr2=0.97, design=['pr1', 'pr2', 'ttd_u'], + ... offdesign=['zeta1', 'zeta2', 'kA_char'] + ... ) - sg.conns['eco_dr'].set_attr(Td_bp=-5, design=['Td_bp']) + >>> sg.comps['eva'].set_attr( + ... pr1=0.999, ttd_l=20, design=['pr1', 'ttd_l'], + ... offdesign=['zeta1', 'kA_char'] + ... ) - # %% solve + >>> sg.comps['sup'].set_attr( + ... pr1=0.999, pr2=0.99, ttd_u=50, design=['pr1', 'pr2', 'ttd_u'], + ... offdesign=['zeta1', 'zeta2', 'kA_char'] + ... ) - # solve design case - nw.solve('design') - nw.print_results() - nw.save('tmp') + >>> sg.conns['eco_dr'].set_attr(Td_bp=-5, design=['Td_bp']) - # offdesign test - nw.solve('offdesign', design_path='tmp') + >>> # %% solve + >>> # solve design case + >>> nw.solve('design') + >>> nw.save('tmp') + >>> # offdesign test + >>> nw.solve('offdesign', design_path='tmp') Add more flexibility -------------------- diff --git a/docs/modules/networks.rst b/docs/modules/networks.rst index b564fb1d4..7da9b37eb 100644 --- a/docs/modules/networks.rst +++ b/docs/modules/networks.rst @@ -163,7 +163,7 @@ in .csv-files rather than your python script. The :code:`init_previous` parameter can be used in design and offdesign calculations and works very similar to specifying an :code:`init_path`. In contrast, starting values are taken from the previous calculation. Specifying -the :code:`ìnit_path` overwrites :code:`init_previous`. +the :code:`init_path` overwrites :code:`init_previous`. Design mode +++++++++++ @@ -234,8 +234,8 @@ consequently the solution is obtained with numerical methods. TESPy uses the n-dimensional Newton-Raphson method to find the system's solution, which may only be found, if the network is parameterized correctly. **The number of** **variables n changes depending on your system's topology and your** -**specifications**. Generally, masA presolving step reduces the amount of variables, see below -for more information. +**specifications**. On top of that, the presolver reduces the number of +variables based on your model structure and your specifications. **General preprocessing** @@ -311,7 +311,7 @@ result. The following steps are performed in finding starting values: * fluid composition guessing. * fluid property initialisation. -* initialisation from previous simulation run (:code:`ìnit_previous`). +* initialisation from previous simulation run (:code:`init_previous`). * initialisation from .csv (setting starting values from :code:`init_path` argument). @@ -499,14 +499,14 @@ over-determined. determine, which parameters are still to be specified. If you are modeling a cycle, e.g. the Clausius Rankine cylce, you need to make -a cut in the cycle using the cycle_closer or a sink and a source not to -over-determine the system. Have a look in the +a cut in the cycle using the :code:`CycleCloser` or a :code:`Sink` and a +:code:`Source` not to over-determine the system. Have a look in the :ref:`tutorial section ` to understand why this is important and how it can be implemented. If you have provided the correct number of parameters in your system and the calculations stops after or even before the first iteration, there might be a -couple reasons for that: +couple of reasons for that: - Sometimes, the fluid property database does not find a specific fluid property in the initialisation process, have you specified the values in the @@ -543,6 +543,15 @@ in this case. variables. Maybe it is only one variable causing the instability, its increment is much larger than the increment of the other variables? +If you run multiple simulations and a simulation crashed due to an internal +error (e.g. fluid property related), and you still want the next simulations to +perform correctly, you have to call the +:py:meth:`tespy.networks.network.Network.reset_topology_reduction_specifications` +method after the failed simulation and before you run the next simulation. +Usually, this happens automatically as part of the post-processing, but in case +the simulation crashed before that, this step cannot be executed. Then, +restarting the simulation is not possible. + Did you experience other errors frequently and have a workaround/tips for resolving them? You are very welcome to contact us and share your experience for other users! @@ -732,7 +741,7 @@ save the network first. This generates a folder structure containing all relevant files defining your network (general network information, components, connections, busses, -characteristics) holding the parametrization of that network. You can re-import +characteristics) holding the parametrisation of that network. You can re-import the network using following code with the path to the saved documents. The generated network object contains the same information as a TESPy network created by a python script. Thus, it is possible to set your parameters in the diff --git a/docs/modules/ude.rst b/docs/modules/ude.rst index cf6ed80d9..ab85d49c5 100644 --- a/docs/modules/ude.rst +++ b/docs/modules/ude.rst @@ -96,14 +96,12 @@ inside the Jacobian of the :code:`UserDefinedEquation` and returns it: - :code:`ude.jacobian` is a dictionary containing numpy arrays for every connection required by the :code:`UserDefinedEquation`. -- derivatives to **mass flow** are placed in the first element of the numpy - array (**index 0**) -- derivatives to **pressure** are placed in the second element of the numpy - array (**index 1**) -- derivatives to **enthalpy** are placed in the third element of the numpy - array (**index 2**) -- derivatives to **fluid composition** are placed in the remaining elements - beginning at the fourth element of the numpy array (**indices 3:**) +- derivatives are referred to with the :code:`J_col` attribute of the + variables of a :code:`Connection` object, i.e. + :code:`c.m.J_col` for mass flow, :code:`c.p.J_col` for pressure, + :code:`c.h.J_col` for enthalpy, and :code:`c.fluid.J_col[fluid_name]` for the + derivative of the fluid composition towards a specific fluid + :code:`fluid_name`. If we calculate the derivatives of our equation, it is easy to find, that only derivatives to mass flow are not zero. @@ -115,12 +113,12 @@ derivatives to mass flow are not zero. .. code-block:: python >>> def my_ude_deriv(ude): - ... c0 = ude.conns[0] - ... c1 = ude.conns[1] - ... if c0.m.is_var: - ... ude.jacobian[c0.m.J_col] = 1 + ... c1 = ude.conns[0] + ... c2 = ude.conns[1] ... if c1.m.is_var: - ... ude.jacobian[c1.m.J_col] = -2 * ude.conns[1].m.val_SI + ... ude.jacobian[c1.m.J_col] = 1 + ... if c2.m.is_var: + ... ude.jacobian[c2.m.J_col] = -2 * ude.conns[1].m.val_SI Now we can create our instance of the :code:`UserDefinedEquation` and add it to the network. The class requires four mandatory arguments to be passed: @@ -186,21 +184,21 @@ respectively to calculate the partial derivatives. >>> from tespy.tools.fluid_properties import dT_mix_pdh >>> def my_ude_deriv(ude): - ... c0 = ude.conns[0] - ... c1 = ude.conns[1] - ... if c0.m.is_var: - ... ude.jacobian[c0.m.J_col] = 1 / ude.conns[0].m.val_SI - ... if c0.p.is_var: - ... ude.jacobian[c0.p.J_col] = - 2 / ude.conns[0].p.val_SI - ... T = c1.calc_T() + ... c1 = ude.conns[0] + ... c2 = ude.conns[1] + ... if c1.m.is_var: + ... ude.jacobian[c1.m.J_col] = 1 / ude.conns[0].m.val_SI ... if c1.p.is_var: - ... ude.jacobian[c1.p.J_col] = ( - ... dT_mix_dph(c1.p.val_SI, c1.h.val_SI, c1.fluid_data, c1.mixing_rule) + ... ude.jacobian[c1.p.J_col] = - 2 / ude.conns[0].p.val_SI + ... T = c2.calc_T() + ... if c2.p.is_var: + ... ude.jacobian[c2.p.J_col] = ( + ... dT_mix_dph(c2.p.val_SI, c2.h.val_SI, c2.fluid_data, c2.mixing_rule) ... * 0.5 / (T ** 0.5) ... ) - ... if c1.h.is_var: - ... ude.jacobian[c1.h.J_col] = ( - ... dT_mix_pdh(c1.p.val_SI, c1.h.val_SI, c1.fluid_data, c1.mixing_rule) + ... if c2.h.is_var: + ... ude.jacobian[c2.h.J_col] = ( + ... dT_mix_pdh(c2.p.val_SI, c2.h.val_SI, c2.fluid_data, c2.mixing_rule) ... * 0.5 / (T ** 0.5) ... ) @@ -214,16 +212,16 @@ for the above derivatives would therefore look like this: .. code-block:: python >>> def my_ude_deriv(ude): - ... c0 = ude.conns[0] - ... c1 = ude.conns[1] - ... if c0.m.is_var: - ... ude.jacobian[c0.m.J_col] = ude.numeric_deriv('m', c0) - ... if c0.p.is_var: - ... ude.jacobian[c0.p.J_col] = ude.numeric_deriv('p', c0) + ... c1 = ude.conns[0] + ... c2 = ude.conns[1] + ... if c1.m.is_var: + ... ude.jacobian[c1.m.J_col] = ude.numeric_deriv('m', c1) ... if c1.p.is_var: ... ude.jacobian[c1.p.J_col] = ude.numeric_deriv('p', c1) - ... if c1.h.is_var: - ... ude.jacobian[c1.h.J_col] = ude.numeric_deriv('h', c1) + ... if c2.p.is_var: + ... ude.jacobian[c2.p.J_col] = ude.numeric_deriv('p', c2) + ... if c2.h.is_var: + ... ude.jacobian[c2.h.J_col] = ude.numeric_deriv('h', c2) >>> ude = UserDefinedEquation('ude numerical', my_ude, my_ude_deriv, [c1, c2]) >>> nw.add_ude(ude) @@ -268,24 +266,24 @@ instance must therefore be changed as below. >>> def my_ude(ude): ... a = ude.params['a'] ... b = ude.params['b'] - ... c0 = ude.conns[0] - ... c1 = ude.conns[1] + ... c1 = ude.conns[0] + ... c2 = ude.conns[1] ... return ( - ... a * (c1.h.val_SI - c0.h.val_SI) - - ... (c1.h.val_SI - h_mix_pQ(c0.p.val_SI, b, c0.fluid_data)) + ... a * (c2.h.val_SI - c1.h.val_SI) - + ... (c2.h.val_SI - h_mix_pQ(c1.p.val_SI, b, c1.fluid_data)) ... ) >>> def my_ude_deriv(ude): ... a = ude.params['a'] ... b = ude.params['b'] - ... c0 = ude.conns[0] - ... c1 = ude.conns[1] - ... if c0.p.is_var: - ... ude.jacobian[c0.p.J_col] = dh_mix_dpQ(c0.p.val_SI, b, c0.fluid_data) - ... if c0.h.is_var: - ... ude.jacobian[c0.h.J_col] = -a + ... c1 = ude.conns[0] + ... c2 = ude.conns[1] ... if c1.p.is_var: - ... ude.jacobian[c1.p.J_col] = a - 1 + ... ude.jacobian[c1.p.J_col] = dh_mix_dpQ(c1.p.val_SI, b, c1.fluid_data) + ... if c1.h.is_var: + ... ude.jacobian[c1.h.J_col] = -a + ... if c2.p.is_var: + ... ude.jacobian[c2.p.J_col] = a - 1 >>> ude = UserDefinedEquation( ... 'my ude', my_ude, my_ude_deriv, [c1, c2], params={'a': 0.5, 'b': 1} diff --git a/docs/references.bib b/docs/references.bib index 07f2fd5e3..92af8f165 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -402,3 +402,12 @@ @book{Knacke1991 year={1991}, publisher={Springer-Verlag} } + +@inproceedings{Fritz2024, + url = {https://energieforschungsverbund.hamburg/norddeutsches/zweite-konferenz-zur-norddeutschen-waermeforschung-768742}, + booktitle = {Tagungsband der zweiten Konferenz zur Norddeutschen Wärmeforschung}, + year = 2024, + publisher = {Energieforschungsverbund Hamburg (EFH)}, + author = {Fritz, Malte and Freißmann, Jonas and Tuschy, Ilja}, + title = {Open-Source Web Dashboard zur Simulation, Analyse und Bewertung von Wärmepumpen} +} diff --git a/docs/whats_new.rst b/docs/whats_new.rst index 91229a011..4c1e6b6aa 100644 --- a/docs/whats_new.rst +++ b/docs/whats_new.rst @@ -3,6 +3,7 @@ What's New Discover notable new features and improvements in each release +.. include:: whats_new/v0-7-7.rst .. include:: whats_new/v0-7-6-001.rst .. include:: whats_new/v0-7-6.rst .. include:: whats_new/v0-7-5.rst diff --git a/docs/whats_new/v0-7-7.rst b/docs/whats_new/v0-7-7.rst new file mode 100644 index 000000000..336d3b138 --- /dev/null +++ b/docs/whats_new/v0-7-7.rst @@ -0,0 +1,34 @@ +Under development ++++++++++++++++++ + +Bug Fixes +######### +- Only :code:`.json` format files are loaded by the `load_network` method. + Furthermore, it is checked whether a file is represented by a class + available in the namespace via the :code:`@component_registry` decorator + (`PR #536 `__). +- Fixed a typo in the Jacobian of the hot side and cold side + :code:`HeatExchanger` effectiveness. + +Other Changes +############# +- Make the :code:`reset_topology_reduction_specifications` method of the + `Network` class a public method + (`PR #559 `__). +- Components of class :code:`SimpleHeatExchanger` need explicit specification + of the :code:`dissipative` attribute in the next major version of tespy in + context of the exergy analysis + (`PR #563 `__). + +Documentation +############# +- Update deprecated information on the indices of variables in the Jacobian of + a :code:`UserDefinedEquation` + (`PR #552 `__). +- A note has been added, that the component and connection labels in subsystems + should be made unique. On top of that, most of the components module docs isn + now testable (`PR #553 `__). + +Contributors +############ +- Francesco Witte (`@fwitte `__) diff --git a/pyproject.toml b/pyproject.toml index 46e2a8527..5b1239b8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ exclude = ["docs/_build"] [project] name = "tespy" -version = "0.7.6.post1" +version = "0.7.7.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 9b1987134..bdbb3cdf0 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.6.post1 - Newton\'s Nature' +__version__ = '0.7.7 - Newton\'s Nature' # tespy data and connections import from . import connections # noqa: F401 diff --git a/src/tespy/components/heat_exchangers/base.py b/src/tespy/components/heat_exchangers/base.py index 31a8445a4..52651e077 100644 --- a/src/tespy/components/heat_exchangers/base.py +++ b/src/tespy/components/heat_exchangers/base.py @@ -769,7 +769,7 @@ def eff_cold_deriv(self, increment_filter, k): self.jacobian[k, c.h.J_col] = self.numeric_deriv(f, 'h', c) if self.is_variable(i2.h): - self.jacobian[k, i2.h.J_col] = 1 - self.eta_cold.val + self.jacobian[k, i2.h.J_col] = 1 - self.eff_cold.val def calc_dh_max_hot(self): r"""Calculate the theoretical maximum enthalpy decrease on the hot side @@ -828,7 +828,7 @@ def eff_hot_deriv(self, increment_filter, k): i2 = self.inl[1] if self.is_variable(i1.h): - self.jacobian[k, i1.h.J_col] = 1 - self.eta_hot.val + self.jacobian[k, i1.h.J_col] = 1 - self.eff_hot.val for c in [o1, i2]: if self.is_variable(c.p, increment_filter): diff --git a/src/tespy/components/heat_exchangers/simple.py b/src/tespy/components/heat_exchangers/simple.py index a8b7614a0..18b6b24b3 100644 --- a/src/tespy/components/heat_exchangers/simple.py +++ b/src/tespy/components/heat_exchangers/simple.py @@ -223,7 +223,7 @@ def get_parameters(self): 'ks_HW': dc_cp(val=10, min_val=1e-1, max_val=1e3, d=1e-2), 'kA': dc_cp(min_val=0, d=1), 'kA_char': dc_cc(param='m'), 'Tamb': dc_cp(), - 'dissipative': dc_simple(val=True), + 'dissipative': dc_simple(val=None), 'darcy_group': dc_gcp( elements=['L', 'ks', 'D'], num_eq=1, latex=self.darcy_func_doc, @@ -1045,6 +1045,14 @@ def exergy_balance(self, T0): \dot{E}_\mathrm{F} & \dot{Q} > 0\\ \end{cases} """ + if self.dissipative.val is None: + self.dissipative.val = True + msg = ( + "In a future version of TESPy, the dissipative property must " + "explicitly be set to True or False in the context of the " + f"exergy analysis for component {self.label}." + ) + logger.warning(msg) if self.Q.val < 0: if self.inl[0].T.val_SI >= T0 and self.outl[0].T.val_SI >= T0: if self.dissipative.val: diff --git a/src/tespy/networks/network.py b/src/tespy/networks/network.py index 45737ad6a..b32842ca3 100644 --- a/src/tespy/networks/network.py +++ b/src/tespy/networks/network.py @@ -1121,7 +1121,7 @@ def presolve_fluid_topology(self): } self.num_conn_vars += 1 - def _reset_topology_reduction_specifications(self): + def reset_topology_reduction_specifications(self): for c in self.conns["object"]: if hasattr(c, "_m_tmp"): value = c.m.val_SI @@ -1661,7 +1661,7 @@ def init_properties(self): Initialise the fluid properties on every connection of the network. - Set generic starting values for mass flow, enthalpy and pressure if - not user specified, read from :code:`ìnit_path` or available from + not user specified, read from :code:`init_path` or available from previous calculation. - For generic starting values precalculate enthalpy value at points of given temperature, vapor mass fraction, temperature difference to @@ -1963,7 +1963,7 @@ def solve(self, mode, init_path=None, design_path=None, self.initialise() if init_only: - self._reset_topology_reduction_specifications() + self.reset_topology_reduction_specifications() return msg = 'Starting solver.' @@ -1974,7 +1974,7 @@ def solve(self, mode, init_path=None, design_path=None, self.solve_loop(print_results=print_results) if not prepare_fast_lane: - self._reset_topology_reduction_specifications() + self.reset_topology_reduction_specifications() if self.lin_dep: msg = ( diff --git a/src/tespy/networks/network_reader.py b/src/tespy/networks/network_reader.py index 77e038bb7..f75ee0334 100644 --- a/src/tespy/networks/network_reader.py +++ b/src/tespy/networks/network_reader.py @@ -186,9 +186,22 @@ def load_network(path): files = os.listdir(path_comps) for f in files: - fn = os.path.join(path_comps, f) + if not f.endswith(".json"): + continue + component = f.replace(".json", "") + if component not in component_registry.items: + msg = ( + f"A class {component} is not available through the " + "tespy.components.component.component_registry decorator. " + "If you are using a custom component make sure to decorate the " + "class." + ) + logger.warning(msg) + continue + + fn = os.path.join(path_comps, f) msg = f"Reading component data ({component}) from {fn}." logger.debug(msg) diff --git a/tests/test_analyses/test_exergy_analysis.py b/tests/test_analyses/test_exergy_analysis.py index 54c27ee8e..b35c0957f 100644 --- a/tests/test_analyses/test_exergy_analysis.py +++ b/tests/test_analyses/test_exergy_analysis.py @@ -80,9 +80,9 @@ def setup_method(self): # component parameters turb.set_attr(eta_s=1) fwp_turb.set_attr(eta_s=1) - condenser.set_attr(pr=1) + condenser.set_attr(pr=1, dissipative=True) fwp.set_attr(eta_s=1) - steam_generator.set_attr(pr=1) + steam_generator.set_attr(pr=1, dissipative=False) # connection parameters fs_in.set_attr(m=10, p=120, T=600, fluid={'water': 1}) @@ -274,14 +274,13 @@ def setup_method(self): """Set up simple refrigerator.""" self.Tamb = 20 self.pamb = 1 - fluids = ['R134a'] self.nw = Network() self.nw.set_attr(p_unit='bar', T_unit='C', h_unit='kJ / kg') # create components va = Valve('expansion valve') cp = Compressor('compressor') - cond = SimpleHeatExchanger('condenser') + cond = SimpleHeatExchanger('condenser', dissipative=True) eva = SimpleHeatExchanger('evaporator', dissipative=False) cc = CycleCloser('cycle closer') @@ -349,7 +348,7 @@ def setup_method(self): # components amb = Source('air intake') cp = Compressor('compressor') - cooler = SimpleHeatExchanger('cooling') + cooler = SimpleHeatExchanger('cooling', dissipative=True) cas = Sink('compressed air storage') # power input bus @@ -399,7 +398,6 @@ def setup_method(self): """Set up air compressed air turbine.""" self.Tamb = 20 self.pamb = 1 - fluids = ['Air'] # turbine part self.nw = Network() @@ -407,7 +405,7 @@ def setup_method(self): # components cas = Source('compressed air storage') - reheater = SimpleHeatExchanger('reheating') + reheater = SimpleHeatExchanger('reheating', dissipative=False) turb = Turbine('turbine') amb = Sink('air outlet') @@ -489,7 +487,6 @@ class TestCompression: def setup_method(self): self.Tamb = 20 self.pamb = 1 - fluids = ['Air'] # turbine part self.nw = Network() @@ -548,18 +545,19 @@ def run_analysis(self): ean.network_data.E_F - ean.network_data.E_P - ean.network_data.E_L - ean.network_data.E_D) msg = ( - 'Exergy balance must be closed (residual value smaller than ' + - str(ERR ** 0.5) + ') for this test but is ' + - str(round(abs(exergy_balance), 4)) + '.') + 'Exergy balance must be closed (residual value smaller than ' + f'{ERR ** 0.5}) for this test but is ' + f'{round(abs(exergy_balance), 4)}.' + ) assert abs(exergy_balance) <= ERR ** 0.5, msg E_D_agg = ean.aggregation_data['E_D'].sum() E_D_nw = ean.network_data.loc['E_D'] msg = ( 'The exergy destruction of the aggregated components and ' - 'respective busses (' + str(round(E_D_agg)) + ') must be equal to ' - 'the exergy destruction of the network (' + str(round(E_D_nw)) + - ').') + f'respective busses ({round(E_D_agg)}) must be equal to the exergy ' + f'destruction of the network ({round(E_D_nw)}).' + ) assert E_D_agg == E_D_nw, msg @@ -568,7 +566,6 @@ class TestExpansion: def setup_method(self): self.Tamb = 20 self.pamb = 1 - fluids = ['Air'] # turbine part self.nw = Network() @@ -628,16 +625,17 @@ def run_analysis(self): ean.network_data.E_F - ean.network_data.E_P - ean.network_data.E_L - ean.network_data.E_D) msg = ( - 'Exergy balance must be closed (residual value smaller than ' + - str(ERR ** 0.5) + ') for this test but is ' + - str(round(abs(exergy_balance), 4)) + '.') + 'Exergy balance must be closed (residual value smaller than ' + f'{ERR ** 0.5}) for this test but is ' + f'{round(abs(exergy_balance), 4)}.' + ) assert abs(exergy_balance) <= ERR ** 0.5, msg E_D_agg = ean.aggregation_data['E_D'].sum() E_D_nw = ean.network_data.loc['E_D'] msg = ( 'The exergy destruction of the aggregated components and ' - 'respective busses (' + str(round(E_D_agg)) + ') must be equal to ' - 'the exergy destruction of the network (' + str(round(E_D_nw)) + - ').') + f'respective busses ({round(E_D_agg)}) must be equal to the exergy ' + f'destruction of the network ({round(E_D_nw)}).' + ) assert E_D_agg == E_D_nw, msg diff --git a/tests/test_components/test_heat_exchangers.py b/tests/test_components/test_heat_exchangers.py index b4479a2de..eee9b8716 100644 --- a/tests/test_components/test_heat_exchangers.py +++ b/tests/test_components/test_heat_exchangers.py @@ -460,7 +460,10 @@ def test_HeatExchanger(self, tmp_path): ) assert round(instance.eff_cold.val, 1) == 0.9, msg - self.c3.set_attr(m=None) + self.c1.set_attr(p=None) + self.c2.set_attr(p=3) + self.c3.set_attr(m=None, p=None) + self.c4.set_attr(p=5) instance.set_attr(eff_max=None, eff_hot=0.9, eff_cold=0.9) self.nw.solve("design") self.nw._convergence_check() diff --git a/tests/test_models/test_CGAM_model.py b/tests/test_models/test_CGAM_model.py index 1857485ac..1a9da1efb 100644 --- a/tests/test_models/test_CGAM_model.py +++ b/tests/test_models/test_CGAM_model.py @@ -31,7 +31,6 @@ class TestCGAM: def setup_method(self): - fluid_list = ['O2', 'H2O', 'N2', 'CO2', 'CH4'] self.nwk = Network(p_unit='bar', T_unit='C') air_molar = { diff --git a/tests/test_models/test_solar_energy_generating_system.py b/tests/test_models/test_solar_energy_generating_system.py index 5dcfcacbf..53c053bbb 100644 --- a/tests/test_models/test_solar_energy_generating_system.py +++ b/tests/test_models/test_solar_energy_generating_system.py @@ -245,7 +245,7 @@ def setup_method(self): 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) + iam_1=1, iam_2=1, dissipative=False) ptpump.set_attr(eta_s=0.6) diff --git a/tests/test_networks/test_network.py b/tests/test_networks/test_network.py index c49eb114a..bd642b985 100644 --- a/tests/test_networks/test_network.py +++ b/tests/test_networks/test_network.py @@ -9,7 +9,7 @@ SPDX-License-Identifier: MIT """ - +import json import os from pytest import mark @@ -149,6 +149,38 @@ def test_Network_reader_checked(self, tmp_path): 'should have been successful, too, but it is not.') assert imported_nwk.checked, msg + def test_Network_reader_superflouus_files(self, tmp_path): + """Test state of network if loaded successfully from export.""" + a = Connection(self.source, 'out1', self.sink, 'in1') + self.nw.add_conns(a) + a.set_attr(fluid={"H2O": 1}) + self.nw.solve('design', init_only=True) + self.nw.export(tmp_path) + with open(os.path.join(tmp_path, "components", "test.csv"), "w") as f: + f.write("nothing to see here") + + imported_nwk = load_network(tmp_path) + 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, msg + + def test_Network_reader_unknown_component_class(self, tmp_path): + """Test state of network if loaded successfully from export.""" + a = Connection(self.source, 'out1', self.sink, 'in1') + self.nw.add_conns(a) + a.set_attr(fluid={"H2O": 1}) + self.nw.solve('design', init_only=True) + self.nw.export(tmp_path) + with open(os.path.join(tmp_path, "components", "Test.json"), "w") as f: + json.dump({}, f) + + imported_nwk = load_network(tmp_path) + 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, msg + def test_Network_missing_data_in_design_case_files(self, tmp_path_factory): """Test for missing data in design case files.""" tmp_path = tmp_path_factory.mktemp("tmp") diff --git a/tutorial/advanced/starting_values.py b/tutorial/advanced/starting_values.py index 357b0403e..ad6798fe5 100644 --- a/tutorial/advanced/starting_values.py +++ b/tutorial/advanced/starting_values.py @@ -107,7 +107,8 @@ nw.solve("design") except ValueError as e: print(e) - nw._reset_topology_reduction_specifications() + nw.reset_topology_reduction_specifications() + # %%[sec_4] import CoolProp.CoolProp as CP