diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..02e0e151 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,33 @@ +name: Release on PyPI + +on: + release: + types: [published] + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install -r requirements.txt + python -m pip install -r requirements_dev.txt + python -m pip install build + + - name: Build + run: python -m build + + - name: Publish on PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_API_PASS_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c639501..3a964bfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,21 @@ # History of changes -## Current version +## Version 2.3 (in development) + +## Version 2.2.2 (2023-01-09) + +### Enhancements + +* [Issue 204](https://github.com/MassimoCimmino/pygfunction/issues/204) - Added support for Python 3.9 and 3.10. [CoolProp](https://www.coolprop.org/) is removed from the dependencies and replace with [SecondaryCoolantProps](https://github.com/mitchute/SecondaryCoolantProps). + +### Bug fixes + +* [Issue 231](https://github.com/MassimoCimmino/pygfunction/issues/231) - Fixed an issue where the evaluation of g-functions at very low times raises an error due a singular matrix. g-Functions below a threshold time value `t=max(r_b)**2/(25*alpha)` are now linearized. + +### Other changes + +* [Issue 229](https://github.com/MassimoCimmino/pygfunction/issues/229), [Issue 247](https://github.com/MassimoCimmino/pygfunction/issues/247) - Added citation to IGSHPA conference paper on *pygfunction* v2.2 in the documention. Added a `CITATION.cff` file to suggest a correct citation on github. +* [Issue 230](https://github.com/MassimoCimmino/pygfunction/issues/230) - Configured github actions to publish *pygfunction* on Pypi on creation of a release on github. ### Enhancements @@ -79,7 +94,7 @@ * [Issue 99](https://github.com/MassimoCimmino/pygfunction/issues/99) - Fixed an issue where `MultipleUTube._continuity_condition()` and `MultipleUTube._general_solution()` returned complex valued coefficient matrices. * [Issue 130](https://github.com/MassimoCimmino/pygfunction/issues/130) - Fix incorrect initialization of variables `_mix_out` and `_mixing_m_flow` in `Network`. * [Issue 155](https://github.com/MassimoCimmino/pygfunction/issues/155) - Fix incorrect initialization of variables in `Network` and `_BasePipe`. Stored variables are now initialized as `numpy.nan` instead of `numpy.empty`. -* [Issue 159](https://github.com/MassimoCimmino/pygfunction/issues/159) - Fix `segment_ratios` function in the `utilities` module to always expect 0 < `end_length_ratio` < 0.5, and allows for `nSegments=1` or `nSegments=2`. If 1<=`nSegments`<3 then the user is warned that the `end_length_ratio` parameter is being over-ridden. +* [Issue 159](https://github.com/MassimoCimmino/pygfunction/issues/159) - Fix `segment_ratios` function in the `utilities` module to always expect 0 < `end_length_ratio` < 0.5, and allows for `nSegments=1` or `nSegments=2`. If 1<=`nSegments`<3 then the user is warned that the `end_length_ratio` parameter is being over-ridden. ## Version 2.0.0 (2021-05-22) diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 00000000..36210d94 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,59 @@ +cff-version: 1.2.0 +message: "If you use this software, please cite it as below." +authors: +- family-names: "Cimmino" + given-names: "Massimo" + orcid: "https://orcid.org/0000-0002-9981-9926" +title: "pygfunction" +version: 2.2.2 +date-released: 2023-01-09 +url: "https://github.com/MassimoCimmino/pygfunction" +preferred-citation: + type: conference-paper + title: "pygfunction 2.2 : New Features and Improvements in Accuracy and Computational Efficiency" + authors: + - family-names: "Cimmino" + given-names: "Massimo" + orcid: "https://orcid.org/0000-0002-9981-9926" + - family-names: "Cook" + given-names: "Jonathan C." + doi: "10.22488/okstate.22.000015" + start: 45 # First page number + end: 52 # Last page number + year: 2022 + collection-type: proceedings + collection-title: "Research Conference Proceedings, IGSHPA Conference 2022" + publisher: + name: International Ground Source Heat Pump Association + institution: + name: International Ground Source Heat Pump Association + collection-doi: 10.22488/okstate.22.000010 + location: Las Vegas NV, USA + editors: + - family-names: "Spitler" + given-names: "Jeff" + - family-names: "Acuña" + given-names: "José" + - family-names: "Bernier" + given-names: "Michel" + - family-names: "Cimmino" + given-names: "Massimo" + - family-names: "Fang" + given-names: "Zhaohong" + - family-names: "Gehlin" + given-names: "Signhild" + - family-names: "Javed" + given-names: "Saqib" + - family-names: "Liu" + given-names: "Xiaobing" + - family-names: "Rees" + given-names: "Simon" + - family-names: "Stumpf" + given-names: "Andrew" + conference: + - name: International Ground Source Heat Pump Association Annual Conference + city: Las Vegas + region: NV + country: USA + date-start: 2022-12-06 + date-end: 2022-12-08 diff --git a/LICENSE.md b/LICENSE.md index 3b3c62ab..4843796b 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,5 +1,5 @@ -Copyright (c) 2017-2022, Massimo Cimmino +Copyright (c) 2017-2023, Massimo Cimmino All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.md b/README.md index 3f72ad74..d6966bda 100644 --- a/README.md +++ b/README.md @@ -42,10 +42,10 @@ fluid temperatures in the boreholes for several U-tube pipe configurations. *pygfunction* was developed and tested using Python 3.7. In addition, the following packages are needed to run *pygfunction* and its examples: -- Coolprop (>= 6.4.1) - matplotlib (>= 3.5.1), - numpy (>= 1.21.5) - scipy (>= 1.7.3) +- SecondaryCoolantProps (>= 1.1) The documentation is generated using [Sphinx](http://www.sphinx-doc.org). The following packages are needed to build the documentation: diff --git a/doc/source/citing.rst b/doc/source/citing.rst index 269f3015..f92096d3 100644 --- a/doc/source/citing.rst +++ b/doc/source/citing.rst @@ -4,6 +4,43 @@ Citing pygfunction in scientific publications *********************************************** +------------------ + pygfunction v2.2 +------------------ + +Improvements to *pygfunction* from v1.0 to v2.2 were presented at the IGSHPA +Annual Conference 2022: + +Cimmino, M., & Cook, J.C. (2022). pygfunction 2.2: New features and improvements +in accuracy and computational efficiency. In +*Research Conference Proceedings, IGSHPA Annual Conference 2022* +(pp. 45-52). International Ground Source Heat Pump Association. +DOI: https://doi.org/10.22488/okstate.22.000015. + +The conference paper is available `here +`__. + +Here is an example of a BibTeX entry: + +.. code-block:: none + + @inproceedings{2022Pygfunction-V2-2, + author = {Cimmino, Massimo and Cook, Jonathan C.}, + title = {{pygfunction} 2.2: New features and improvements in accuracy and computational efficiency}, + crossref = {IGSHPA2022}, + pages = {45--52}, + } + + @proceedings{IGSHPA2022, + title = "Research Conference Proceedings, IGSHPA Annual Conference 2022", + booktitle = "Research Conference Proceedings, IGSHPA Annual Conference 2022", + publisher = {International Ground Source Heat Pump Association} + venue = {Las Vegas NV, USA}, + month = december, + year = {2022}, + doi = {10.22488/okstate.22.000010}, + } + ------------------ pygfunction v1.0 ------------------ @@ -16,7 +53,7 @@ of thermal response factors for geothermal borehole fields. In (pp. 492-501). IBPSA. ISBN 978-2-921145-88-6. The conference paper is available `here -`_. +`__. Here is an example of a BibTeX entry: diff --git a/doc/source/examples/bore_field_thermal_resistance.rst b/doc/source/examples/bore_field_thermal_resistance.rst index 5300da1b..d4e5663f 100644 --- a/doc/source/examples/bore_field_thermal_resistance.rst +++ b/doc/source/examples/bore_field_thermal_resistance.rst @@ -13,7 +13,7 @@ The following script evaluates the effective bore field thermal resistance for fields of 1 to 5 series-connected boreholes with fluid flow rates ranging from 0.01 kg/s ti 1.00 kg/s. -The script is located in: +The script is located in: `pygfunction/examples/bore_field_thermal_resistance.py` .. literalinclude:: ../../../examples/bore_field_thermal_resistance.py diff --git a/doc/source/examples/comparison_gfunction_solvers.rst b/doc/source/examples/comparison_gfunction_solvers.rst index 355c7333..ef7725d1 100644 --- a/doc/source/examples/comparison_gfunction_solvers.rst +++ b/doc/source/examples/comparison_gfunction_solvers.rst @@ -22,7 +22,7 @@ compared using the 'similarities' solver as a reference. This shows that the 'equivalent' solver evaluates g-functions at a very high calculation speed while maintaining reasonable accuracy. -The script is located in: +The script is located in: `pygfunction/examples/comparison_gfunction_solvers.py` .. literalinclude:: ../../../examples/comparison_gfunction_solvers.py diff --git a/doc/source/examples/comparison_load_aggregation.rst b/doc/source/examples/comparison_load_aggregation.rst index 43759400..cebee39d 100644 --- a/doc/source/examples/comparison_load_aggregation.rst +++ b/doc/source/examples/comparison_load_aggregation.rst @@ -17,7 +17,7 @@ Bernier et al. [1]_. The following script validates the load aggregation schemes with the exact solution obtained from convolution in the Fourier domain (see ref. [4]_). -The script is located in: +The script is located in: `pygfunction/examples/comparison_load_aggregation.py` .. literalinclude:: ../../../examples/comparison_load_aggregation.py diff --git a/doc/source/examples/custom_bore_field.rst b/doc/source/examples/custom_bore_field.rst index a88460c7..7a42128e 100644 --- a/doc/source/examples/custom_bore_field.rst +++ b/doc/source/examples/custom_bore_field.rst @@ -15,7 +15,7 @@ function. The following script generates a bore field with 5 boreholes. The field is then plotted on a figure. -The script is located in: +The script is located in: `pygfunction/examples/custom_bore_field.py` .. literalinclude:: ../../../examples/custom_bore_field.py diff --git a/doc/source/examples/custom_bore_field_from_file.rst b/doc/source/examples/custom_bore_field_from_file.rst index c98b14fc..1c98c532 100644 --- a/doc/source/examples/custom_bore_field_from_file.rst +++ b/doc/source/examples/custom_bore_field_from_file.rst @@ -11,7 +11,7 @@ text file. The following script generates a bore field with 32 boreholes. The field is then plotted on a figure. -The script is located in: +The script is located in: `./pygfunction/examples/custom_bore_field_from_file.py` .. literalinclude:: ../../../examples/custom_bore_field_from_file.py diff --git a/doc/source/examples/custom_borehole.rst b/doc/source/examples/custom_borehole.rst index 370c4c94..b03e6243 100644 --- a/doc/source/examples/custom_borehole.rst +++ b/doc/source/examples/custom_borehole.rst @@ -11,7 +11,7 @@ top view of a borehole. The following script generates boreholes with a single U-tube, a double U-tube (in series and parallel configurations), and a coaxial borehole. The borehole cross-sections are then plotted. -The script is located in: +The script is located in: `pygfunction/examples/custom_borehole.py` .. literalinclude:: ../../../examples/custom_borehole.py diff --git a/doc/source/examples/equal_inlet_temperature.rst b/doc/source/examples/equal_inlet_temperature.rst index c4c4a160..cafdcafa 100644 --- a/doc/source/examples/equal_inlet_temperature.rst +++ b/doc/source/examples/equal_inlet_temperature.rst @@ -16,7 +16,7 @@ fluid temperature is compared to the *g*-functions obtained using boundary conditions of uniform heat extraction rate and of uniform borehole wall temperature. -The script is located in: +The script is located in: `pygfunction/examples/equal_inlet_temperature.py` .. literalinclude:: ../../../examples/equal_inlet_temperature.py diff --git a/doc/source/examples/fluid_temperature.rst b/doc/source/examples/fluid_temperature.rst index e6663640..0cb29827 100644 --- a/doc/source/examples/fluid_temperature.rst +++ b/doc/source/examples/fluid_temperature.rst @@ -12,11 +12,11 @@ The g-function of a single borehole is first calculated. Then, the borehole wall temperature variations are calculated using the load aggregation scheme of Claesson and Javed [1]_. The time-variation of heat extraction rates is given by the synthetic load profile of Bernier et al. [2]_. Three pipe configurations are -compared: (1) a single U-tube, using the model of Eskilson and Claesson +compared: (1) a single U-tube, using the model of Eskilson and Claesson [3]_, (2) a double U-tube in parallel, using the model of Cimmino [4]_, and (3) a double U-tube in series, using the model of Cimmino [4]_. -The script is located in: +The script is located in: `pygfunction/examples/fluid_temperature.py` .. literalinclude:: ../../../examples/fluid_temperature.py diff --git a/doc/source/examples/fluid_temperature_multiple_boreholes.rst b/doc/source/examples/fluid_temperature_multiple_boreholes.rst index 50e390a6..861eccdd 100644 --- a/doc/source/examples/fluid_temperature_multiple_boreholes.rst +++ b/doc/source/examples/fluid_temperature_multiple_boreholes.rst @@ -16,8 +16,8 @@ synthetic load profile of Bernier et al. [3]_. Predicted inlet and outlet fluid temperatures of double U-tube boreholes are calculated using the model of Cimmino [4]_. -The script is located in: -`pygfunction/examples/fluid_temperature.py` +The script is located in: +`pygfunction/examples/fluid_temperature_multiple_boreholes.py` .. literalinclude:: ../../../examples/fluid_temperature_multiple_boreholes.py :language: python diff --git a/doc/source/examples/inclined_boreholes.rst b/doc/source/examples/inclined_boreholes.rst index 77b16b0e..b03d6fae 100644 --- a/doc/source/examples/inclined_boreholes.rst +++ b/doc/source/examples/inclined_boreholes.rst @@ -24,4 +24,4 @@ The script is located in: .. rubric:: References .. [1] Claesson J, and Eskilson, P. (1987). Conductive heat extraction by thermally interacting deep boreholes, in "Thermal analysis of heat - extraction boreholes". Ph.D. Thesis, University of Lund, Lund, Sweden. + extraction boreholes". Ph.D. Thesis, University of Lund, Lund, Sweden. diff --git a/doc/source/examples/load_aggregation.rst b/doc/source/examples/load_aggregation.rst index 9ed606d1..48396b89 100644 --- a/doc/source/examples/load_aggregation.rst +++ b/doc/source/examples/load_aggregation.rst @@ -16,7 +16,7 @@ the synthetic load profile of Bernier et al. [2]_. The following script validates the load aggregation scheme with the exact solution obtained from convolution in the Fourier domain (see ref. [3]_). -The script is located in: +The script is located in: `pygfunction/examples/load_aggregation.py` .. literalinclude:: ../../../examples/load_aggregation.py diff --git a/doc/source/examples/mixed_inlet_conditions.rst b/doc/source/examples/mixed_inlet_conditions.rst index eeb7d4b7..bff4afb7 100644 --- a/doc/source/examples/mixed_inlet_conditions.rst +++ b/doc/source/examples/mixed_inlet_conditions.rst @@ -19,7 +19,7 @@ different lengths. The *g*-function considering piping conections is compared to the *g*-function obtained using a boundary condition of uniform borehole wall temperature. -The script is located in: +The script is located in: `pygfunction/examples/mixed_inlet_conditions.py` .. literalinclude:: ../../../examples/mixed_inlet_conditions.py diff --git a/doc/source/examples/multiple_independent_Utubes.rst b/doc/source/examples/multiple_independent_Utubes.rst index 3086765e..59328360 100644 --- a/doc/source/examples/multiple_independent_Utubes.rst +++ b/doc/source/examples/multiple_independent_Utubes.rst @@ -14,7 +14,7 @@ independent U-tubes with different inlet fluid temperatures and different inlet fluid mass flow rates. The resulting fluid temperature profiles are verified against the fluid temeprature profiles presented by Cimmino [1]_. -The script is located in: +The script is located in: `pygfunction/examples/multiple_independent_Utubes.py` .. literalinclude:: ../../../examples/multiple_independent_Utubes.py diff --git a/doc/source/examples/multipole_temperature.rst b/doc/source/examples/multipole_temperature.rst index 0a8d5d76..c5744a12 100644 --- a/doc/source/examples/multipole_temperature.rst +++ b/doc/source/examples/multipole_temperature.rst @@ -17,7 +17,7 @@ temperatures of 1 degC are evaluated. The temperatures in and around the borehole with 2 pipes are then calculated. Results are verified against the results of Claesson and Hellstrom [1]_. -The script is located in: +The script is located in: `pygfunction/examples/multipole_temperature.py` .. literalinclude:: ../../../examples/multipole_temperature.py @@ -27,4 +27,4 @@ The script is located in: .. rubric:: References .. [1] Claesson, J., & Hellstrom, G. (2011). Multipole method to calculate borehole thermal resistances in a borehole heat exchanger. HVAC&R Research, - 17(6), 895-911. + 17(6), 895-911. diff --git a/doc/source/examples/regular_bore_field.rst b/doc/source/examples/regular_bore_field.rst index 6cf36428..5b3aa721 100644 --- a/doc/source/examples/regular_bore_field.rst +++ b/doc/source/examples/regular_bore_field.rst @@ -12,7 +12,7 @@ The following script generates rectangular, box-shaped, U-shaped and L-shaped bore fields in a 4 x 3 configuration as well as a circular field of 8 boreholes. Each field is then plotted on a separate figure. -The script is located in: +The script is located in: `pygfunction/examples/regular_bore_field.py` .. literalinclude:: ../../../examples/regular_bore_field.py diff --git a/doc/source/examples/unequal_segments.rst b/doc/source/examples/unequal_segments.rst index 2d26bfc3..11761e20 100644 --- a/doc/source/examples/unequal_segments.rst +++ b/doc/source/examples/unequal_segments.rst @@ -5,7 +5,7 @@ Calculation of g-functions with unequal numbers of segments *********************************************************** This example demonstrates the use of the :doc:`g-function <../modules/gfunction>` module -to calculate *g*-functions using a boundary condition of uniform and equal +to calculate *g*-functions using a boundary condition of uniform and equal borehole wall temperature for all boreholes. The total rate of heat extraction in the bore field is constant. The discretization along three of the boreholes is refined for the calculation of the *g*-function and to draw the heat extraction rate profiles @@ -14,7 +14,7 @@ along their lengths. The following script generates the *g*-functions of a rectangular field of 6 x 4. *g*-Functions using equal and unequal numbers of segments are compared. -The script is located in: +The script is located in: `pygfunction/examples/unequal_segments.py` .. literalinclude:: ../../../examples/unequal_segments.py diff --git a/doc/source/examples/uniform_heat_extraction_rate.rst b/doc/source/examples/uniform_heat_extraction_rate.rst index 1b1d0b3a..74f91aa2 100644 --- a/doc/source/examples/uniform_heat_extraction_rate.rst +++ b/doc/source/examples/uniform_heat_extraction_rate.rst @@ -5,14 +5,14 @@ Calculation of g-functions with uniform borehole heat extraction rates ********************************************************************** This example demonstrates the use of the :doc:`g-function <../modules/gfunction>` module -to calculate *g*-functions using a boundary condition of uniform and equal +to calculate *g*-functions using a boundary condition of uniform and equal heat extraction rate for all boreholes, constant in time. The following script generates the *g*-functions of rectangular fields of 3 x 2, 6 x 4 and 10 x 10 boreholes. *g*-Functions are verified against the *g*-functions presented by Cimmino and Bernier [1]_. -The script is located in: +The script is located in: `pygfunction/examples/uniform_heat_extraction_rate.py` .. literalinclude:: ../../../examples/uniform_heat_extraction_rate.py diff --git a/doc/source/examples/uniform_temperature.rst b/doc/source/examples/uniform_temperature.rst index 43943b2d..9dac52df 100644 --- a/doc/source/examples/uniform_temperature.rst +++ b/doc/source/examples/uniform_temperature.rst @@ -5,7 +5,7 @@ Calculation of g-functions with uniform borehole wall temperature ***************************************************************** This example demonstrates the use of the :doc:`g-function <../modules/gfunction>` module -to calculate *g*-functions using a boundary condition of uniform and equal +to calculate *g*-functions using a boundary condition of uniform and equal borehole wall temperature for all boreholes. The total rate of heat extraction in the bore field is constant. @@ -13,7 +13,7 @@ The following script generates the *g*-functions of rectangular fields of 3 x 2, 6 x 4 and 10 x 10 boreholes. *g*-Functions are verified against the *g*-functions presented by Cimmino and Bernier [1]_. -The script is located in: +The script is located in: `pygfunction/examples/uniform_temperature.py` .. literalinclude:: ../../../examples/uniform_temperature.py diff --git a/doc/source/index.rst b/doc/source/index.rst index e51f9226..cdda15b6 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -12,7 +12,7 @@ analytical solutions. .. toctree:: :maxdepth: 2 - + install citing modules diff --git a/doc/source/install.rst b/doc/source/install.rst index bfdc5b8c..ce540629 100644 --- a/doc/source/install.rst +++ b/doc/source/install.rst @@ -5,10 +5,10 @@ Setting up pygfunction ********************** *pygfunction* uses Python 3.7, along with the following packages: - - Coolprop (>= 6.4.1) - matplotlib (>= 3.5.1), - numpy (>= 1.21.5) - scipy (>= 1.7.3) + - SecondaryCoolantProps (>= 1.1) *pygfunction*'s- documentation is built using: - sphinx (>= 4.4.0) diff --git a/doc/source/nomenclature.rst b/doc/source/nomenclature.rst index a2f31066..2598c5e6 100644 --- a/doc/source/nomenclature.rst +++ b/doc/source/nomenclature.rst @@ -6,37 +6,37 @@ Nomenclature .. toctree:: :maxdepth: 2 - + Geometry ================== Parameters in the table below are related to the definition of borehole and pipe geometries. - -.. csv-table:: + +.. csv-table:: :file: nomenclature_tables/geometry.csv :widths: 30, 30, 70 :header-rows: 1 - + Fluid properties =================== Parameters in the table below are related to the evaluation of fluid thermal properties in the :doc:`media ` module. - -.. csv-table:: - :file: nomenclature_tables/fluid_properties.csv + +.. csv-table:: + :file: nomenclature_tables/fluid_properties.csv :widths: 30, 30, 70 :header-rows: 1 - + Heat transfer =================== Parameters in the table below are used throughout **pygfunction** for the calculation of heat transfer in geothermal bore fields. - -.. csv-table:: - :file: nomenclature_tables/heat_transfer.csv + +.. csv-table:: + :file: nomenclature_tables/heat_transfer.csv :widths: 30, 30, 70 :header-rows: 1 diff --git a/examples/inclined_boreholes.py b/examples/inclined_boreholes.py index 40020724..b2b9a0eb 100644 --- a/examples/inclined_boreholes.py +++ b/examples/inclined_boreholes.py @@ -8,10 +8,10 @@ configuration presented by Claesson and Eskilson (1987) and the second field corresponds to the configuration comprised of 8 boreholes in a circle. - + Claesson J, and Eskilson, P. (1987). Conductive heat extraction by thermally interacting deep boreholes, in "Thermal analysis of heat - extraction boreholes". Ph.D. Thesis, University of Lund, Lund, Sweden. + extraction boreholes". Ph.D. Thesis, University of Lund, Lund, Sweden. """ import pygfunction as gt diff --git a/pygfunction/boreholes.py b/pygfunction/boreholes.py index 4cc98f89..5da7bf23 100644 --- a/pygfunction/boreholes.py +++ b/pygfunction/boreholes.py @@ -272,7 +272,7 @@ class _EquivalentBorehole(object): Direction (in radians) of the tilt of the borehole. nBoreholes : int Number of boreholes represented by the equivalent borehole. - + References ---------- .. [#EqBorehole-PriCim2021] Prieto, C., & Cimmino, M., 2021. Thermal @@ -485,7 +485,7 @@ def unique_distance(self, target, disTol=0.01): dis.append(np.mean(all_dis[j0:])) wDis.append(nDis-j0) j0 = j1 - + return np.array(dis), np.array(wDis) def _segment_edges(self, nSegments, segment_ratios=None): @@ -1237,7 +1237,7 @@ def visualize_field( np.array([borehole.D, z_H]), 'k-') - + if viewTop and view3D: plt.tight_layout(rect=[0, 0.0, 0.90, 1.0]) else: diff --git a/pygfunction/gfunction.py b/pygfunction/gfunction.py index 99e1d8ad..2a431544 100644 --- a/pygfunction/gfunction.py +++ b/pygfunction/gfunction.py @@ -115,6 +115,13 @@ class gFunction(object): Number of Gauss-Legendre sample points for the integral over :math:`u` in the inclined FLS solution. Default is 11. + linear_threshold : float, optional + Threshold time (in seconds) under which the g-function is + linearized. The g-function value is then interpolated between 0 + and its value at the threshold. If linear_threshold==None, the + g-function is linearized for times + `t < r_b**2 / (25 * self.alpha)`. + Default is None. disp : bool, optional Set to true to print progression messages. Default is False. @@ -174,6 +181,9 @@ class gFunction(object): - The 'equivalent' solver does not support the 'MIFT' boundary condition when boreholes are connected in series. - The 'equivalent' solver does not support inclined boreholes. + - The g-function is linearized for times `t < r_b**2 / (25 * self.alpha)`. + The g-function value is then interpolated between 0 and its value at the + threshold. References ---------- @@ -250,7 +260,7 @@ def evaluate_g_function(self, time): Values of the g-function """ - time = np.atleast_1d(time) + time = np.maximum(np.atleast_1d(time), 0.) assert len(time) == 1 or np.all(time[:-1] <= time[1:]), \ "Time values must be provided in increasing order." # Save time values @@ -1411,6 +1421,13 @@ class _BaseSolver(object): Number of Gauss-Legendre sample points for the integral over :math:`u` in the inclined FLS solution. Default is 11. + linear_threshold : float, optional + Threshold time (in seconds) under which the g-function is + linearized. The g-function value is then interpolated between 0 + and its value at the threshold. If linear_threshold==None, the + g-function is linearized for times + `t < r_b**2 / (25 * self.alpha)`. + Default is None. disp : bool, optional Set to true to print progression messages. Default is False. @@ -1430,13 +1447,15 @@ class _BaseSolver(object): """ def __init__(self, boreholes, network, time, boundary_condition, nSegments=8, segment_ratios=utilities.segment_ratios, - approximate_FLS=False, mQuad=11, nFLS=10, disp=False, - profiles=False, kind='linear', dtype=np.double, - **other_options): + approximate_FLS=False, mQuad=11, nFLS=10, + linear_threshold=None, disp=False, profiles=False, + kind='linear', dtype=np.double, **other_options): self.boreholes = boreholes self.network = network # Convert time to a 1d array self.time = np.atleast_1d(time).flatten() + self.linear_threshold = linear_threshold + self.r_b_max = np.max([b.r_b for b in self.boreholes]) self.boundary_condition = boundary_condition nBoreholes = len(self.boreholes) # Format number of segments and segment ratios @@ -1524,6 +1543,18 @@ def solve(self, time, alpha): # Number of time values self.time = time nt = len(self.time) + # Evaluate threshold time for g-function linearization + if self.linear_threshold is None: + time_threshold = self.r_b_max**2 / (25 * alpha) + else: + time_threshold = self.linear_threshold + # Find the number of g-function values to be linearized + p_long = np.searchsorted(self.time, time_threshold, side='right') + if p_long > 0: + time_long = np.concatenate([[time_threshold], self.time[p_long:]]) + else: + time_long = self.time + nt_long = len(time_long) # Initialize g-function gFunc = np.zeros(nt) # Initialize segment heat extraction rates @@ -1536,7 +1567,7 @@ def solve(self, time, alpha): else: T_b = np.zeros((self.nSources, nt), dtype=self.dtype) # Calculate segment to segment thermal response factors - h_ij = self.thermal_response_factors(time, alpha, kind=self.kind) + h_ij = self.thermal_response_factors(time_long, alpha, kind=self.kind) # Segment lengths H_b = self.segment_lengths() if self.boundary_condition == 'MIFT': @@ -1548,7 +1579,8 @@ def solve(self, time, alpha): tic = perf_counter() # Build and solve the system of equations at all times - for p in range(nt): + p0 = max(0, p_long-1) + for p in range(nt_long): if self.boundary_condition == 'UHTR': # Evaluate the g-function with uniform heat extraction along # boreholes @@ -1557,21 +1589,21 @@ def solve(self, time, alpha): h_dt = h_ij.y[:,:,p+1] # Borehole wall temperatures are calculated by the sum of # contributions of all segments - T_b[:,p] = np.sum(h_dt, axis=1) + T_b[:,p+p0] = np.sum(h_dt, axis=1) # The g-function is the average of all borehole wall # temperatures - gFunc[p] = np.sum(T_b[:,p]*H_b)/H_tot + gFunc[p+p0] = np.sum(T_b[:,p+p0]*H_b)/H_tot else: # Current thermal response factor matrix if p > 0: - dt = self.time[p] - self.time[p-1] + dt = time_long[p] - time_long[p-1] else: - dt = self.time[p] + dt = time_long[p] # Thermal response factors evaluated at t=dt h_dt = h_ij(dt) # Reconstructed load history Q_reconstructed = self.load_history_reconstruction( - self.time[0:p+1], Q_b[:,0:p+1]) + time_long[0:p+1], Q_b[:,p0:p+p0+1]) # Borehole wall temperature for zero heat extraction at # current step T_b0 = self.temporal_superposition( @@ -1598,10 +1630,10 @@ def solve(self, time, alpha): # Solve the system of equations X = np.linalg.solve(A, B) # Store calculated heat extraction rates - Q_b[:,p] = X[0:self.nSources] + Q_b[:,p+p0] = X[0:self.nSources] # The borehole wall temperatures are equal for all segments - T_b[p] = X[-1] - gFunc[p] = T_b[p] + T_b[p+p0] = X[-1] + gFunc[p+p0] = T_b[p+p0] elif self.boundary_condition == 'MIFT': # Evaluate the g-function with mixed inlet fluid # temperatures @@ -1639,8 +1671,8 @@ def solve(self, time, alpha): # Solve the system of equations X = np.linalg.solve(A, B) # Store calculated heat extraction rates - Q_b[:,p] = X[0:self.nSources] - T_b[:,p] = X[self.nSources:2*self.nSources] + Q_b[:,p+p0] = X[0:self.nSources] + T_b[:,p+p0] = X[self.nSources:2*self.nSources] T_f_in = X[-1] # The gFunction is equal to the effective borehole wall # temperature @@ -1655,7 +1687,16 @@ def solve(self, time, alpha): self.network.cp_f) # Effective borehole wall temperature T_b_eff = T_f - 2*pi*self.network.p[0].k_s*R_field - gFunc[p] = T_b_eff + gFunc[p+p0] = T_b_eff + # Linearize g-function for times under threshold + if p_long > 0: + gFunc[:p_long] = gFunc[p_long-1] * self.time[:p_long] / time_threshold + if not self.boundary_condition == 'UHTR': + Q_b[:,:p_long] = 1 + (Q_b[:,p_long-1:p_long] - 1) * self.time[:p_long] / time_threshold + if self.boundary_condition == 'UBWT': + T_b[:p_long] = T_b[p_long-1] * self.time[:p_long] / time_threshold + else: + T_b[:,:p_long] = T_b[:,p_long-1:p_long] * self.time[:p_long] / time_threshold # Store temperature and heat extraction rate profiles if self.profiles: self.Q_b = Q_b @@ -1730,7 +1771,7 @@ def temporal_superposition(self, h_ij, Q_reconstructed): Q_reconstructed[:,1:] - Q_reconstructed[:,0:-1]), axis=1)[:,::-1] # Borehole wall temperature T_b0 = np.einsum('ijk,jk', h_ij[:,:,:nt], dQ) - + return T_b0 def load_history_reconstruction(self, time, Q_b): @@ -1900,6 +1941,13 @@ class _Detailed(_BaseSolver): Number of Gauss-Legendre sample points for the integral over :math:`u` in the inclined FLS solution. Default is 11. + linear_threshold : float, optional + Threshold time (in seconds) under which the g-function is + linearized. The g-function value is then interpolated between 0 + and its value at the threshold. If linear_threshold==None, the + g-function is linearized for times + `t < r_b**2 / (25 * self.alpha)`. + Default is None. disp : bool, optional Set to true to print progression messages. Default is False. @@ -2073,7 +2121,7 @@ def _thermal_response_factors_borehole_to_self(self, time, alpha): dis = np.maximum( np.sqrt((x[i_segment] - x[j_segment])**2 + (y[i_segment] - y[j_segment])**2), r_b[i_segment]) - # FlS solution + # FLS solution if np.all([b.is_vertical() for b in self.boreholes]): h = finite_line_source_vectorized( time, alpha, @@ -2090,7 +2138,7 @@ def _thermal_response_factors_borehole_to_self(self, time, alpha): tilt[j_segment], orientation[j_segment], M=self.mQuad, approximation=self.approximate_FLS, N=self.nFLS) return h, i_segment, j_segment - + class _Similarities(_BaseSolver): @@ -2156,6 +2204,13 @@ class _Similarities(_BaseSolver): Number of Gauss-Legendre sample points for the integral over :math:`u` in the inclined FLS solution. Default is 11. + linear_threshold : float, optional + Threshold time (in seconds) under which the g-function is + linearized. The g-function value is then interpolated between 0 + and its value at the threshold. If linear_threshold==None, the + g-function is linearized for times + `t < r_b**2 / (25 * self.alpha)`. + Default is None. disp : bool, optional Set to true to print progression messages. Default is False. @@ -2969,7 +3024,7 @@ def _find_axial_borehole_pairs(self, boreholes): # If no similar pairs are known, append the groups else: borehole_to_borehole.append([(i, j)]) - + else: # Outputs for a single borehole if boreholes[0].is_vertical: @@ -3453,6 +3508,13 @@ class _Equivalent(_BaseSolver): Number of Gauss-Legendre sample points for the integral over :math:`u` in the inclined FLS solution. Default is 11. + linear_threshold : float, optional + Threshold time (in seconds) under which the g-function is + linearized. The g-function value is then interpolated between 0 + and its value at the threshold. If linear_threshold==None, the + g-function is linearized for times + `t < r_b**2 / (25 * self.alpha)`. + Default is None. disp : bool, optional Set to true to print progression messages. Default is False. @@ -3529,7 +3591,7 @@ def initialize(self, disTol=0.01, tol=1.0e-6, kClusters=1, **kwargs): self.nBoreSegments = [self.nBoreSegments[0]] * self.nEqBoreholes self.segment_ratios = [self.segment_ratios[0]] * self.nEqBoreholes self.boreSegments = self.borehole_segments() - self._i0Segments = [sum(self.nBoreSegments[0:i]) + self._i0Segments = [sum(self.nBoreSegments[0:i]) for i in range(self.nEqBoreholes)] self._i1Segments = [sum(self.nBoreSegments[0:(i + 1)]) for i in range(self.nEqBoreholes)] @@ -3727,7 +3789,7 @@ def find_groups(self, tol=1e-6): self.clusters = range(self.nBoreholes) # Overwrite boreholes with equivalent boreholes self.boreholes = [_EquivalentBorehole( - [borehole + [borehole for borehole, cluster in zip(self.boreholes, self.clusters) if cluster==i]) for i in range(self.nEqBoreholes)] @@ -3998,7 +4060,7 @@ def _find_unique_boreholes(self, boreholes): else: # If no similar boreholes are known, append the groups unique_boreholes.append([i]) - + return unique_boreholes def _find_unique_distances(self, dis, indices): diff --git a/pygfunction/heat_transfer.py b/pygfunction/heat_transfer.py index 6ebd92d3..cc1792db 100644 --- a/pygfunction/heat_transfer.py +++ b/pygfunction/heat_transfer.py @@ -17,7 +17,7 @@ def finite_line_source( of the FLS solution. For vertical boreholes, the FLS solution was proposed by Claesson and Javed [#FLS-ClaJav2011]_ and extended to boreholes with different vertical positions by Cimmino and Bernier [#FLS-CimBer2014]_. - The FlS solution is given by: + The FLS solution is given by: .. math:: h_{1\\rightarrow2}(t) &= \\frac{1}{2H_2} @@ -387,7 +387,7 @@ def finite_line_source_approximation( drop on the wall of borehole2 due to heat extracted from borehole1 is: .. math:: \\Delta T_{b,2} = T_g - \\frac{Q_1}{2\\pi k_s H_2} h - + References ---------- @@ -456,7 +456,7 @@ def finite_line_source_approximation( def finite_line_source_inclined_approximation( - time, alpha, + time, alpha, rb1, x1, y1, H1, D1, tilt1, orientation1, x2, y2, H2, D2, tilt2, orientation2, reaSource=True, imgSource=True, M=11, N=10): @@ -659,7 +659,7 @@ def finite_line_source_vectorized( This function uses a numerical quadrature to evaluate the one-integral form of the FLS solution, as proposed by Claesson and Javed [#FLSVec-ClaJav2011]_ and extended to boreholes with different vertical - positions by Cimmino and Bernier [#FLSVec-CimBer2014]_. The FlS solution + positions by Cimmino and Bernier [#FLSVec-CimBer2014]_. The FLS solution is given by: .. math:: @@ -736,7 +736,7 @@ def finite_line_source_vectorized( (dis, H1, D1, H2, D2) must follow numpy array broadcasting rules. If time is an array, the integrals for different time values are stacked on the last axis. - + References ---------- @@ -756,7 +756,7 @@ def finite_line_source_vectorized( # Integrand of the finite line source solution f = _finite_line_source_integrand( dis, H1, D1, H2, D2, reaSource, imgSource) - + # Evaluate integral if isinstance(time, (np.floating, float)): # Lower bound of integration @@ -857,7 +857,7 @@ def finite_line_source_equivalent_boreholes_vectorized( (dis, H1, D1, H2, D2) must follow numpy array broadcasting rules. If time is an array, the integrals for different time values are stacked on the last axis. - + References ---------- @@ -1049,7 +1049,7 @@ def finite_line_source_inclined_vectorized( rb1, x1, y1, H1, D1, tilt1, orientation1, x2, y2, H2, D2, tilt2, orientation2, reaSource, imgSource, M) - + # Evaluate integral if isinstance(time, (np.floating, float)): # Lower bound of integration diff --git a/pygfunction/load_aggregation.py b/pygfunction/load_aggregation.py index 1a613f05..9b7dcbe6 100644 --- a/pygfunction/load_aggregation.py +++ b/pygfunction/load_aggregation.py @@ -120,10 +120,10 @@ def get_thermal_response_factor_increment(self): ------- dg : array Array of **dimensional** thermal response factor increments used - for temporal superposition + for temporal superposition (:math:`g(t_{i+1})/(2 \\pi k_s) - g(t_{i})/(2 \\pi k_s)`), in correspondance with the intialized values of the thermal - response factors in + response factors in :func:`~load_aggregation.ClaessonJaved.initialize`. The output size of the array is (nSources, nSources, Nt) if nSources>1. If nSources=1, then the method returns a 1d array. @@ -217,7 +217,7 @@ def _build_cells(self, dt, tmax, nSources, cells_per_level): # time steps : Q(t+1) = Q(t) @ A self.A = (1. - 1./self._width) * np.eye(nt) \ + np.diag(1./self._width[1:], k=1) - + class MLAA(_LoadAggregation): diff --git a/pygfunction/media.py b/pygfunction/media.py index 18b88dde..6d95349e 100644 --- a/pygfunction/media.py +++ b/pygfunction/media.py @@ -32,32 +32,32 @@ class Fluid: >>> T_f = 20. # Temp at 20 C >>> # complete water solution - >>> mix = 'Water' + >>> fluid_str = 'Water' >>> percent = 0 - >>> fluid = gt.media.Fluid(mix, percent, T=T_f) + >>> fluid = gt.media.Fluid(fluid_str, percent, T=T_f) >>> print(fluid) >>> # 20 % propylene glycol mixed with water - >>> mix = 'MPG' + >>> fluid_str = 'MPG' >>> percent = 20 - >>> fluid = gt.media.Fluid(mix, percent, T=T_f) + >>> fluid = gt.media.Fluid(fluid_str, percent, T=T_f) >>> # 60% ethylene glycol mixed with water - >>> mix = 'MEG' + >>> fluid_str = 'MEG' >>> percent = 60 - >>> fluid = gt.media.Fluid(mix, percent, T=T_f) + >>> fluid = gt.media.Fluid(fluid_str, percent, T=T_f) >>> print(fluid) >>> # 5% methanol mixed with water - >>> mix = 'MMA' + >>> fluid_str = 'MMA' >>> percent = 5 - >>> fluid = gt.media.Fluid(mix, percent, T=T_f) + >>> fluid = gt.media.Fluid(fluid_str, percent, T=T_f) >>> print(fluid) >>> # ethanol / water - >>> mix = 'MEA' + >>> fluid_str = 'MEA' >>> percent = 10 - >>> fluid = gt.media.Fluid(mix, percent, T=T_f) + >>> fluid = gt.media.Fluid(fluid_str, percent, T=T_f) >>> print(fluid) """ def __init__(self, fluid_str: str, percent: float, T: float = 20.): @@ -78,6 +78,8 @@ def __init__(self, fluid_str: str, percent: float, T: float = 20.): raise ValueError(f'Unsupported fluid mixture: "{fluid_str}".') # Initialize all fluid properties + # Name + self.name = self.fluid.fluid_name # Temperature of the fluid (in Celsius) self.T_C = T # Density (in kg/m3) @@ -190,4 +192,4 @@ def Prandlt_number(self): Prandlt number. """ - return self.fluid.prandtl(self.T_C) + return self.fluid.prandtl(self.T_C) \ No newline at end of file diff --git a/pygfunction/networks.py b/pygfunction/networks.py index 002e8c86..f4ba8c7f 100644 --- a/pygfunction/networks.py +++ b/pygfunction/networks.py @@ -982,7 +982,7 @@ def _initialize_coefficients_connectivity(self): for i in range(self.nBoreholes): if not self.c[i] == -1: self._c_out[i, self.c[i]] = 1. - + return def _initialize_stored_coefficients( diff --git a/pygfunction/pipes.py b/pygfunction/pipes.py index 68729ebe..d6476346 100644 --- a/pygfunction/pipes.py +++ b/pygfunction/pipes.py @@ -90,7 +90,7 @@ def get_temperature( def get_inlet_temperature( self, Q_f, T_b, m_flow_borehole, cp_f, segment_ratios=None): """ - Returns the outlet fluid temperatures of the borehole. + Returns the inlet fluid temperatures of the borehole. Parameters ---------- @@ -120,7 +120,7 @@ def get_inlet_temperature( # Build coefficient matrices a_qf, a_b = self.coefficients_inlet_temperature( m_flow_borehole, cp_f, nSegments, segment_ratios=segment_ratios) - # Evaluate outlet temperatures + # Evaluate inlet temperatures T_f_in = a_qf @ np.atleast_1d(Q_f) + a_b @ T_b # Return float if Qf was supplied as scalar if np.isscalar(Q_f) and not np.isscalar(T_f_in): @@ -278,7 +278,7 @@ def get_total_heat_extraction_rate( def coefficients_inlet_temperature( self, m_flow_borehole, cp_f, nSegments, segment_ratios=None): """ - Build coefficient matrices to evaluate outlet fluid temperature. + Build coefficient matrices to evaluate inlet fluid temperature. Returns coefficients for the relation: @@ -832,7 +832,7 @@ def _check_coefficients( check = False return check - + def _check_geometry(self): """ Verifies the inputs to the pipe object and raises an error if the geometry is not valid. @@ -1752,7 +1752,7 @@ def _continuity_condition( exp_Lz = np.exp(np.multiply.outer(L, z)) dexp_Lz = exp_Lz[:,:-1] - exp_Lz[:,1:] a_b = np.real(((IIm1 @ V @ Dm1) * (Vm1 @ sumA)) @ dexp_Lz) - + # Configuration-specific inlet and outlet coefficient matrices IZER = np.vstack((np.eye(self.nPipes), @@ -2342,7 +2342,7 @@ def visualize_pipes(self): plt.tight_layout() return fig - + def _check_geometry(self): """ Verifies the inputs to the pipe object and raises an error if the geometry is not valid. @@ -2822,7 +2822,7 @@ def convective_heat_transfer_coefficient_concentric_annulus( Evaluate the inner and outer convective heat transfer coefficient for the annulus region of a concentric pipe. - Grundman (2007) referenced Hellström (1991) [#Hellstrom1991b]_ in the + Grundmann (2016) referenced Hellström (1991) [#Hellstrom1991b]_ in the discussion about inner and outer convection coefficients in an annulus region of a concentric pipe arrangement. @@ -2876,7 +2876,7 @@ def convective_heat_transfer_coefficient_concentric_annulus( References ---------- - .. [#Grundman2007] Grundman, R. (2007) Improved design methods for ground + .. [#Grundmann2016] Grundmann, R. (2016) Improved design methods for ground heat exchangers. Oklahoma State University, M.S. Thesis. .. [#ConvCoeff-CengelGhajar2015] Çengel, Y.A., & Ghajar, A.J. (2015). Heat and mass transfer: fundamentals & applications (Fifth edition.). @@ -2896,7 +2896,7 @@ def convective_heat_transfer_coefficient_concentric_annulus( Re = rho_f * V * D_h / mu_f # Prandtl number Pr = cp_f * mu_f / k_f - # Ratio of radii (Grundman, 2007) + # Ratio of radii (Grundmann, 2016) r_star = r_a_in / r_a_out # Darcy-Wiesbach friction factor fDarcy = fluid_friction_factor_circular_pipe( @@ -3148,7 +3148,7 @@ def _F_mk(q_p, P, n_p, J, r_b, r_out, z, pikg, sigma): r_out : float or array Outer radius of the pipes (in meters). z : array - Array of pipe coordinates in complex notation (x + 1.j*y). + Array of pipe coordinates in complex notation (x + 1.j*y). pikg : float Inverse of 2*pi times the grout thermal conductivity, 1.0/(2.0*pi*k_g). sigma : array diff --git a/pygfunction/utilities.py b/pygfunction/utilities.py index e3f98554..b1121edf 100644 --- a/pygfunction/utilities.py +++ b/pygfunction/utilities.py @@ -514,7 +514,7 @@ def _erf_coeffs(N): .. math:: erf(x) \\approx \\sum_{n=0}^{N} a_n exp(-b_n^2 x^2) - + for `x > 0`. Parameters diff --git a/tests/gfunction_test.py b/tests/gfunction_test.py index 2cfab47f..151f1d6c 100644 --- a/tests/gfunction_test.py +++ b/tests/gfunction_test.py @@ -272,3 +272,26 @@ def test_gfunctions_UBWT(two_boreholes_inclined, method, opts, expected, request boreholes, alpha, time=time, method=method, options=options, boundary_condition='UBWT') assert np.allclose(gFunc.gFunc, expected) + + +# ============================================================================= +# Test gFunction linearization +# ============================================================================= +# Test 'UBWT' g-functions for a single borehole at low time values +@pytest.mark.parametrize("field, method, opts, expected", [ + # 'equivalent' solver - unequal segments + ('single_borehole', 'equivalent', 'unequal_segments', np.array([1.35223554e-05, 1.35223554e-04, 2.16066268e-01])), + ]) +def test_gfunctions_UBWT_linearization(field, method, opts, expected, request): + # Extract the bore field from the fixture + boreholes = request.getfixturevalue(field) + # Extract the g-function options from the fixture + options = request.getfixturevalue(opts) + alpha = 1e-6 # Ground thermal diffusivity [m2/s] + # Times for the g-function [s] + time = np.array([0.1, 1., 10.]) * boreholes[0].r_b**2 / (25 * alpha) + # g-Function + gFunc = gt.gfunction.gFunction( + boreholes, alpha, time=time, method=method, options=options, + boundary_condition='UBWT') + assert np.allclose(gFunc.gFunc, expected)