├── .coveragerc ├── .gitignore ├── AUTHORS.rst ├── CHANGELOG.rst ├── LICENSE.txt ├── README.rst ├── _BUILDNOTE.txt ├── docs ├── Makefile ├── _static │ └── .gitignore ├── authors.rst ├── changelog.rst ├── conf.py ├── index.rst └── license.rst ├── requirements.txt ├── setup.cfg ├── setup.py ├── src └── jeppson │ ├── __init__.py │ ├── ch2.py │ ├── ch4.py │ ├── ch5.py │ ├── ch6a.py │ ├── ch6b.py │ ├── ch7.py │ ├── constants.py │ ├── input.py │ └── pipe.py ├── tests ├── conftest.py ├── test_ch2.py ├── test_ch2 │ ├── ch2_test10.inp │ ├── ch2_test10.ref.out │ ├── ch2_test11.inp │ ├── ch2_test11.ref.out │ ├── ch2_test20.inp │ ├── ch2_test20.ref.out │ ├── ch2_test21.inp │ ├── ch2_test21.ref.out │ ├── ch2_test_all.inp │ └── ch2_test_all.ref.out ├── test_ch4.py ├── test_ch4 │ ├── ch4_test1.inp │ └── ch4_test1.ref.out ├── test_ch5.py ├── test_ch5 │ ├── ch5_test1.inp │ ├── ch5_test1.ref.out │ ├── ch5_test2.inp │ └── ch5_test2.ref.out ├── test_ch6a.py ├── test_ch6a │ ├── ch6a_test1.inp │ ├── ch6a_test1.ref.out │ ├── ch6a_test2.inp │ ├── ch6a_test2.ref.out │ ├── ch6a_test3.inp │ └── ch6a_test3.ref.out ├── test_ch6b.py ├── test_ch6b │ ├── ch6b_test0.inp │ ├── ch6b_test0.ref.out │ ├── ch6b_test1.inp │ ├── ch6b_test1.ref.out │ ├── ch6b_test2.inp │ └── ch6b_test2.ref.out ├── test_ch7.py ├── test_ch7 │ ├── ch7_test1.inp │ └── ch7_test1.ref.out ├── test_constants.py ├── test_input.py └── test_pipe.py └── userdoc ├── ch5_case_dom_example.txt ├── ch5_case_dom_input_example.txt ├── ch6a_case_dom_example.txt ├── ch6a_case_dom_input_example.txt ├── ch6b_case_dom_example.txt ├── ch6b_case_dom_input_example.txt ├── ch7_case_dom_example.txt └── ch7_case_dom_input_example.txt /.coveragerc: -------------------------------------------------------------------------------- 1 | # .coveragerc to control coverage.py 2 | [run] 3 | branch = True 4 | source = */jeppson/* 5 | # omit = bad_file.py 6 | 7 | [report] 8 | # Regexes for lines to exclude from consideration 9 | exclude_lines = 10 | # Have to re-enable the standard pragma 11 | pragma: no cover 12 | 13 | # Don't complain about missing debug-only code: 14 | def __repr__ 15 | if self\.debug 16 | 17 | # Don't complain if tests don't hit defensive assertion code: 18 | raise AssertionError 19 | raise NotImplementedError 20 | 21 | # Don't complain if non-runnable code isn't run: 22 | if 0: 23 | if __name__ == .__main__.: 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Temporary and binary files 2 | *~ 3 | *.py[cod] 4 | *.so 5 | *.cfg 6 | !setup.cfg 7 | *.orig 8 | *.log 9 | *.pot 10 | __pycache__/* 11 | .cache/* 12 | .*.swp 13 | */.ipynb_checkpoints/* 14 | 15 | # Project files 16 | .ropeproject 17 | .project 18 | .pydevproject 19 | .settings 20 | .idea 21 | 22 | # Package files 23 | *.egg 24 | *.eggs/ 25 | .installed.cfg 26 | *.egg-info 27 | 28 | # Unittest and coverage 29 | htmlcov/* 30 | .coverage 31 | .tox 32 | junit.xml 33 | coverage.xml 34 | 35 | # Build and docs folder/files 36 | build/* 37 | dist/* 38 | sdist/* 39 | docs/api/* 40 | docs/_build/* 41 | cover/* 42 | MANIFEST 43 | 44 | # Extras 45 | .pytest_cache/ 46 | _vault 47 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributors 3 | ============ 4 | 5 | * Bob Apthorpe 6 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Changelog 3 | ========= 4 | 5 | Version 0.0.4 6 | ============= 7 | 8 | Added topology diagram generation to jeppson_ch6b and jeppson_ch7. Added output 9 | control options to all applications. 10 | 11 | Version 0.0.3 12 | ============= 13 | 14 | Extended test suite to automate integral tests. 15 | 16 | Version 0.0.2 17 | ============= 18 | 19 | Full implementation of all 6 Jeppson applications. 20 | 21 | Version 0.0.1 22 | ============= 23 | 24 | Pre-release build. 25 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Bob Apthorpe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | jeppson-python 3 | ============== 4 | 5 | 6 | Pipe network flow analysis toolkit based on the work of Roland W. Jeppson. 7 | 8 | 9 | Description 10 | =========== 11 | 12 | 13 | A library and set of applications replicating the software in *Steady Flow 14 | Analysis of Pipe Networks: An Instructional Manual* (1974). *Reports.* Paper 15 | 300. http://digitalcommons.usu.edu/water_rep/300 and 16 | *Analysis of Flow in Pipe Networks* (1976). Ann Arbor Science Publishers, Inc. 17 | http://www.worldcat.org/title/analysis-of-flow-in-pipe-networks/oclc/927534147 18 | by Roland W. Jeppson. 19 | 20 | Six command line applications are included, providing the functionality of the 21 | original Fortran applications described in the Jeppson texts: 22 | 23 | * ``jeppson_ch2`` - Frictional head loss calculator 24 | * ``jeppson_ch4`` - Incompressible flow calculator 25 | * ``jeppson_ch5`` - Linear method pipe network flow solver 26 | * ``jeppson_ch6a`` - Newton-Raphson solver which determines junction pressures 27 | * ``jeppson_ch6b`` - Newton-Raphson solver which generates corrective loop flows 28 | * ``jeppson_ch7`` - Hardy Cross method pipe network flow solver 29 | 30 | Each program takes the same input file structure as its Fortran equivalent and 31 | generates the same results, though the output format may differ substantially 32 | from the original Fortran application. 33 | 34 | The original Fortran applications were recovered and modernized in a separate 35 | project, located at https://bitbucket.org/apthorpe/jeppson_pipeflow A brief 36 | description of the recovery process and a whitepaper summarizing the insights 37 | gained from the recovery and modernization work can be found at 38 | https://www.linkedin.com/pulse/case-study-revitalizing-legacy-engineering-jeppson-pipe-bob-apthorpe/ 39 | 40 | Consider this project to be thematically related, demonstrating the 41 | implementation of these programs in Python. 42 | 43 | 44 | Disclaimer 45 | ========== 46 | 47 | 48 | This software is provided as an educational resource and no warranty is made to 49 | its accuracy or suitability for engineering use, especially in any use 50 | involving protection of human life and environmental quality. *Caveat 51 | utilitor!* 52 | 53 | 54 | Project Goals 55 | ============= 56 | 57 | The primary goals of this project were to write Python equivalents to the six 58 | command-line interface (CLI) applications described in *Steady Flow Analysis of 59 | Pipe Networks: An Instructional Manual* (1974) and *Analysis of Flow in Pipe 60 | Networks* (1976); see above. The programs should be able to process the input 61 | files used by the original Fortran applicaitons and should produce (at minimum) 62 | the same output (content, precision, accuracy). The Python applications should 63 | be written in standard Python idiom (i.e. should not be a transliteration of 64 | the original Fortran into Python), and should demonstrate the use of diagnostic 65 | logging, standard CLI argument and option processing, error- and exception 66 | handling, and user-centric error messages. If possible, pipe network topology 67 | and pipe flows should be presented visually using GraphViz - 68 | https://www.graphviz.org/ 69 | 70 | The Python applications are not required to follow the implementation methods 71 | used in the original Fortran; it is expected that full advantage will be taken 72 | of Python's data structures and coding constructs as well as standard and 73 | third-party libraries. 74 | 75 | Similarly, the output produced by the Python applications may vary 76 | substantially from the original Fortran applications provided the same content 77 | may be found in the output of the new applications. 78 | 79 | A major goal is to demonstrate the use of the Pint unit conversion and physical 80 | quantity library. Since poor unit conversion and specification have led to a 81 | number of dramatic and expensive failures of mission- and safety-critical 82 | software, the project serves to demonstrate the use and limitations of 83 | unit-aware physical computing. 84 | 85 | Finally, a goal in rewriting the network flow solvers was to iteratively refine 86 | and 'standardize' applications to find common elements, structures, or methods 87 | which could be extracted into resuable components. It was not clear at the 88 | outset if a flow solver object model would suggest itself in the course of 89 | writing the Python applications so this project was partly intended to find 90 | code duplicated between applications which could be refactored into independent 91 | objects or libraries. Rather than proposing an object model early in the 92 | project which may or may not serve the application, the applications were 93 | intentionally developed in a 'naive' manner, hoping similarities between them 94 | would suggest evolutionary refactoring and componentization. This allowed focus 95 | to be put primarily on generating correct results, secondarily on 96 | object-oriented design. This focus allowed for rapid development and provided 97 | test cases to detect any errors which appeared during refactoring. 98 | 99 | 100 | Design Information 101 | ================== 102 | 103 | 104 | The underlying theory of operation and input file format description for each 105 | of the applications can be found in the appropriate chapter of the Jeppson 106 | documents, for example http://digitalcommons.usu.edu/water_rep/300 107 | 108 | All command-line applications support the ``-v`` and ``-vv`` flags to increase 109 | the verbosity of diagnostic information shown. This is implemented via Python's 110 | standard logging module. Logging is extended to the underlying libraries 111 | (jeppson.input). 112 | 113 | All programs make use of the InputLine class (in jeppson.input) for tokenizing 114 | input lines and differentiating between blank, comment, and input data lines. 115 | The original Fortran applications could not process blank lines or '#'-prefixed 116 | comment lines; the Python versions are substantially more robust in input 117 | processing and validation. 118 | 119 | Rather than implementing the Darcy-Weisbach friction factor correlation given 120 | in the original Fortran applications, the jeppson_ch2, jeppson_ch4, 121 | jeppson_ch5, and jeppson_ch7 programs all make use of the friction_factor() 122 | from the fluids.friction library. While this makes each program more of a 123 | 'black box', hiding the complexity of friction factor calculation in the call 124 | to an external library, friction factors are calculated from empirical 125 | correlations which themselves are a different sort of black box. It's not clear 126 | anything substantial is lost in using an external library versus independently 127 | coding the friction factor correlation. The ``fluids`` library contains a large 128 | number of correlations which had been developed after the Jeppson texts had 129 | been published. In practice, the friction_factor() routine produces results 130 | very similar to the original Fortran implementation as should be expected. 131 | 132 | The Pint physical unit library was used in each program to ensure dimensional 133 | consistency and allow for simplified unit conversions. These applications show 134 | the distinction between *physical computing* and *numerical computing*. 135 | Whenever possible, variables representing physical quantities are stored as 136 | Pint Quantity objects and physical equations are performed using Quantity 137 | objects as well. This removes the need to mannually apply conversion factors in 138 | calculation or display. These programs serve as technical demonstrators for 139 | using the Pint library for physical computing and analysis. 140 | 141 | The Python applications make extensive changes to the data models used in the 142 | original Fortan applications. The most advanced data structure used by the 143 | Fortran programs is the array; in the Python versions, state data is stored in 144 | a complex data structure composed of lists, dicts, and sets. The complex data 145 | storage allow for more direct, more obvious access to data which (ideally) 146 | makes the underlying theory of each program much clearer. Additionally, since 147 | Python data structures are flexible, this should result in reduced memory use. 148 | 149 | An object-oriented approach toward the full application was not taken since 150 | there was little chance of code reuse. A data model was defined specifically 151 | for each of the network flow solver programs (jeppson_ch5, jeppson_ch6a, 152 | jeppson_ch6b, and jeppson_ch7). The data models have similar sections and 153 | overal structure, but they are not interchangeable. Example data models for 154 | each network flow solver can be found in the ``userdoc`` directory. 155 | 156 | In the network flow solver programs, the pygraphviz library was used to 157 | generate flow topology diagrams for displaying results and validating input. At 158 | present the diagram layout and quantitative elements need tuning to improve 159 | presentation, however diagrams are complete and accurate with respect to 160 | topology, pressure and flow display, inflows, outflows, and flow direction. 161 | 162 | Unit testing is applied principally to object-oriented components, mainly 163 | the classes in jeppson.pipe and jeppson.input. Integral testing was used to 164 | manually compare the Python applications with the original Fortran 165 | applications. Had the applications been designed as objects, unit testing would 166 | have been a more reasonable choice since it can easily be automated. The choice 167 | of application architecture makes the individual programs rather difficult to 168 | test; a more modular or object-oriented design would simplify testing but would 169 | also complicate implementation. In this case, the decision was to go with a 170 | simpler application architecture and trade ease of implementation for ease of 171 | testing. This is reasonable in a prototype or demonstrator application such as 172 | this; it may not be appropriate for other application roles and use cases. 173 | 174 | 175 | Possible Future Work 176 | ==================== 177 | 178 | 179 | Data serialization 180 | ------------------ 181 | 182 | Serializing the case_dom data model used in the network flow solvers in a 183 | format such as YAML or JSON would simplify post-processing the code results. 184 | 185 | Structured input 186 | ---------------- 187 | 188 | Converting from free-form text input to a serialized input format such as JSON 189 | or YAML would allow the code to be driven with a different user interface (e.g. 190 | web, desktop GUI) 191 | 192 | The case_dom data structure may be more useful if converted to several 193 | independent Pandas data frames, then joined or queried in order to simplify 194 | data access. This may be useful both for matrix and vector construction while 195 | solving for network flows or for post-processing, analysis, and visualization. 196 | 197 | Improved data visualization 198 | --------------------------- 199 | 200 | The network flow solvers produce a conservative directed graph of volumetric 201 | flow, ideal for representation in a Sankey plot; see 202 | https://www.sciencedirect.com/science/article/pii/S0921344917301167?via%3Dihub 203 | 204 | 205 | Documentation 206 | ============= 207 | 208 | The code has been developed with the intent of using Sphinx as the project 209 | documentation processor; http://www.sphinx-doc.org/en/master/ All files, 210 | classes, and functions should have the appropriate docstring present. Functions 211 | will additionally describe required and optional arguments, return values, and 212 | exceptions raised, if any. 213 | 214 | Documentation of the code theory or input format are available in the original 215 | Jeppson documents and are not repeated here. This is considered reasonable 216 | since this project is part of a larger whole based on Jeppson's original 217 | texts. 218 | 219 | 220 | Testing 221 | ======= 222 | 223 | Testing is discussed near the end of the *Design Information* section. Unit 224 | testing has been used extensively on component classes (InputLine and Pipe 225 | classes). The command-line applications are primarily tested via integral 226 | testing to ensure existing input files may be read and results of the original 227 | and Python applications are comparable and reasonably close (within 5-10%). 228 | Identical numerical results are not expected due to differences in precision 229 | and calculational method. Note the discussion of design trade-offs and ease of 230 | testing in the *Design Information* section. 231 | 232 | The network flow solvers (``jeppson_ch5``, ``jeppson_ch6a``, ``jeppson_ch6b``, 233 | and ``jeppson_ch7``) are all tested via integral tests using the pytest 234 | framework, so it is possible to automate integral testing. It is not as simple 235 | as unit testing (relying on several fixtures) and the fidelity is rather coarse 236 | but it can be done. 237 | 238 | Additionally, flake8 compliance is incorporated in the test suite to enforce a 239 | reasonable level of stylistic quality. 240 | 241 | 242 | Development Note 243 | ================ 244 | 245 | 246 | This project has been set up using PyScaffold 3.0.3 via 247 | 248 | putup -p jeppson -d "Pipe network flow analysis toolkit" -l mit jeppson-python 249 | 250 | For details and usage information on PyScaffold see http://pyscaffold.org/. 251 | -------------------------------------------------------------------------------- /_BUILDNOTE.txt: -------------------------------------------------------------------------------- 1 | Created with pyscaffold via: 2 | putup -p jeppson -d "Pipe network flow analysis toolkit" -l mit jeppson-python 3 | 4 | Versions set via `git tag`: 5 | git tag v0.0.3 6 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | AUTODOCDIR = api 10 | AUTODOCBUILD = sphinx-apidoc 11 | PROJECT = jeppson-python 12 | MODULEDIR = ../src/jeppson 13 | 14 | # User-friendly check for sphinx-build 15 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $?), 1) 16 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 17 | endif 18 | 19 | # Internal variables. 20 | PAPEROPT_a4 = -D latex_paper_size=a4 21 | PAPEROPT_letter = -D latex_paper_size=letter 22 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 23 | # the i18n builder cannot share the environment and doctrees with the others 24 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 25 | 26 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext doc-requirements 27 | 28 | help: 29 | @echo "Please use \`make ' where is one of" 30 | @echo " html to make standalone HTML files" 31 | @echo " dirhtml to make HTML files named index.html in directories" 32 | @echo " singlehtml to make a single large HTML file" 33 | @echo " pickle to make pickle files" 34 | @echo " json to make JSON files" 35 | @echo " htmlhelp to make HTML files and a HTML help project" 36 | @echo " qthelp to make HTML files and a qthelp project" 37 | @echo " devhelp to make HTML files and a Devhelp project" 38 | @echo " epub to make an epub" 39 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 40 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 41 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 42 | @echo " text to make text files" 43 | @echo " man to make manual pages" 44 | @echo " texinfo to make Texinfo files" 45 | @echo " info to make Texinfo files and run them through makeinfo" 46 | @echo " gettext to make PO message catalogs" 47 | @echo " changes to make an overview of all changed/added/deprecated items" 48 | @echo " xml to make Docutils-native XML files" 49 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 50 | @echo " linkcheck to check all external links for integrity" 51 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 52 | 53 | clean: 54 | rm -rf $(BUILDDIR)/* $(AUTODOCDIR) 55 | 56 | $(AUTODOCDIR): $(MODULEDIR) 57 | mkdir -p $@ 58 | $(AUTODOCBUILD) -f -o $@ $^ 59 | 60 | doc-requirements: $(AUTODOCDIR) 61 | 62 | html: doc-requirements 63 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 64 | @echo 65 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 66 | 67 | dirhtml: doc-requirements 68 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 69 | @echo 70 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 71 | 72 | singlehtml: doc-requirements 73 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 74 | @echo 75 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 76 | 77 | pickle: doc-requirements 78 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 79 | @echo 80 | @echo "Build finished; now you can process the pickle files." 81 | 82 | json: doc-requirements 83 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 84 | @echo 85 | @echo "Build finished; now you can process the JSON files." 86 | 87 | htmlhelp: doc-requirements 88 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 89 | @echo 90 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 91 | ".hhp project file in $(BUILDDIR)/htmlhelp." 92 | 93 | qthelp: doc-requirements 94 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 95 | @echo 96 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 97 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 98 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/$(PROJECT).qhcp" 99 | @echo "To view the help file:" 100 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/$(PROJECT).qhc" 101 | 102 | devhelp: doc-requirements 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $HOME/.local/share/devhelp/$(PROJECT)" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $HOME/.local/share/devhelp/$(PROJEC)" 109 | @echo "# devhelp" 110 | 111 | epub: doc-requirements 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | patch-latex: 117 | find _build/latex -iname "*.tex" | xargs -- \ 118 | sed -i'' 's~includegraphics{~includegraphics\[keepaspectratio,max size={\\textwidth}{\\textheight}\]{~g' 119 | 120 | latex: doc-requirements 121 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 122 | $(MAKE) patch-latex 123 | @echo 124 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 125 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 126 | "(use \`make latexpdf' here to do that automatically)." 127 | 128 | latexpdf: doc-requirements 129 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 130 | $(MAKE) patch-latex 131 | @echo "Running LaTeX files through pdflatex..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | latexpdfja: doc-requirements 136 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 137 | @echo "Running LaTeX files through platex and dvipdfmx..." 138 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 139 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 140 | 141 | text: doc-requirements 142 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 143 | @echo 144 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 145 | 146 | man: doc-requirements 147 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 148 | @echo 149 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 150 | 151 | texinfo: doc-requirements 152 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 153 | @echo 154 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 155 | @echo "Run \`make' in that directory to run these through makeinfo" \ 156 | "(use \`make info' here to do that automatically)." 157 | 158 | info: doc-requirements 159 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 160 | @echo "Running Texinfo files through makeinfo..." 161 | make -C $(BUILDDIR)/texinfo info 162 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 163 | 164 | gettext: doc-requirements 165 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 166 | @echo 167 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 168 | 169 | changes: doc-requirements 170 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 171 | @echo 172 | @echo "The overview file is in $(BUILDDIR)/changes." 173 | 174 | linkcheck: doc-requirements 175 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 176 | @echo 177 | @echo "Link check complete; look for any errors in the above output " \ 178 | "or in $(BUILDDIR)/linkcheck/output.txt." 179 | 180 | doctest: doc-requirements 181 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 182 | @echo "Testing of doctests in the sources finished, look at the " \ 183 | "results in $(BUILDDIR)/doctest/output.txt." 184 | 185 | xml: doc-requirements 186 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 187 | @echo 188 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 189 | 190 | pseudoxml: doc-requirements 191 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 192 | @echo 193 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 194 | -------------------------------------------------------------------------------- /docs/_static/.gitignore: -------------------------------------------------------------------------------- 1 | # Empty directory 2 | -------------------------------------------------------------------------------- /docs/authors.rst: -------------------------------------------------------------------------------- 1 | .. _authors: 2 | .. include:: ../AUTHORS.rst 3 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | .. _changes: 2 | .. include:: ../CHANGELOG.rst 3 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # This file is execfile()d with the current directory set to its containing dir. 4 | # 5 | # Note that not all possible configuration values are present in this 6 | # autogenerated file. 7 | # 8 | # All configuration values have a default; values that are commented out 9 | # serve to show the default. 10 | 11 | import os 12 | import sys 13 | import inspect 14 | import shutil 15 | 16 | __location__ = os.path.join(os.getcwd(), os.path.dirname( 17 | inspect.getfile(inspect.currentframe()))) 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | sys.path.insert(0, os.path.join(__location__, '../src')) 23 | 24 | # -- Run sphinx-apidoc ------------------------------------------------------ 25 | # This hack is necessary since RTD does not issue `sphinx-apidoc` before running 26 | # `sphinx-build -b html . _build/html`. See Issue: 27 | # https://github.com/rtfd/readthedocs.org/issues/1139 28 | # DON'T FORGET: Check the box "Install your project inside a virtualenv using 29 | # setup.py install" in the RTD Advanced Settings. 30 | # Additionally it helps us to avoid running apidoc manually 31 | 32 | try: # for Sphinx >= 1.7 33 | from sphinx.ext import apidoc 34 | except ImportError: 35 | from sphinx import apidoc 36 | 37 | output_dir = os.path.join(__location__, "api") 38 | module_dir = os.path.join(__location__, "../src/jeppson") 39 | try: 40 | shutil.rmtree(output_dir) 41 | except FileNotFoundError: 42 | pass 43 | 44 | try: 45 | import sphinx 46 | from distutils.version import LooseVersion 47 | 48 | cmd_line_template = "sphinx-apidoc -f -o {outputdir} {moduledir}" 49 | cmd_line = cmd_line_template.format(outputdir=output_dir, moduledir=module_dir) 50 | 51 | args = cmd_line.split(" ") 52 | if LooseVersion(sphinx.__version__) >= LooseVersion('1.7'): 53 | args = args[1:] 54 | 55 | apidoc.main(args) 56 | except Exception as e: 57 | print("Running `sphinx-apidoc` failed!\n{}".format(e)) 58 | 59 | # -- General configuration ----------------------------------------------------- 60 | 61 | # If your documentation needs a minimal Sphinx version, state it here. 62 | # needs_sphinx = '1.0' 63 | 64 | # Add any Sphinx extension module names here, as strings. They can be extensions 65 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 66 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 67 | 'sphinx.ext.autosummary', 'sphinx.ext.viewcode', 'sphinx.ext.coverage', 68 | 'sphinx.ext.doctest', 'sphinx.ext.ifconfig', 'sphinx.ext.mathjax', 69 | 'sphinx.ext.napoleon'] 70 | 71 | # Add any paths that contain templates here, relative to this directory. 72 | templates_path = ['_templates'] 73 | 74 | # The suffix of source filenames. 75 | source_suffix = '.rst' 76 | 77 | # The encoding of source files. 78 | # source_encoding = 'utf-8-sig' 79 | 80 | # The master toctree document. 81 | master_doc = 'index' 82 | 83 | # General information about the project. 84 | project = u'jeppson-python' 85 | copyright = u'2018, Bob Apthorpe' 86 | 87 | # The version info for the project you're documenting, acts as replacement for 88 | # |version| and |release|, also used in various other places throughout the 89 | # built documents. 90 | # 91 | # The short X.Y version. 92 | version = '' # Is set by calling `setup.py docs` 93 | # The full version, including alpha/beta/rc tags. 94 | release = '' # Is set by calling `setup.py docs` 95 | 96 | # The language for content autogenerated by Sphinx. Refer to documentation 97 | # for a list of supported languages. 98 | # language = None 99 | 100 | # There are two options for replacing |today|: either, you set today to some 101 | # non-false value, then it is used: 102 | # today = '' 103 | # Else, today_fmt is used as the format for a strftime call. 104 | # today_fmt = '%B %d, %Y' 105 | 106 | # List of patterns, relative to source directory, that match files and 107 | # directories to ignore when looking for source files. 108 | exclude_patterns = ['_build'] 109 | 110 | # The reST default role (used for this markup: `text`) to use for all documents. 111 | # default_role = None 112 | 113 | # If true, '()' will be appended to :func: etc. cross-reference text. 114 | # add_function_parentheses = True 115 | 116 | # If true, the current module name will be prepended to all description 117 | # unit titles (such as .. function::). 118 | # add_module_names = True 119 | 120 | # If true, sectionauthor and moduleauthor directives will be shown in the 121 | # output. They are ignored by default. 122 | # show_authors = False 123 | 124 | # The name of the Pygments (syntax highlighting) style to use. 125 | pygments_style = 'sphinx' 126 | 127 | # A list of ignored prefixes for module index sorting. 128 | # modindex_common_prefix = [] 129 | 130 | # If true, keep warnings as "system message" paragraphs in the built documents. 131 | # keep_warnings = False 132 | 133 | 134 | # -- Options for HTML output --------------------------------------------------- 135 | 136 | # The theme to use for HTML and HTML Help pages. See the documentation for 137 | # a list of builtin themes. 138 | html_theme = 'alabaster' 139 | 140 | # Theme options are theme-specific and customize the look and feel of a theme 141 | # further. For a list of options available for each theme, see the 142 | # documentation. 143 | # html_theme_options = {} 144 | 145 | # Add any paths that contain custom themes here, relative to this directory. 146 | # html_theme_path = [] 147 | 148 | # The name for this set of Sphinx documents. If None, it defaults to 149 | # " v documentation". 150 | try: 151 | from jeppson import __version__ as version 152 | except ImportError: 153 | pass 154 | else: 155 | release = version 156 | 157 | # A shorter title for the navigation bar. Default is the same as html_title. 158 | # html_short_title = None 159 | 160 | # The name of an image file (relative to this directory) to place at the top 161 | # of the sidebar. 162 | # html_logo = "" 163 | 164 | # The name of an image file (within the static path) to use as favicon of the 165 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 166 | # pixels large. 167 | # html_favicon = None 168 | 169 | # Add any paths that contain custom static files (such as style sheets) here, 170 | # relative to this directory. They are copied after the builtin static files, 171 | # so a file named "default.css" will overwrite the builtin "default.css". 172 | html_static_path = ['_static'] 173 | 174 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 175 | # using the given strftime format. 176 | # html_last_updated_fmt = '%b %d, %Y' 177 | 178 | # If true, SmartyPants will be used to convert quotes and dashes to 179 | # typographically correct entities. 180 | # html_use_smartypants = True 181 | 182 | # Custom sidebar templates, maps document names to template names. 183 | # html_sidebars = {} 184 | 185 | # Additional templates that should be rendered to pages, maps page names to 186 | # template names. 187 | # html_additional_pages = {} 188 | 189 | # If false, no module index is generated. 190 | # html_domain_indices = True 191 | 192 | # If false, no index is generated. 193 | # html_use_index = True 194 | 195 | # If true, the index is split into individual pages for each letter. 196 | # html_split_index = False 197 | 198 | # If true, links to the reST sources are added to the pages. 199 | # html_show_sourcelink = True 200 | 201 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 202 | # html_show_sphinx = True 203 | 204 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 205 | # html_show_copyright = True 206 | 207 | # If true, an OpenSearch description file will be output, and all pages will 208 | # contain a tag referring to it. The value of this option must be the 209 | # base URL from which the finished HTML is served. 210 | # html_use_opensearch = '' 211 | 212 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 213 | # html_file_suffix = None 214 | 215 | # Output file base name for HTML help builder. 216 | htmlhelp_basename = 'jeppson-doc' 217 | 218 | 219 | # -- Options for LaTeX output -------------------------------------------------- 220 | 221 | latex_elements = { 222 | # The paper size ('letterpaper' or 'a4paper'). 223 | # 'papersize': 'letterpaper', 224 | 225 | # The font size ('10pt', '11pt' or '12pt'). 226 | # 'pointsize': '10pt', 227 | 228 | # Additional stuff for the LaTeX preamble. 229 | # 'preamble': '', 230 | } 231 | 232 | # Grouping the document tree into LaTeX files. List of tuples 233 | # (source start file, target name, title, author, documentclass [howto/manual]). 234 | latex_documents = [ 235 | ('index', 'user_guide.tex', u'jeppson-python Documentation', 236 | u'Bob Apthorpe', 'manual'), 237 | ] 238 | 239 | # The name of an image file (relative to this directory) to place at the top of 240 | # the title page. 241 | # latex_logo = "" 242 | 243 | # For "manual" documents, if this is true, then toplevel headings are parts, 244 | # not chapters. 245 | # latex_use_parts = False 246 | 247 | # If true, show page references after internal links. 248 | # latex_show_pagerefs = False 249 | 250 | # If true, show URL addresses after external links. 251 | # latex_show_urls = False 252 | 253 | # Documents to append as an appendix to all manuals. 254 | # latex_appendices = [] 255 | 256 | # If false, no module index is generated. 257 | # latex_domain_indices = True 258 | 259 | # -- External mapping ------------------------------------------------------------ 260 | python_version = '.'.join(map(str, sys.version_info[0:2])) 261 | intersphinx_mapping = { 262 | 'sphinx': ('http://www.sphinx-doc.org/en/stable', None), 263 | 'python': ('https://docs.python.org/' + python_version, None), 264 | 'matplotlib': ('https://matplotlib.org', None), 265 | 'numpy': ('https://docs.scipy.org/doc/numpy', None), 266 | 'sklearn': ('http://scikit-learn.org/stable', None), 267 | 'pandas': ('http://pandas.pydata.org/pandas-docs/stable', None), 268 | 'scipy': ('https://docs.scipy.org/doc/scipy/reference', None), 269 | } 270 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | jeppson-python 3 | ============== 4 | 5 | Pipe network flow analysis toolkit based on the work of Roland W. Jeppson. 6 | 7 | ``python-jeppson`` is a library and set of applications replicating the 8 | software in *Steady Flow Analysis of Pipe Networks: An Instructional Manual* 9 | (1974). *Reports.* Paper 300. http://digitalcommons.usu.edu/water_rep/300 and 10 | *Analysis of Flow in Pipe Networks* (1976). Ann Arbor Science Publishers, Inc. 11 | http://www.worldcat.org/title/analysis-of-flow-in-pipe-networks/oclc/927534147 12 | by Roland W. Jeppson. 13 | 14 | Six command line applications are included, providing the functionality of the 15 | original Fortran applications described in the Jeppson texts: 16 | 17 | * ``jeppson_ch2`` - Frictional head loss calculator 18 | * ``jeppson_ch4`` - Incompressible flow calculator 19 | * ``jeppson_ch5`` - Linear method pipe network flow solver 20 | * ``jeppson_ch6a`` - Newton-Raphson solver which determines junction pressures 21 | * ``jeppson_ch6b`` - Newton-Raphson solver which generates corrective loop flows 22 | * ``jeppson_ch7`` - Hardy Cross method pipe network flow solver 23 | 24 | Each program takes the same input file structure as its Fortran equivalent and 25 | generates the same results, though the output format may differ substantially 26 | from the original Fortran application. 27 | 28 | The original Fortran applications were recovered and modernized in a separate 29 | project, located at https://bitbucket.org/apthorpe/jeppson_pipeflow A brief 30 | description of the recovery process and a whitepaper summarizing the insights 31 | gained from the recovery and modernization work can be found at 32 | https://www.linkedin.com/pulse/case-study-revitalizing-legacy-engineering-jeppson-pipe-bob-apthorpe/ 33 | 34 | Consider this project to be thematically related, demonstrating the 35 | implementation of these programs in Python. 36 | 37 | Contents 38 | ======== 39 | 40 | .. toctree:: 41 | :maxdepth: 2 42 | 43 | License 44 | Authors 45 | Changelog 46 | Module Reference 47 | 48 | 49 | Indices and tables 50 | ================== 51 | 52 | * :ref:`genindex` 53 | * :ref:`modindex` 54 | * :ref:`search` 55 | -------------------------------------------------------------------------------- /docs/license.rst: -------------------------------------------------------------------------------- 1 | .. _license: 2 | 3 | ======= 4 | License 5 | ======= 6 | 7 | .. literalinclude:: ../LICENSE.txt 8 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Add your pinned requirements so that they can be easily installed with: 2 | # pip install -r requirements.txt 3 | # Remember to also add them in setup.cfg but unpinned. 4 | # Example: 5 | # numpy==1.13.3 6 | # scipy==1.0 7 | cycler>=0.10.0 8 | fluids>=0.1.71 9 | # iapws>=1.0.1 10 | kiwisolver>=1.0.1 11 | matplotlib>=2.2.2 12 | nose>=1.3.7 13 | numpy>=1.14.2 14 | pandas>=0.22.0 15 | pyparsing>=2.2.0 16 | PyScaffold>=3.0.3 17 | python-dateutil>=2.7.2 18 | pytz>=2018.4 19 | scipy>=1.0.1 20 | setuptools>=39.0.1 21 | six>=1.11.0 22 | # thermo>=0.1.39 23 | tabulate>=0.8.2 24 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # This file is used to configure your project. 2 | # Read more about the various options under: 3 | # http://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files 4 | 5 | [metadata] 6 | name = jeppson-python 7 | description = Pipe network flow analysis toolkit 8 | author = Bob Apthorpe 9 | author-email = bob.apthorpe@gmail.com 10 | license = mit 11 | url = https://github.com/apthorpe/jeppson-python 12 | long-description = file: README.rst 13 | # Change if running only on Windows, Mac or Linux (comma-separated) 14 | platforms = any 15 | # Add here all kinds of additional classifiers as defined under 16 | # https://pypi.python.org/pypi?%3Aaction=list_classifiers 17 | classifiers = 18 | Development Status :: 4 - Beta 19 | Programming Language :: Python 20 | 21 | [options] 22 | zip_safe = False 23 | packages = find: 24 | include_package_data = True 25 | package_dir = 26 | =src 27 | # Add here dependencies of your project (semicolon-separated), e.g. 28 | # install_requires = numpy; scipy 29 | install_requires = fluids; matplotlib; numpy; scipy; tabulate; thermo 30 | # Add here test requirements (semicolon-separated) 31 | tests_require = pytest; pytest-cov; pytest-flakes 32 | 33 | [options.packages.find] 34 | where = src 35 | exclude = 36 | tests 37 | 38 | [options.extras_require] 39 | # Add here additional requirements for extra features, to install with: 40 | # `pip install jeppson-python[PDF]` like: 41 | # PDF = ReportLab; RXP 42 | 43 | [test] 44 | # py.test options when running `python setup.py test` 45 | addopts = tests 46 | 47 | [tool:pytest] 48 | # Options for py.test: 49 | # Specify command line options as you would do when invoking py.test directly. 50 | # e.g. --cov-report html (or xml) for html/xml output or --junitxml junit.xml 51 | # in order to write a coverage file that can be read by Jenkins. 52 | addopts = 53 | --cov jeppson --cov-report term-missing 54 | --verbose 55 | norecursedirs = 56 | dist 57 | build 58 | .tox 59 | 60 | [aliases] 61 | release = sdist bdist_wheel upload 62 | 63 | [bdist_wheel] 64 | # Use this option if your package is pure-python 65 | universal = 1 66 | 67 | [build_sphinx] 68 | source_dir = docs 69 | build_dir = docs/_build 70 | 71 | [devpi:upload] 72 | # Options for the devpi: PyPI server and packaging tool 73 | # VCS export must be deactivated since we are using setuptools-scm 74 | no-vcs = 1 75 | formats = bdist_wheel 76 | 77 | [flake8] 78 | # Some sane defaults for the code style checker flake8 79 | exclude = 80 | .tox 81 | build 82 | dist 83 | .eggs 84 | docs/conf.py 85 | 86 | [pyscaffold] 87 | # PyScaffold's parameters when the project was created. 88 | # This will be used when updating. Do not change! 89 | version = 3.0.3 90 | package = jeppson 91 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Setup file for jeppson. 5 | 6 | This file was generated with PyScaffold 3.0.3. 7 | PyScaffold helps you to put up the scaffold of your new Python project. 8 | Learn more under: http://pyscaffold.org/ 9 | """ 10 | 11 | import sys 12 | from setuptools import setup 13 | 14 | # Add here console scripts and other entry points in ini-style format 15 | entry_points = """ 16 | [console_scripts] 17 | # script_name = jeppson.module:function 18 | # For example: 19 | # fibonacci = jeppson.skeleton:run 20 | jeppson_ch2 = jeppson.ch2:run 21 | jeppson_ch4 = jeppson.ch4:run 22 | jeppson_ch5 = jeppson.ch5:run 23 | jeppson_ch6a = jeppson.ch6a:run 24 | jeppson_ch6b = jeppson.ch6b:run 25 | jeppson_ch7 = jeppson.ch7:run 26 | """ 27 | 28 | 29 | def setup_package(): 30 | needs_sphinx = {'build_sphinx', 'upload_docs'}.intersection(sys.argv) 31 | sphinx = ['sphinx'] if needs_sphinx else [] 32 | setup(setup_requires=['pyscaffold>=3.0a0,<3.1a0'] + sphinx, 33 | entry_points=entry_points, 34 | use_pyscaffold=True) 35 | 36 | 37 | if __name__ == "__main__": 38 | setup_package() 39 | -------------------------------------------------------------------------------- /src/jeppson/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from pkg_resources import get_distribution, DistributionNotFound 3 | from pint import UnitRegistry 4 | 5 | try: 6 | # Change here if project is renamed and does not equal the package name 7 | dist_name = 'jeppson-python' 8 | __version__ = get_distribution(dist_name).version 9 | except DistributionNotFound: 10 | __version__ = 'unknown' 11 | 12 | import logging 13 | # Set default logging handler to avoid "No handler found" warnings. 14 | try: # Python 2.7+ 15 | from logging import NullHandler 16 | except ImportError: 17 | class NullHandler(logging.Handler): 18 | def emit(self, record): 19 | pass 20 | 21 | _logger = logging.getLogger(__name__) 22 | _logger.addHandler(NullHandler()) 23 | 24 | # Pint unit registry; 'mks' used as default (base) unit system. 25 | # Share with all functions in an application via 'from . import ureg, Q_ 26 | ureg = UnitRegistry(system='mks') 27 | # Alias for Quantity constructor. 28 | Q_ = ureg.Quantity 29 | -------------------------------------------------------------------------------- /src/jeppson/ch2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | This reimplements the pipe flow analysis program given in chapter 2 of *Steady 5 | Flow Analysis of Pipe Networks: An Instructional Manual* (1974). *Reports.* 6 | Paper 300. http://digitalcommons.usu.edu/water_rep/300 and *Analysis of Flow 7 | in Pipe Networks* (1976). Ann Arbor Science Publishers, Inc. 8 | http://www.worldcat.org/title/analysis-of-flow-in-pipe-networks/oclc/927534147 9 | by Roland W. Jeppson. 10 | 11 | Then run `python setup.py install` which will install the command `jeppson_ch2` 12 | inside your current environment. 13 | """ 14 | from __future__ import division, print_function, absolute_import 15 | 16 | import argparse 17 | from collections import namedtuple, OrderedDict 18 | import logging 19 | from math import isnan 20 | import sys 21 | 22 | import scipy.constants as sc 23 | from fluids.friction import friction_factor 24 | 25 | from . import _logger, ureg, Q_ 26 | from jeppson.input import get_data_line 27 | from jeppson import __version__ 28 | 29 | __author__ = "Bob Apthorpe" 30 | __copyright__ = "Bob Apthorpe" 31 | __license__ = "mit" 32 | 33 | #: Nomenclature is a named tuple which stores a short variable name ('tag'), a 34 | #: longger string description, and Pint unit specifiers in the 'mks_units' and 35 | #: 'us_units' fields. 36 | Nomenclature = namedtuple('Nomenclature', 37 | ['tag', 'description', 'mks_units', 'us_units']) 38 | 39 | #: idataprop is an ordered dictionary of Nomenclature named tuples which serve 40 | #: as a directory of input data fields, conversion factors, and documentation. 41 | idataprop = OrderedDict([ 42 | ('idiameter', Nomenclature('idiameter', 43 | 'pipe inner diameter', 44 | 'm', 45 | 'ft')), 46 | ('vol_flow', Nomenclature('vol_flow', 47 | 'volumetric flowrate', 48 | 'm**3 / s', 49 | 'ft**3 / s')), 50 | ('lpipe', Nomenclature('lpipe', 51 | 'pipe length', 52 | 'm', 53 | 'ft')), 54 | ('kin_visc', Nomenclature('kin_visc', 55 | 'kinematic viscosity', 56 | 'm**2 / s', 57 | 'ft**2 / s')), 58 | ('froughness', Nomenclature('froughness', 59 | 'absolute pipe roughness', 60 | 'm', 61 | 'ft')), 62 | ('grav', Nomenclature('grav', 63 | 'gravitational acceleration', 64 | 'm / s**2', 65 | 'ft / s**2')), 66 | ('flow_area', Nomenclature('flow_area', 67 | 'pipe flow area', 68 | 'm**2', 69 | 'ft**2')), 70 | ('vflow', Nomenclature('vflow', 71 | 'flow velocity', 72 | 'm / s', 73 | 'ft / s')), 74 | ('Re', Nomenclature('Re', 75 | 'reynolds number', 76 | ureg.parse_expression('dimensionless'), 77 | ureg.parse_expression('dimensionless'))), 78 | ('eroughness', Nomenclature('eroughness', 79 | 'relative pipe roughness', 80 | ureg.parse_expression('dimensionless'), 81 | ureg.parse_expression('dimensionless'))), 82 | ('head_loss', Nomenclature('head_loss', 83 | 'head loss', 84 | 'm', 85 | 'ft')), 86 | ('friction', Nomenclature('friction', 87 | 'darcy-weisback friction factor', 88 | ureg.parse_expression('dimensionless'), 89 | ureg.parse_expression('dimensionless'))) 90 | ]) 91 | 92 | #: inputconv_us is an ordered dictionary of input variable tags and US 93 | #: Traditional units used for assigning units to input quantities. 94 | inputconv_us = OrderedDict([ 95 | ('idiameter', 'ft'), 96 | ('vol_flow', 'ft**3/s'), 97 | ('lpipe', 'ft'), 98 | ('kin_visc', 'ft**2/s'), 99 | ('froughness', 'ft'), 100 | ('grav', 'ft/s**2') 101 | ]) 102 | 103 | #: inputconv_us is an ordered dictionary of input variable tags and SI units 104 | #: used for assigning units to input quantities. 105 | inputconv_mks = OrderedDict([ 106 | ('idiameter', 'm'), 107 | ('vol_flow', 'm**3/s'), 108 | ('lpipe', 'm'), 109 | ('kin_visc', 'm**2/s'), 110 | ('froughness', 'm'), 111 | ('grav', 'm/s**2') 112 | ]) 113 | 114 | #: ikeys is a sorted list of input variable tags 115 | ikeys = inputconv_us.keys() 116 | 117 | 118 | def parse_args(args): 119 | """Parse command line parameters 120 | 121 | Args: 122 | args ([str]): command line parameters as list of strings 123 | 124 | Returns: 125 | :obj:`argparse.Namespace`: command line parameters namespace 126 | """ 127 | parser = argparse.ArgumentParser( 128 | description="Frictional head loss calculator") 129 | parser.add_argument( 130 | '--version', 131 | action='version', 132 | version='jeppson-python {ver}'.format(ver=__version__)) 133 | parser.add_argument( 134 | dest="file", 135 | help="input files (STDIN if not specified)", 136 | type=argparse.FileType('r'), 137 | nargs='*', 138 | default=[sys.stdin], 139 | metavar="FILE") 140 | parser.add_argument( 141 | '-L', 142 | '--legacy', 143 | dest="legacy", 144 | help="show legacy format results", 145 | action='store_true', 146 | default=False) 147 | parser.add_argument( 148 | '-Lx', 149 | '--no-legacy', 150 | dest="legacy", 151 | help="omit legacy format results (true if not specified)", 152 | action='store_false', 153 | default=False) 154 | parser.add_argument( 155 | '-M', 156 | '--modern', 157 | dest="modern", 158 | help="show modern format results (true if not specified)", 159 | action='store_true', 160 | default=True) 161 | parser.add_argument( 162 | '-Mx', 163 | '--no-modern', 164 | dest="modern", 165 | help="omit modern format results", 166 | action='store_false', 167 | default=True) 168 | parser.add_argument( 169 | '-v', 170 | '--verbose', 171 | dest="loglevel", 172 | help="set loglevel to INFO", 173 | action='store_const', 174 | const=logging.INFO) 175 | parser.add_argument( 176 | '-vv', 177 | '--very-verbose', 178 | dest="loglevel", 179 | help="set loglevel to DEBUG", 180 | action='store_const', 181 | const=logging.DEBUG) 182 | return parser.parse_args(args) 183 | 184 | 185 | def setup_logging(loglevel): 186 | """Setup basic logging 187 | 188 | Args: 189 | loglevel (int): minimum loglevel for emitting messages 190 | """ 191 | logformat = "[%(asctime)s] %(levelname)s:%(name)s: %(message)s" 192 | logging.basicConfig(level=loglevel, stream=sys.stdout, 193 | format=logformat, datefmt="%Y-%m-%d %H:%M:%S") 194 | 195 | 196 | def extract_case_input(iline, force_units=None): 197 | """Extract and validate case input from pre-processed data line 198 | 199 | Args: 200 | iline (InputLine): Pre-processed input data line 201 | force_units (str): Unit system of input data. Accepted values are 202 | 'SI' (mks) and 'Traditional' (foot-pound-second). If left 203 | undefined, units will be inferred from gravitational 204 | acceleration. 205 | 206 | Returns: 207 | (dict): Case input data and metadata""" 208 | _logger.debug('Extracting input data for single case') 209 | 210 | # Set default results 211 | results = { 212 | 'status': 'undefined', 213 | 'msg': 'No results generated yet', 214 | 'input': {} 215 | } 216 | 217 | idata = results['input'] 218 | 219 | for ikey in ikeys: 220 | idata[ikey] = float('NaN') 221 | 222 | # Check number of tokens 223 | mintok = len(ikeys) 224 | if iline.ntok < mintok: 225 | results['status'] = 'error' 226 | results['msg'] = 'Too few tokens ({} found, {} expected)' \ 227 | .format(iline.ntok, mintok) 228 | return results 229 | 230 | elif iline.ntok > mintok: 231 | results['status'] = 'warning' 232 | results['msg'] = 'Too many tokens ({} found, {} expected)' \ 233 | .format(iline.ntok, mintok) 234 | else: 235 | results['status'] = 'ok' 236 | results['msg'] = 'Proper token count ({})'.format(mintok) 237 | 238 | tmpdata = {} 239 | # Convert tokens to floats; check for parsing errors 240 | _logger.debug('On read:') 241 | for i, ikey in enumerate(ikeys): 242 | desc = idataprop[ikey].description 243 | try: 244 | tmpdata[ikey] = float(iline.token[i]) 245 | _logger.debug('{0:s} "{1:s}" is {2:0.4E}' 246 | .format(ikey, desc, tmpdata[ikey])) 247 | except ValueError as err: 248 | _logger.error('Numeric parse failure, ' 249 | 'field {0:d}, {1:s} "{2:s}": {3:s}' 250 | .format(i, ikey, desc, str(err))) 251 | results['status'] = 'error' 252 | results['msg'] = 'Cannot parse values from input line, field ' \ 253 | '{0:d}, "{1:s}"'.format(i, ikey) 254 | return results 255 | 256 | if isnan(tmpdata[ikey]): 257 | _logger.error('Numeric parse failure, field {0:d}, {1:s}, "{2:s}"' 258 | .format(i, ikey, desc)) 259 | results['status'] = 'error' 260 | results['msg'] = 'Cannot parse values from input line, field ' \ 261 | '{0:d}, {1:s}, "{2:s}"'.format(i, ikey, desc) 262 | return results 263 | 264 | # Select units via heuristic (within 10% of SI gravitational acceleration) 265 | if abs(tmpdata['grav'] - sc.g) / sc.g < 0.1: 266 | _logger.info("Assuming SI units") 267 | results['units'] = 'SI' 268 | else: 269 | _logger.info("Assuming traditional units (US/English)") 270 | results['units'] = 'Traditional' 271 | 272 | # Attempt to coerce units 273 | if force_units: 274 | if force_units.trim().upper() == 'SI': 275 | _logger.info("Forcing use of SI units") 276 | results['units'] = 'SI' 277 | elif force_units.trim().lower() == 'traditional': 278 | _logger.info("Forcing use of traditional units") 279 | results['units'] = 'Traditional' 280 | else: 281 | msg = 'Cannot force units of measure to "{0:s}"' \ 282 | .format(force_units) 283 | _logger.warning(msg) 284 | if results['status'] == 'ok': 285 | results['status'] = 'warning' 286 | results['msg'] = msg 287 | # pass through; use inferred units 288 | 289 | # Set units (Pint) 290 | _logger.debug('As Pint quantities:') 291 | for ikey in ikeys: 292 | desc = idataprop[ikey].description 293 | if results['units'] == 'SI': 294 | idata[ikey] = Q_(tmpdata[ikey], idataprop[ikey].mks_units) 295 | else: 296 | idata[ikey] = Q_(tmpdata[ikey], idataprop[ikey].us_units) 297 | 298 | _logger.debug(' {0:s} = {1:0.4E~P}' 299 | .format(desc, idata[ikey].to_base_units())) 300 | 301 | _logger.info(results['status'] + ': ' + results['msg']) 302 | 303 | return results 304 | 305 | 306 | def calculate_headloss(vol_flow, flow_area, lpipe, idiameter, eroughness, 307 | kin_visc, grav): 308 | """Calculate Darcy-Weisbach friction factor and head loss 309 | 310 | Calculates head loss (m) and Darcy-Wesibach friction factor and 311 | intermediate quantities flow velocity (m/s), and Reynolds number. These 312 | values are returned in a named tuple with key names ``head_loss``, 313 | ``friction``, ``vflow``, and ``Re`` respectively. 314 | 315 | Args: 316 | vol_flow (float): volumetric flow, in cubic meters per second 317 | flow_area (float): pipe flow area, in square meters 318 | lpipe (float): pipe length, in meters 319 | idiameter (float): pipe inner diameter, in meters 320 | eroughness (float): pipe relative roughness, dimensionless 321 | kin_visc (float): kinematic viscosity, in square meters per second 322 | grav (float): gravitational acceleration, in meters per second squared 323 | 324 | Returns: 325 | (namedtuple): Results and intermediate quantities 326 | 327 | """ 328 | HeadLoss = namedtuple('HeadLoss', ['head_loss', 'friction', 'vflow', 'Re']) 329 | 330 | flow_vel = vol_flow / flow_area 331 | Re = flow_vel * idiameter / kin_visc 332 | friction = friction_factor(Re=Re, eD=eroughness) 333 | head_loss = (friction * lpipe * flow_vel**2) / (2.0 * grav * idiameter) 334 | 335 | hldat = HeadLoss(head_loss, friction, flow_vel, Re) 336 | 337 | return hldat 338 | 339 | 340 | def generate_results(idata): 341 | """Generate head loss and Darcy-Weisbach friction factor from processed 342 | input. Intermediate and final results will be returned with metadata 343 | 344 | Args: 345 | idata (dict): A dict with the following keys with values as Pint 346 | objects: ``vflow`` (volumetric flowrate), ``idiameter`` (pipe inner 347 | diameter), ``lpipe`` (pipe length), ``kin_visc`` (kinematic 348 | viscosity), ``froughness`` (absolute pipe roughness), and ``grav`` 349 | (gravitational acceleration) 350 | 351 | Returns: 352 | (dict): Calculation results and diagnostic info 353 | """ 354 | dfmt = ' {0:s} = {1:0.4E~}' 355 | # dufmt = ' {0:s} = {1:0.4E~P}' 356 | 357 | dkeys = ( 358 | 'flow_area', 359 | 'eroughness', 360 | 'vflow', 361 | 'Re' 362 | ) 363 | 364 | okeys = ( 365 | 'friction', 366 | 'head_loss' 367 | ) 368 | 369 | _logger.debug('Calculating head loss and friction factor') 370 | 371 | # Set up results structure 372 | results = { 373 | 'status': 'undefined', 374 | 'msg': 'No results generated yet', 375 | 'derived': {}, 376 | 'output': {} 377 | } 378 | 379 | # Alias the parameter dicts 380 | ddata = results['derived'] 381 | odata = results['output'] 382 | 383 | # _logger.debug('Input object echo') 384 | # for kk in idata: 385 | # _logger.debug(dfmt.format(kk, idata[kk])) 386 | 387 | for kk in dkeys: 388 | ddata[kk] = float('NaN') 389 | 390 | for kk in okeys: 391 | odata[kk] = float('NaN') 392 | 393 | for kk in ikeys: 394 | if kk not in idata: 395 | _logger.debug('Cannot find {0:s} in input'.format(kk)) 396 | # Record the first error 397 | if results['status'] != 'error': 398 | results['status'] = 'error' 399 | results['msg'] = 'Required input "{0:s}" not specified' \ 400 | .format(kk) 401 | 402 | if results['status'] == 'error': 403 | return results 404 | 405 | ddata['flow_area'] = sc.pi / 4.0 * idata['idiameter']**2 406 | ddata['eroughness'] = idata['froughness'] / idata['idiameter'] 407 | 408 | # Calculate results 409 | hldat = calculate_headloss( 410 | vol_flow=idata['vol_flow'].to('m**3/s').magnitude, 411 | flow_area=ddata['flow_area'].to('m**2').magnitude, 412 | lpipe=idata['lpipe'].to('m').magnitude, 413 | idiameter=idata['idiameter'].to('m').magnitude, 414 | eroughness=ddata['eroughness'].magnitude, 415 | kin_visc=idata['kin_visc'].to('m**2/s').magnitude, 416 | grav=idata['grav'].to('m/s**2').magnitude) 417 | 418 | ddata['vflow'] = Q_(hldat.vflow, 'm/s') 419 | ddata['Re'] = Q_(hldat.Re, '') 420 | 421 | odata['head_loss'] = Q_(hldat.head_loss, 'm') 422 | odata['friction'] = Q_(hldat.friction, '') 423 | 424 | results['status'] = 'ok' 425 | results['msg'] = 'Calculation complete' 426 | 427 | # Diagnostics/audit trail 428 | _logger.debug('Input values:') 429 | for kk in ikeys: 430 | _logger.debug(dfmt.format(kk, idata[kk])) 431 | 432 | _logger.debug('Derived values:') 433 | for kk in dkeys: 434 | _logger.debug(dfmt.format(kk, ddata[kk])) 435 | 436 | _logger.debug('Output values:') 437 | for kk in okeys: 438 | _logger.debug(dfmt.format(kk, odata[kk])) 439 | 440 | _logger.info(results['status'] + ': ' + results['msg']) 441 | 442 | return results 443 | 444 | 445 | def generate_legacy_output(idata, odata, units): 446 | """Return a head loss, friction factor, and initial conditions in the 447 | format of the original Jeppson code 448 | 449 | Args: 450 | idata (dict): Input to head loss calculations 451 | odata (dict): Results of head loss calculations 452 | units (str): Display units for reporting results - either 'SI' 453 | or 'Traditional' 454 | 455 | Returns: 456 | (str): FORTRAN-formatted head loss results 457 | """ 458 | unitmap = { 459 | 'vol_flow': {'SI': 'm**3/s', 'Traditional': 'ft**3/s'}, 460 | 'idiameter': {'SI': 'm', 'Traditional': 'ft'}, 461 | 'lpipe': {'SI': 'm', 'Traditional': 'ft'}, 462 | 'friction': {'SI': '', 'Traditional': ''}, 463 | 'head_loss': {'SI': 'm', 'Traditional': 'ft'} 464 | } 465 | 466 | if units in ('SI', 'Traditional'): 467 | dunits = units 468 | else: 469 | dunits = 'SI' 470 | 471 | ofmt = 'Q={0:10.4f}D={1:10.4f}L={2:10.2f}F={3:10.5f}HEADLOSS={4:10.4f}' 472 | 473 | _logger.debug('Display units are {0:s}'.format(units)) 474 | outstr = ofmt.format( 475 | idata['vol_flow'].to(unitmap['vol_flow'][dunits]).magnitude, 476 | idata['idiameter'].to(unitmap['idiameter'][dunits]).magnitude, 477 | idata['lpipe'].to(unitmap['lpipe'][dunits]).magnitude, 478 | odata['friction'].magnitude, 479 | odata['head_loss'].to(unitmap['head_loss'][dunits]).magnitude) 480 | 481 | return outstr 482 | 483 | 484 | def generate_modern_output(idata, ddata, odata, units): 485 | """Return head loss, friction factor, and initial conditions 486 | 487 | Args: 488 | idata (dict): Input to head loss calculations 489 | ddata (dict): Intermediate results of head loss calculations 490 | odata (dict): Results of head loss calculations 491 | units (str): Display units for reporting results - either 'SI' 492 | or 'Traditional' 493 | 494 | Returns: 495 | (str): Tabular results with descriptions and units 496 | """ 497 | unitmap = { 498 | 'idiameter': {'SI': 'm', 'Traditional': 'ft'}, 499 | 'lpipe': {'SI': 'm', 'Traditional': 'ft'}, 500 | 'flow_area': {'SI': 'm**2', 'Traditional': 'ft**2'}, 501 | 'froughness': {'SI': 'm', 'Traditional': 'ft'}, 502 | 'eroughness': {'SI': '', 'Traditional': ''}, 503 | 'vol_flow': {'SI': 'm**3/s', 'Traditional': 'ft**3/s'}, 504 | 'vflow': {'SI': 'm/s', 'Traditional': 'ft/s'}, 505 | 'Re': {'SI': '', 'Traditional': ''}, 506 | 'friction': {'SI': '', 'Traditional': ''}, 507 | 'head_loss': {'SI': 'm', 'Traditional': 'ft'} 508 | } 509 | 510 | if units in ('SI', 'Traditional'): 511 | dunits = units 512 | else: 513 | dunits = 'SI' 514 | 515 | ofmt_d = '{0:30s} {1:0.4E~}' 516 | ofmt_nd = '{0:30s} {1:0.4E}' 517 | 518 | _logger.debug('Display units are {0:s}'.format(units)) 519 | outary = [] 520 | for tag in unitmap: 521 | if tag in idata: 522 | fval = idata[tag] 523 | elif tag in ddata: 524 | fval = ddata[tag] 525 | elif tag in odata: 526 | fval = odata[tag] 527 | 528 | uval = unitmap[tag][dunits] 529 | desc = idataprop[tag].description 530 | if uval == '': 531 | outary.append(ofmt_nd.format(desc, fval)) 532 | else: 533 | outary.append(ofmt_d.format(desc, fval.to(uval))) 534 | 535 | outstr = '\n\n' + '\n'.join(outary) 536 | 537 | return outstr 538 | 539 | 540 | def main(args): 541 | """Main entry point allowing external calls 542 | 543 | Args: 544 | args ([str]): command line parameter list 545 | """ 546 | args = parse_args(args) 547 | setup_logging(args.loglevel) 548 | _logger.info("Starting jeppson_ch2") 549 | 550 | for fh in args.file: 551 | msg = 'Processing file: {0:s}'.format(fh.name) 552 | _logger.info(msg) 553 | 554 | for iline in get_data_line(fh): 555 | 556 | indat = extract_case_input(iline) 557 | idata = indat['input'] 558 | results = generate_results(idata) 559 | 560 | # Log calcularion status; 561 | if results['status'] == 'ok': 562 | _logger.debug('Case processed successfully') 563 | elif results['status'] == 'warning': 564 | _logger.warning('Case processed with warning: {0:s}' 565 | .format(results['msg'])) 566 | elif results['status'] == 'error': 567 | _logger.error('Case not processed due to input error: {0:s}' 568 | .format(results['msg'])) 569 | else: 570 | _logger.error('Unknown status processing case: "{0:s}", {1:s}' 571 | .format(results['status'], results['msg'])) 572 | 573 | # Finally, print results as original code; infer output units 574 | # from input units 575 | if results['status'] in ('ok', 'warning'): 576 | if args.legacy: 577 | ostr = generate_legacy_output(idata, 578 | results['output'], 579 | indat['units']) 580 | print(ostr) 581 | 582 | if args.modern: 583 | ostr = generate_modern_output(idata, 584 | results['derived'], 585 | results['output'], 586 | indat['units']) 587 | print(ostr) 588 | else: 589 | # Differs from original code; broken input would crash and 590 | # all input was provided via STDIN. Here multiple files may 591 | # be procssed, each potentially containing multiple cases. 592 | # Report errors with line and source file name 593 | print('! Case defined on line {0:d} of {1:s} failed' 594 | .format(iline.ipos, fh.name)) 595 | 596 | _logger.info("Ending jeppson_ch2") 597 | 598 | 599 | def run(): 600 | """Entry point for console_scripts 601 | """ 602 | main(sys.argv[1:]) 603 | 604 | 605 | if __name__ == "__main__": 606 | run() 607 | -------------------------------------------------------------------------------- /src/jeppson/ch4.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | This reimplements the pipe flow analysis program given in chapter 4 of *Steady 5 | Flow Analysis of Pipe Networks: An Instructional Manual* (1974). *Reports.* 6 | Paper 300. http://digitalcommons.usu.edu/water_rep/300 and *Analysis of Flow 7 | in Pipe Networks* (1976). Ann Arbor Science Publishers, Inc. 8 | http://www.worldcat.org/title/analysis-of-flow-in-pipe-networks/oclc/927534147 9 | by Roland W. Jeppson. 10 | 11 | Then run `python setup.py install` which will install the command `jeppson_ch4` 12 | inside your current environment. 13 | """ 14 | from __future__ import division, print_function, absolute_import 15 | 16 | import argparse 17 | from collections import namedtuple, OrderedDict 18 | import logging 19 | from math import isnan, log 20 | import sys 21 | 22 | import scipy.constants as sc 23 | from fluids.friction import friction_factor 24 | 25 | from . import _logger, ureg, Q_ 26 | from jeppson.input import get_data_line 27 | from jeppson import __version__ 28 | 29 | __author__ = "Bob Apthorpe" 30 | __copyright__ = "Bob Apthorpe" 31 | __license__ = "mit" 32 | 33 | #: Nomenclature is a named tuple which stores a short variable name ('tag'), a 34 | #: longger string description, and Pint unit specifiers in the 'mks_units' and 35 | #: 'us_units' fields. 36 | Nomenclature = namedtuple('Nomenclature', 37 | ['tag', 'description', 'mks_units', 'us_units']) 38 | 39 | #: idataprop is an ordered dictionary of Nomenclature named tuples which serve 40 | #: as a directory of input data fields, conversion factors, and documentation. 41 | idataprop = OrderedDict([ 42 | ('vol_flow', Nomenclature('vol_flow', 43 | 'volumetric flowrate', 44 | 'm**3 / s', 45 | 'ft**3 / s')), 46 | ('idiameter', Nomenclature('idiameter', 47 | 'pipe inner diameter', 48 | 'm', 49 | 'ft')), 50 | ('lpipe', Nomenclature('lpipe', 51 | 'pipe length', 52 | 'm', 53 | 'ft')), 54 | ('froughness', Nomenclature('froughness', 55 | 'absolute pipe roughness', 56 | 'm', 57 | 'ft')), 58 | ('fvol_flow', Nomenclature('fvol_flow', 59 | 'fractional volumetric flow', 60 | ureg.parse_expression('dimensionless'), 61 | ureg.parse_expression('dimensionless'))), 62 | ('kin_visc', Nomenclature('kin_visc', 63 | 'kinematic viscosity', 64 | 'm**2 / s', 65 | 'ft**2 / s')), 66 | ('flow_area', Nomenclature('flow_area', 67 | 'pipe flow area', 68 | 'm**2', 69 | 'ft**2')), 70 | ('arl', Nomenclature('arl', 71 | 'power law model constant term', 72 | 's**2 / m**5', 73 | 's**2 / ft**5' 74 | )), 75 | ('dvol_flow', Nomenclature('dvol_flow', 76 | 'volumentric flowrate deviation', 77 | 'm**3 / s', 78 | 'ft**3 / s')), 79 | ('eroughness', Nomenclature('eroughness', 80 | 'relative pipe roughness', 81 | ureg.parse_expression('dimensionless'), 82 | ureg.parse_expression('dimensionless'))), 83 | ('friction', Nomenclature('friction', 84 | 'darcy-weisback friction factor', 85 | ureg.parse_expression('dimensionless'), 86 | ureg.parse_expression('dimensionless'))) 87 | ]) 88 | 89 | #: inputconv is an ordered dictionary of input variable tags and US 90 | #: Traditional units used for assigning units to input quantities. 91 | inputconv = OrderedDict([ 92 | ('vol_flow', 'ft**3/s'), 93 | ('idiameter', 'in'), 94 | ('lpipe', 'ft'), 95 | ('froughness', 'in'), 96 | ('fvol_flow', ''), 97 | ('kin_visc', 'ft**2/s')]) 98 | 99 | #: ikeys is a sorted list of input variable tags 100 | ikeys = inputconv.keys() 101 | 102 | 103 | def parse_args(args): 104 | """Parse command line parameters 105 | 106 | Args: 107 | args ([str]): command line parameters as list of strings 108 | 109 | Returns: 110 | :obj:`argparse.Namespace`: command line parameters namespace 111 | """ 112 | parser = argparse.ArgumentParser( 113 | description="Incompressible flow calculator") 114 | parser.add_argument( 115 | '--version', 116 | action='version', 117 | version='jeppson-python {ver}'.format(ver=__version__)) 118 | parser.add_argument( 119 | dest="file", 120 | help="input files (STDIN if not specified)", 121 | type=argparse.FileType('r'), 122 | nargs='*', 123 | default=[sys.stdin], 124 | metavar="FILE") 125 | parser.add_argument( 126 | '-v', 127 | '--verbose', 128 | dest="loglevel", 129 | help="set loglevel to INFO", 130 | action='store_const', 131 | const=logging.INFO) 132 | parser.add_argument( 133 | '-vv', 134 | '--very-verbose', 135 | dest="loglevel", 136 | help="set loglevel to DEBUG", 137 | action='store_const', 138 | const=logging.DEBUG) 139 | return parser.parse_args(args) 140 | 141 | 142 | def setup_logging(loglevel): 143 | """Setup basic logging 144 | 145 | Args: 146 | loglevel (int): minimum loglevel for emitting messages 147 | """ 148 | logformat = "[%(asctime)s] %(levelname)s:%(name)s: %(message)s" 149 | logging.basicConfig(level=loglevel, stream=sys.stdout, 150 | format=logformat, datefmt="%Y-%m-%d %H:%M:%S") 151 | 152 | 153 | def extract_case_input(iline, force_units=None): 154 | """Extract and validate case input from pre-processed data line 155 | 156 | Args: 157 | iline (InputLine): Pre-processed input data line 158 | force_units (str): Unit system of input data. Accepted values are 159 | 'SI' (mks) and 'Traditional' (foot-pound-second). If left 160 | undefined, units will be inferred from gravitational 161 | acceleration. 162 | 163 | Returns: 164 | (dict): Case input data and metadata""" 165 | _logger.debug('Extracting input data for single case') 166 | 167 | # Set default results 168 | results = { 169 | 'status': 'undefined', 170 | 'msg': 'No results generated yet', 171 | 'idata': {} 172 | } 173 | 174 | idata = results['idata'] 175 | 176 | for ikey in ikeys: 177 | idata[ikey] = float('NaN') 178 | 179 | # Check number of tokens 180 | mintok = len(ikeys) 181 | if iline.ntok < mintok: 182 | results['status'] = 'error' 183 | results['msg'] = 'Too few tokens ({} found, {} expected)' \ 184 | .format(iline.ntok, mintok) 185 | return results 186 | 187 | elif iline.ntok > mintok: 188 | results['status'] = 'warning' 189 | results['msg'] = 'Too many tokens ({} found, {} expected)' \ 190 | .format(iline.ntok, mintok) 191 | else: 192 | results['status'] = 'ok' 193 | results['msg'] = 'Proper token count ({})'.format(mintok) 194 | 195 | # Convert tokens to floats; check for parsing errors 196 | _logger.debug('On read:') 197 | for i, ikey in enumerate(ikeys): 198 | desc = idataprop[ikey].description 199 | try: 200 | idata[ikey] = Q_(float(iline.token[i]), inputconv[ikey]) 201 | _logger.debug('{0:s} "{1:s}" is {2:0.4E~}' 202 | .format(ikey, desc, 203 | idata[ikey].to(inputconv[ikey]))) 204 | except ValueError as err: 205 | _logger.error('Numeric parse failure, ' 206 | 'field {0:d}, {1:s} "{2:s}": {3:s}' 207 | .format(i, ikey, desc, str(err))) 208 | results['status'] = 'error' 209 | results['msg'] = 'Cannot parse values from input line, field ' \ 210 | '{0:d}, "{1:s}"'.format(i, ikey) 211 | return results 212 | 213 | if isnan(idata[ikey].magnitude): 214 | _logger.error('Numeric parse failure, field {0:d}, {1:s}, "{2:s}"' 215 | .format(i, ikey, desc)) 216 | results['status'] = 'error' 217 | results['msg'] = 'Cannot parse values from input line, field ' \ 218 | '{0:d}, {1:s}, "{2:s}"'.format(i, ikey, desc) 219 | return results 220 | 221 | # Set units (Pint) 222 | _logger.debug('As Pint quantities:') 223 | for ikey in ikeys: 224 | desc = idataprop[ikey].description 225 | _logger.debug(' {0:s} = {1:0.4E~P}' 226 | .format(desc, idata[ikey].to_base_units())) 227 | 228 | _logger.info(results['status'] + ': ' + results['msg']) 229 | 230 | return results 231 | 232 | 233 | def pipe_friction(vol_flow, idiameter, kin_visc, flow_area, eroughness): 234 | """Calculate friction factor from pipe geometry, flow conditions, and fluid 235 | properties. Input values should be supplied in a consistent unit system 236 | (all SI or all US Traditional) 237 | 238 | Args: 239 | vol_flow (float): Volumetric flow 240 | idiameter (float): Pipe inner diameter 241 | kin_visc (float): Fluid kinematic viscosity 242 | flow_area (float): Pipe flow area 243 | eroughness (float): Relative pipe roughness. 244 | 245 | Returns: 246 | (float): Darcy-Weisbach friction factor""" 247 | vflow = vol_flow / flow_area 248 | Re = vflow * idiameter / kin_visc 249 | friction = friction_factor(Re=Re, eD=eroughness) 250 | _logger.debug('{0:16s}{1:12.4E}'.format('vflow', vflow)) 251 | _logger.debug('{0:16s}{1:12.4E}'.format('Re', Re)) 252 | 253 | return friction 254 | 255 | 256 | def generate_results(idata): 257 | """ Generate intermediate quantities and modeling 258 | coeffcients for incompressible pipe flow cases 259 | 260 | Args: 261 | idata (dict): Dict containing pipe geometry, fluid properties, 262 | flow conditions, and model parameters 263 | 264 | Returns: 265 | ([dict]): Pair of dicts containing derived and output data describing 266 | flow and pressure conditions as well as intermediate values used in 267 | calculations 268 | """ 269 | ugrav = Q_(sc.g, "m/s**2") 270 | 271 | # Results 272 | odata = {} 273 | 274 | ddata = {} 275 | ddata['flow_area'] = sc.pi / 4.0 * idata['idiameter']**2 276 | ddata['dvol_flow'] = idata['fvol_flow'] * idata['vol_flow'] 277 | ddata['eroughness'] = idata['froughness'] / idata['idiameter'] 278 | ddata['arl'] = idata['lpipe'] \ 279 | / (2.0 * ugrav * ddata['flow_area']**2 * idata['idiameter']) 280 | 281 | # Logging 282 | _logger.debug('Input data:') 283 | for tag in ikeys: 284 | _logger.debug(' {0:s} is {1:0.4E~}' 285 | .format(tag, idata[tag].to(idataprop[tag].us_units))) 286 | 287 | _logger.debug('Derived data:') 288 | for tag in ddata: 289 | _logger.debug(' {0:s} is {1:0.4E~}' 290 | .format(tag, ddata[tag].to(idataprop[tag].us_units))) 291 | 292 | vol_flow1 = idata['vol_flow'] - ddata['dvol_flow'] 293 | friction1 = Q_(pipe_friction(vol_flow1.to('m**3/s').magnitude, 294 | idata['idiameter'].to('m').magnitude, 295 | idata['kin_visc'].to('m**2/s').magnitude, 296 | ddata['flow_area'].to('m**2').magnitude, 297 | ddata['eroughness'].to('').magnitude), '') 298 | 299 | vol_flow2 = idata['vol_flow'] + ddata['dvol_flow'] 300 | friction2 = Q_(pipe_friction(vol_flow2.to('m**3/s').magnitude, 301 | idata['idiameter'].to('m').magnitude, 302 | idata['kin_visc'].to('m**2/s').magnitude, 303 | ddata['flow_area'].to('m**2').magnitude, 304 | ddata['eroughness'].to('').magnitude), '') 305 | 306 | # Logging 307 | _logger.debug('{0:16s}{1:12.4E~} {2:12.4E~}' 308 | .format('vol_flow', vol_flow1, vol_flow2)) 309 | _logger.debug('{0:16s}{1:12.4E~} {2:12.4E~}' 310 | .format('friction', friction1, friction2)) 311 | 312 | odata['be'] = ((log(friction1.magnitude) - log(friction2.magnitude)) 313 | / (log(vol_flow2.magnitude) - log(vol_flow1.magnitude))) 314 | odata['ae'] = friction1.magnitude \ 315 | * (vol_flow1.to('ft**3/s').magnitude)**odata['be'] 316 | odata['ep'] = 2.0 - odata['be'] 317 | odata['ck'] = odata['ae'] * ddata['arl'].to('s**2/ft**5').magnitude 318 | 319 | # Logging 320 | _logger.debug('Output data:') 321 | for ikey in ('ae', 'be', 'ck', 'ep'): 322 | _logger.debug('{0:s} = {1:0.4E}'.format(ikey, odata[ikey])) 323 | 324 | return ddata, odata 325 | 326 | 327 | def generate_legacy_output(idata, odata): 328 | """Return a string containing volumetric flowrate, pipe diameter, 329 | and flow correlation parameters in the format of the original Jeppson 330 | code. 331 | 332 | Args: 333 | idata (dict): Input to incompressible flow calculations (values are 334 | Pint Quantity objects) 335 | odata (dict): Correlation parameters (values are floats) 336 | 337 | Returns: 338 | (str): FORTRAN-formatted head loss results 339 | """ 340 | 341 | outstr = '' 342 | 343 | outstr = '{:12.5f}{:12.5f}{:12.5f}{:12.5f}{:12.5f}{:16.6E}' \ 344 | .format(idata['vol_flow'].to('ft**3/s').magnitude, 345 | idata['idiameter'].to('ft').magnitude, 346 | odata['be'], odata['ae'], 347 | odata['ep'], odata['ck']) 348 | 349 | return outstr 350 | 351 | 352 | def main(args): 353 | """Main entry point allowing external calls 354 | 355 | Args: 356 | args ([str]): command line parameter list 357 | """ 358 | 359 | args = parse_args(args) 360 | setup_logging(args.loglevel) 361 | _logger.info("Starting jeppson_ch4") 362 | 363 | for fh in args.file: 364 | msg = 'Processing file: {0:s}'.format(fh.name) 365 | _logger.info(msg) 366 | 367 | for iline in get_data_line(fh): 368 | 369 | results = extract_case_input(iline) 370 | 371 | # Log parse status; 372 | if results['status'] == 'ok': 373 | _logger.debug('Case processed successfully') 374 | elif results['status'] == 'warning': 375 | _logger.warning('Case processed with warning: {0:s}' 376 | .format(results['msg'])) 377 | elif results['status'] == 'error': 378 | _logger.error('Case not processed due to input ' 379 | 'error: {0:s}'.format(results['msg'])) 380 | _logger.debug('Skipping to next case') 381 | continue 382 | else: 383 | _logger.error('Unknown status processing case: ' 384 | '"{0:s}", {1:s}' 385 | .format(results['status'], results['msg'])) 386 | _logger.debug('Skipping to next case') 387 | continue 388 | 389 | idata = results['idata'] 390 | 391 | ddata, odata = generate_results(idata) 392 | 393 | ostr = generate_legacy_output(idata, odata) 394 | 395 | print(ostr) 396 | 397 | _logger.info("Ending jeppson_ch4") 398 | 399 | 400 | def run(): 401 | """Entry point for console_scripts 402 | """ 403 | main(sys.argv[1:]) 404 | 405 | 406 | if __name__ == "__main__": 407 | run() 408 | -------------------------------------------------------------------------------- /src/jeppson/constants.py: -------------------------------------------------------------------------------- 1 | """Hazen-Williams correlation constants needed by Jeppson applications. 2 | 3 | The Hazen-Williams correlation constants are taken from Chapter 2 of *Steady 4 | Flow Analysis of Pipe Networks: An Instructional Manual* and *Analysis of Flow 5 | in Pipe Networks* by Roland W. Jeppson. Note that the Hazen-Williams 6 | correlation is *dimensional*, not dimensionless. This requires particular care 7 | when selecting leading coefficients for the flow and head loss 8 | correrlations.""" 9 | 10 | # from jeppson import __version__ 11 | 12 | __author__ = "Bob Apthorpe" 13 | __copyright__ = "Bob Apthorpe" 14 | __license__ = "mit" 15 | 16 | #: Exponent on *S* term of Hazen-Williams flow correlation, 0.54 17 | eshw = 0.54 18 | 19 | #: Exponent on *R* term of Hazen-Williams flow correlation, 0.63 20 | erhw = 0.63 21 | 22 | #: Exponent on *C* and *Q* term of Hazen-Williams head loss correlationm, about 23 | #: 1.852 24 | echw = 1.0 / eshw 25 | 26 | #: Exponent on *D* term of Hazen-Williams head loss correlation, about 4.87 27 | edhw = (2.0 + erhw) / eshw 28 | 29 | #: Leading coefficient on Hazen-Williams head loss correlation, SI units, 10.67 30 | ahws_si = 10.67 31 | 32 | #: Leading coefficient on Hazen-Williams head loss correlation, US traditional 33 | #: units (feet), 4.73 34 | ahws_us = 4.73 35 | 36 | #: Leading coefficient on Hazen-Williams flow correlation, SI units, 0.849 37 | ahwq_si = 0.849 38 | 39 | #: Leading coefficient on Hazen-Williams flow correlation, US traditional units 40 | #: (feet), 1.318 41 | ahwq_us = 1.318 42 | -------------------------------------------------------------------------------- /src/jeppson/input.py: -------------------------------------------------------------------------------- 1 | """Input processing utilities for reading legacy Jeppson code input""" 2 | 3 | from __future__ import absolute_import, division, print_function 4 | 5 | import logging 6 | # Set default logging handler to avoid "No handler found" warnings. 7 | try: # Python 2.7+ 8 | from logging import NullHandler 9 | except ImportError: 10 | class NullHandler(logging.Handler): 11 | def emit(self, record): 12 | pass 13 | 14 | _logger = logging.getLogger(__name__) 15 | _logger.addHandler(NullHandler()) 16 | 17 | 18 | class InputLine(dict): 19 | """Categorized and tokenized user input for Jeppson Ch. 2 friction factor 20 | and head loss calculator. 21 | 22 | All attributes are immutable except ``ipos``. 23 | 24 | Attributes: 25 | line (str): Original line of input text with newline(s) removed 26 | type (str): One of 'blank', 'comment', or 'data' 27 | typecode (str): One of 'B', 'C', or 'D', corresponding to type 28 | ipos (int): Line number in original file (count starts at 1) 29 | ntok (int): Number of tokens found (0 except for 'data' lines) 30 | token ([str]): Tokens parsed from line ('data' lines only, 31 | otherwise empty) 32 | 33 | Args: 34 | line (str): Original line of input text with newline(s) removed 35 | ipos (int): Line number in original file 36 | commentchar (str): leading character/string denoting that a line is 37 | a comment 38 | """ 39 | 40 | def __init__(self, line, ipos=0, commentchar='#'): 41 | """Constructor """ 42 | self.ipos = ipos 43 | 44 | self._line = line.rstrip('\r\n') 45 | self._type = 'unknown' 46 | self._ntok = 0 47 | self._token = () 48 | 49 | tline = self._line.strip() 50 | ltline = len(tline) 51 | 52 | if ltline == 0: 53 | self._type = 'blank' 54 | else: 55 | if commentchar and tline.startswith(commentchar): 56 | self._type = 'comment' 57 | else: 58 | self._type = 'data' 59 | self._token = tline.split() 60 | self._ntok = len(self.token) 61 | 62 | @property 63 | def line(self): 64 | """Line read accessor 65 | 66 | Returns: 67 | (str): Original input line stripped of line terminators 68 | """ 69 | return self._line 70 | 71 | # @line.setter 72 | # def line(self, line): 73 | # """Line write accessor 74 | # Raises: 75 | # ValueError 76 | # """ 77 | # return self._line 78 | 79 | @property 80 | def type(self): 81 | """Type read accessor 82 | 83 | Returns: 84 | (str): Type of input line; one of 'blank', 'comment', or 'data' 85 | """ 86 | return self._type 87 | 88 | @property 89 | def typecode(self): 90 | """Type code read accessor 91 | 92 | Returns: 93 | (str): Type code of input line; one of 'B', 'C', or 'D', 94 | corresponding to 'blank', 'comment', or 'data', respectively 95 | """ 96 | return self._type[0].upper() 97 | 98 | @property 99 | def ntok(self): 100 | """Token count read accessor 101 | 102 | Returns: 103 | (int): Number of tokens found (0 except for 'data' lines) 104 | """ 105 | return self._ntok 106 | 107 | @property 108 | def token(self): 109 | """Token list read accessor 110 | 111 | Returns: 112 | ([str]): Tokens parsed from line ('data' lines only, otherwise 113 | empty) 114 | """ 115 | return self._token 116 | 117 | def as_log(self, logfmt='{0:-6d} [{1:s}] {2:s}'): 118 | """Return line number (ipos), type code, and original line to assist 119 | in finding input errors. Type code is 'B', 'C', or 'D', corresponding 120 | to 'blank', 'comment', and 'data', respectively 121 | 122 | Args: 123 | logfmt (str): Format string for producing log output. Field 0 124 | is the `ipos` attribute, field 1 is the type code, and field 125 | 2 is the `line` attribute 126 | 127 | Returns: 128 | (str): Formatted line with metadata 129 | """ 130 | return logfmt.format(self.ipos, self.typecode, self._line) 131 | 132 | 133 | def get_data_line(fh): 134 | """ Generator returning a non-blank, non-comment InputLine from a file 135 | handle 136 | 137 | Args: 138 | fh (filehandle): Open readable filehandle 139 | 140 | Return: 141 | (InputLine): Data line from input file 142 | """ 143 | for ict, rawline in enumerate(fh): 144 | iline = InputLine(line=rawline, ipos=ict+1) 145 | _logger.debug(iline.as_log()) 146 | 147 | if iline.typecode == 'D': 148 | _logger.debug('Yielding line {:d}'.format(iline.ipos)) 149 | yield iline 150 | 151 | _logger.debug('All lines consumed in {:s}.'.format(fh.name)) 152 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Dummy conftest.py for jeppson. 5 | 6 | If you don't know what this is for, just leave it empty. 7 | Read more about conftest.py under: 8 | https://pytest.org/latest/plugins.html 9 | """ 10 | from __future__ import print_function, absolute_import, division 11 | 12 | # import pytest 13 | -------------------------------------------------------------------------------- /tests/test_ch2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from distutils import dir_util 5 | from os.path import isdir, splitext 6 | from pytest import fixture #, approx, raises 7 | import jeppson.ch2 as jch2 8 | 9 | __author__ = "Bob Apthorpe" 10 | __copyright__ = "Bob Apthorpe" 11 | __license__ = "mit" 12 | 13 | 14 | # we reuse a bit of pytest's own testing machinery, this should eventually come 15 | # from a separatedly installable pytest-cli plugin. 16 | pytest_plugins = ["pytester"] 17 | _APPNAME = 'jeppson_ch2' 18 | 19 | @fixture 20 | def datadir(tmpdir, request): 21 | ''' 22 | Fixture responsible for searching a folder with the same name of test 23 | module and, if available, moving all contents to a temporary directory so 24 | tests can use them freely. 25 | 26 | Shamelessly purloined from https://stackoverflow.com/questions/29627341/pytest-where-to-store-expected-data 27 | and mangled until it worked right 28 | ''' 29 | filename = request.module.__file__ 30 | test_dir, _ = splitext(filename) 31 | 32 | if isdir(test_dir): 33 | dir_util.copy_tree(test_dir, str(tmpdir)) 34 | 35 | return tmpdir 36 | 37 | @fixture 38 | def run(testdir): 39 | def do_run(*args): 40 | args = [_APPNAME] + list(args) 41 | return testdir._run(*args) 42 | return do_run 43 | 44 | def test_ch2(datadir, run): 45 | # Set up input, output, and reference data file paths 46 | bfn = [ 47 | "ch2_test10", 48 | "ch2_test11", 49 | "ch2_test20", 50 | "ch2_test21", 51 | "ch2_test_all" 52 | ] 53 | 54 | for basefn in bfn: 55 | 56 | ifn = datadir.join("{:s}.inp".format(basefn)) 57 | # ofn = datadir.join("{:s}.out".format(basefn)) 58 | rfn = datadir.join("{:s}.ref.out".format(basefn)) 59 | 60 | # Run ch2.py and check for successful termination 61 | result = run('--legacy', '--no-modern', ifn) 62 | assert result.ret == 0 63 | 64 | # Compare code output with reference output 65 | # with ofn.open("r") as ofh: 66 | # outcontent = ofh.read() 67 | outcontent = result.outlines 68 | 69 | with rfn.open("r") as rfh: 70 | refcontent = rfh.read().splitlines() 71 | 72 | nout = len(refcontent) 73 | assert len(outcontent) == nout 74 | 75 | if len(outcontent) == nout: 76 | for i, outln in enumerate(outcontent): 77 | assert outln == refcontent[i] 78 | 79 | return 80 | 81 | # extract_case_input(iline, force_units=None): 82 | # calculate_headloss(vol_flow, flow_area, lpipe, idiameter, eroughness, 83 | # generate_results(kwinput): 84 | # generate_legacy_output(idata, odata, units): 85 | -------------------------------------------------------------------------------- /tests/test_ch2/ch2_test10.inp: -------------------------------------------------------------------------------- 1 | 0.33333 0.5 150.0 1.217E-5 7.0E-6 32.2 2 | -------------------------------------------------------------------------------- /tests/test_ch2/ch2_test10.ref.out: -------------------------------------------------------------------------------- 1 | Q= 0.5000D= 0.3333L= 150.00F= 0.01655HEADLOSS= 3.7976 2 | -------------------------------------------------------------------------------- /tests/test_ch2/ch2_test11.inp: -------------------------------------------------------------------------------- 1 | 0.33333 0.497 150.0 1.217E-5 7.0E-6 32.2 2 | -------------------------------------------------------------------------------- /tests/test_ch2/ch2_test11.ref.out: -------------------------------------------------------------------------------- 1 | Q= 0.4970D= 0.3333L= 150.00F= 0.01657HEADLOSS= 3.7565 2 | -------------------------------------------------------------------------------- /tests/test_ch2/ch2_test20.inp: -------------------------------------------------------------------------------- 1 | 0.1016 0.014 45.72 1.131E-6 2.1E-6 9.81 2 | -------------------------------------------------------------------------------- /tests/test_ch2/ch2_test20.ref.out: -------------------------------------------------------------------------------- 1 | Q= 0.0140D= 0.1016L= 45.72F= 0.01659HEADLOSS= 1.1346 2 | -------------------------------------------------------------------------------- /tests/test_ch2/ch2_test21.inp: -------------------------------------------------------------------------------- 1 | 0.1016 0.0141 45.72 1.131E-6 2.1E-6 9.81 2 | -------------------------------------------------------------------------------- /tests/test_ch2/ch2_test21.ref.out: -------------------------------------------------------------------------------- 1 | Q= 0.0141D= 0.1016L= 45.72F= 0.01657HEADLOSS= 1.1493 2 | -------------------------------------------------------------------------------- /tests/test_ch2/ch2_test_all.inp: -------------------------------------------------------------------------------- 1 | # All ch2 test cases 2 | 3 | # ch2_test10.inp 4 | 0.33333 0.5 150.0 1.217E-5 7.0E-6 32.2 5 | 6 | # ch2_test11.inp 7 | 0.33333 0.497 150.0 1.217E-5 7.0E-6 32.2 8 | 9 | # ch2_test20.inp 10 | 0.1016 0.014 45.72 1.131E-6 2.1E-6 9.81 11 | 12 | # ch2_test21.inp 13 | 0.1016 0.0141 45.72 1.131E-6 2.1E-6 9.81 14 | 15 | # __END__ 16 | -------------------------------------------------------------------------------- /tests/test_ch2/ch2_test_all.ref.out: -------------------------------------------------------------------------------- 1 | Q= 0.5000D= 0.3333L= 150.00F= 0.01655HEADLOSS= 3.7976 2 | Q= 0.4970D= 0.3333L= 150.00F= 0.01657HEADLOSS= 3.7565 3 | Q= 0.0140D= 0.1016L= 45.72F= 0.01659HEADLOSS= 1.1346 4 | Q= 0.0141D= 0.1016L= 45.72F= 0.01657HEADLOSS= 1.1493 5 | -------------------------------------------------------------------------------- /tests/test_ch4.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from distutils import dir_util 5 | from os.path import isdir, splitext 6 | from pytest import fixture #, approx, raises 7 | import jeppson.ch4 as jch4 8 | 9 | __author__ = "Bob Apthorpe" 10 | __copyright__ = "Bob Apthorpe" 11 | __license__ = "mit" 12 | 13 | 14 | # we reuse a bit of pytest's own testing machinery, this should eventually come 15 | # from a separatedly installable pytest-cli plugin. 16 | pytest_plugins = ["pytester"] 17 | _APPNAME = 'jeppson_ch4' 18 | 19 | @fixture 20 | def datadir(tmpdir, request): 21 | ''' 22 | Fixture responsible for searching a folder with the same name of test 23 | module and, if available, moving all contents to a temporary directory so 24 | tests can use them freely. 25 | 26 | Shamelessly purloined from https://stackoverflow.com/questions/29627341/pytest-where-to-store-expected-data 27 | and mangled until it worked right 28 | ''' 29 | filename = request.module.__file__ 30 | test_dir, _ = splitext(filename) 31 | 32 | if isdir(test_dir): 33 | dir_util.copy_tree(test_dir, str(tmpdir)) 34 | 35 | return tmpdir 36 | 37 | @fixture 38 | def run(testdir): 39 | def do_run(*args): 40 | args = [_APPNAME] + list(args) 41 | return testdir._run(*args) 42 | return do_run 43 | 44 | def test_ch4(datadir, run): 45 | # Set up input, output, and reference data file paths 46 | bfn = [ 47 | "ch4_test1" 48 | ] 49 | 50 | for basefn in bfn: 51 | 52 | ifn = datadir.join("{:s}.inp".format(basefn)) 53 | # ofn = datadir.join("{:s}.out".format(basefn)) 54 | rfn = datadir.join("{:s}.ref.out".format(basefn)) 55 | 56 | # Run ch4.py and check for successful termination 57 | result = run(ifn) 58 | assert result.ret == 0 59 | 60 | # Compare code output with reference output 61 | # with ofn.open("r") as ofh: 62 | # outcontent = ofh.read() 63 | outcontent = result.outlines 64 | 65 | with rfn.open("r") as rfh: 66 | refcontent = rfh.read().splitlines() 67 | 68 | nout = len(refcontent) 69 | assert len(outcontent) == nout 70 | 71 | if len(outcontent) == nout: 72 | for i, outln in enumerate(outcontent): 73 | assert outln == refcontent[i] 74 | 75 | return 76 | 77 | -------------------------------------------------------------------------------- /tests/test_ch4/ch4_test1.inp: -------------------------------------------------------------------------------- 1 | 0.12 4.0 150.0 0.0102 0.1 1.217E-5 2 | 0.10 3.0 100.0 0.0102 0.1 1.217E-5 3 | 0.05 3.0 150.0 0.0102 0.1 1.217E-5 4 | 0.05 3.0 100.0 0.0102 0.1 1.217E-5 5 | 0.10 4.0 100.0 0.0102 0.1 1.217E-5 6 | 0.05 3.0 141.0 0.0102 0.1 1.217E-5 7 | -------------------------------------------------------------------------------- /tests/test_ch4/ch4_test1.ref.out: -------------------------------------------------------------------------------- 1 | 0.12000 0.33333 0.10170 0.02293 1.89830 2.105782E+01 2 | 0.10000 0.25000 0.07934 0.02481 1.92066 6.401589E+01 3 | 0.05000 0.25000 0.12335 0.02207 1.87665 8.539451E+01 4 | 0.05000 0.25000 0.12335 0.02207 1.87665 5.692967E+01 5 | 0.10000 0.33333 0.11346 0.02234 1.88654 1.367725E+01 6 | 0.05000 0.25000 0.12335 0.02207 1.87665 8.027084E+01 7 | -------------------------------------------------------------------------------- /tests/test_ch5.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from distutils import dir_util 5 | from os.path import isdir, splitext 6 | from pytest import fixture #, approx, raises 7 | import jeppson.ch5 as jch5 8 | 9 | __author__ = "Bob Apthorpe" 10 | __copyright__ = "Bob Apthorpe" 11 | __license__ = "mit" 12 | 13 | 14 | # we reuse a bit of pytest's own testing machinery, this should eventually come 15 | # from a separatedly installable pytest-cli plugin. 16 | pytest_plugins = ["pytester"] 17 | _APPNAME = 'jeppson_ch5' 18 | 19 | @fixture 20 | def datadir(tmpdir, request): 21 | ''' 22 | Fixture responsible for searching a folder with the same name of test 23 | module and, if available, moving all contents to a temporary directory so 24 | tests can use them freely. 25 | 26 | Shamelessly purloined from https://stackoverflow.com/questions/29627341/pytest-where-to-store-expected-data 27 | and mangled until it worked right 28 | ''' 29 | filename = request.module.__file__ 30 | test_dir, _ = splitext(filename) 31 | 32 | if isdir(test_dir): 33 | dir_util.copy_tree(test_dir, str(tmpdir)) 34 | 35 | return tmpdir 36 | 37 | @fixture 38 | def run(testdir): 39 | def do_run(*args): 40 | args = [_APPNAME] + list(args) 41 | return testdir._run(*args) 42 | return do_run 43 | 44 | def test_ch5(datadir, run): 45 | # Set up input, output, and reference data file paths 46 | bfn = [ 47 | "ch5_test1", 48 | "ch5_test2" 49 | ] 50 | 51 | for basefn in bfn: 52 | 53 | ifn = datadir.join("{:s}.inp".format(basefn)) 54 | # ofn = datadir.join("{:s}.out".format(basefn)) 55 | rfn = datadir.join("{:s}.ref.out".format(basefn)) 56 | 57 | # Run ch5.py and check for successful termination 58 | result = run(ifn) 59 | assert result.ret == 0 60 | 61 | # Compare code output with reference output 62 | # with ofn.open("r") as ofh: 63 | # outcontent = ofh.read() 64 | outcontent = result.outlines 65 | 66 | with rfn.open("r") as rfh: 67 | refcontent = rfh.read().splitlines() 68 | 69 | nout = len(refcontent) 70 | assert len(outcontent) == nout 71 | 72 | if len(outcontent) == nout: 73 | for i, outln in enumerate(outcontent): 74 | assert outln == refcontent[i] 75 | 76 | return 77 | 78 | -------------------------------------------------------------------------------- /tests/test_ch5/ch5_test1.inp: -------------------------------------------------------------------------------- 1 | 7 6 2 10 0 0.001 0.00001217 0.1 2 | 8.0 12.0 10.0 12.0 10.0 6.0 8.0 3 | 1106.0 751.0 1000.0 500.0 1200.0 600.0 800.0 4 | 0.0102 0.0102 0.0102 0.0102 0.0102 0.0102 0.0102 5 | 1 2 1 4 6 | 2000.0 7 | 0 3 -1 -2 5 8 | 1 3 2 -3 -7 9 | -1500.0 10 | 1 2 3 -4 11 | -1000.0 12 | 1 2 -5 -6 13 | -1500.0 14 | 1 2 6 7 15 | 2000.0 16 | 4 1 -2 -3 -4 17 | 4 5 -6 7 2 18 | -------------------------------------------------------------------------------- /tests/test_ch5/ch5_test1.ref.out: -------------------------------------------------------------------------------- 1 | Pipe Diameter Length Rel. Roughness 2 | 0 8.0000E+00 in 1.1060E+03 ft 1.2750E-03 3 | 1 1.2000E+01 in 7.5100E+02 ft 8.5000E-04 4 | 2 1.0000E+01 in 1.0000E+03 ft 1.0200E-03 5 | 3 1.2000E+01 in 5.0000E+02 ft 8.5000E-04 6 | 4 1.0000E+01 in 1.2000E+03 ft 1.0200E-03 7 | 5 6.0000E+00 in 6.0000E+02 ft 1.7000E-03 8 | 6 8.0000E+00 in 8.0000E+02 ft 1.2750E-03 9 | 10 | 11 | Iteration 0 12 | Deviation 1.0000E+02 (Tolerance 1.0000E-03) 13 | 14 | Pipe Kp expp Qcurrent Qpredict 15 | 0 2.0564E+00 2.1105E+00 3.9299E-01 ft ** 3 / s 3.9299E-01 ft ** 3 / s 16 | 1 6.6809E-02 2.2303E+00 1.0745E-01 ft ** 3 / s 1.0745E-01 ft ** 3 / s 17 | 2 2.4370E+00 2.0528E+00 1.8350E+00 ft ** 3 / s 1.8350E+00 ft ** 3 / s 18 | 3 1.0145E+00 2.0372E+00 4.0630E+00 ft ** 3 / s 4.0630E+00 ft ** 3 / s 19 | 4 9.0329E-01 2.1217E+00 5.0044E-01 ft ** 3 / s 5.0044E-01 ft ** 3 / s 20 | 5 3.1426E+01 2.0150E+00 2.8416E+00 ft ** 3 / s 2.8416E+00 ft ** 3 / s 21 | 6 5.4375E+00 2.0412E+00 1.6144E+00 ft ** 3 / s 1.6144E+00 ft ** 3 / s 22 | 23 | Iteration 1 24 | Deviation 3.2058E-01 (Tolerance 1.0000E-03) 25 | 26 | Pipe Kp expp Qcurrent Qpredict 27 | 0 5.2540E+00 2.0556E+00 1.8163E+00 ft ** 3 / s 1.1046E+00 ft ** 3 / s 28 | 1 2.1129E-01 2.1486E+00 8.0283E-01 ft ** 3 / s 4.5514E-01 ft ** 3 / s 29 | 2 1.5464E+00 2.0753E+00 4.1174E-01 ft ** 3 / s 1.1234E+00 ft ** 3 / s 30 | 3 8.4441E-01 2.0436E+00 2.6398E+00 ft ** 3 / s 3.3514E+00 ft ** 3 / s 31 | 4 2.5121E+00 2.0596E+00 2.6191E+00 ft ** 3 / s 1.5598E+00 ft ** 3 / s 32 | 5 1.9917E+01 2.0231E+00 7.2292E-01 ft ** 3 / s 1.7822E+00 ft ** 3 / s 33 | 6 8.8279E+00 2.0268E+00 3.7331E+00 ft ** 3 / s 2.6738E+00 ft ** 3 / s 34 | 35 | Iteration 2 36 | Deviation 4.3938E-02 (Tolerance 1.0000E-03) 37 | 38 | Pipe Kp expp Qcurrent Qpredict 39 | 0 4.9458E+00 2.0584E+00 9.6617E-01 ft ** 3 / s 1.0354E+00 ft ** 3 / s 40 | 1 2.8567E-01 2.1268E+00 8.4305E-01 ft ** 3 / s 6.4910E-01 ft ** 3 / s 41 | 2 1.6335E+00 2.0723E+00 1.2618E+00 ft ** 3 / s 1.1926E+00 ft ** 3 / s 42 | 3 8.6096E-01 2.0429E+00 3.4898E+00 ft ** 3 / s 3.4206E+00 ft ** 3 / s 43 | 4 2.6991E+00 2.0563E+00 1.8092E+00 ft ** 3 / s 1.6845E+00 ft ** 3 / s 44 | 5 1.8561E+01 2.0246E+00 1.5328E+00 ft ** 3 / s 1.6575E+00 ft ** 3 / s 45 | 6 9.2266E+00 2.0257E+00 2.9232E+00 ft ** 3 / s 2.7985E+00 ft ** 3 / s 46 | 47 | Iteration 3 48 | Deviation 1.3722E-03 (Tolerance 1.0000E-03) 49 | 50 | Pipe Kp expp Qcurrent Qpredict 51 | 0 4.9342E+00 2.0585E+00 1.0302E+00 ft ** 3 / s 1.0328E+00 ft ** 3 / s 52 | 1 2.8796E-01 2.1263E+00 6.6121E-01 ft ** 3 / s 6.5515E-01 ft ** 3 / s 53 | 2 1.6367E+00 2.0722E+00 1.1978E+00 ft ** 3 / s 1.1952E+00 ft ** 3 / s 54 | 3 8.6159E-01 2.0428E+00 3.4259E+00 ft ** 3 / s 3.4232E+00 ft ** 3 / s 55 | 4 2.7042E+00 2.0562E+00 1.6914E+00 ft ** 3 / s 1.6879E+00 ft ** 3 / s 56 | 5 1.8524E+01 2.0247E+00 1.6506E+00 ft ** 3 / s 1.6541E+00 ft ** 3 / s 57 | 6 9.2376E+00 2.0257E+00 2.8054E+00 ft ** 3 / s 2.8019E+00 ft ** 3 / s 58 | 59 | Iteration 4 60 | Deviation 3.6335E-05 (Tolerance 1.0000E-03) 61 | 62 | Pipe Kp expp Qcurrent Qpredict 63 | 0 4.9337E+00 2.0585E+00 1.0326E+00 ft ** 3 / s 1.0327E+00 ft ** 3 / s 64 | 1 2.8802E-01 2.1263E+00 6.5547E-01 ft ** 3 / s 6.5531E-01 ft ** 3 / s 65 | 2 1.6369E+00 2.0722E+00 1.1954E+00 ft ** 3 / s 1.1953E+00 ft ** 3 / s 66 | 3 8.6161E-01 2.0428E+00 3.4234E+00 ft ** 3 / s 3.4233E+00 ft ** 3 / s 67 | 4 2.7043E+00 2.0562E+00 1.6881E+00 ft ** 3 / s 1.6880E+00 ft ** 3 / s 68 | 5 1.8523E+01 2.0247E+00 1.6540E+00 ft ** 3 / s 1.6540E+00 ft ** 3 / s 69 | 6 9.2378E+00 2.0257E+00 2.8021E+00 ft ** 3 / s 2.8020E+00 ft ** 3 / s 70 | 71 | 72 | Final results: 73 | 74 | Pipe Flow Flow Flow Head Loss Head Loss 75 | 0 2.9239E-02 m ** 3 / s 1.0326E+00 ft ** 3 / s 4.6345E+02 gal / min 1.5528E+00 m 5.0945E+00 ft 76 | 1 1.8561E-02 m ** 3 / s 6.5547E-01 ft ** 3 / s 2.9420E+02 gal / min 5.7543E-02 m 1.8879E-01 ft 77 | 2 3.3851E-02 m ** 3 / s 1.1954E+00 ft ** 3 / s 5.3655E+02 gal / min 5.9642E-01 m 1.9568E+00 ft 78 | 3 9.6941E-02 m ** 3 / s 3.4234E+00 ft ** 3 / s 1.5365E+03 gal / min 8.9906E-01 m 2.9497E+00 ft 79 | 4 4.7800E-02 m ** 3 / s 1.6881E+00 ft ** 3 / s 7.5765E+02 gal / min 1.3914E+00 m 4.5650E+00 ft 80 | 5 4.6835E-02 m ** 3 / s 1.6540E+00 ft ** 3 / s 7.4235E+02 gal / min 9.3381E+00 m 3.0637E+01 ft 81 | 6 7.9345E-02 m ** 3 / s 2.8021E+00 ft ** 3 / s 1.2577E+03 gal / min 7.8897E+00 m 2.5885E+01 ft 82 | 83 | -------------------------------------------------------------------------------- /tests/test_ch5/ch5_test2.inp: -------------------------------------------------------------------------------- 1 | 7 6 2 10 0 0.001 0.00001217 0.1 2 | 8.0 12.0 10.0 12.0 10.0 6.0 8.0 3 | 1106.0 801.0 1000.0 500.0 1200.0 600.0 800.0 4 | 0.0102 0.0102 0.0102 0.0102 0.0102 0.0102 0.0102 5 | 1 2 1 4 6 | 2000.0 7 | 0 3 -1 -2 5 8 | 1 3 2 -3 -7 9 | -1500.0 10 | 1 2 3 -4 11 | -1000.0 12 | 1 2 -5 -6 13 | -1500.0 14 | 1 2 6 7 15 | 2000.0 16 | 4 1 -2 -3 -4 17 | 4 5 -6 7 2 18 | -------------------------------------------------------------------------------- /tests/test_ch5/ch5_test2.ref.out: -------------------------------------------------------------------------------- 1 | Pipe Diameter Length Rel. Roughness 2 | 0 8.0000E+00 in 1.1060E+03 ft 1.2750E-03 3 | 1 1.2000E+01 in 8.0100E+02 ft 8.5000E-04 4 | 2 1.0000E+01 in 1.0000E+03 ft 1.0200E-03 5 | 3 1.2000E+01 in 5.0000E+02 ft 8.5000E-04 6 | 4 1.0000E+01 in 1.2000E+03 ft 1.0200E-03 7 | 5 6.0000E+00 in 6.0000E+02 ft 1.7000E-03 8 | 6 8.0000E+00 in 8.0000E+02 ft 1.2750E-03 9 | 10 | 11 | Iteration 0 12 | Deviation 1.0000E+02 (Tolerance 1.0000E-03) 13 | 14 | Pipe Kp expp Qcurrent Qpredict 15 | 0 2.0594E+00 2.1104E+00 3.9364E-01 ft ** 3 / s 3.9364E-01 ft ** 3 / s 16 | 1 7.0491E-02 2.2311E+00 1.0593E-01 ft ** 3 / s 1.0593E-01 ft ** 3 / s 17 | 2 2.4362E+00 2.0528E+00 1.8344E+00 ft ** 3 / s 1.8344E+00 ft ** 3 / s 18 | 3 1.0143E+00 2.0372E+00 4.0624E+00 ft ** 3 / s 4.0624E+00 ft ** 3 / s 19 | 4 9.0194E-01 2.1218E+00 4.9957E-01 ft ** 3 / s 4.9957E-01 ft ** 3 / s 20 | 5 3.1435E+01 2.0150E+00 2.8424E+00 ft ** 3 / s 2.8424E+00 ft ** 3 / s 21 | 6 5.4348E+00 2.0412E+00 1.6136E+00 ft ** 3 / s 1.6136E+00 ft ** 3 / s 22 | 23 | Iteration 1 24 | Deviation 3.2066E-01 (Tolerance 1.0000E-03) 25 | 26 | Pipe Kp expp Qcurrent Qpredict 27 | 0 5.2542E+00 2.0556E+00 1.8157E+00 ft ** 3 / s 1.1047E+00 ft ** 3 / s 28 | 1 2.2526E-01 2.1487E+00 8.0387E-01 ft ** 3 / s 4.5490E-01 ft ** 3 / s 29 | 2 1.5464E+00 2.0753E+00 4.1232E-01 ft ** 3 / s 1.1233E+00 ft ** 3 / s 30 | 3 8.4440E-01 2.0436E+00 2.6403E+00 ft ** 3 / s 3.3514E+00 ft ** 3 / s 31 | 4 2.5118E+00 2.0596E+00 2.6196E+00 ft ** 3 / s 1.5596E+00 ft ** 3 / s 32 | 5 1.9919E+01 2.0231E+00 7.2245E-01 ft ** 3 / s 1.7824E+00 ft ** 3 / s 33 | 6 8.8272E+00 2.0268E+00 3.7336E+00 ft ** 3 / s 2.6736E+00 ft ** 3 / s 34 | 35 | Iteration 2 36 | Deviation 4.3779E-02 (Tolerance 1.0000E-03) 37 | 38 | Pipe Kp expp Qcurrent Qpredict 39 | 0 4.9492E+00 2.0584E+00 9.6763E-01 ft ** 3 / s 1.0361E+00 ft ** 3 / s 40 | 1 3.0431E-01 2.1269E+00 8.4141E-01 ft ** 3 / s 6.4816E-01 ft ** 3 / s 41 | 2 1.6325E+00 2.0723E+00 1.2604E+00 ft ** 3 / s 1.1919E+00 ft ** 3 / s 42 | 3 8.6078E-01 2.0429E+00 3.4884E+00 ft ** 3 / s 3.4199E+00 ft ** 3 / s 43 | 4 2.6988E+00 2.0563E+00 1.8090E+00 ft ** 3 / s 1.6843E+00 ft ** 3 / s 44 | 5 1.8563E+01 2.0246E+00 1.5330E+00 ft ** 3 / s 1.6577E+00 ft ** 3 / s 45 | 6 9.2260E+00 2.0257E+00 2.9230E+00 ft ** 3 / s 2.7983E+00 ft ** 3 / s 46 | 47 | Iteration 3 48 | Deviation 1.3512E-03 (Tolerance 1.0000E-03) 49 | 50 | Pipe Kp expp Qcurrent Qpredict 51 | 0 4.9379E+00 2.0585E+00 1.0311E+00 ft ** 3 / s 1.0336E+00 ft ** 3 / s 52 | 1 3.0672E-01 2.1264E+00 6.6009E-01 ft ** 3 / s 6.5412E-01 ft ** 3 / s 53 | 2 1.6357E+00 2.0722E+00 1.1969E+00 ft ** 3 / s 1.1944E+00 ft ** 3 / s 54 | 3 8.6139E-01 2.0428E+00 3.4249E+00 ft ** 3 / s 3.4224E+00 ft ** 3 / s 55 | 4 2.7039E+00 2.0562E+00 1.6912E+00 ft ** 3 / s 1.6877E+00 ft ** 3 / s 56 | 5 1.8526E+01 2.0247E+00 1.6508E+00 ft ** 3 / s 1.6543E+00 ft ** 3 / s 57 | 6 9.2370E+00 2.0257E+00 2.8052E+00 ft ** 3 / s 2.8017E+00 ft ** 3 / s 58 | 59 | Iteration 4 60 | Deviation 3.5758E-05 (Tolerance 1.0000E-03) 61 | 62 | Pipe Kp expp Qcurrent Qpredict 63 | 0 4.9374E+00 2.0585E+00 1.0334E+00 ft ** 3 / s 1.0335E+00 ft ** 3 / s 64 | 1 3.0678E-01 2.1264E+00 6.5444E-01 ft ** 3 / s 6.5428E-01 ft ** 3 / s 65 | 2 1.6358E+00 2.0722E+00 1.1946E+00 ft ** 3 / s 1.1945E+00 ft ** 3 / s 66 | 3 8.6141E-01 2.0428E+00 3.4226E+00 ft ** 3 / s 3.4225E+00 ft ** 3 / s 67 | 4 2.7040E+00 2.0562E+00 1.6879E+00 ft ** 3 / s 1.6878E+00 ft ** 3 / s 68 | 5 1.8526E+01 2.0247E+00 1.6542E+00 ft ** 3 / s 1.6542E+00 ft ** 3 / s 69 | 6 9.2372E+00 2.0257E+00 2.8019E+00 ft ** 3 / s 2.8018E+00 ft ** 3 / s 70 | 71 | 72 | Final results: 73 | 74 | Pipe Flow Flow Flow Head Loss Head Loss 75 | 0 2.9263E-02 m ** 3 / s 1.0334E+00 ft ** 3 / s 4.6383E+02 gal / min 1.5552E+00 m 5.1025E+00 ft 76 | 1 1.8532E-02 m ** 3 / s 6.5444E-01 ft ** 3 / s 2.9373E+02 gal / min 6.1194E-02 m 2.0077E-01 ft 77 | 2 3.3827E-02 m ** 3 / s 1.1946E+00 ft ** 3 / s 5.3617E+02 gal / min 5.9562E-01 m 1.9541E+00 ft 78 | 3 9.6917E-02 m ** 3 / s 3.4226E+00 ft ** 3 / s 1.5362E+03 gal / min 8.9863E-01 m 2.9483E+00 ft 79 | 4 4.7795E-02 m ** 3 / s 1.6879E+00 ft ** 3 / s 7.5756E+02 gal / min 1.3911E+00 m 4.5640E+00 ft 80 | 5 4.6841E-02 m ** 3 / s 1.6542E+00 ft ** 3 / s 7.4244E+02 gal / min 9.3403E+00 m 3.0644E+01 ft 81 | 6 7.9340E-02 m ** 3 / s 2.8019E+00 ft ** 3 / s 1.2576E+03 gal / min 7.8886E+00 m 2.5881E+01 ft 82 | 83 | -------------------------------------------------------------------------------- /tests/test_ch6a.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from distutils import dir_util 5 | from os.path import isdir, splitext 6 | from pytest import fixture #, approx, raises 7 | import jeppson.ch6a as jch6a 8 | 9 | __author__ = "Bob Apthorpe" 10 | __copyright__ = "Bob Apthorpe" 11 | __license__ = "mit" 12 | 13 | 14 | # we reuse a bit of pytest's own testing machinery, this should eventually come 15 | # from a separatedly installable pytest-cli plugin. 16 | pytest_plugins = ["pytester"] 17 | _APPNAME = 'jeppson_ch6a' 18 | 19 | @fixture 20 | def datadir(tmpdir, request): 21 | ''' 22 | Fixture responsible for searching a folder with the same name of test 23 | module and, if available, moving all contents to a temporary directory so 24 | tests can use them freely. 25 | 26 | Shamelessly purloined from https://stackoverflow.com/questions/29627341/pytest-where-to-store-expected-data 27 | and mangled until it worked right 28 | ''' 29 | filename = request.module.__file__ 30 | test_dir, _ = splitext(filename) 31 | 32 | if isdir(test_dir): 33 | dir_util.copy_tree(test_dir, str(tmpdir)) 34 | 35 | return tmpdir 36 | 37 | @fixture 38 | def run(testdir): 39 | def do_run(*args): 40 | args = [_APPNAME] + list(args) 41 | return testdir._run(*args) 42 | return do_run 43 | 44 | def test_ch6a(datadir, run): 45 | # Set up input, output, and reference data file paths 46 | bfn = [ 47 | "ch6a_test1", 48 | "ch6a_test2", 49 | "ch6a_test3" 50 | ] 51 | 52 | for basefn in bfn: 53 | 54 | ifn = datadir.join("{:s}.inp".format(basefn)) 55 | # ofn = datadir.join("{:s}.out".format(basefn)) 56 | rfn = datadir.join("{:s}.ref.out".format(basefn)) 57 | 58 | # Run ch6a.py and check for successful termination 59 | result = run(ifn) 60 | assert result.ret == 0 61 | 62 | # Compare code output with reference output 63 | # with ofn.open("r") as ofh: 64 | # outcontent = ofh.read() 65 | outcontent = result.outlines 66 | 67 | with rfn.open("r") as rfh: 68 | refcontent = rfh.read().splitlines() 69 | 70 | nout = len(refcontent) 71 | assert len(outcontent) == nout 72 | 73 | if len(outcontent) == nout: 74 | for i, outln in enumerate(outcontent): 75 | assert outln == refcontent[i] 76 | 77 | return 78 | 79 | -------------------------------------------------------------------------------- /tests/test_ch6a/ch6a_test1.inp: -------------------------------------------------------------------------------- 1 | 3 3 1 6 5.0E-4 2 | 1 2 10.0 120.0 1.0E3 3 | 1 3 10.0 120.0 1.5E3 4 | 2 3 12.0 120.0 1.0E3 5 | 1 0.0 100.0 6 | 2 -1.5 95.0 7 | 3 -3.0 85.0 8 | -------------------------------------------------------------------------------- /tests/test_ch6a/ch6a_test1.ref.out: -------------------------------------------------------------------------------- 1 | Pipe Diameter Length CHW 2 | 0 1.0000E+01 in 1.0000E+03 ft 120.000 3 | 1 1.0000E+01 in 1.5000E+03 ft 120.000 4 | 2 1.2000E+01 in 1.0000E+03 ft 120.000 5 | 6 | 7 | Flow reversal detected in pipe 2 from 1 to 2 8 | 9 | Iteration 0 10 | Deviation 1.3375E+01 (Tolerance 5.0000E-04 11 | 12 | Pipe Kp Head loss Volumetric Flow 13 | 0 1.6224E+00 9.3445E+00 ft 7.1956E+00 ft ** 3 / s 14 | 1 2.4337E+00 5.9700E+00 ft 3.6931E+00 ft ** 3 / s 15 | 2 6.6761E-01 3.3746E+00 ft 4.1973E+00 ft ** 3 / s 16 | 17 | Junction Head 18 | 0 1.0000E+02 ft 19 | 1 9.0655E+01 ft 20 | 2 9.4030E+01 ft 21 | 22 | Flow reversal detected in pipe 2 from 2 to 1 23 | 24 | Iteration 1 25 | Deviation 7.9192E+00 (Tolerance 5.0000E-04 26 | 27 | Pipe Kp Head loss Volumetric Flow 28 | 0 1.6224E+00 6.4338E+00 ft 4.9542E+00 ft ** 3 / s 29 | 1 2.4337E+00 1.0978E+01 ft 6.7914E+00 ft ** 3 / s 30 | 2 6.6761E-01 4.5446E+00 ft 5.6527E+00 ft ** 3 / s 31 | 32 | Junction Head 33 | 0 1.0000E+02 ft 34 | 1 9.3566E+01 ft 35 | 2 8.9022E+01 ft 36 | 37 | Flow reversal detected in pipe 2 from 1 to 2 38 | 39 | Iteration 2 40 | Deviation 5.2795E+00 (Tolerance 5.0000E-04 41 | 42 | Pipe Kp Head loss Volumetric Flow 43 | 0 1.6224E+00 8.9576E+00 ft 6.8977E+00 ft ** 3 / s 44 | 1 2.4337E+00 8.2228E+00 ft 5.0867E+00 ft ** 3 / s 45 | 2 6.6761E-01 7.3483E-01 ft 9.1400E-01 ft ** 3 / s 46 | 47 | Junction Head 48 | 0 1.0000E+02 ft 49 | 1 9.1042E+01 ft 50 | 2 9.1777E+01 ft 51 | 52 | Flow reversal detected in pipe 2 from 2 to 1 53 | 54 | Iteration 3 55 | Deviation 2.4895E+00 (Tolerance 5.0000E-04 56 | 57 | Pipe Kp Head loss Volumetric Flow 58 | 0 1.6224E+00 8.0191E+00 ft 6.1750E+00 ft ** 3 / s 59 | 1 2.4337E+00 9.7738E+00 ft 6.0462E+00 ft ** 3 / s 60 | 2 6.6761E-01 1.7547E+00 ft 2.1825E+00 ft ** 3 / s 61 | 62 | Junction Head 63 | 0 1.0000E+02 ft 64 | 1 9.1981E+01 ft 65 | 2 9.0226E+01 ft 66 | 67 | Iteration 4 68 | Deviation 1.3795E+00 (Tolerance 5.0000E-04 69 | 70 | Pipe Kp Head loss Volumetric Flow 71 | 0 1.6224E+00 8.6441E+00 ft 6.6563E+00 ft ** 3 / s 72 | 1 2.4337E+00 9.0193E+00 ft 5.5795E+00 ft ** 3 / s 73 | 2 6.6761E-01 3.7515E-01 ft 4.6662E-01 ft ** 3 / s 74 | 75 | Junction Head 76 | 0 1.0000E+02 ft 77 | 1 9.1356E+01 ft 78 | 2 9.0981E+01 ft 79 | 80 | Iteration 5 81 | Deviation 2.1144E-01 (Tolerance 5.0000E-04 82 | 83 | Pipe Kp Head loss Volumetric Flow 84 | 0 1.6224E+00 8.5631E+00 ft 6.5939E+00 ft ** 3 / s 85 | 1 2.4337E+00 9.1497E+00 ft 5.6602E+00 ft ** 3 / s 86 | 2 6.6761E-01 5.8659E-01 ft 7.2961E-01 ft ** 3 / s 87 | 88 | Junction Head 89 | 0 1.0000E+02 ft 90 | 1 9.1437E+01 ft 91 | 2 9.0850E+01 ft 92 | 93 | Final results: 94 | 95 | WARNING: Case 1 did not converge. 96 | 97 | Pipe From To Diameter Length CHW Volumetric Flow Head Loss From Head To Head 98 | 0 0 1 1.0000E+01 in 1.0000E+03 ft 120.000 6.594 ft ** 3 / s 8.5631E+00 ft 1.0000E+02 ft 9.1437E+01 ft 99 | 1 0 2 1.0000E+01 in 1.5000E+03 ft 120.000 5.660 ft ** 3 / s 9.1497E+00 ft 1.0000E+02 ft 9.0850E+01 ft 100 | 2 1 2 1.2000E+01 in 1.0000E+03 ft 120.000 0.730 ft ** 3 / s 5.8659E-01 ft 9.1437E+01 ft 9.0850E+01 ft 101 | 102 | 103 | Junction Inflow Head 104 | 0 0.0000E+00 ft ** 3 / s 1.0000E+02 ft Fixed head 105 | 1 -1.5000E+00 ft ** 3 / s 9.5000E+01 ft 106 | 2 -3.0000E+00 ft ** 3 / s 8.5000E+01 ft 107 | 108 | -------------------------------------------------------------------------------- /tests/test_ch6a/ch6a_test2.inp: -------------------------------------------------------------------------------- 1 | 6 5 3 100 5.0E-4 2 | 1 2 4.0 130.0 150.0 3 | 1 4 3.0 130.0 100.0 4 | 2 3 3.0 130.0 100.0 5 | 2 5 4.0 130.0 100.0 6 | 3 5 3.0 130.0 141.0 7 | 4 3 3.0 130.0 150.0 8 | 1 1.0 115.0 9 | 2 0.0 103.0 10 | 3 -0.25 100.0 11 | 4 -0.25 103.0 12 | 5 -0.5 99.0 13 | -------------------------------------------------------------------------------- /tests/test_ch6a/ch6a_test2.ref.out: -------------------------------------------------------------------------------- 1 | Pipe Diameter Length CHW 2 | 0 4.0000E+00 in 1.5000E+02 ft 130.000 3 | 1 3.0000E+00 in 1.0000E+02 ft 130.000 4 | 2 3.0000E+00 in 1.0000E+02 ft 130.000 5 | 3 4.0000E+00 in 1.0000E+02 ft 130.000 6 | 4 3.0000E+00 in 1.4100E+02 ft 130.000 7 | 5 3.0000E+00 in 1.5000E+02 ft 130.000 8 | 9 | 10 | Iteration 0 11 | Deviation 8.8526E+00 (Tolerance 5.0000E-04 12 | 13 | Pipe Kp Head loss Volumetric Flow 14 | 0 1.8197E+01 7.0884E+00 ft 1.4796E+00 ft ** 3 / s 15 | 1 4.9250E+01 7.7776E+00 ft 9.4830E-01 ft ** 3 / s 16 | 2 4.9250E+01 2.1408E+00 ft 2.6102E-01 ft ** 3 / s 17 | 3 1.2131E+01 2.4667E+00 ft 6.4092E-01 ft ** 3 / s 18 | 4 6.9443E+01 3.2588E-01 ft 3.3005E-02 ft ** 3 / s 19 | 5 7.3875E+01 1.4516E+00 ft 1.4218E-01 ft ** 3 / s 20 | 21 | Junction Head 22 | 0 1.0923E+02 ft 23 | 1 1.0214E+02 ft 24 | 2 1.0000E+02 ft 25 | 3 1.0145E+02 ft 26 | 4 9.9674E+01 ft 27 | 28 | Iteration 1 29 | Deviation 8.3129E-01 (Tolerance 5.0000E-04 30 | 31 | Pipe Kp Head loss Volumetric Flow 32 | 0 1.8197E+01 7.5530E+00 ft 1.5766E+00 ft ** 3 / s 33 | 1 4.9250E+01 8.1126E+00 ft 9.8915E-01 ft ** 3 / s 34 | 2 4.9250E+01 2.1887E+00 ft 2.6686E-01 ft ** 3 / s 35 | 3 1.2131E+01 2.6079E+00 ft 6.7761E-01 ft ** 3 / s 36 | 4 6.9443E+01 4.1922E-01 ft 4.2459E-02 ft ** 3 / s 37 | 5 7.3875E+01 1.6291E+00 ft 1.5957E-01 ft ** 3 / s 38 | 39 | Junction Head 40 | 0 1.0974E+02 ft 41 | 1 1.0219E+02 ft 42 | 2 1.0000E+02 ft 43 | 3 1.0163E+02 ft 44 | 4 9.9581E+01 ft 45 | 46 | Iteration 2 47 | Deviation 1.6601E-02 (Tolerance 5.0000E-04 48 | 49 | Pipe Kp Head loss Volumetric Flow 50 | 0 1.8197E+01 7.5608E+00 ft 1.5782E+00 ft ** 3 / s 51 | 1 4.9250E+01 8.1146E+00 ft 9.8939E-01 ft ** 3 / s 52 | 2 4.9250E+01 2.1872E+00 ft 2.6667E-01 ft ** 3 / s 53 | 3 1.2131E+01 2.6110E+00 ft 6.7843E-01 ft ** 3 / s 54 | 4 6.9443E+01 4.2389E-01 ft 4.2932E-02 ft ** 3 / s 55 | 5 7.3875E+01 1.6333E+00 ft 1.5998E-01 ft ** 3 / s 56 | 57 | Junction Head 58 | 0 1.0975E+02 ft 59 | 1 1.0219E+02 ft 60 | 2 1.0000E+02 ft 61 | 3 1.0163E+02 ft 62 | 4 9.9576E+01 ft 63 | 64 | Iteration 3 65 | Deviation 1.5382E-05 (Tolerance 5.0000E-04 66 | 67 | Pipe Kp Head loss Volumetric Flow 68 | 0 1.8197E+01 7.5608E+00 ft 1.5782E+00 ft ** 3 / s 69 | 1 4.9250E+01 8.1146E+00 ft 9.8939E-01 ft ** 3 / s 70 | 2 4.9250E+01 2.1871E+00 ft 2.6667E-01 ft ** 3 / s 71 | 3 1.2131E+01 2.6111E+00 ft 6.7843E-01 ft ** 3 / s 72 | 4 6.9443E+01 4.2390E-01 ft 4.2933E-02 ft ** 3 / s 73 | 5 7.3875E+01 1.6333E+00 ft 1.5998E-01 ft ** 3 / s 74 | 75 | Junction Head 76 | 0 1.0975E+02 ft 77 | 1 1.0219E+02 ft 78 | 2 1.0000E+02 ft 79 | 3 1.0163E+02 ft 80 | 4 9.9576E+01 ft 81 | 82 | Final results: 83 | 84 | Pipe From To Diameter Length CHW Volumetric Flow Head Loss From Head To Head 85 | 0 0 1 4.0000E+00 in 1.5000E+02 ft 130.000 1.578 ft ** 3 / s 7.5608E+00 ft 1.0975E+02 ft 1.0219E+02 ft 86 | 1 0 3 3.0000E+00 in 1.0000E+02 ft 130.000 0.989 ft ** 3 / s 8.1146E+00 ft 1.0975E+02 ft 1.0163E+02 ft 87 | 2 1 2 3.0000E+00 in 1.0000E+02 ft 130.000 0.267 ft ** 3 / s 2.1871E+00 ft 1.0219E+02 ft 1.0000E+02 ft 88 | 3 1 4 4.0000E+00 in 1.0000E+02 ft 130.000 0.678 ft ** 3 / s 2.6111E+00 ft 1.0219E+02 ft 9.9576E+01 ft 89 | 4 2 4 3.0000E+00 in 1.4100E+02 ft 130.000 0.043 ft ** 3 / s 4.2390E-01 ft 1.0000E+02 ft 9.9576E+01 ft 90 | 5 3 2 3.0000E+00 in 1.5000E+02 ft 130.000 0.160 ft ** 3 / s 1.6333E+00 ft 1.0163E+02 ft 1.0000E+02 ft 91 | 92 | 93 | Junction Inflow Head 94 | 0 1.0000E+00 ft ** 3 / s 1.1500E+02 ft 95 | 1 0.0000E+00 ft ** 3 / s 1.0300E+02 ft 96 | 2 -2.5000E-01 ft ** 3 / s 1.0000E+02 ft Fixed head 97 | 3 -2.5000E-01 ft ** 3 / s 1.0300E+02 ft 98 | 4 -5.0000E-01 ft ** 3 / s 9.9000E+01 ft 99 | 100 | -------------------------------------------------------------------------------- /tests/test_ch6a/ch6a_test3.inp: -------------------------------------------------------------------------------- 1 | 9 7 1 100 5.0E-4 2 | 1 2 10.0 120.0 1.0E3 3 | 2 3 8.0 120.0 1.5E3 4 | 2 5 8.0 120.0 2.0E3 5 | 3 4 8.0 120.0 2.0E3 6 | 5 4 8.0 120.0 1.5E3 7 | 4 7 10.0 120.0 2.0E3 8 | 5 6 8.0 120.0 2.0E3 9 | 5 7 6.0 120.0 3.0E3 10 | 6 7 10.0 120.0 2.0E3 11 | 1 0.0 200.0 12 | 2 0.0 190.0 13 | 3 1.0 180.0 14 | 4 -0.5 160.0 15 | 5 -0.5 170.0 16 | 6 -1.0 150.0 17 | 7 -1.5 145.0 18 | -------------------------------------------------------------------------------- /tests/test_ch6a/ch6a_test3.ref.out: -------------------------------------------------------------------------------- 1 | Pipe Diameter Length CHW 2 | 0 1.0000E+01 in 1.0000E+03 ft 120.000 3 | 1 8.0000E+00 in 1.5000E+03 ft 120.000 4 | 2 8.0000E+00 in 2.0000E+03 ft 120.000 5 | 3 8.0000E+00 in 2.0000E+03 ft 120.000 6 | 4 8.0000E+00 in 1.5000E+03 ft 120.000 7 | 5 1.0000E+01 in 2.0000E+03 ft 120.000 8 | 6 8.0000E+00 in 2.0000E+03 ft 120.000 9 | 7 6.0000E+00 in 3.0000E+03 ft 120.000 10 | 8 1.0000E+01 in 2.0000E+03 ft 120.000 11 | 12 | 13 | Flow reversal detected in pipe 4 from 4 to 3 14 | Flow reversal detected in pipe 7 from 4 to 6 15 | Flow reversal detected in pipe 8 from 5 to 6 16 | 17 | Iteration 0 18 | Deviation 5.0664E+01 (Tolerance 5.0000E-04 19 | 20 | Pipe Kp Head loss Volumetric Flow 21 | 0 1.6224E+00 8.8210E+00 ft 6.7925E+00 ft ** 3 / s 22 | 1 7.2152E+00 1.2401E+00 ft 4.2659E-01 ft ** 3 / s 23 | 2 9.6202E+00 2.9648E+01 ft 8.7314E+00 ft ** 3 / s 24 | 3 9.6202E+00 2.3588E+01 ft 6.9467E+00 ft ** 3 / s 25 | 4 7.2152E+00 4.8200E+00 ft 1.6580E+00 ft ** 3 / s 26 | 5 3.2449E+00 3.8313E+00 ft 2.0291E+00 ft ** 3 / s 27 | 6 9.6202E+00 4.3241E+00 ft 1.2734E+00 ft ** 3 / s 28 | 7 5.8583E+01 9.8869E-01 ft 1.0976E-01 ft ** 3 / s 29 | 8 3.2449E+00 5.3128E+00 ft 2.8137E+00 ft ** 3 / s 30 | 31 | Junction Head 32 | 0 2.0000E+02 ft 33 | 1 1.9118E+02 ft 34 | 2 1.8994E+02 ft 35 | 3 1.6635E+02 ft 36 | 4 1.6153E+02 ft 37 | 5 1.5721E+02 ft 38 | 6 1.6252E+02 ft 39 | 40 | Flow reversal detected in pipe 4 from 3 to 4 41 | Flow reversal detected in pipe 7 from 6 to 4 42 | Flow reversal detected in pipe 8 from 6 to 5 43 | 44 | Iteration 1 45 | Deviation 1.9211E+01 (Tolerance 5.0000E-04 46 | 47 | Pipe Kp Head loss Volumetric Flow 48 | 0 1.6224E+00 8.8530E+00 ft 6.8171E+00 ft ** 3 / s 49 | 1 7.2152E+00 3.4587E+00 ft 1.1898E+00 ft ** 3 / s 50 | 2 9.6202E+00 2.6778E+01 ft 7.8859E+00 ft ** 3 / s 51 | 3 9.6202E+00 2.7264E+01 ft 8.0292E+00 ft ** 3 / s 52 | 4 7.2152E+00 3.9453E+00 ft 1.3572E+00 ft ** 3 / s 53 | 5 3.2449E+00 4.8128E+00 ft 2.5489E+00 ft ** 3 / s 54 | 6 9.6202E+00 5.9079E+00 ft 1.7399E+00 ft ** 3 / s 55 | 7 5.8583E+01 8.7582E+00 ft 9.7234E-01 ft ** 3 / s 56 | 8 3.2449E+00 2.8503E+00 ft 1.5095E+00 ft ** 3 / s 57 | 58 | Junction Head 59 | 0 2.0000E+02 ft 60 | 1 1.9115E+02 ft 61 | 2 1.8769E+02 ft 62 | 3 1.6042E+02 ft 63 | 4 1.6437E+02 ft 64 | 5 1.5846E+02 ft 65 | 6 1.5561E+02 ft 66 | 67 | Flow reversal detected in pipe 4 from 4 to 3 68 | Flow reversal detected in pipe 8 from 5 to 6 69 | 70 | Iteration 2 71 | Deviation 1.1354E+01 (Tolerance 5.0000E-04 72 | 73 | Pipe Kp Head loss Volumetric Flow 74 | 0 1.6224E+00 8.8531E+00 ft 6.8172E+00 ft ** 3 / s 75 | 1 7.2152E+00 3.4035E+00 ft 1.1708E+00 ft ** 3 / s 76 | 2 9.6202E+00 2.9499E+01 ft 8.6874E+00 ft ** 3 / s 77 | 3 9.6202E+00 2.4716E+01 ft 7.2786E+00 ft ** 3 / s 78 | 4 7.2152E+00 1.3802E+00 ft 4.7476E-01 ft ** 3 / s 79 | 5 3.2449E+00 5.7130E+00 ft 3.0256E+00 ft ** 3 / s 80 | 6 9.6202E+00 7.4557E+00 ft 2.1957E+00 ft ** 3 / s 81 | 7 5.8583E+01 4.3328E+00 ft 4.8103E-01 ft ** 3 / s 82 | 8 3.2449E+00 3.1229E+00 ft 1.6539E+00 ft ** 3 / s 83 | 84 | Junction Head 85 | 0 2.0000E+02 ft 86 | 1 1.9115E+02 ft 87 | 2 1.8774E+02 ft 88 | 3 1.6303E+02 ft 89 | 4 1.6165E+02 ft 90 | 5 1.5419E+02 ft 91 | 6 1.5731E+02 ft 92 | 93 | Flow reversal detected in pipe 4 from 3 to 4 94 | Flow reversal detected in pipe 8 from 6 to 5 95 | 96 | Iteration 3 97 | Deviation 8.4781E+00 (Tolerance 5.0000E-04 98 | 99 | Pipe Kp Head loss Volumetric Flow 100 | 0 1.6224E+00 8.8531E+00 ft 6.8172E+00 ft ** 3 / s 101 | 1 7.2152E+00 3.8880E+00 ft 1.3374E+00 ft ** 3 / s 102 | 2 9.6202E+00 2.8035E+01 ft 8.2562E+00 ft ** 3 / s 103 | 3 9.6202E+00 2.6179E+01 ft 7.7096E+00 ft ** 3 / s 104 | 4 7.2152E+00 2.0320E+00 ft 6.9898E-01 ft ** 3 / s 105 | 5 3.2449E+00 5.6944E+00 ft 3.0158E+00 ft ** 3 / s 106 | 6 9.6202E+00 6.2679E+00 ft 1.8459E+00 ft ** 3 / s 107 | 7 5.8583E+01 7.7264E+00 ft 8.5778E-01 ft ** 3 / s 108 | 8 3.2449E+00 1.4585E+00 ft 7.7241E-01 ft ** 3 / s 109 | 110 | Junction Head 111 | 0 2.0000E+02 ft 112 | 1 1.9115E+02 ft 113 | 2 1.8726E+02 ft 114 | 3 1.6108E+02 ft 115 | 4 1.6311E+02 ft 116 | 5 1.5684E+02 ft 117 | 6 1.5539E+02 ft 118 | 119 | Flow reversal detected in pipe 4 from 4 to 3 120 | Flow reversal detected in pipe 8 from 5 to 6 121 | 122 | Iteration 4 123 | Deviation 6.0534E+00 (Tolerance 5.0000E-04 124 | 125 | Pipe Kp Head loss Volumetric Flow 126 | 0 1.6224E+00 8.8531E+00 ft 6.8172E+00 ft ** 3 / s 127 | 1 7.2152E+00 3.5411E+00 ft 1.2181E+00 ft ** 3 / s 128 | 2 9.6202E+00 2.9103E+01 ft 8.5707E+00 ft ** 3 / s 129 | 3 9.6202E+00 2.5180E+01 ft 7.4155E+00 ft ** 3 / s 130 | 4 7.2152E+00 3.8156E-01 ft 1.3125E-01 ft ** 3 / s 131 | 5 3.2449E+00 5.7541E+00 ft 3.0474E+00 ft ** 3 / s 132 | 6 9.6202E+00 7.2070E+00 ft 2.1224E+00 ft ** 3 / s 133 | 7 5.8583E+01 5.3726E+00 ft 5.9646E-01 ft ** 3 / s 134 | 8 3.2449E+00 1.8345E+00 ft 9.7154E-01 ft ** 3 / s 135 | 136 | Junction Head 137 | 0 2.0000E+02 ft 138 | 1 1.9115E+02 ft 139 | 2 1.8761E+02 ft 140 | 3 1.6243E+02 ft 141 | 4 1.6204E+02 ft 142 | 5 1.5484E+02 ft 143 | 6 1.5667E+02 ft 144 | 145 | Flow reversal detected in pipe 4 from 3 to 4 146 | Flow reversal detected in pipe 8 from 6 to 5 147 | 148 | Iteration 5 149 | Deviation 3.9993E+00 (Tolerance 5.0000E-04 150 | 151 | Pipe Kp Head loss Volumetric Flow 152 | 0 1.6224E+00 8.8531E+00 ft 6.8172E+00 ft ** 3 / s 153 | 1 7.2152E+00 3.7405E+00 ft 1.2867E+00 ft ** 3 / s 154 | 2 9.6202E+00 2.8520E+01 ft 8.3991E+00 ft ** 3 / s 155 | 3 9.6202E+00 2.5743E+01 ft 7.5811E+00 ft ** 3 / s 156 | 4 7.2152E+00 9.6297E-01 ft 3.3125E-01 ft ** 3 / s 157 | 5 3.2449E+00 5.9393E+00 ft 3.1455E+00 ft ** 3 / s 158 | 6 9.6202E+00 6.2814E+00 ft 1.8498E+00 ft ** 3 / s 159 | 7 5.8583E+01 6.9023E+00 ft 7.6629E-01 ft ** 3 / s 160 | 8 3.2449E+00 6.2090E-01 ft 3.2883E-01 ft ** 3 / s 161 | 162 | Junction Head 163 | 0 2.0000E+02 ft 164 | 1 1.9115E+02 ft 165 | 2 1.8741E+02 ft 166 | 3 1.6166E+02 ft 167 | 4 1.6263E+02 ft 168 | 5 1.5635E+02 ft 169 | 6 1.5572E+02 ft 170 | 171 | Flow reversal detected in pipe 8 from 5 to 6 172 | 173 | Iteration 6 174 | Deviation 2.5502E+00 (Tolerance 5.0000E-04 175 | 176 | Pipe Kp Head loss Volumetric Flow 177 | 0 1.6224E+00 8.8531E+00 ft 6.8172E+00 ft ** 3 / s 178 | 1 7.2152E+00 3.6212E+00 ft 1.2457E+00 ft ** 3 / s 179 | 2 9.6202E+00 2.8886E+01 ft 8.5067E+00 ft ** 3 / s 180 | 3 9.6202E+00 2.5400E+01 ft 7.4801E+00 ft ** 3 / s 181 | 4 7.2152E+00 1.3528E-01 ft 4.6534E-02 ft ** 3 / s 182 | 5 3.2449E+00 5.8111E+00 ft 3.0776E+00 ft ** 3 / s 183 | 6 9.6202E+00 6.9286E+00 ft 2.0405E+00 ft ** 3 / s 184 | 7 5.8583E+01 5.9464E+00 ft 6.6017E-01 ft ** 3 / s 185 | 8 3.2449E+00 9.8222E-01 ft 5.2019E-01 ft ** 3 / s 186 | 187 | Junction Head 188 | 0 2.0000E+02 ft 189 | 1 1.9115E+02 ft 190 | 2 1.8753E+02 ft 191 | 3 1.6213E+02 ft 192 | 4 1.6226E+02 ft 193 | 5 1.5533E+02 ft 194 | 6 1.5631E+02 ft 195 | 196 | Flow reversal detected in pipe 8 from 6 to 5 197 | 198 | Iteration 7 199 | Deviation 1.3377E+00 (Tolerance 5.0000E-04 200 | 201 | Pipe Kp Head loss Volumetric Flow 202 | 0 1.6224E+00 8.8531E+00 ft 6.8172E+00 ft ** 3 / s 203 | 1 7.2152E+00 3.6509E+00 ft 1.2559E+00 ft ** 3 / s 204 | 2 9.6202E+00 2.8799E+01 ft 8.4812E+00 ft ** 3 / s 205 | 3 9.6202E+00 2.5483E+01 ft 7.5047E+00 ft ** 3 / s 206 | 4 7.2152E+00 3.3530E-01 ft 1.1534E-01 ft ** 3 / s 207 | 5 3.2449E+00 6.0663E+00 ft 3.2127E+00 ft ** 3 / s 208 | 6 9.6202E+00 6.2759E+00 ft 1.8482E+00 ft ** 3 / s 209 | 7 5.8583E+01 6.4016E+00 ft 7.1071E-01 ft ** 3 / s 210 | 8 3.2449E+00 1.2570E-01 ft 6.6570E-02 ft ** 3 / s 211 | 212 | Junction Head 213 | 0 2.0000E+02 ft 214 | 1 1.9115E+02 ft 215 | 2 1.8750E+02 ft 216 | 3 1.6201E+02 ft 217 | 4 1.6235E+02 ft 218 | 5 1.5607E+02 ft 219 | 6 1.5595E+02 ft 220 | 221 | Flow reversal detected in pipe 8 from 5 to 6 222 | 223 | Iteration 8 224 | Deviation 4.8289E-01 (Tolerance 5.0000E-04 225 | 226 | Pipe Kp Head loss Volumetric Flow 227 | 0 1.6224E+00 8.8531E+00 ft 6.8172E+00 ft ** 3 / s 228 | 1 7.2152E+00 3.6515E+00 ft 1.2561E+00 ft ** 3 / s 229 | 2 9.6202E+00 2.8798E+01 ft 8.4808E+00 ft ** 3 / s 230 | 3 9.6202E+00 2.5485E+01 ft 7.5052E+00 ft ** 3 / s 231 | 4 7.2152E+00 3.3855E-01 ft 1.1646E-01 ft ** 3 / s 232 | 5 3.2449E+00 5.9220E+00 ft 3.1363E+00 ft ** 3 / s 233 | 6 9.6202E+00 6.6140E+00 ft 1.9478E+00 ft ** 3 / s 234 | 7 5.8583E+01 6.2606E+00 ft 6.9505E-01 ft ** 3 / s 235 | 8 3.2449E+00 3.5342E-01 ft 1.8717E-01 ft ** 3 / s 236 | 237 | Junction Head 238 | 0 2.0000E+02 ft 239 | 1 1.9115E+02 ft 240 | 2 1.8750E+02 ft 241 | 3 1.6201E+02 ft 242 | 4 1.6235E+02 ft 243 | 5 1.5574E+02 ft 244 | 6 1.5609E+02 ft 245 | 246 | Iteration 9 247 | Deviation 2.6438E-01 (Tolerance 5.0000E-04 248 | 249 | Pipe Kp Head loss Volumetric Flow 250 | 0 1.6224E+00 8.8531E+00 ft 6.8172E+00 ft ** 3 / s 251 | 1 7.2152E+00 3.6550E+00 ft 1.2573E+00 ft ** 3 / s 252 | 2 9.6202E+00 2.8787E+01 ft 8.4776E+00 ft ** 3 / s 253 | 3 9.6202E+00 2.5495E+01 ft 7.5082E+00 ft ** 3 / s 254 | 4 7.2152E+00 3.6321E-01 ft 1.2494E-01 ft ** 3 / s 255 | 5 3.2449E+00 5.9854E+00 ft 3.1699E+00 ft ** 3 / s 256 | 6 9.6202E+00 6.4659E+00 ft 1.9042E+00 ft ** 3 / s 257 | 7 5.8583E+01 6.3486E+00 ft 7.0483E-01 ft ** 3 / s 258 | 8 3.2449E+00 1.1728E-01 ft 6.2113E-02 ft ** 3 / s 259 | 260 | Junction Head 261 | 0 2.0000E+02 ft 262 | 1 1.9115E+02 ft 263 | 2 1.8749E+02 ft 264 | 3 1.6200E+02 ft 265 | 4 1.6236E+02 ft 266 | 5 1.5589E+02 ft 267 | 6 1.5601E+02 ft 268 | 269 | Iteration 10 270 | Deviation 3.6405E-02 (Tolerance 5.0000E-04 271 | 272 | Pipe Kp Head loss Volumetric Flow 273 | 0 1.6224E+00 8.8531E+00 ft 6.8172E+00 ft ** 3 / s 274 | 1 7.2152E+00 3.6546E+00 ft 1.2571E+00 ft ** 3 / s 275 | 2 9.6202E+00 2.8788E+01 ft 8.4780E+00 ft ** 3 / s 276 | 3 9.6202E+00 2.5494E+01 ft 7.5078E+00 ft ** 3 / s 277 | 4 7.2152E+00 3.6012E-01 ft 1.2388E-01 ft ** 3 / s 278 | 5 3.2449E+00 5.9770E+00 ft 3.1654E+00 ft ** 3 / s 279 | 6 9.6202E+00 6.4872E+00 ft 1.9105E+00 ft ** 3 / s 280 | 7 5.8583E+01 6.3371E+00 ft 7.0354E-01 ft ** 3 / s 281 | 8 3.2449E+00 1.5015E-01 ft 7.9520E-02 ft ** 3 / s 282 | 283 | Junction Head 284 | 0 2.0000E+02 ft 285 | 1 1.9115E+02 ft 286 | 2 1.8749E+02 ft 287 | 3 1.6200E+02 ft 288 | 4 1.6236E+02 ft 289 | 5 1.5587E+02 ft 290 | 6 1.5602E+02 ft 291 | 292 | Iteration 11 293 | Deviation 2.2066E-03 (Tolerance 5.0000E-04 294 | 295 | Pipe Kp Head loss Volumetric Flow 296 | 0 1.6224E+00 8.8531E+00 ft 6.8172E+00 ft ** 3 / s 297 | 1 7.2152E+00 3.6546E+00 ft 1.2571E+00 ft ** 3 / s 298 | 2 9.6202E+00 2.8788E+01 ft 8.4780E+00 ft ** 3 / s 299 | 3 9.6202E+00 2.5494E+01 ft 7.5078E+00 ft ** 3 / s 300 | 4 7.2152E+00 3.5991E-01 ft 1.2381E-01 ft ** 3 / s 301 | 5 3.2449E+00 5.9764E+00 ft 3.1652E+00 ft ** 3 / s 302 | 6 9.6202E+00 6.4885E+00 ft 1.9108E+00 ft ** 3 / s 303 | 7 5.8583E+01 6.3364E+00 ft 7.0346E-01 ft ** 3 / s 304 | 8 3.2449E+00 1.5212E-01 ft 8.0566E-02 ft ** 3 / s 305 | 306 | Junction Head 307 | 0 2.0000E+02 ft 308 | 1 1.9115E+02 ft 309 | 2 1.8749E+02 ft 310 | 3 1.6200E+02 ft 311 | 4 1.6236E+02 ft 312 | 5 1.5587E+02 ft 313 | 6 1.5602E+02 ft 314 | 315 | Iteration 12 316 | Deviation 6.2718E-06 (Tolerance 5.0000E-04 317 | 318 | Pipe Kp Head loss Volumetric Flow 319 | 0 1.6224E+00 8.8531E+00 ft 6.8172E+00 ft ** 3 / s 320 | 1 7.2152E+00 3.6546E+00 ft 1.2571E+00 ft ** 3 / s 321 | 2 9.6202E+00 2.8788E+01 ft 8.4780E+00 ft ** 3 / s 322 | 3 9.6202E+00 2.5494E+01 ft 7.5078E+00 ft ** 3 / s 323 | 4 7.2152E+00 3.5991E-01 ft 1.2381E-01 ft ** 3 / s 324 | 5 3.2449E+00 5.9764E+00 ft 3.1652E+00 ft ** 3 / s 325 | 6 9.6202E+00 6.4885E+00 ft 1.9108E+00 ft ** 3 / s 326 | 7 5.8583E+01 6.3364E+00 ft 7.0346E-01 ft ** 3 / s 327 | 8 3.2449E+00 1.5213E-01 ft 8.0568E-02 ft ** 3 / s 328 | 329 | Junction Head 330 | 0 2.0000E+02 ft 331 | 1 1.9115E+02 ft 332 | 2 1.8749E+02 ft 333 | 3 1.6200E+02 ft 334 | 4 1.6236E+02 ft 335 | 5 1.5587E+02 ft 336 | 6 1.5602E+02 ft 337 | 338 | Final results: 339 | 340 | Pipe From To Diameter Length CHW Volumetric Flow Head Loss From Head To Head 341 | 0 0 1 1.0000E+01 in 1.0000E+03 ft 120.000 6.817 ft ** 3 / s 8.8531E+00 ft 2.0000E+02 ft 1.9115E+02 ft 342 | 1 1 2 8.0000E+00 in 1.5000E+03 ft 120.000 1.257 ft ** 3 / s 3.6546E+00 ft 1.9115E+02 ft 1.8749E+02 ft 343 | 2 1 4 8.0000E+00 in 2.0000E+03 ft 120.000 8.478 ft ** 3 / s 2.8788E+01 ft 1.9115E+02 ft 1.6236E+02 ft 344 | 3 2 3 8.0000E+00 in 2.0000E+03 ft 120.000 7.508 ft ** 3 / s 2.5494E+01 ft 1.8749E+02 ft 1.6200E+02 ft 345 | 4 4 3 8.0000E+00 in 1.5000E+03 ft 120.000 0.124 ft ** 3 / s 3.5991E-01 ft 1.6236E+02 ft 1.6200E+02 ft 346 | 5 3 6 1.0000E+01 in 2.0000E+03 ft 120.000 3.165 ft ** 3 / s 5.9764E+00 ft 1.6200E+02 ft 1.5602E+02 ft 347 | 6 4 5 8.0000E+00 in 2.0000E+03 ft 120.000 1.911 ft ** 3 / s 6.4885E+00 ft 1.6236E+02 ft 1.5587E+02 ft 348 | 7 4 6 6.0000E+00 in 3.0000E+03 ft 120.000 0.703 ft ** 3 / s 6.3364E+00 ft 1.6236E+02 ft 1.5602E+02 ft 349 | 8 6 5 1.0000E+01 in 2.0000E+03 ft 120.000 0.081 ft ** 3 / s 1.5213E-01 ft 1.5602E+02 ft 1.5587E+02 ft 350 | 351 | 352 | Junction Inflow Head 353 | 0 0.0000E+00 ft ** 3 / s 2.0000E+02 ft Fixed head 354 | 1 0.0000E+00 ft ** 3 / s 1.9000E+02 ft 355 | 2 1.0000E+00 ft ** 3 / s 1.8000E+02 ft 356 | 3 -5.0000E-01 ft ** 3 / s 1.6000E+02 ft 357 | 4 -5.0000E-01 ft ** 3 / s 1.7000E+02 ft 358 | 5 -1.0000E+00 ft ** 3 / s 1.5000E+02 ft 359 | 6 -1.5000E+00 ft ** 3 / s 1.4500E+02 ft 360 | 361 | -------------------------------------------------------------------------------- /tests/test_ch6b.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from distutils import dir_util 5 | from os.path import isdir, splitext 6 | from pytest import fixture #, approx, raises 7 | import jeppson.ch6b as jch6b 8 | 9 | __author__ = "Bob Apthorpe" 10 | __copyright__ = "Bob Apthorpe" 11 | __license__ = "mit" 12 | 13 | 14 | # we reuse a bit of pytest's own testing machinery, this should eventually come 15 | # from a separatedly installable pytest-cli plugin. 16 | pytest_plugins = ["pytester"] 17 | _APPNAME = 'jeppson_ch6b' 18 | 19 | @fixture 20 | def datadir(tmpdir, request): 21 | ''' 22 | Fixture responsible for searching a folder with the same name of test 23 | module and, if available, moving all contents to a temporary directory so 24 | tests can use them freely. 25 | 26 | Shamelessly purloined from https://stackoverflow.com/questions/29627341/pytest-where-to-store-expected-data 27 | and mangled until it worked right 28 | ''' 29 | filename = request.module.__file__ 30 | test_dir, _ = splitext(filename) 31 | 32 | if isdir(test_dir): 33 | dir_util.copy_tree(test_dir, str(tmpdir)) 34 | 35 | return tmpdir 36 | 37 | @fixture 38 | def run(testdir): 39 | def do_run(*args): 40 | args = [_APPNAME] + list(args) 41 | return testdir._run(*args) 42 | return do_run 43 | 44 | def test_ch6b(datadir, run): 45 | # Set up input, output, and reference data file paths 46 | bfn = [ 47 | "ch6b_test0", 48 | "ch6b_test1", 49 | "ch6b_test2" 50 | ] 51 | 52 | for basefn in bfn: 53 | 54 | ifn = datadir.join("{:s}.inp".format(basefn)) 55 | # ofn = datadir.join("{:s}.out".format(basefn)) 56 | rfn = datadir.join("{:s}.ref.out".format(basefn)) 57 | 58 | # Run ch6b.py and check for successful termination 59 | result = run(ifn) 60 | assert result.ret == 0 61 | 62 | # Compare code output with reference output 63 | # with ofn.open("r") as ofh: 64 | # outcontent = ofh.read() 65 | outcontent = result.outlines 66 | 67 | with rfn.open("r") as rfh: 68 | refcontent = rfh.read().splitlines() 69 | 70 | nout = len(refcontent) 71 | assert len(outcontent) == nout 72 | 73 | if len(outcontent) == nout: 74 | for i, outln in enumerate(outcontent): 75 | assert outln == refcontent[i] 76 | 77 | return 78 | 79 | -------------------------------------------------------------------------------- /tests/test_ch6b/ch6b_test0.inp: -------------------------------------------------------------------------------- 1 | 7 2 10 0 0 2 | 1 12. 2000.0 95. 5.0 3 | 2 10. 1500.0 95. 2.0 4 | 3 6. 2000.0 95. 2.5 5 | 4 8. 1500.0 95. 5.0 6 | 5 8. 2000.0 95. 3.0 7 | 6 6. 1000.0 95. 0.5 8 | 7 10. 2000.0 95. 4.5 9 | 4 1 2 -3 -4 10 | 4 5 6 -7 -2 11 | -------------------------------------------------------------------------------- /tests/test_ch6b/ch6b_test0.ref.out: -------------------------------------------------------------------------------- 1 | Pipe Diameter Length CHW 2 | 0 1.2000E+01 in 2.0000E+03 ft 95.0 3 | 1 1.0000E+01 in 1.5000E+03 ft 95.0 4 | 2 6.0000E+00 in 2.0000E+03 ft 95.0 5 | 3 8.0000E+00 in 1.5000E+03 ft 95.0 6 | 4 8.0000E+00 in 2.0000E+03 ft 95.0 7 | 5 6.0000E+00 in 1.0000E+03 ft 95.0 8 | 6 1.0000E+01 in 2.0000E+03 ft 95.0 9 | 10 | 11 | Iteration 0: Deviation 1.4650E+00, Tolerance 1.0000E-03 12 | Iteration 1: Deviation 5.0564E-01, Tolerance 1.0000E-03 13 | Iteration 2: Deviation 5.8433E-02, Tolerance 1.0000E-03 14 | Iteration 3: Deviation 1.0041E-03, Tolerance 1.0000E-03 15 | Iteration 4: Deviation 2.8327E-07, Tolerance 1.0000E-03 16 | 17 | Final results: 18 | 19 | Pipe Flow Flow Flow Head Loss Head Loss 20 | 0 1.9443E-01 m ** 3 / s 6.8661E+00 ft ** 3 / s 3.0817E+03 gal / min 2.2229E+01 m 7.2930E+01 ft 21 | 1 1.0854E-01 m ** 3 / s 3.8331E+00 ft ** 3 / s 1.7204E+03 gal / min 1.3766E+01 m 4.5164E+01 ft 22 | 2 1.7950E-02 m ** 3 / s 6.3389E-01 ft ** 3 / s 2.8451E+02 gal / min 7.8875E+00 m 2.5878E+01 ft 23 | 3 8.8742E-02 m ** 3 / s 3.1339E+00 ft ** 3 / s 1.4066E+03 gal / min 2.8107E+01 m 9.2216E+01 ft 24 | 4 8.5886E-02 m ** 3 / s 3.0330E+00 ft ** 3 / s 1.3613E+03 gal / min 3.5273E+01 m 1.1573E+02 ft 25 | 5 1.5093E-02 m ** 3 / s 5.3302E-01 ft ** 3 / s 2.3924E+02 gal / min 2.8610E+00 m 9.3865E+00 ft 26 | 6 1.2649E-01 m ** 3 / s 4.4670E+00 ft ** 3 / s 2.0049E+03 gal / min 2.4368E+01 m 7.9949E+01 ft 27 | 28 | -------------------------------------------------------------------------------- /tests/test_ch6b/ch6b_test1.inp: -------------------------------------------------------------------------------- 1 | 8 3 10 0 0 2 | 1 12. 2000.0 95. 5.0 3 | 2 10. 1500.0 95. 2.0 4 | 3 6. 2000.0 95. 2.5 5 | 4 8. 1500.0 95. 5.0 6 | 5 8. 2000.0 95. 3.0 7 | 6 6. 1000.0 95. 0.5 8 | 7 10. 2000.0 95. 4.5 9 | 8 10. 3000.0 95. 2.0 10 | 3 1 2 -8 11 | 4 5 6 -7 -2 12 | 3 8 -3 -4 13 | -------------------------------------------------------------------------------- /tests/test_ch6b/ch6b_test1.ref.out: -------------------------------------------------------------------------------- 1 | Pipe Diameter Length CHW 2 | 0 1.2000E+01 in 2.0000E+03 ft 95.0 3 | 1 1.0000E+01 in 1.5000E+03 ft 95.0 4 | 2 6.0000E+00 in 2.0000E+03 ft 95.0 5 | 3 8.0000E+00 in 1.5000E+03 ft 95.0 6 | 4 8.0000E+00 in 2.0000E+03 ft 95.0 7 | 5 6.0000E+00 in 1.0000E+03 ft 95.0 8 | 6 1.0000E+01 in 2.0000E+03 ft 95.0 9 | 7 1.0000E+01 in 3.0000E+03 ft 95.0 10 | 11 | 12 | Iteration 0: Deviation 1.8330E+00, Tolerance 1.0000E-03 13 | Iteration 1: Deviation 1.0892E+00, Tolerance 1.0000E-03 14 | Iteration 2: Deviation 2.3840E-01, Tolerance 1.0000E-03 15 | Iteration 3: Deviation 2.2307E-02, Tolerance 1.0000E-03 16 | Iteration 4: Deviation 1.9017E-04, Tolerance 1.0000E-03 17 | 18 | Final results: 19 | 20 | Pipe Flow Flow Flow Head Loss Head Loss 21 | 0 1.6176E-01 m ** 3 / s 5.7124E+00 ft ** 3 / s 2.5639E+03 gal / min 1.5811E+01 m 5.1875E+01 ft 22 | 1 8.0062E-02 m ** 3 / s 2.8274E+00 ft ** 3 / s 1.2690E+03 gal / min 7.8352E+00 m 2.5706E+01 ft 23 | 2 7.2712E-03 m ** 3 / s 2.5678E-01 ft ** 3 / s 1.1525E+02 gal / min 1.4797E+00 m 4.8548E+00 ft 24 | 3 7.8063E-02 m ** 3 / s 2.7568E+00 ft ** 3 / s 1.2373E+03 gal / min 2.2167E+01 m 7.2726E+01 ft 25 | 4 8.1695E-02 m ** 3 / s 2.8850E+00 ft ** 3 / s 1.2949E+03 gal / min 3.2153E+01 m 1.0549E+02 ft 26 | 5 1.0903E-02 m ** 3 / s 3.8505E-01 ft ** 3 / s 1.7282E+02 gal / min 1.5667E+00 m 5.1401E+00 ft 27 | 6 1.3068E-01 m ** 3 / s 4.6150E+00 ft ** 3 / s 2.0713E+03 gal / min 2.5884E+01 m 8.4922E+01 ft 28 | 7 9.9981E-02 m ** 3 / s 3.5308E+00 ft ** 3 / s 1.5847E+03 gal / min 2.3647E+01 m 7.7581E+01 ft 29 | 30 | -------------------------------------------------------------------------------- /tests/test_ch6b/ch6b_test2.inp: -------------------------------------------------------------------------------- 1 | 16 7 15 1 1 2 | 1 12. 1000.0 130. 2.5 3 | 2 8. 1000.0 130. 1.0 4 | 3 10. 1200.0 130. 0.75 5 | 4 10. 900.0 130. 2.0 6 | 5 10. 1000.0 130. 0.5 7 | 6 10. 2500.0 130. 1.0 8 | 7 8. 1000.0 130. 0.25 9 | 8 10. 1400.0 130. 0.75 10 | 9 12. 2000.0 130. 0.5 11 | 10 8. 1200.0 130. 2.0 12 | 11 8. 1300.0 130. 1.0 13 | 12 10. 1600.0 130. 0.5 14 | 13 10. 1000.0 130. 1.0 15 | 14 10. 1300.0 130. 1.0 16 | 15 12. 900.0 130. 2.25 17 | 16 12. 500.0 130. 2.5 18 | 4 2 3 -4 -5 19 | 4 -7 8 -15 -3 20 | 4 2 -7 8 -6 21 | 3 11 12 -9 22 | 3 9 -10 -8 23 | 3 10 13 -14 24 | 4 16 7 -2 -1 25 | -1 -2.505 16.707 155.286 26 | 7 150.0 27 | -------------------------------------------------------------------------------- /tests/test_ch6b/ch6b_test2.ref.out: -------------------------------------------------------------------------------- 1 | Pipe Diameter Length CHW 2 | 0 1.2000E+01 in 1.0000E+03 ft 130.0 3 | 1 8.0000E+00 in 1.0000E+03 ft 130.0 4 | 2 1.0000E+01 in 1.2000E+03 ft 130.0 5 | 3 1.0000E+01 in 9.0000E+02 ft 130.0 6 | 4 1.0000E+01 in 1.0000E+03 ft 130.0 7 | 5 1.0000E+01 in 2.5000E+03 ft 130.0 8 | 6 8.0000E+00 in 1.0000E+03 ft 130.0 9 | 7 1.0000E+01 in 1.4000E+03 ft 130.0 10 | 8 1.2000E+01 in 2.0000E+03 ft 130.0 11 | 9 8.0000E+00 in 1.2000E+03 ft 130.0 12 | 10 8.0000E+00 in 1.3000E+03 ft 130.0 13 | 11 1.0000E+01 in 1.6000E+03 ft 130.0 14 | 12 1.0000E+01 in 1.0000E+03 ft 130.0 15 | 13 1.0000E+01 in 1.3000E+03 ft 130.0 16 | 14 1.2000E+01 in 9.0000E+02 ft 130.0 17 | 15 1.2000E+01 in 5.0000E+02 ft 130.0 18 | 19 | 20 | Iteration 0: Deviation 9.1704E+00, Tolerance 1.0000E-03 21 | Iteration 1: Deviation 6.2685E+00, Tolerance 1.0000E-03 22 | Iteration 2: Deviation 2.6055E+00, Tolerance 1.0000E-03 23 | Iteration 3: Deviation 7.9633E-01, Tolerance 1.0000E-03 24 | Iteration 4: Deviation 2.9315E-01, Tolerance 1.0000E-03 25 | Iteration 5: Deviation 1.1523E-01, Tolerance 1.0000E-03 26 | Iteration 6: Deviation 4.5822E-02, Tolerance 1.0000E-03 27 | Iteration 7: Deviation 1.8199E-02, Tolerance 1.0000E-03 28 | Iteration 8: Deviation 7.2447E-03, Tolerance 1.0000E-03 29 | Iteration 9: Deviation 2.8814E-03, Tolerance 1.0000E-03 30 | Iteration 10: Deviation 1.1464E-03, Tolerance 1.0000E-03 31 | Iteration 11: Deviation 4.5604E-04, Tolerance 1.0000E-03 32 | 33 | Final results: 34 | 35 | Pipe Flow Flow Flow Head Loss Head Loss 36 | 0 1.4119E-01 m ** 3 / s 4.9861E+00 ft ** 3 / s 2.2379E+03 gal / min 3.4381E+00 m 1.1280E+01 ft 37 | 1 4.6894E-02 m ** 3 / s 1.6560E+00 ft ** 3 / s 7.4328E+02 gal / min 3.2172E+00 m 1.0555E+01 ft 38 | 2 3.2307E-03 m ** 3 / s 1.1409E-01 ft ** 3 / s 5.1207E+01 gal / min 9.1866E-03 m 3.0140E-02 ft 39 | 3 7.8763E-02 m ** 3 / s 2.7815E+00 ft ** 3 / s 1.2484E+03 gal / min 2.5515E+00 m 8.3709E+00 ft 40 | 4 3.6288E-02 m ** 3 / s 1.2815E+00 ft ** 3 / s 5.7518E+02 gal / min 6.7497E-01 m 2.2145E+00 ft 41 | 5 5.8009E-02 m ** 3 / s 2.0486E+00 ft ** 3 / s 9.1946E+02 gal / min 4.0226E+00 m 1.3198E+01 ft 42 | 6 -2.9505E-02 m ** 3 / s -1.0420E+00 ft ** 3 / s -4.6766E+02 gal / min 1.3641E+00 m 4.4754E+00 ft 43 | 7 -2.7323E-02 m ** 3 / s -9.6489E-01 ft ** 3 / s -4.3307E+02 gal / min 5.5872E-01 m 1.8331E+00 ft 44 | 8 4.0001E-02 m ** 3 / s 1.4126E+00 ft ** 3 / s 6.3402E+02 gal / min 6.6530E-01 m 2.1827E+00 ft 45 | 9 2.5219E-02 m ** 3 / s 8.9059E-01 ft ** 3 / s 3.9972E+02 gal / min 1.2240E+00 m 4.0158E+00 ft 46 | 10 1.7220E-02 m ** 3 / s 6.0812E-01 ft ** 3 / s 2.7294E+02 gal / min 6.5421E-01 m 2.1464E+00 ft 47 | 11 3.0616E-03 m ** 3 / s 1.0812E-01 ft ** 3 / s 4.8527E+01 gal / min 1.1088E-02 m 3.6378E-02 ft 48 | 12 1.1647E-02 m ** 3 / s 4.1132E-01 ft ** 3 / s 1.8461E+02 gal / min 8.2285E-02 m 2.6996E-01 ft 49 | 13 4.4986E-02 m ** 3 / s 1.5887E+00 ft ** 3 / s 7.1305E+02 gal / min 1.3063E+00 m 4.2858E+00 ft 50 | 14 6.7835E-02 m ** 3 / s 2.3956E+00 ft ** 3 / s 1.0752E+03 gal / min 7.9620E-01 m 2.6122E+00 ft 51 | 15 3.9314E-04 m ** 3 / s 1.3884E-02 ft ** 3 / s 6.2315E+00 gal / min 3.1867E-05 m 1.0455E-04 ft 52 | 53 | -------------------------------------------------------------------------------- /tests/test_ch7.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from distutils import dir_util 5 | from os.path import isdir, splitext 6 | from pytest import fixture #, approx, raises 7 | import jeppson.ch7 as jch7 8 | 9 | __author__ = "Bob Apthorpe" 10 | __copyright__ = "Bob Apthorpe" 11 | __license__ = "mit" 12 | 13 | 14 | # we reuse a bit of pytest's own testing machinery, this should eventually come 15 | # from a separatedly installable pytest-cli plugin. 16 | pytest_plugins = ["pytester"] 17 | _APPNAME = 'jeppson_ch7' 18 | 19 | @fixture 20 | def datadir(tmpdir, request): 21 | ''' 22 | Fixture responsible for searching a folder with the same name of test 23 | module and, if available, moving all contents to a temporary directory so 24 | tests can use them freely. 25 | 26 | Shamelessly purloined from https://stackoverflow.com/questions/29627341/pytest-where-to-store-expected-data 27 | and mangled until it worked right 28 | ''' 29 | filename = request.module.__file__ 30 | test_dir, _ = splitext(filename) 31 | 32 | if isdir(test_dir): 33 | dir_util.copy_tree(test_dir, str(tmpdir)) 34 | 35 | return tmpdir 36 | 37 | @fixture 38 | def run(testdir): 39 | def do_run(*args): 40 | args = [_APPNAME] + list(args) 41 | return testdir._run(*args) 42 | return do_run 43 | 44 | def test_ch7(datadir, run): 45 | # Set up input, output, and reference data file paths 46 | bfn = [ 47 | "ch7_test1" 48 | ] 49 | 50 | for basefn in bfn: 51 | 52 | ifn = datadir.join("{:s}.inp".format(basefn)) 53 | # ofn = datadir.join("{:s}.out".format(basefn)) 54 | rfn = datadir.join("{:s}.ref.out".format(basefn)) 55 | 56 | # Run ch7.py and check for successful termination 57 | result = run(ifn) 58 | assert result.ret == 0 59 | 60 | # Compare code output with reference output 61 | # with ofn.open("r") as ofh: 62 | # outcontent = ofh.read() 63 | outcontent = result.outlines 64 | 65 | with rfn.open("r") as rfh: 66 | refcontent = rfh.read().splitlines() 67 | 68 | nout = len(refcontent) 69 | assert len(outcontent) == nout 70 | 71 | if len(outcontent) == nout: 72 | for i, outln in enumerate(outcontent): 73 | assert outln == refcontent[i] 74 | 75 | return 76 | 77 | -------------------------------------------------------------------------------- /tests/test_ch7/ch7_test1.inp: -------------------------------------------------------------------------------- 1 | 7 2 100 1.217E-5 0.0001 64.4 2 | 1 12.0 3.0E3 1.2E-2 1.67 3 | 2 14.0 2.0E3 1.2E-2 3.45 4 | 3 10.0 3.0E3 1.2E-2 1.0 5 | 4 10.0 2.0E3 1.2E-2 1.67 6 | 5 14.0 3.0E3 1.2E-2 1.78 7 | 6 10.0 2.0E3 1.2E-2 1.56 8 | 7 12.0 3.0E3 1.2E-2 0.67 9 | 4 1 2 -3 -4 10 | 4 -2 -5 6 7 11 | -------------------------------------------------------------------------------- /tests/test_ch7/ch7_test1.ref.out: -------------------------------------------------------------------------------- 1 | Pipe Diameter Length Rel. Roughness 2 | 0 1.2000E+01 in 3.0000E+03 ft 1.0000E-03 3 | 1 1.4000E+01 in 2.0000E+03 ft 8.5714E-04 4 | 2 1.0000E+01 in 3.0000E+03 ft 1.2000E-03 5 | 3 1.0000E+01 in 2.0000E+03 ft 1.2000E-03 6 | 4 1.4000E+01 in 3.0000E+03 ft 8.5714E-04 7 | 5 1.0000E+01 in 2.0000E+03 ft 1.2000E-03 8 | 6 1.2000E+01 in 3.0000E+03 ft 1.0000E-03 9 | 10 | 11 | Final results: 12 | 13 | Pipe Flow Flow Flow Head Loss Head Loss 14 | 0 4.9481E-02 m ** 3 / s 1.7474E+00 ft ** 3 / s 7.8429E+02 gal / min 1.4879E+00 m 4.8817E+00 ft 15 | 1 9.8750E-02 m ** 3 / s 3.4873E+00 ft ** 3 / s 1.5652E+03 gal / min 1.7244E+00 m 5.6573E+00 ft 16 | 2 2.6125E-02 m ** 3 / s 9.2260E-01 ft ** 3 / s 4.1409E+02 gal / min 1.1007E+00 m 3.6113E+00 ft 17 | 3 4.5098E-02 m ** 3 / s 1.5926E+00 ft ** 3 / s 7.1481E+02 gal / min 2.1116E+00 m 6.9278E+00 ft 18 | 4 4.9270E-02 m ** 3 / s 1.7399E+00 ft ** 3 / s 7.8094E+02 gal / min 6.7222E-01 m 2.2054E+00 ft 19 | 5 4.5309E-02 m ** 3 / s 1.6001E+00 ft ** 3 / s 7.1816E+02 gal / min 2.1309E+00 m 6.9913E+00 ft 20 | 6 2.0107E-02 m ** 3 / s 7.1006E-01 ft ** 3 / s 3.1870E+02 gal / min 2.6564E-01 m 8.7151E-01 ft 21 | 22 | -------------------------------------------------------------------------------- /tests/test_constants.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pytest import approx, raises 5 | import jeppson.constants as jc 6 | 7 | __author__ = "Bob Apthorpe" 8 | __copyright__ = "Bob Apthorpe" 9 | __license__ = "mit" 10 | 11 | 12 | def test_constants(): 13 | assert jc.eshw == approx(0.54) 14 | 15 | assert jc.erhw == approx(0.63) 16 | 17 | assert jc.ahwq_us == approx(1.318) 18 | 19 | assert jc.ahwq_si == approx(0.849) 20 | 21 | assert jc.echw == approx(1.85185185) 22 | 23 | assert jc.edhw == approx(4.87037037) 24 | 25 | assert jc.ahws_us == approx(4.73) 26 | 27 | assert jc.ahws_si == approx(10.67) 28 | 29 | return 30 | -------------------------------------------------------------------------------- /tests/test_input.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pytest import approx, raises 5 | from jeppson.input import InputLine 6 | 7 | __author__ = "Bob Apthorpe" 8 | __copyright__ = "Bob Apthorpe" 9 | __license__ = "mit" 10 | 11 | 12 | def test_input(): 13 | blank = ( 14 | '', 15 | '\r', 16 | '\r\n', 17 | ' ' 18 | ' \r\n', 19 | '\n', 20 | '\r\n\r\n', 21 | ' \n', 22 | '\t\n', 23 | '\t \v\n', 24 | ) 25 | 26 | for line in blank: 27 | iline = InputLine(line) 28 | tline = line.rstrip('\n\r') 29 | assert iline.type == 'blank' 30 | assert iline.typecode == 'B' 31 | assert iline.ntok == 0 32 | assert len(iline.token) == 0 33 | assert iline.line == tline 34 | assert iline.as_log() 35 | 36 | # Comments: 37 | comment = ( 38 | '#\r\n', 39 | ' # ' 40 | ' \r', 41 | ' ' 42 | ' #\n', 43 | '! Alternate comment character\r\n', 44 | '##### Just a normal comment\n', 45 | 'an abnormal # non-comment line\n' 46 | ) 47 | 48 | icmt = 0 49 | line = comment[icmt] 50 | iline = InputLine(line) 51 | tline = line.rstrip('\n\r') 52 | assert iline.type == 'comment' 53 | assert iline.typecode == 'C' 54 | assert iline.ntok == 0 55 | assert len(iline.token) == 0 56 | assert iline.line == tline 57 | assert iline.as_log() 58 | 59 | icmt += 1 60 | line = comment[icmt] 61 | iline = InputLine(line) 62 | tline = line.rstrip('\n\r') 63 | assert iline.type == 'comment' 64 | assert iline.typecode == 'C' 65 | assert iline.ntok == 0 66 | assert len(iline.token) == 0 67 | assert iline.line == tline 68 | assert iline.as_log() 69 | 70 | icmt += 1 71 | line = comment[icmt] 72 | iline = InputLine(line) 73 | tline = line.rstrip('\n\r') 74 | assert iline.type == 'comment' 75 | assert iline.typecode == 'C' 76 | assert iline.ntok == 0 77 | assert len(iline.token) == 0 78 | assert iline.line == tline 79 | assert iline.as_log() 80 | 81 | icmt += 1 82 | line = comment[icmt] 83 | iline = InputLine(line) 84 | tline = line.rstrip('\n\r') 85 | assert iline.type != 'comment' 86 | assert iline.typecode != 'C' 87 | assert iline.ntok != 0 88 | assert len(iline.token) != 0 89 | assert iline.line == tline 90 | assert iline.as_log() 91 | 92 | line = comment[icmt] 93 | iline = InputLine(line, commentchar='!') 94 | tline = line.rstrip('\n\r') 95 | assert iline.type == 'comment' 96 | assert iline.typecode == 'C' 97 | assert iline.ntok == 0 98 | assert len(iline.token) == 0 99 | assert iline.line == tline 100 | assert iline.as_log() 101 | 102 | icmt += 1 103 | line = comment[icmt] 104 | iline = InputLine(line) 105 | tline = line.rstrip('\n\r') 106 | assert iline.type == 'comment' 107 | assert iline.typecode == 'C' 108 | assert iline.ntok == 0 109 | assert len(iline.token) == 0 110 | assert iline.line == tline 111 | assert iline.as_log() 112 | 113 | icmt += 1 114 | line = comment[icmt] 115 | iline = InputLine(line) 116 | tline = line.rstrip('\n\r') 117 | assert iline.type != 'comment' 118 | assert iline.typecode != 'C' 119 | assert iline.ntok != 0 120 | assert len(iline.token) != 0 121 | assert iline.line == tline 122 | assert iline.as_log() 123 | 124 | # Data 125 | data = ( 126 | '0 3 -1 -2 5', 127 | ' 0.33333 0.497 150.0 1.217E-5 7.0E-6 32.2 ', 128 | '-1000.0', 129 | ' 1 0.0 200.0 ', 130 | ' 10 8. 1200.0 130. 2.0 ', 131 | '1106.0 751.0 1000.0 500.0 1200.0 600.0 800.0', 132 | ' The wheels 4.0 turn slowly -1.9E-17 ' 133 | 'but they grind 8 exceedingly fine.#' 134 | ) 135 | 136 | idat = 0 137 | line = data[idat] 138 | iline = InputLine(line) 139 | tline = line.rstrip('\n\r') 140 | assert iline.type == 'data' 141 | assert iline.typecode == 'D' 142 | assert iline.ntok == 5 143 | assert len(iline.token) == 5 144 | assert iline.line == tline 145 | assert iline.as_log() 146 | 147 | idat += 1 148 | line = data[idat] 149 | iline = InputLine(line) 150 | tline = line.rstrip('\n\r') 151 | assert iline.type == 'data' 152 | assert iline.typecode == 'D' 153 | assert iline.ntok == 6 154 | assert len(iline.token) == 6 155 | assert iline.line == tline 156 | assert iline.as_log() 157 | 158 | idat += 1 159 | line = data[idat] 160 | iline = InputLine(line) 161 | tline = line.rstrip('\n\r') 162 | assert iline.type == 'data' 163 | assert iline.typecode == 'D' 164 | assert iline.ntok == 1 165 | assert len(iline.token) == 1 166 | assert iline.line == tline 167 | assert iline.as_log() 168 | 169 | idat += 1 170 | line = data[idat] 171 | iline = InputLine(line) 172 | tline = line.rstrip('\n\r') 173 | assert iline.type == 'data' 174 | assert iline.typecode == 'D' 175 | assert iline.ntok == 3 176 | assert len(iline.token) == 3 177 | assert iline.line == tline 178 | assert iline.as_log() 179 | 180 | idat += 1 181 | line = data[idat] 182 | iline = InputLine(line) 183 | tline = line.rstrip('\n\r') 184 | assert iline.type == 'data' 185 | assert iline.typecode == 'D' 186 | assert iline.ntok == 5 187 | assert len(iline.token) == 5 188 | assert iline.line == tline 189 | assert iline.as_log() 190 | 191 | idat += 1 192 | line = data[idat] 193 | iline = InputLine(line) 194 | tline = line.rstrip('\n\r') 195 | assert iline.type == 'data' 196 | assert iline.typecode == 'D' 197 | assert iline.ntok == 7 198 | assert len(iline.token) == 7 199 | assert iline.line == tline 200 | assert iline.as_log() 201 | 202 | idat += 1 203 | line = data[idat] 204 | iline = InputLine(line) 205 | tline = line.rstrip('\n\r') 206 | assert iline.type == 'data' 207 | assert iline.typecode == 'D' 208 | assert iline.ntok == 12 209 | assert len(iline.token) == 12 210 | assert iline.line == tline 211 | assert iline.as_log() 212 | 213 | return 214 | -------------------------------------------------------------------------------- /tests/test_pipe.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from pytest import approx, raises 5 | from jeppson.pipe import Pipe, SimplePipe, SimpleCHWPipe, SimpleEFPipe, \ 6 | ureg, Q_ 7 | import scipy.constants as sc 8 | 9 | __author__ = "Bob Apthorpe" 10 | __copyright__ = "Bob Apthorpe" 11 | __license__ = "mit" 12 | 13 | 14 | def test_simple_pipe(): 15 | p1 = SimplePipe(label='Pipe 1', length=100.0 * ureg.foot, 16 | idiameter=12.0 * ureg.inch) 17 | assert p1.label == 'Pipe 1' 18 | assert p1.length.to('m').magnitude == approx(30.48) 19 | assert p1.idiameter.to('m').magnitude == approx(0.3048) 20 | assert p1.ld_ratio.to_base_units().magnitude == approx(100.0) 21 | assert p1.flow_area.to('m**2').magnitude == approx(0.07296587699003966) 22 | 23 | with raises(ValueError): 24 | p1.ld_ratio = Q_(50.0, '') 25 | 26 | with raises(ValueError): 27 | p1.flow_area = Q_(2.9186E-1, 'm**2') 28 | 29 | with raises(ValueError): 30 | p1.length = Q_(1200.0, 'm') 31 | 32 | with raises(ValueError): 33 | p1.length = Q_(9.0E-4, 'm') 34 | 35 | with raises(ValueError): 36 | p1.idiameter = Q_(35.0, 'ft') 37 | 38 | with raises(ValueError): 39 | p1.idiameter = Q_(9.0E-4, 'm') 40 | 41 | p2 = SimplePipe(label='Pipe 2', length=100.0 * ureg.foot, 42 | idiameter=12.0 * ureg.inch) 43 | 44 | p2.length = Q_(50.0, 'ft') 45 | assert p2.length.to('m').magnitude == approx(15.24) 46 | assert p2.idiameter.to('m').magnitude == approx(0.3048) 47 | assert p2.ld_ratio.to_base_units().magnitude == approx(50.0) 48 | assert p2.flow_area.to('m**2').magnitude == approx(0.07296587699003966) 49 | 50 | p2.idiameter = Q_(24.0, 'in') 51 | assert p2.length.to('m').magnitude == approx(15.24) 52 | assert p2.idiameter.to('m').magnitude == approx(0.6096) 53 | assert p2.ld_ratio.to_base_units().magnitude == approx(25.0) 54 | assert p2.flow_area.to('m**2').magnitude == approx(0.291863261) 55 | 56 | return 57 | 58 | def test_simple_chw_pipe(): 59 | p1 = SimpleCHWPipe(label='Pipe 1', length=100.0 * ureg.foot, 60 | idiameter=12.0 * ureg.inch, chw=150.0) 61 | assert p1.label == 'Pipe 1' 62 | assert p1.length.to('m').magnitude == approx(30.48) 63 | assert p1.idiameter.to('m').magnitude == approx(0.3048) 64 | assert p1.ld_ratio.to_base_units().magnitude == approx(100.0) 65 | assert p1.flow_area.to('m**2').magnitude == approx(0.07296587699003966) 66 | assert p1.chw == approx(150.0) 67 | 68 | p1.chw = 130.0 69 | assert p1.chw == approx(130.0) 70 | 71 | with raises(ValueError): 72 | p1.chw = -4.0 73 | 74 | with raises(ValueError): 75 | p1.chw = 3600.0 76 | 77 | def test_simple_ef_pipe(): 78 | p1 = SimpleEFPipe(label='Pipe 1', length=100.0 * ureg.foot, 79 | idiameter=12.0 * ureg.inch, eroughness=8.5E-4) 80 | assert p1.label == 'Pipe 1' 81 | assert p1.length.to('m').magnitude == approx(30.48) 82 | assert p1.idiameter.to('m').magnitude == approx(0.3048) 83 | assert p1.ld_ratio.to_base_units().magnitude == approx(100.0) 84 | assert p1.flow_area.to('m**2').magnitude == approx(0.07296587699003966) 85 | assert p1.eroughness == approx(8.5E-4) 86 | assert p1.froughness.to('in').magnitude == approx(1.02E-2) 87 | 88 | p1.froughness = 2.04E-2 * ureg.inch 89 | assert p1.eroughness == approx(1.7E-3) 90 | 91 | with raises(ValueError): 92 | p1.eroughness = -0.0004 93 | 94 | with raises(ValueError): 95 | p1.eroughness = 0.1001 96 | 97 | with raises(ValueError): 98 | p1.froughness = -0.0004 * ureg.inch 99 | 100 | with raises(ValueError): 101 | p1.froughness = 0.1001 * p1.idiameter 102 | 103 | with raises(ValueError): 104 | p1.vol_flow = Q_(0.5, 'ft**3/s') 105 | 106 | with raises(ValueError): 107 | print(p1.vol_flow) 108 | 109 | with raises(ValueError): 110 | p1.kin_visc = Q_(1.217E-5, 'ft**2/s') 111 | 112 | with raises(ValueError): 113 | print(p1.kin_visc) 114 | 115 | with raises(ValueError): 116 | p1.vflow = Q_(5.0, 'ft/s') 117 | 118 | with raises(ValueError): 119 | print(p1.vflow) 120 | 121 | with raises(ValueError): 122 | p1.Re = 500000.0 123 | 124 | with raises(ValueError): 125 | print(p1.Re) 126 | 127 | with raises(ValueError): 128 | p1.friction = 0.0014 129 | 130 | with raises(ValueError): 131 | print(p1.friction) 132 | 133 | p1 = SimpleEFPipe(label='Pipe 1', length=150.0 * ureg.foot, 134 | idiameter=4.0 * ureg.inch, froughness=7.0E-6 * ureg.foot) 135 | 136 | p1.set_flow_conditions(vol_flow=Q_(0.5, 'ft**3/s'), 137 | kin_visc=Q_(1.217E-5, 'ft**2/s')) 138 | 139 | assert p1.vol_flow.to('ft**3/s').magnitude == approx(0.5) 140 | assert p1.kin_visc.to('ft**2/s').magnitude == approx(1.217E-5) 141 | assert p1.vflow.to('ft/s').magnitude == approx(5.72957795) 142 | assert p1.Re == approx(156931.7) 143 | assert p1.friction == approx(0.016554318) 144 | 145 | def test_pipe(): 146 | p1 = Pipe(label='Pipe 1', length=100.0 * sc.foot, 147 | idiameter=12.0 * sc.inch, odiameter=12.5 * sc.inch, 148 | eroughness=8.5E-4) 149 | assert p1.label == 'Pipe 1' 150 | assert p1.length == approx(30.48) 151 | assert p1.idiameter == approx(0.3048) 152 | assert p1.eroughness == approx(8.5E-4) 153 | 154 | assert p1.as_table(headers=['length', 'idiameter', 'eroughness']) 155 | 156 | p2 = Pipe(label='Pipe 2', length=100.0 * sc.foot, 157 | idiameter=12.0 * sc.inch, twall=0.25 * sc.inch, 158 | eroughness=8.5E-4) 159 | 160 | assert p2.odiameter == approx(12.5 * sc.inch) 161 | assert p2.twall == approx(0.25 * sc.inch) 162 | assert p2.idiameter == approx(12.0 * sc.inch) 163 | assert p2.flow_area == approx(7.2965877E-2) 164 | 165 | p2.odiameter = 13.0 * sc.inch 166 | assert p2.idiameter == approx(12.0 * sc.inch) 167 | assert p2.odiameter == approx(13.0 * sc.inch) 168 | assert p2.twall == approx(0.5 * sc.inch) 169 | 170 | p2.twall = 0.25 * sc.inch 171 | assert p2.idiameter == approx(12.0 * sc.inch) 172 | assert p2.odiameter == approx(12.5 * sc.inch) 173 | assert p2.twall == approx(0.25 * sc.inch) 174 | 175 | p2.idiameter = 12.25 * sc.inch 176 | assert p2.idiameter == approx(12.25 * sc.inch) 177 | assert p2.odiameter == approx(12.5 * sc.inch) 178 | assert p2.twall == approx(0.125 * sc.inch) 179 | 180 | with raises(ValueError): 181 | p2.idiameter = 1.0E-4 182 | 183 | with raises(ValueError): 184 | p2.idiameter = 11.0 185 | 186 | with raises(ValueError): 187 | p2.odiameter = 1.0E-4 188 | 189 | with raises(ValueError): 190 | p2.odiameter = 11.0 191 | 192 | with raises(ValueError): 193 | p2.twall = 1.0E-6 194 | 195 | with raises(ValueError): 196 | p2.twall = 1.01 197 | 198 | with raises(ValueError): 199 | p2.eroughness = -1.0E-6 200 | 201 | with raises(ValueError): 202 | p2.eroughness = 0.2 203 | 204 | with raises(ValueError): 205 | p2.length = 1.0E-4 206 | 207 | with raises(ValueError): 208 | p2.length = 1001.0 209 | 210 | with raises(ValueError): 211 | p2.flow_area = 7.2965877E-2 212 | 213 | with raises(ValueError): 214 | p2.nearest_material_roughness('cheese', is_clean=False) 215 | 216 | p2.nearest_material_roughness('cast iron', is_clean=True) 217 | assert p2.eroughness == approx(2.59E-4) 218 | 219 | p2.nearest_dimensions_from_schedule('80') 220 | # The schedule 80 pipe size with an inner diameter closest to 12" is 221 | # 14" with Di = 12.75", Do = 14.0, and twall = 0.75" 222 | assert p2._twall == approx(0.75 * sc.inch) 223 | assert p2._odiameter == approx(14.0 * sc.inch) 224 | assert p2.idiameter == approx(12.5 * sc.inch) 225 | 226 | with raises(ValueError): 227 | p2.nearest_dimensions_from_schedule(schedule='80', dnominal=120) 228 | 229 | p2.nearest_dimensions_from_schedule(schedule='80', dnominal=12) 230 | assert p2.idiameter == approx(11.38 * sc.inch, abs=1.0E-3) 231 | assert p2._odiameter == approx(12.75 * sc.inch, abs=1.0E-3) 232 | assert p2._twall == approx((p2._odiameter - p2.idiameter) / 2.0) 233 | 234 | p2.odiameter = 38.0 * sc.inch 235 | p2.idiameter = 36.0 * sc.inch 236 | with raises(ValueError): 237 | p2.nearest_dimensions_from_schedule(schedule='80') 238 | 239 | with raises(ValueError): 240 | p2.clean = 'manky' 241 | 242 | with raises(ValueError): 243 | p2.surface = 'smooth' 244 | 245 | # 12" Schedule 80 pipe has a Di of 11.38", Do = 12.75", and twall = 0.68" 246 | p3 = Pipe(label='Pipe 3', length=10.0 * sc.foot, 247 | idiameter=11.37 * sc.inch, schedule='80', 248 | eroughness=8.5E-4) 249 | 250 | assert p3.idiameter == approx(11.38 * sc.inch, abs=1.0E-3) 251 | assert p3._odiameter == approx(12.75 * sc.inch, abs=1.0E-3) 252 | assert p3._twall == approx((p3._odiameter - p3.idiameter) / 2.0) 253 | 254 | # 12" Schedule 80 pipe has a Di of 11.38", Do = 12.75", and twall = 0.68" 255 | p4 = Pipe(label='Pipe 4', length=10.0 * sc.foot, 256 | idiameter=11.37 * sc.inch, 257 | odiameter=12.75 * sc.inch, 258 | eroughness=8.5E-4) 259 | 260 | assert p4._twall == approx((p4._odiameter - p4.idiameter) / 2.0) 261 | 262 | # 12" Schedule 80 pipe has a Di of 11.38", Do = 12.75", and twall = 0.68" 263 | p5 = Pipe(label='Pipe 5', length=10.0 * sc.foot, 264 | idiameter=11.37 * sc.inch, 265 | twall=0.68 * sc.inch, 266 | eroughness=8.5E-4) 267 | 268 | assert p5._odiameter == approx(12.75 * sc.inch, abs=1.0E-3) 269 | 270 | # 12" Schedule 80 pipe has a Di of 11.38", Do = 12.75", and twall = 0.68" 271 | p6 = Pipe(label='Pipe 6', length=10.0 * sc.foot, 272 | schedule='80', nps=12) 273 | 274 | assert p6.idiameter == approx(11.38 * sc.inch, abs=1.0E-3) 275 | assert p6._odiameter == approx(12.75 * sc.inch, abs=1.0E-3) 276 | assert p6._twall == approx((p6._odiameter - p6.idiameter) / 2.0) 277 | assert p6.eroughness == 0.0 278 | assert p6.as_table(headers=['length', 'idiameter', 'eroughness']) 279 | 280 | # 12" Schedule 80 pipe has a Di of 11.38", Do = 12.75", and twall = 0.68" 281 | p7 = Pipe(label='Pipe 7', length=10.0 * sc.foot, 282 | schedule='80', nps=12, froughness=2.46850408E-4) 283 | 284 | assert p7.idiameter == approx(11.38 * sc.inch, abs=1.0E-3) 285 | assert p7._odiameter == approx(12.75 * sc.inch, abs=1.0E-3) 286 | assert p7._twall == approx((p7._odiameter - p7.idiameter) / 2.0) 287 | assert p7.flow_area == approx(6.5524E-2, abs=1.0E-6) 288 | assert p7.eroughness == approx(8.54E-4, abs=1.0E-6) 289 | 290 | p7 = Pipe(label='Pipe 7', length=10.0 * sc.foot, 291 | schedule='80', nps=12, surface='cast iron') 292 | 293 | assert p7.eroughness == approx(2.59E-4) 294 | 295 | p7 = Pipe(label='Pipe 7', length=10.0 * sc.foot, 296 | schedule='80', nps=12, surface='Steel tubes', is_clean=False) 297 | 298 | assert p7.eroughness == approx(1.0E-3) 299 | 300 | p8 = Pipe(label='Pipe 8', length=30.0 * sc.foot) 301 | p8.twall = 0.5 * sc.inch 302 | p8.idiameter = 12 * sc.inch 303 | assert p8.idiameter == approx(12 * sc.inch) 304 | assert p8.odiameter == approx(13 * sc.inch) 305 | assert p8.twall == approx(0.5 * sc.inch) 306 | 307 | p8 = Pipe(label='Pipe 8', length=30.0 * sc.foot) 308 | p8.twall = 0.5 * sc.inch 309 | p8.odiameter = 13 * sc.inch 310 | assert p8.idiameter == approx(12 * sc.inch) 311 | assert p8.odiameter == approx(13 * sc.inch) 312 | assert p8.twall == approx(0.5 * sc.inch) 313 | 314 | return 315 | -------------------------------------------------------------------------------- /userdoc/ch5_case_dom_example.txt: -------------------------------------------------------------------------------- 1 | This is the result of repr(case_dom) reformatted for clarity. 2 | 3 | case_dom is a dict composed of heterogenous substructures - lists, dicts, and 4 | basic data types (int, float). The Jeppson codes make use of the Pint physical 5 | unit library. Quantity objects from the Pint library are the only non-standard 6 | data types stored in case_dom. The original Fortran input format limits pipe 7 | dimensions, flows, etc. to a few hard-coded units which are not obvious when 8 | examining code input. case_dom stores all physical quantities with units as 9 | Quantity objects, making unit conversion and display much more convenient. 10 | 11 | The top level keys of the case_dom structure are 'params' 'pipe' 'inflows' and 12 | 'loop'. 13 | 14 | case_dom['params'] is a dict of case parameters which define the number of 15 | elements in the case (pipes, junctions, loops), set numerical convergence 16 | criteria (maximum number of iterations, numerical tolerance), fluid properties 17 | (kinematic viscosity), units of measure, and modeling paramters. These 18 | parameters are specified in the first line of the original Fortran input. The 19 | parameters are: 20 | 21 | npipes (int): Number of pipes in model 22 | njunctions (int): Number of junctions in model 23 | nloops (int): Number of loops defined in the model 24 | maxiter (int): Maximum number of iterations for iterative solver loop 25 | unitcode (int): An integer code ranging from 0 to 3 which sets the physical 26 | units of kinematic viscosity and pipe dimensions when 27 | reading the original Fortran input. 28 | tolerance (float): The numerical tolerance of the iterative solver; when the 29 | sum of the absolute difference between the previous and 30 | the current calculated flows is less than the tolerance, 31 | the solution is considered to have converged. 32 | kin_visc (Quantity): Kinematic viscosity of the fluid in the pipe network. 33 | This is stored as a Pint Quantity object with 34 | dimensionality of [length]**2 / [time]. 35 | fvol_flow (float): Model paramter which sets the fractional flow taken when 36 | estimating flow resistance in a pipe. The value may range 37 | between 0.0 and 1.0; 0.1 or 10% of nominal flow is 38 | typical. 39 | 40 | case_dom['pipe'] is a list of dicts of length case_dom['npipes']; the dict 41 | contains pipe geometry, routing, pressure and flow information. This dict 42 | contains 14 fields, 6 of which are set in the case definition, 6 of which are 43 | intermediate quantities derived from input data or calculated during the 44 | iterative solution, and 2 which are results for reporting. Input 45 | case_dom['pipe'] fields are: 46 | 47 | id (int): Pipe ID; corresponds to list index and ranges from 0 to 48 | (case_dom['params']['npipes'] - 1). This value is one less than 49 | the pipe ID given in the original Fortran code input; when the 50 | original case input files are read, 1 is automatically subtracted 51 | from the pipe ID. Pipe, junction, and loop ID are zero-indexed in 52 | the Python version of the Jeppson Chapter 5 code; they are 53 | one-indexed in the original Fortran input. This reflects the 54 | default array indexing schemes in the respective implementation 55 | languages. 56 | idiameter (Quantity): Pipe inner diameter with dimensionality [length]. 57 | lpipe (Quantity): Pipe length with dimensionality [length]. 58 | froughness (Quantity): Absolute pipe roughness with dimensionality of 59 | [length]. 60 | from (int): Junction ID of pipe source, ranging from 0 to 61 | (case_dom['njunctions'] - 1). 62 | to (int): Junction ID of pipe destination, ranging from 0 to 63 | (case_dom['njunctions'] - 1). 64 | 65 | Derived and intermediate quantities are: 66 | 67 | LD (float): Length/diameter ratio, dimensionless. 68 | flow_area (Quantity): Pipe flow area with dimensionality of [length]**2 69 | arl (Quantity): Constant time in kp term with dimensionality 70 | [time]**2 / [length]**5. Evaluates to 71 | (L/D) / (2 g Aflow**2) 72 | eroughness (float): Relative pipe roughness; ratio of absolute pipe 73 | roughness to diameter. Dimensionless. 74 | expp (float): Head loss/flow model exponent on volumetric flow 75 | kp (float): Constant term in head loss/flow model. 76 | 77 | Note that kp and expp are terms of an archaic correlation which has no easy 78 | dimensional decomposition. kp has associated dimensions however tracking 79 | dimensionality would unnecessarily complicate the code so alternate steps are 80 | taken to ensure the arguments and results of this correlation have the proper 81 | dimensionality. 82 | 83 | Output (reported) quantities are: 84 | 85 | vol_flow (Quantity): Volumetric flow rate through pipe as calculated the 86 | iterative solver. Quantity has dimensionality of 87 | [length]**3 / [time] 88 | head_loss (Quantity): Head loss along pipe with dimensionality of [length] 89 | 90 | case_dom['inflows'] is a list of length (case_dom['params']['njunctions']) and 91 | contains the fixed volumetric flow rates into or out of each junction to 92 | outside the piping network. Flow rates have dimensionality of 93 | [length]**3 / [time]. Flow into a junction is considered positive; outflow is 94 | considered negative. These values are read from the junction definition lines 95 | in the original Fortran code input. 96 | 97 | case_dom['loop'] contains a list of case_dom['params']['nloops'] elements. Each 98 | element is a list of dicts, the dicts containing the zero-indexed pipe ID and 99 | flow direction of each pipe in a pipe loop. The dict for each pipe contains two 100 | elements: the zero-indexed pipe ID keyed as 'pipe_id', the flow direction 101 | within the loop (1.0 indicating forward or clockwise flow, -1.0 indicating 102 | reverse or counter-clockwise flow), keyed as 'flow_dir'. The data in 103 | case_dom['loop'] correspond to the last case_dom['params']['nloops'] lines in 104 | the original Fortran case input. 105 | 106 | An example case_dom structure follows: 107 | 108 | { 109 | 'params': { 110 | 'npipes': 7, 111 | 'njunctions': 6, 112 | 'nloops': 2, 113 | 'maxiter': 10, 114 | 'unitcode': 0, 115 | 'tolerance': 0.001, 116 | 'kin_visc': , 117 | 'fvol_flow': 0.1}, 118 | 'pipe': [ 119 | {'id': 0, 120 | 'idiameter': , 121 | 'lpipe': , 122 | 'froughness': , 123 | 'to': 0, 124 | 'from': 1, 125 | 'LD': 1659.0, 126 | 'flow_area': , 127 | 'arl': , 128 | 'eroughness': 0.001275, 129 | 'expp': 2.058525152318605, 130 | 'kp': 4.933732590078341, 131 | 'vol_flow': , 132 | 'head_loss': }, 133 | {'id': 1, 'idiameter': , 134 | 'lpipe': , 135 | 'froughness': , 136 | 'to': 2, 137 | 'from': 1, 138 | 'LD': 751.0, 139 | 'flow_area': , 140 | 'arl': , 141 | 'eroughness': 0.0008500000000000001, 142 | 'expp': 2.1262549765578527, 143 | 'kp': 0.28802209850142646, 144 | 'vol_flow': , 145 | 'head_loss': }, 146 | {'id': 2, 'idiameter': , 147 | 'lpipe': , 148 | 'froughness': , 149 | 'to': 3, 150 | 'from': 2, 151 | 'LD': 1200.0, 152 | 'flow_area': , 153 | 'arl': , 154 | 'eroughness': 0.00102, 155 | 'expp': 2.0721600073109654, 156 | 'kp': 1.6368647096270097, 157 | 'vol_flow': , 158 | 'head_loss': }, 159 | {'id': 3, 'idiameter': , 160 | 'lpipe': , 161 | 'froughness': , 162 | 'to': 0, 163 | 'from': 3, 164 | 'LD': 500.0, 165 | 'flow_area': , 166 | 'arl': , 167 | 'eroughness': 0.0008500000000000001, 168 | 'expp': 2.042833314618604, 169 | 'kp': 0.8616124875151363, 170 | 'vol_flow': , 171 | 'head_loss': }, 172 | {'id': 4, 'idiameter': , 173 | 'lpipe': , 174 | 'froughness': , 175 | 'to': 1, 176 | 'from': 4, 177 | 'LD': 1440.0, 178 | 'flow_area': , 179 | 'arl': , 180 | 'eroughness': 0.00102, 181 | 'expp': 2.056219982149255, 182 | 'kp': 2.7042991766939433, 183 | 'vol_flow': , 184 | 'head_loss': }, 185 | {'id': 5, 'idiameter': , 186 | 'lpipe': , 187 | 'froughness': , 188 | 'to': 5, 189 | 'from': 4, 190 | 'LD': 1200.0, 191 | 'flow_area': , 192 | 'arl': , 193 | 'eroughness': 0.0017000000000000001, 194 | 'expp': 2.0246727593594356, 195 | 'kp': 18.523362453484452, 196 | 'vol_flow': , 197 | 'head_loss': }, 198 | {'id': 6, 'idiameter': , 199 | 'lpipe': , 200 | 'froughness': , 201 | 'to': 5, 202 | 'from': 2, 203 | 'LD': 1200.0, 204 | 'flow_area': , 205 | 'arl': , 206 | 'eroughness': 0.001275, 207 | 'expp': 2.0256643663944516, 208 | 'kp': 9.237806789553956, 209 | 'vol_flow': , 210 | 'head_loss': }], 211 | 'inflows': [ 212 | , 213 | , 214 | , 215 | , 216 | , 217 | ], 218 | 'loop': [ 219 | [{'pipe_id': 0, 'flow_dir': 1.0}, 220 | {'pipe_id': 1, 'flow_dir': -1.0}, 221 | {'pipe_id': 2, 'flow_dir': -1.0}, 222 | {'pipe_id': 3, 'flow_dir': -1.0}], 223 | [{'pipe_id': 4, 'flow_dir': 1.0}, 224 | {'pipe_id': 5, 'flow_dir': -1.0}, 225 | {'pipe_id': 6, 'flow_dir': 1.0}, 226 | {'pipe_id': 1, 'flow_dir': 1.0}]] 227 | } 228 | -------------------------------------------------------------------------------- /userdoc/ch5_case_dom_input_example.txt: -------------------------------------------------------------------------------- 1 | {'params': { 2 | 'npipes': 7, 3 | 'njunctions': 6, 4 | 'nloops': 2, 5 | 'maxiter': 10, 6 | 'unitcode': 0, 7 | 'tolerance': 0.001, 8 | 'kin_visc': , 9 | 'fvol_flow': 0.1}, 10 | 'pipe': [ 11 | {'id': 0, 12 | 'idiameter': , 13 | 'lpipe': , 14 | 'froughness': , 15 | 'to': 0, 16 | 'from': 1}, 17 | {'id': 1, 18 | 'idiameter': , 19 | 'lpipe': , 20 | 'froughness': , 21 | 'to': 2, 22 | 'from': 1}, 23 | {'id': 2, 24 | 'idiameter': , 25 | 'lpipe': , 26 | 'froughness': , 27 | 'to': 3, 28 | 'from': 2}, 29 | {'id': 3, 30 | 'idiameter': , 31 | 'lpipe': , 32 | 'froughness': , 33 | 'to': 0, 34 | 'from': 3}, 35 | {'id': 4, 36 | 'idiameter': , 37 | 'lpipe': , 38 | 'froughness': , 39 | 'to': 1, 40 | 'from': 4}, 41 | {'id': 5, 42 | 'idiameter': , 43 | 'lpipe': , 44 | 'froughness': , 45 | 'to': 5, 46 | 'from': 4}, 47 | {'id': 6, 48 | 'idiameter': , 49 | 'lpipe': , 50 | 'froughness': , 51 | 'to': 5, 52 | 'from': 2}] 53 | 'inflows': [ 54 | , 55 | , 56 | , 57 | , 58 | , 59 | ], 60 | 'loop': [ 61 | [{'pipe_id': 0, 'flow_dir': 1.0}, 62 | {'pipe_id': 1, 'flow_dir': -1.0}, 63 | {'pipe_id': 2, 'flow_dir': -1.0}, 64 | {'pipe_id': 3, 'flow_dir': -1.0}], 65 | [{'pipe_id': 4, 'flow_dir': 1.0}, 66 | {'pipe_id': 5, 'flow_dir': -1.0}, 67 | {'pipe_id': 6, 'flow_dir': 1.0}, 68 | {'pipe_id': 1, 'flow_dir': 1.0}]] 69 | } 70 | -------------------------------------------------------------------------------- /userdoc/ch6a_case_dom_example.txt: -------------------------------------------------------------------------------- 1 | { 'junc': [ { 'head': , 2 | 'id': 0, 3 | 'inflow': , 4 | 'init_head': }, 5 | { 'head': , 6 | 'id': 1, 7 | 'inflow': , 8 | 'init_head': }, 9 | { 'head': , 10 | 'id': 2, 11 | 'inflow': , 12 | 'init_head': }, 13 | { 'head': , 14 | 'id': 3, 15 | 'inflow': , 16 | 'init_head': }, 17 | { 'head': , 18 | 'id': 4, 19 | 'inflow': , 20 | 'init_head': }], 21 | 'params': { 'jfixed': 2, 22 | 'maxiter': 100, 23 | 'njunctions': 5, 24 | 'npipes': 6, 25 | 'tolerance': 0.0005}, 26 | 'pipe': [ { 'LD': 450.0, 27 | 'chw': 130.0, 28 | 'from': 0, 29 | 'head_loss': , 30 | 'id': 0, 31 | 'idiameter': , 32 | 'kp': 18.196996442379092, 33 | 'lpipe': , 34 | 'to': 1, 35 | 'vol_flow': }, 36 | { 'LD': 400.0, 37 | 'chw': 130.0, 38 | 'from': 0, 39 | 'head_loss': , 40 | 'id': 1, 41 | 'idiameter': , 42 | 'kp': 49.25001555585221, 43 | 'lpipe': , 44 | 'to': 3, 45 | 'vol_flow': }, 46 | { 'LD': 400.0, 47 | 'chw': 130.0, 48 | 'from': 1, 49 | 'head_loss': , 50 | 'id': 2, 51 | 'idiameter': , 52 | 'kp': 49.25001555585221, 53 | 'lpipe': , 54 | 'to': 2, 55 | 'vol_flow': }, 56 | { 'LD': 300.0, 57 | 'chw': 130.0, 58 | 'from': 1, 59 | 'head_loss': , 60 | 'id': 3, 61 | 'idiameter': , 62 | 'kp': 12.13133096158606, 63 | 'lpipe': , 64 | 'to': 4, 65 | 'vol_flow': }, 66 | { 'LD': 564.0, 67 | 'chw': 130.0, 68 | 'from': 2, 69 | 'head_loss': , 70 | 'id': 4, 71 | 'idiameter': , 72 | 'kp': 69.4425219337516, 73 | 'lpipe': , 74 | 'to': 4, 75 | 'vol_flow': }, 76 | { 'LD': 600.0, 77 | 'chw': 130.0, 78 | 'from': 3, 79 | 'head_loss': , 80 | 'id': 5, 81 | 'idiameter': , 82 | 'kp': 73.87502333377832, 83 | 'lpipe': , 84 | 'to': 2, 85 | 'vol_flow': }]} 86 | 87 | -------------------------------------------------------------------------------- /userdoc/ch6a_case_dom_input_example.txt: -------------------------------------------------------------------------------- 1 | { 'junc': [ { 'id': 0, 2 | 'inflow': , 3 | 'init_head': }, 4 | { 'id': 1, 5 | 'inflow': , 6 | 'init_head': }, 7 | { 'id': 2, 8 | 'inflow': , 9 | 'init_head': }, 10 | { 'id': 3, 11 | 'inflow': , 12 | 'init_head': }, 13 | { 'id': 4, 14 | 'inflow': , 15 | 'init_head': }], 16 | 'params': { 'jfixed': 2, 17 | 'maxiter': 100, 18 | 'njunctions': 5, 19 | 'npipes': 6, 20 | 'tolerance': 0.0005}, 21 | 'pipe': [ { 'chw': 130.0, 22 | 'from': 0, 23 | 'id': 0, 24 | 'idiameter': , 25 | 'lpipe': , 26 | 'to': 1}, 27 | { 'chw': 130.0, 28 | 'from': 0, 29 | 'id': 1, 30 | 'idiameter': , 31 | 'lpipe': , 32 | 'to': 3}, 33 | { 'chw': 130.0, 34 | 'from': 1, 35 | 'id': 2, 36 | 'idiameter': , 37 | 'lpipe': , 38 | 'to': 2}, 39 | { 'chw': 130.0, 40 | 'from': 1, 41 | 'id': 3, 42 | 'idiameter': , 43 | 'lpipe': , 44 | 'to': 4}, 45 | { 'chw': 130.0, 46 | 'from': 2, 47 | 'id': 4, 48 | 'idiameter': , 49 | 'lpipe': , 50 | 'to': 4}, 51 | { 'chw': 130.0, 52 | 'from': 3, 53 | 'id': 5, 54 | 'idiameter': , 55 | 'lpipe': , 56 | 'to': 2}]} 57 | 58 | -------------------------------------------------------------------------------- /userdoc/ch6b_case_dom_example.txt: -------------------------------------------------------------------------------- 1 | { 'loop': [ [ {'flow_dir': 1.0, 'pipe_id': 1}, 2 | {'flow_dir': 1.0, 'pipe_id': 2}, 3 | {'flow_dir': -1.0, 'pipe_id': 3}, 4 | {'flow_dir': -1.0, 'pipe_id': 4}], 5 | [ {'flow_dir': -1.0, 'pipe_id': 6}, 6 | {'flow_dir': 1.0, 'pipe_id': 7}, 7 | {'flow_dir': -1.0, 'pipe_id': 14}, 8 | {'flow_dir': -1.0, 'pipe_id': 2}], 9 | [ {'flow_dir': 1.0, 'pipe_id': 1}, 10 | {'flow_dir': -1.0, 'pipe_id': 6}, 11 | {'flow_dir': 1.0, 'pipe_id': 7}, 12 | {'flow_dir': -1.0, 'pipe_id': 5}], 13 | [ {'flow_dir': 1.0, 'pipe_id': 10}, 14 | {'flow_dir': 1.0, 'pipe_id': 11}, 15 | {'flow_dir': -1.0, 'pipe_id': 8}], 16 | [ {'flow_dir': 1.0, 'pipe_id': 8}, 17 | {'flow_dir': -1.0, 'pipe_id': 9}, 18 | {'flow_dir': -1.0, 'pipe_id': 7}], 19 | [ {'flow_dir': 1.0, 'pipe_id': 9}, 20 | {'flow_dir': 1.0, 'pipe_id': 12}, 21 | {'flow_dir': -1.0, 'pipe_id': 13}], 22 | [ {'flow_dir': 1.0, 'pipe_id': 15}, 23 | {'flow_dir': 1.0, 'pipe_id': 6}, 24 | {'flow_dir': -1.0, 'pipe_id': 1}, 25 | {'flow_dir': -1.0, 'pipe_id': 0}]], 26 | 'params': { 'maxiter': 15, 27 | 'nloops': 7, 28 | 'npipes': 16, 29 | 'npseudoloops': 1, 30 | 'npumps': 1, 31 | 'tolerance': 0.001}, 32 | 'pipe': [ { 'chw': 130.0, 33 | 'expp': 0.0, 34 | 'flow_area': , 35 | 'head_loss': , 36 | 'id': 0, 37 | 'idiameter': , 38 | 'init_vol_flow': , 39 | 'kp': 0.575640350643895, 40 | 'loopdir': {6: -1.0}, 41 | 'lpipe': , 42 | 'vol_flow': }, 43 | { 'chw': 130.0, 44 | 'expp': 0.0, 45 | 'flow_area': , 46 | 'head_loss': , 47 | 'id': 1, 48 | 'idiameter': , 49 | 'init_vol_flow': , 50 | 'kp': 4.147447331744893, 51 | 'loopdir': {0: 1.0, 2: 1.0, 6: -1.0}, 52 | 'lpipe': , 53 | 'vol_flow': }, 54 | { 'chw': 130.0, 55 | 'expp': 0.0, 56 | 'flow_area': , 57 | 'head_loss': , 58 | 'id': 2, 59 | 'idiameter': , 60 | 'init_vol_flow': , 61 | 'kp': 1.6787053315494642, 62 | 'loopdir': {0: 1.0, 1: -1.0}, 63 | 'lpipe': , 64 | 'vol_flow': }, 65 | { 'chw': 130.0, 66 | 'expp': 0.0, 67 | 'flow_area': , 68 | 'head_loss': , 69 | 'id': 3, 70 | 'idiameter': , 71 | 'init_vol_flow': , 72 | 'kp': 1.259028998662098, 73 | 'loopdir': {0: -1.0}, 74 | 'lpipe': , 75 | 'vol_flow': }, 76 | { 'chw': 130.0, 77 | 'expp': 0.0, 78 | 'flow_area': , 79 | 'head_loss': , 80 | 'id': 4, 81 | 'idiameter': , 82 | 'init_vol_flow': , 83 | 'kp': 1.3989211096245533, 84 | 'loopdir': {0: -1.0}, 85 | 'lpipe': , 86 | 'vol_flow': }, 87 | { 'chw': 130.0, 88 | 'expp': 0.0, 89 | 'flow_area': , 90 | 'head_loss': , 91 | 'id': 5, 92 | 'idiameter': , 93 | 'init_vol_flow': , 94 | 'kp': 3.497302774061384, 95 | 'loopdir': {2: -1.0}, 96 | 'lpipe': , 97 | 'vol_flow': }, 98 | { 'chw': 130.0, 99 | 'expp': 0.0, 100 | 'flow_area': , 101 | 'head_loss': , 102 | 'id': 6, 103 | 'idiameter': , 104 | 'init_vol_flow': , 105 | 'kp': 4.147447331744893, 106 | 'loopdir': {1: -1.0, 2: -1.0, 6: 1.0}, 107 | 'lpipe': , 108 | 'vol_flow': }, 109 | { 'chw': 130.0, 110 | 'expp': 0.0, 111 | 'flow_area': , 112 | 'head_loss': , 113 | 'id': 7, 114 | 'idiameter': , 115 | 'init_vol_flow': , 116 | 'kp': 1.958489553474375, 117 | 'loopdir': {1: 1.0, 2: 1.0, 4: -1.0}, 118 | 'lpipe': , 119 | 'vol_flow': }, 120 | { 'chw': 130.0, 121 | 'expp': 0.0, 122 | 'flow_area': , 123 | 'head_loss': , 124 | 'id': 8, 125 | 'idiameter': , 126 | 'init_vol_flow': , 127 | 'kp': 1.15128070128779, 128 | 'loopdir': {3: -1.0, 4: 1.0}, 129 | 'lpipe': , 130 | 'vol_flow': }, 131 | { 'chw': 130.0, 132 | 'expp': 0.0, 133 | 'flow_area': , 134 | 'head_loss': , 135 | 'id': 9, 136 | 'idiameter': , 137 | 'init_vol_flow': , 138 | 'kp': 4.976936798093873, 139 | 'loopdir': {4: -1.0, 5: 1.0}, 140 | 'lpipe': , 141 | 'vol_flow': }, 142 | { 'chw': 130.0, 143 | 'expp': 0.0, 144 | 'flow_area': , 145 | 'head_loss': , 146 | 'id': 10, 147 | 'idiameter': , 148 | 'init_vol_flow': , 149 | 'kp': 5.391681531268362, 150 | 'loopdir': {3: 1.0}, 151 | 'lpipe': , 152 | 'vol_flow': }, 153 | { 'chw': 130.0, 154 | 'expp': 0.0, 155 | 'flow_area': , 156 | 'head_loss': , 157 | 'id': 11, 158 | 'idiameter': , 159 | 'init_vol_flow': , 160 | 'kp': 2.2382737753992856, 161 | 'loopdir': {3: 1.0}, 162 | 'lpipe': , 163 | 'vol_flow': }, 164 | { 'chw': 130.0, 165 | 'expp': 0.0, 166 | 'flow_area': , 167 | 'head_loss': , 168 | 'id': 12, 169 | 'idiameter': , 170 | 'init_vol_flow': , 171 | 'kp': 1.3989211096245533, 172 | 'loopdir': {5: 1.0}, 173 | 'lpipe': , 174 | 'vol_flow': }, 175 | { 'chw': 130.0, 176 | 'expp': 0.0, 177 | 'flow_area': , 178 | 'head_loss': , 179 | 'id': 13, 180 | 'idiameter': , 181 | 'init_vol_flow': , 182 | 'kp': 1.8185974425119196, 183 | 'loopdir': {5: -1.0}, 184 | 'lpipe': , 185 | 'vol_flow': }, 186 | { 'chw': 130.0, 187 | 'expp': 0.0, 188 | 'flow_area': , 189 | 'head_loss': , 190 | 'id': 14, 191 | 'idiameter': , 192 | 'init_vol_flow': , 193 | 'kp': 0.5180763155795055, 194 | 'loopdir': {1: -1.0}, 195 | 'lpipe': , 196 | 'vol_flow': }, 197 | { 'chw': 130.0, 198 | 'expp': 0.0, 199 | 'flow_area': , 200 | 'head_loss': , 201 | 'id': 15, 202 | 'idiameter': , 203 | 'init_vol_flow': , 204 | 'kp': 0.2878201753219475, 205 | 'loopdir': {6: 1.0}, 206 | 'lpipe': , 207 | 'vol_flow': }], 208 | 'pseudoloop': [{'head_diff': , 'loop_id': 6}], 209 | 'pump': [ { 'a': , 210 | 'b': , 211 | 'flow_dir': -1.0, 212 | 'h0': , 213 | 'pipe_id': 0}]} 214 | 215 | -------------------------------------------------------------------------------- /userdoc/ch6b_case_dom_input_example.txt: -------------------------------------------------------------------------------- 1 | { 'loop': [ [ {'flow_dir': 1.0, 'pipe_id': 1}, 2 | {'flow_dir': 1.0, 'pipe_id': 2}, 3 | {'flow_dir': -1.0, 'pipe_id': 3}, 4 | {'flow_dir': -1.0, 'pipe_id': 4}], 5 | [ {'flow_dir': -1.0, 'pipe_id': 6}, 6 | {'flow_dir': 1.0, 'pipe_id': 7}, 7 | {'flow_dir': -1.0, 'pipe_id': 14}, 8 | {'flow_dir': -1.0, 'pipe_id': 2}], 9 | [ {'flow_dir': 1.0, 'pipe_id': 1}, 10 | {'flow_dir': -1.0, 'pipe_id': 6}, 11 | {'flow_dir': 1.0, 'pipe_id': 7}, 12 | {'flow_dir': -1.0, 'pipe_id': 5}], 13 | [ {'flow_dir': 1.0, 'pipe_id': 10}, 14 | {'flow_dir': 1.0, 'pipe_id': 11}, 15 | {'flow_dir': -1.0, 'pipe_id': 8}], 16 | [ {'flow_dir': 1.0, 'pipe_id': 8}, 17 | {'flow_dir': -1.0, 'pipe_id': 9}, 18 | {'flow_dir': -1.0, 'pipe_id': 7}], 19 | [ {'flow_dir': 1.0, 'pipe_id': 9}, 20 | {'flow_dir': 1.0, 'pipe_id': 12}, 21 | {'flow_dir': -1.0, 'pipe_id': 13}], 22 | [ {'flow_dir': 1.0, 'pipe_id': 15}, 23 | {'flow_dir': 1.0, 'pipe_id': 6}, 24 | {'flow_dir': -1.0, 'pipe_id': 1}, 25 | {'flow_dir': -1.0, 'pipe_id': 0}]], 26 | 'params': { 'maxiter': 15, 27 | 'nloops': 7, 28 | 'npipes': 16, 29 | 'npseudoloops': 1, 30 | 'npumps': 1, 31 | 'tolerance': 0.001}, 32 | 'pipe': [ { 'chw': 130.0, 33 | 'id': 0, 34 | 'idiameter': , 35 | 'init_vol_flow': , 36 | 'lpipe': }, 37 | { 'chw': 130.0, 38 | 'id': 1, 39 | 'idiameter': , 40 | 'init_vol_flow': , 41 | 'lpipe': }, 42 | { 'chw': 130.0, 43 | 'id': 2, 44 | 'idiameter': , 45 | 'init_vol_flow': , 46 | 'lpipe': }, 47 | { 'chw': 130.0, 48 | 'id': 3, 49 | 'idiameter': , 50 | 'init_vol_flow': , 51 | 'lpipe': }, 52 | { 'chw': 130.0, 53 | 'id': 4, 54 | 'idiameter': , 55 | 'init_vol_flow': , 56 | 'lpipe': }, 57 | { 'chw': 130.0, 58 | 'id': 5, 59 | 'idiameter': , 60 | 'init_vol_flow': , 61 | 'lpipe': }, 62 | { 'chw': 130.0, 63 | 'id': 6, 64 | 'idiameter': , 65 | 'init_vol_flow': , 66 | 'lpipe': }, 67 | { 'chw': 130.0, 68 | 'id': 7, 69 | 'idiameter': , 70 | 'init_vol_flow': , 71 | 'lpipe': }, 72 | { 'chw': 130.0, 73 | 'id': 8, 74 | 'idiameter': , 75 | 'init_vol_flow': , 76 | 'lpipe': }, 77 | { 'chw': 130.0, 78 | 'id': 9, 79 | 'idiameter': , 80 | 'init_vol_flow': , 81 | 'lpipe': }, 82 | { 'chw': 130.0, 83 | 'id': 10, 84 | 'idiameter': , 85 | 'init_vol_flow': , 86 | 'lpipe': }, 87 | { 'chw': 130.0, 88 | 'id': 11, 89 | 'idiameter': , 90 | 'init_vol_flow': , 91 | 'lpipe': }, 92 | { 'chw': 130.0, 93 | 'id': 12, 94 | 'idiameter': , 95 | 'init_vol_flow': , 96 | 'lpipe': }, 97 | { 'chw': 130.0, 98 | 'id': 13, 99 | 'idiameter': , 100 | 'init_vol_flow': , 101 | 'lpipe': }, 102 | { 'chw': 130.0, 103 | 'id': 14, 104 | 'idiameter': , 105 | 'init_vol_flow': , 106 | 'lpipe': }, 107 | { 'chw': 130.0, 108 | 'id': 15, 109 | 'idiameter': , 110 | 'init_vol_flow': , 111 | 'lpipe': }], 112 | 'pseudoloop': [{'head_diff': , 'loop_id': 6}], 113 | 'pump': [ { 'a': , 114 | 'b': , 115 | 'flow_dir': -1.0, 116 | 'h0': , 117 | 'pipe_id': 0}]} 118 | 119 | -------------------------------------------------------------------------------- /userdoc/ch7_case_dom_example.txt: -------------------------------------------------------------------------------- 1 | { 'loop': [ [ {'flow_dir': 1.0, 'pipe_id': 0}, 2 | {'flow_dir': 1.0, 'pipe_id': 1}, 3 | {'flow_dir': -1.0, 'pipe_id': 2}, 4 | {'flow_dir': -1.0, 'pipe_id': 3}], 5 | [ {'flow_dir': -1.0, 'pipe_id': 1}, 6 | {'flow_dir': -1.0, 'pipe_id': 4}, 7 | {'flow_dir': 1.0, 'pipe_id': 5}, 8 | {'flow_dir': 1.0, 'pipe_id': 6}]], 9 | 'params': { 'fvol_flow': 0.1, 10 | 'grav2': , 11 | 'kin_visc': , 12 | 'maxiter': 100, 13 | 'nloops': 2, 14 | 'npipes': 7, 15 | 'tolerance': 0.0001}, 16 | 'pipe': [ { 'LD': 3000.0, 17 | 'arl': , 18 | 'eroughness': 0.001, 19 | 'expp': 1.934292536665832, 20 | 'flow_area': , 21 | 'froughness': , 22 | 'head_loss': , 23 | 'id': 0, 24 | 'idiameter': , 25 | 'init_vol_flow': , 26 | 'kp': 1.6584967843083311, 27 | 'lpipe': , 28 | 'vol_flow': }, 29 | { 'LD': 1714.2857142857142, 30 | 'arl': , 31 | 'eroughness': 0.0008571428571428572, 32 | 'expp': 1.9521818394957096, 33 | 'flow_area': , 34 | 'froughness': , 35 | 'head_loss': , 36 | 'id': 1, 37 | 'idiameter': , 38 | 'init_vol_flow': , 39 | 'kp': 0.49381537307862666, 40 | 'lpipe': , 41 | 'vol_flow': }, 42 | { 'LD': 3600.0, 43 | 'arl': , 44 | 'eroughness': 0.0012000000000000001, 45 | 'expp': 1.9266254211599905, 46 | 'flow_area': , 47 | 'froughness': , 48 | 'head_loss': , 49 | 'id': 2, 50 | 'idiameter': , 51 | 'init_vol_flow': , 52 | 'kp': 4.217518859661509, 53 | 'lpipe': , 54 | 'vol_flow': }, 55 | { 'LD': 2400.0, 56 | 'arl': , 57 | 'eroughness': 0.0012000000000000001, 58 | 'expp': 1.9497641521941458, 59 | 'flow_area': , 60 | 'froughness': , 61 | 'head_loss': , 62 | 'id': 3, 63 | 'idiameter': , 64 | 'init_vol_flow': , 65 | 'kp': 2.79596465837528, 66 | 'lpipe': , 67 | 'vol_flow': }, 68 | { 'LD': 2571.4285714285716, 69 | 'arl': , 70 | 'eroughness': 0.0008571428571428572, 71 | 'expp': 1.9226268567212585, 72 | 'flow_area': , 73 | 'froughness': , 74 | 'head_loss': , 75 | 'id': 4, 76 | 'idiameter': , 77 | 'init_vol_flow': , 78 | 'kp': 0.7603948348660675, 79 | 'lpipe': , 80 | 'vol_flow': }, 81 | { 'LD': 2400.0, 82 | 'arl': , 83 | 'eroughness': 0.0012000000000000001, 84 | 'expp': 1.9470302499156507, 85 | 'flow_area': , 86 | 'froughness': , 87 | 'head_loss': , 88 | 'id': 5, 89 | 'idiameter': , 90 | 'init_vol_flow': , 91 | 'kp': 2.7996010430269647, 92 | 'lpipe': , 93 | 'vol_flow': }, 94 | { 'LD': 3000.0, 95 | 'arl': , 96 | 'eroughness': 0.001, 97 | 'expp': 1.8839261999050851, 98 | 'flow_area': , 99 | 'froughness': , 100 | 'head_loss': , 101 | 'id': 6, 102 | 'idiameter': , 103 | 'init_vol_flow': , 104 | 'kp': 1.6611978143224688, 105 | 'lpipe': , 106 | 'vol_flow': }]} 107 | 108 | -------------------------------------------------------------------------------- /userdoc/ch7_case_dom_input_example.txt: -------------------------------------------------------------------------------- 1 | { 'loop': [ [ {'flow_dir': 1.0, 'pipe_id': 0}, 2 | {'flow_dir': 1.0, 'pipe_id': 1}, 3 | {'flow_dir': -1.0, 'pipe_id': 2}, 4 | {'flow_dir': -1.0, 'pipe_id': 3}], 5 | [ {'flow_dir': -1.0, 'pipe_id': 1}, 6 | {'flow_dir': -1.0, 'pipe_id': 4}, 7 | {'flow_dir': 1.0, 'pipe_id': 5}, 8 | {'flow_dir': 1.0, 'pipe_id': 6}]], 9 | 'params': { 'fvol_flow': 0.1, 10 | 'grav2': , 11 | 'kin_visc': , 12 | 'maxiter': 100, 13 | 'nloops': 2, 14 | 'npipes': 7, 15 | 'tolerance': 0.0001}, 16 | 'pipe': [ { 'froughness': , 17 | 'id': 0, 18 | 'idiameter': , 19 | 'init_vol_flow': , 20 | 'lpipe': }, 21 | { 'froughness': , 22 | 'id': 1, 23 | 'idiameter': , 24 | 'init_vol_flow': , 25 | 'lpipe': }, 26 | { 'froughness': , 27 | 'id': 2, 28 | 'idiameter': , 29 | 'init_vol_flow': , 30 | 'lpipe': }, 31 | { 'froughness': , 32 | 'id': 3, 33 | 'idiameter': , 34 | 'init_vol_flow': , 35 | 'lpipe': }, 36 | { 'froughness': , 37 | 'id': 4, 38 | 'idiameter': , 39 | 'init_vol_flow': , 40 | 'lpipe': }, 41 | { 'froughness': , 42 | 'id': 5, 43 | 'idiameter': , 44 | 'init_vol_flow': , 45 | 'lpipe': }, 46 | { 'froughness': , 47 | 'id': 6, 48 | 'idiameter': , 49 | 'init_vol_flow': , 50 | 'lpipe': }]} 51 | --------------------------------------------------------------------------------