├── docs ├── requirements.txt ├── images │ ├── delfzijl1.jpg │ ├── delfzijl2.jpg │ ├── lockphase_1.png │ ├── lockphase_2.png │ ├── lockphase_3.png │ ├── lockphase_4.png │ ├── Krammersluizen.jpg │ ├── getting_started_excel.png │ ├── getting_started_pyzsf.png │ ├── examples_excel_steady_input.png │ ├── flushing_diagram_salt_enter.png │ ├── flushing_diagram_salt_final.png │ ├── introduction_model_concept.png │ ├── examples_excel_steady_output.png │ ├── flushing_diagram_fresh_enter.png │ ├── flushing_diagram_fresh_final.png │ ├── flushing_diagram_salt_reflect.png │ ├── procdef_phases_locking_cycle.png │ ├── examples_excel_phase_diffcolumns.png │ ├── examples_excel_steady_etacolumns.png │ ├── flushing_diagram_fresh_reflect.png │ ├── source │ │ ├── flushing_diagram_legend.png │ │ ├── flushing_diagram_salt_enter.png │ │ ├── flushing_diagram_salt_final.png │ │ ├── introduction_model_concept.xlsx │ │ ├── flushing_diagram_fresh_enter.png │ │ ├── flushing_diagram_fresh_final.png │ │ ├── flushing_diagram_fresh_reflect.png │ │ ├── flushing_diagram_salt_reflect.png │ │ ├── procdef_phases_locking_cycle.xlsx │ │ └── flushing_diagrams.py │ ├── examples_excel_phase_routine_2_inputs.png │ ├── examples_excel_phase_routine_4_inputs.png │ ├── examples_excel_steady_timedoorcolumns.png │ ├── examples_excel_phase_initialize_output.png │ ├── examples_excel_phase_routine_2_outputs.png │ ├── examples_excel_phase_routine_4_outputs.png │ ├── flushing_diagram_salt_side_alternatives.png │ ├── getting_started_excel_enable_developer.png │ └── getting_started_excel_run_macro_manually.png ├── _static │ └── css │ │ └── custom.css ├── api │ ├── index.rst │ ├── python-api.rst │ └── c-api.rst ├── examples │ ├── index.rst │ ├── excel │ │ ├── index.rst │ │ ├── steady.rst │ │ └── phase.rst │ └── python │ │ ├── index.rst │ │ ├── phase-multiple-lockages.rst │ │ ├── steady.rst │ │ └── phase.rst ├── support.rst ├── spelling_wordlist.txt ├── theory │ ├── index.rst │ ├── introduction.rst │ ├── cycle_averaged_flows_salinities.rst │ ├── numerical_approach_cycle_averaged.rst │ └── equations_per_locking_phase.rst ├── Makefile ├── make.bat ├── index.rst ├── conf.py └── getting-started.rst ├── AUTHORS ├── wrappers ├── excel │ └── zsf.xlsm ├── python │ ├── setup.cfg │ ├── src │ │ ├── pyzsf │ │ │ ├── __init__.py │ │ │ └── pyzsf.py │ │ └── _pyzsf_build.py │ ├── .gitignore │ ├── tox.ini │ ├── setup.py │ └── tests │ │ ├── test_unsteady.py │ │ └── test_steady.py └── fortran │ ├── test.f90 │ └── zsf.f90 ├── .clang-format ├── .gitignore ├── config.h.in ├── README.md ├── examples └── python │ ├── phase.py │ ├── steady.py │ └── phase_multiple_lockages.py ├── src └── util.h ├── CMakeLists.txt ├── include └── zsf.h ├── COPYING.LESSER ├── .github └── workflows │ └── ci.yml └── COPYING /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinxcontrib-images 3 | sphinx_rtd_theme 4 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Otto Weiler (Deltares) 2 | Tjerk Vreeken (Deltares) 3 | Lefki Loverdou (Deltares) 4 | -------------------------------------------------------------------------------- /wrappers/excel/zsf.xlsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/wrappers/excel/zsf.xlsm -------------------------------------------------------------------------------- /docs/images/delfzijl1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/delfzijl1.jpg -------------------------------------------------------------------------------- /docs/images/delfzijl2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/delfzijl2.jpg -------------------------------------------------------------------------------- /docs/_static/css/custom.css: -------------------------------------------------------------------------------- 1 | .math { 2 | text-align: left; 3 | } 4 | .eqno { 5 | float: right; 6 | } -------------------------------------------------------------------------------- /docs/api/index.rst: -------------------------------------------------------------------------------- 1 | API 2 | === 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | c-api 8 | python-api -------------------------------------------------------------------------------- /docs/images/lockphase_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/lockphase_1.png -------------------------------------------------------------------------------- /docs/images/lockphase_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/lockphase_2.png -------------------------------------------------------------------------------- /docs/images/lockphase_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/lockphase_3.png -------------------------------------------------------------------------------- /docs/images/lockphase_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/lockphase_4.png -------------------------------------------------------------------------------- /docs/images/Krammersluizen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/Krammersluizen.jpg -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | 3 | ColumnLimit: 100 4 | ReflowComments: false 5 | IndentPPDirectives: AfterHash 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build and distributions 2 | build 3 | dist 4 | 5 | # Editors 6 | .idea 7 | .ipynb_checkpoints 8 | .vscode 9 | -------------------------------------------------------------------------------- /docs/images/getting_started_excel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/getting_started_excel.png -------------------------------------------------------------------------------- /docs/images/getting_started_pyzsf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/getting_started_pyzsf.png -------------------------------------------------------------------------------- /config.h.in: -------------------------------------------------------------------------------- 1 | #ifndef ZSF_CONFIG_H 2 | #define ZSF_CONFIG_H 3 | 4 | #define ZSF_GIT_DESCRIBE "${git_describe}" 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /wrappers/python/setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | license_file = ../../COPYING.LESSER 3 | 4 | 5 | [flake8] 6 | max-line-length = 100 7 | -------------------------------------------------------------------------------- /docs/examples/index.rst: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | excel/index 8 | python/index 9 | -------------------------------------------------------------------------------- /docs/images/examples_excel_steady_input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/examples_excel_steady_input.png -------------------------------------------------------------------------------- /docs/images/flushing_diagram_salt_enter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/flushing_diagram_salt_enter.png -------------------------------------------------------------------------------- /docs/images/flushing_diagram_salt_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/flushing_diagram_salt_final.png -------------------------------------------------------------------------------- /docs/images/introduction_model_concept.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/introduction_model_concept.png -------------------------------------------------------------------------------- /docs/examples/excel/index.rst: -------------------------------------------------------------------------------- 1 | Excel Examples 2 | ============== 3 | 4 | .. toctree:: 5 | :maxdepth: 1 6 | 7 | steady 8 | phase 9 | -------------------------------------------------------------------------------- /docs/images/examples_excel_steady_output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/examples_excel_steady_output.png -------------------------------------------------------------------------------- /docs/images/flushing_diagram_fresh_enter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/flushing_diagram_fresh_enter.png -------------------------------------------------------------------------------- /docs/images/flushing_diagram_fresh_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/flushing_diagram_fresh_final.png -------------------------------------------------------------------------------- /docs/images/flushing_diagram_salt_reflect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/flushing_diagram_salt_reflect.png -------------------------------------------------------------------------------- /docs/images/procdef_phases_locking_cycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/procdef_phases_locking_cycle.png -------------------------------------------------------------------------------- /docs/images/examples_excel_phase_diffcolumns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/examples_excel_phase_diffcolumns.png -------------------------------------------------------------------------------- /docs/images/examples_excel_steady_etacolumns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/examples_excel_steady_etacolumns.png -------------------------------------------------------------------------------- /docs/images/flushing_diagram_fresh_reflect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/flushing_diagram_fresh_reflect.png -------------------------------------------------------------------------------- /docs/images/source/flushing_diagram_legend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/source/flushing_diagram_legend.png -------------------------------------------------------------------------------- /docs/images/source/flushing_diagram_salt_enter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/source/flushing_diagram_salt_enter.png -------------------------------------------------------------------------------- /docs/images/source/flushing_diagram_salt_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/source/flushing_diagram_salt_final.png -------------------------------------------------------------------------------- /docs/images/source/introduction_model_concept.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/source/introduction_model_concept.xlsx -------------------------------------------------------------------------------- /docs/images/examples_excel_phase_routine_2_inputs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/examples_excel_phase_routine_2_inputs.png -------------------------------------------------------------------------------- /docs/images/examples_excel_phase_routine_4_inputs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/examples_excel_phase_routine_4_inputs.png -------------------------------------------------------------------------------- /docs/images/examples_excel_steady_timedoorcolumns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/examples_excel_steady_timedoorcolumns.png -------------------------------------------------------------------------------- /docs/images/source/flushing_diagram_fresh_enter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/source/flushing_diagram_fresh_enter.png -------------------------------------------------------------------------------- /docs/images/source/flushing_diagram_fresh_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/source/flushing_diagram_fresh_final.png -------------------------------------------------------------------------------- /docs/images/source/flushing_diagram_fresh_reflect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/source/flushing_diagram_fresh_reflect.png -------------------------------------------------------------------------------- /docs/images/source/flushing_diagram_salt_reflect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/source/flushing_diagram_salt_reflect.png -------------------------------------------------------------------------------- /docs/images/source/procdef_phases_locking_cycle.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/source/procdef_phases_locking_cycle.xlsx -------------------------------------------------------------------------------- /docs/images/examples_excel_phase_initialize_output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/examples_excel_phase_initialize_output.png -------------------------------------------------------------------------------- /docs/images/examples_excel_phase_routine_2_outputs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/examples_excel_phase_routine_2_outputs.png -------------------------------------------------------------------------------- /docs/images/examples_excel_phase_routine_4_outputs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/examples_excel_phase_routine_4_outputs.png -------------------------------------------------------------------------------- /docs/images/flushing_diagram_salt_side_alternatives.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/flushing_diagram_salt_side_alternatives.png -------------------------------------------------------------------------------- /docs/images/getting_started_excel_enable_developer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/getting_started_excel_enable_developer.png -------------------------------------------------------------------------------- /docs/images/getting_started_excel_run_macro_manually.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deltares/D-SLE/master/docs/images/getting_started_excel_run_macro_manually.png -------------------------------------------------------------------------------- /docs/support.rst: -------------------------------------------------------------------------------- 1 | ####### 2 | Support 3 | ####### 4 | 5 | Raise any issue on `GitLab `_ such that we can address your problem. 6 | -------------------------------------------------------------------------------- /wrappers/python/src/pyzsf/__init__.py: -------------------------------------------------------------------------------- 1 | from .pyzsf import ZSFUnsteady, zsf_calc_steady # noqa: F401 2 | from .pyzsf import _zsf_version 3 | 4 | __version__ = _zsf_version() 5 | -------------------------------------------------------------------------------- /docs/examples/python/index.rst: -------------------------------------------------------------------------------- 1 | Python Examples 2 | =============== 3 | 4 | .. toctree:: 5 | :maxdepth: 1 6 | 7 | steady 8 | phase 9 | phase-multiple-lockages 10 | -------------------------------------------------------------------------------- /docs/api/python-api.rst: -------------------------------------------------------------------------------- 1 | Python API 2 | ========== 3 | 4 | .. autoclass:: pyzsf.ZSFUnsteady 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | .. autofunction:: pyzsf.zsf_calc_steady 10 | -------------------------------------------------------------------------------- /docs/spelling_wordlist.txt: -------------------------------------------------------------------------------- 1 | Celcius 2 | CSV 3 | inbetween 4 | Jupyter 5 | libzsf 6 | moore 7 | mDAT 8 | mNAP 9 | momentaneous 10 | preprocessing 11 | Rijkswaterstaat 12 | salinities 13 | salinization 14 | salinized 15 | subphase 16 | subphases 17 | Zeesluisformulering -------------------------------------------------------------------------------- /docs/theory/index.rst: -------------------------------------------------------------------------------- 1 | Theory 2 | ====== 3 | 4 | .. toctree:: 5 | :numbered: 6 | :maxdepth: 2 7 | 8 | introduction 9 | processes_and_definitions 10 | equations_per_locking_phase 11 | cycle_averaged_flows_salinities 12 | numerical_approach_cycle_averaged 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | libzsf is a library for salt intrusion through shipping locks 2 | 3 | [![CI](https://github.com/deltares/libzsf/workflows/CI/badge.svg)](https://github.com/deltares/libzsf/actions?query=workflow%3ACI) 4 | 5 | See the [documentation](https://libzsf.readthedocs.io/) for more details. 6 | -------------------------------------------------------------------------------- /wrappers/python/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.c 7 | *.so 8 | *.dll 9 | 10 | # Distribution & Packaging 11 | .eggs 12 | build 13 | dist 14 | src/*.egg-info 15 | 16 | # Testing & Coverage 17 | .pytest_cache 18 | .tox 19 | .coverage 20 | -------------------------------------------------------------------------------- /wrappers/python/tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | flake8,black,py{36,37,38} 4 | 5 | 6 | [testenv] 7 | deps = 8 | pytest 9 | numpy 10 | cffi 11 | extras = all 12 | commands = pytest tests 13 | 14 | 15 | [testenv:flake8] 16 | skip_install = True 17 | deps = 18 | flake8 19 | flake8-bugbear 20 | flake8-comprehensions 21 | flake8-import-order 22 | pep8-naming 23 | commands = flake8 src tests setup.py ../../examples/python 24 | 25 | 26 | [testenv:black] 27 | skip_install = True 28 | deps = 29 | click==8.0.4 30 | black==19.10b 31 | commands = 32 | black --line-length 100 --target-version py36 --check --diff src tests setup.py ../../examples/python 33 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/images/source/flushing_diagrams.py: -------------------------------------------------------------------------------- 1 | # To get the source png's of the diagram and the legend 2 | # 1. open the svg in Inkscape 3 | # 2. Select the items 4 | # 3. Export to png, and export the _selection_ with 96 dpi 5 | 6 | import glob 7 | 8 | from PIL import Image 9 | 10 | spacing_legend = 30 11 | 12 | 13 | def add_padding(img, pad=25): 14 | new = Image.new("RGBA", (img.size[0] + 2 * pad, img.size[1] + 2 * pad), (255, 255, 255, 0)) 15 | new.paste(img, (pad, pad)) 16 | return new 17 | 18 | 19 | def remove_alpha(img, color=(255, 255, 255)): 20 | background = Image.new("RGBA", img.size, color) 21 | return Image.alpha_composite(background, img) 22 | 23 | 24 | all_images = glob.glob("flushing_diagram_*.png") 25 | diagrams = [x for x in all_images if "legend" not in x] 26 | legend = next(x for x in all_images if "legend" in x) 27 | 28 | offset_x = min([Image.open(img).size[0] for img in diagrams]) 29 | 30 | lg = Image.open(legend) 31 | 32 | for img in diagrams: 33 | d = Image.open(img) 34 | n = Image.new("RGBA", (offset_x + spacing_legend + lg.size[0], d.size[1]), (255, 255, 255, 0)) 35 | n.paste(d, (0, 0)) 36 | n.paste(lg, (offset_x + spacing_legend, 0)) 37 | n = add_padding(n) 38 | n.save(f"../{img}", "PNG") 39 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. libzsf documentation master file, created by 2 | sphinx-quickstart on Sun Jun 28 11:56:44 2020. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | ZSF documentation 7 | ================= 8 | 9 | This is the documentation for the ZSF, a `Deltares `_ tool to calculate salt intrusion through shipping locks. 10 | The documentation covers: 11 | 12 | - How to get the ZSF running on your computer. 13 | For end-users this means downloading either the Excel workbook, or installing the Python package. 14 | - Making your own calculations, by means of several illustrative examples for both the Excel workbook and Python package. 15 | - The theory on which the ZSF is based, serving as background information on the inner workings of the libzsf core. 16 | - An overview of the C and Python API, e.g. to embed the ZSF in other software. 17 | 18 | .. note:: 19 | 20 | ZSF is an abbreviation for "Zeesluisformulering", which would translate to Sea Lock Formulation. 21 | 22 | .. toctree:: 23 | :maxdepth: 2 24 | :caption: Contents: 25 | 26 | getting-started 27 | examples/index 28 | theory/index 29 | api/index 30 | support 31 | 32 | 33 | Indices and tables 34 | ================== 35 | 36 | * :ref:`genindex` 37 | * :ref:`modindex` 38 | * :ref:`search` 39 | -------------------------------------------------------------------------------- /examples/python/phase.py: -------------------------------------------------------------------------------- 1 | import pprint 2 | 3 | import pyzsf 4 | 5 | 6 | lock_parameters = { 7 | "lock_length": 148.0, 8 | "lock_width": 14.0, 9 | "lock_bottom": -4.4, 10 | } 11 | 12 | boundary_conditions = { 13 | "head_lake": 0.0, 14 | "salinity_lake": 5.0, 15 | "temperature_lake": 15.0, 16 | "head_sea": 2.0, 17 | "salinity_sea": 25.0, 18 | "temperature_sea": 15.0, 19 | } 20 | 21 | operational_parameters = { 22 | "ship_volume_sea_to_lake": 1000.0, 23 | "ship_volume_lake_to_sea": 1000.0, 24 | } 25 | 26 | z = pyzsf.ZSFUnsteady(15.0, 0.0, **lock_parameters, **boundary_conditions, **operational_parameters) 27 | 28 | print("State after initialization") 29 | pprint.pprint(z.state) 30 | 31 | print("\nPhase 1:\n" + "*" * 8) 32 | 33 | results = z.step_phase_1(300.0) 34 | print("Transports:") 35 | pprint.pprint(results) 36 | print("State:") 37 | pprint.pprint(z.state) 38 | 39 | print("\nPhase 2:\n" + "*" * 8) 40 | results = z.step_phase_2(840.0) 41 | print("Transports:") 42 | pprint.pprint(results) 43 | print("State:") 44 | pprint.pprint(z.state) 45 | 46 | print("\nPhase 3:\n" + "*" * 8) 47 | results = z.step_phase_3(300.0) 48 | print("Transports:") 49 | pprint.pprint(results) 50 | print("State:") 51 | pprint.pprint(z.state) 52 | 53 | print("\nPhase 4:\n" + "*" * 8) 54 | results = z.step_phase_4(840.0, ship_volume_sea_to_lake=800.0) 55 | print("Transports:") 56 | pprint.pprint(results) 57 | print("State:") 58 | pprint.pprint(z.state) 59 | -------------------------------------------------------------------------------- /wrappers/python/setup.py: -------------------------------------------------------------------------------- 1 | """Model for salt intrusion through shipping locks. 2 | 3 | pyzsf calculates the salt intrusion through shipping locks based on 4 | easy-to-estimate operational parameters. 5 | """ 6 | import sys 7 | 8 | from setuptools import find_packages, setup 9 | 10 | 11 | DOCLINES = __doc__.split("\n") 12 | 13 | CLASSIFIERS = """\ 14 | Development Status :: 3 - Alpha 15 | Intended Audience :: Science/Research 16 | Intended Audience :: Information Technology 17 | License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3) 18 | Programming Language :: Other 19 | Topic :: Scientific/Engineering :: GIS 20 | Topic :: Scientific/Engineering :: Mathematics 21 | Topic :: Scientific/Engineering :: Physics 22 | Operating System :: Microsoft :: Windows 23 | Operating System :: POSIX 24 | Operating System :: Unix 25 | Operating System :: MacOS 26 | """ 27 | 28 | if sys.version_info < (3, 6): 29 | sys.exit("Sorry, Python 3.6 or newer is required.") 30 | 31 | try: 32 | version = open("../git_describe.txt").read().strip() 33 | if not version: 34 | raise ValueError("Version was empty") 35 | except (FileNotFoundError, ValueError): 36 | version = "0.0.1dev0" 37 | 38 | setup( 39 | name="pyzsf", 40 | version=version, 41 | description=DOCLINES[0], 42 | classifiers=[_f for _f in CLASSIFIERS.split("\n") if _f], 43 | url="https://www.deltares.nl", 44 | author="Jack Vreeken", 45 | author_email="jack@vreeken.me", 46 | maintainer="Jack Vreeken", 47 | license="LGPLv3", 48 | keywords="zsf shipping locks salt intrusion", 49 | platforms=["Windows", "Linux", "Mac OS-X", "Unix"], 50 | packages=find_packages("src"), 51 | package_dir={"": "src"}, 52 | setup_requires=["cffi >= 1.0.0"], 53 | cffi_modules=["src/_pyzsf_build.py:ffibuilder"], 54 | install_requires=["cffi >= 1.0.0"], 55 | tests_require=["pytest", "pytest-runner", "numpy"], 56 | python_requires=">=3.6", 57 | ) 58 | -------------------------------------------------------------------------------- /examples/python/steady.py: -------------------------------------------------------------------------------- 1 | import pyzsf 2 | 3 | 4 | lock_parameters = { 5 | "lock_length": 148.0, 6 | "lock_width": 14.0, 7 | "lock_bottom": -4.4, 8 | } 9 | 10 | boundary_conditions = { 11 | "head_lake": 0.0, 12 | "salinity_lake": 5.0, 13 | "temperature_lake": 15.0, 14 | "head_sea": 0.0, 15 | "salinity_sea": 25.0, 16 | "temperature_sea": 15.0, 17 | } 18 | 19 | operational_parameters = { 20 | "num_cycles": 30, 21 | "door_time_to_open": 300.0, 22 | "leveling_time": 300.0, 23 | "ship_volume_sea_to_lake": 1000.0, 24 | "ship_volume_lake_to_sea": 1000.0, 25 | } 26 | 27 | daytime_parameters = {**lock_parameters, **boundary_conditions, **operational_parameters} 28 | nighttime_parameters = {**daytime_parameters, "num_cycles": 10} 29 | 30 | # Calculate the transports without protection 31 | print("No measures:") 32 | results = pyzsf.zsf_calc_steady(**daytime_parameters) 33 | print("Day = {:.1f} kg/s".format(-1 * results["salt_load_lake"])) 34 | 35 | results = pyzsf.zsf_calc_steady(**nighttime_parameters) 36 | print("Night = {:.1f} kg/s".format(-1 * results["salt_load_lake"])) 37 | 38 | # Transports with a bubble screen 39 | bubble_screen_parameters = { 40 | "density_current_factor_lake": 0.25, 41 | "density_current_factor_sea": 0.25, 42 | } 43 | 44 | print("\nBubble screen (25%):") 45 | results = pyzsf.zsf_calc_steady(**daytime_parameters, **bubble_screen_parameters) 46 | print("Day = {:.1f} kg/s".format(-1 * results["salt_load_lake"])) 47 | 48 | results = pyzsf.zsf_calc_steady(**nighttime_parameters, **bubble_screen_parameters) 49 | print("Night = {:.1f} kg/s".format(-1 * results["salt_load_lake"])) 50 | 51 | # Auxiliary results, showing how long the doors are open 52 | print("\nDoor open times at night:") 53 | results = pyzsf.zsf_calc_steady(True, **nighttime_parameters, **bubble_screen_parameters) 54 | for k, v in results.items(): 55 | if k.startswith("t_open"): 56 | print(f"{k} = {v}") 57 | 58 | # Transports at night with bubble screen and closing the doors sooner 59 | print("\nBubble screen (25%), and close doors sooner:") 60 | results = pyzsf.zsf_calc_steady( 61 | **nighttime_parameters, **bubble_screen_parameters, calibration_coefficient=0.3 62 | ) 63 | print("Night = {:.1f} kg/s".format(-1 * results["salt_load_lake"])) 64 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | #ifndef ZSF_UTIL_H 2 | #define ZSF_UTIL_H 3 | 4 | #include "zsf.h" 5 | #include 6 | 7 | int is_close(double a, double b, double rtol, double atol); 8 | double sal_psu_2_density(double sal_psu, double temperature); 9 | double sal_2_density(double sal_kgm3, double temperature, double rtol, double atol); 10 | 11 | int is_close(double a, double b, double rtol, double atol) { 12 | double max_abs = fmax(fabs(a), fabs(b)); 13 | if (fabs(a - b) <= fmax(rtol * max_abs, atol)) 14 | return 1; 15 | else 16 | return 0; 17 | } 18 | 19 | double sal_psu_2_density(double sal_psu, double temperature) { 20 | // Calculates the density of sea water using the UNESCO 1981 algorithm. 21 | double a = (8.24493E-1 - 4.0899E-3 * temperature + 7.6438E-5 * pow(temperature, 2.0) - 22 | 8.2467E-7 * pow(temperature, 3.0) + 5.3875E-9 * pow(temperature, 4.0)); 23 | double b = -5.72466E-3 + 1.0227E-4 * temperature - 1.6546E-6 * pow(temperature, 2.0); 24 | double c = 4.8314E-4; 25 | 26 | double rho_ref = (999.842594 + 6.793952E-2 * temperature - 9.095290E-3 * pow(temperature, 2.0) + 27 | 1.001685E-4 * pow(temperature, 3.0) - 1.120083E-6 * pow(temperature, 4.0) + 28 | 6.536332E-9 * pow(temperature, 5.0)); 29 | 30 | return rho_ref + a * sal_psu + b * pow(sal_psu, 1.5) + c * pow(sal_psu, 2.0); 31 | } 32 | 33 | double sal_2_density(double sal_kgm3, double temperature, double rtol, double atol) { 34 | /* 35 | Calculates the density of sea water using the UNESCO 1981 algorith, but 36 | using salinity in kg/m3 as input. 37 | 38 | It defers to the reference implementation (with salinity in psu), and 39 | loops until absolute or relative convergence tolerance (on the density) 40 | has been reached. 41 | 42 | Typically only a handful (1-10) of iterations are needed to reach any 43 | reasonably desired absolute tolerance. An upper bound of 100 iterations is 44 | used to catch any case where the algorithm does not converge. 45 | */ 46 | 47 | double sal_psu = sal_kgm3; 48 | double rho = 1000.0; 49 | 50 | for (int i = 0; i < 100; i++) { 51 | double rho_new = sal_psu_2_density(sal_psu, temperature); 52 | sal_psu = sal_kgm3 / rho_new * 1000.0; 53 | 54 | if (is_close(rho_new, rho, rtol, atol)) 55 | return rho_new; 56 | 57 | rho = rho_new; 58 | } 59 | return ZSF_NAN; 60 | } 61 | #endif 62 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | 16 | sys.path.insert(0, os.path.abspath('../wrappers/python/src')) 17 | 18 | import sphinx_rtd_theme 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = 'libzsf' 23 | copyright = '2020, Tjerk Vreeken, Otto Weiler' 24 | author = 'Tjerk Vreeken, Otto Weiler' 25 | 26 | 27 | # -- General configuration --------------------------------------------------- 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | 'sphinx.ext.autodoc', 34 | 'sphinxcontrib.images', 35 | ] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ['_templates'] 39 | 40 | # List of patterns, relative to source directory, that match files and 41 | # directories to ignore when looking for source files. 42 | # This pattern also affects html_static_path and html_extra_path. 43 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 44 | 45 | 46 | # -- Options for HTML output ------------------------------------------------- 47 | 48 | # The theme to use for HTML and HTML Help pages. See the documentation for 49 | # a list of builtin themes. 50 | # 51 | html_theme = 'sphinx_rtd_theme' 52 | 53 | # Add any paths that contain custom static files (such as style sheets) here, 54 | # relative to this directory. They are copied after the builtin static files, 55 | # so a file named "default.css" will overwrite the builtin "default.css". 56 | html_static_path = ['_static'] 57 | 58 | html_css_files = [ 59 | 'css/custom.css', 60 | ] 61 | 62 | # We do not need or want to install the module when building documentation. 63 | # See also Section 2.12.5 "I get import errors on libraries that depend on C 64 | # modules" in the ReadTheDocs documentation. 65 | autodoc_mock_imports = ["pyzsf._zsf_cffi"] 66 | -------------------------------------------------------------------------------- /wrappers/python/tests/test_unsteady.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import numpy as np 4 | 5 | from pyzsf import ZSFUnsteady 6 | 7 | 8 | class TestSaltLoadUnsteady(unittest.TestCase): 9 | def setUp(self): 10 | self.parameters = { 11 | "lock_length": 240.0, 12 | "lock_width": 12.0, 13 | "lock_bottom": -4.0, 14 | "num_cycles": 24.0, 15 | "door_time_to_open": 300.0, 16 | "leveling_time": 300.0, 17 | "calibration_coefficient": 1.0, 18 | "symmetry_coefficient": 1.0, 19 | "ship_volume_sea_to_lake": 0.0, 20 | "ship_volume_lake_to_sea": 0.0, 21 | "head_sea": 0.0, 22 | "salinity_sea": 25.0, 23 | "temperature_sea": 15.0, 24 | "head_lake": 0.0, 25 | "salinity_lake": 5.0, 26 | "temperature_lake": 15.0, 27 | "flushing_discharge_high_tide": 0.0, 28 | "flushing_discharge_low_tide": 0.0, 29 | "density_current_factor_sea": 1.0, 30 | "density_current_factor_lake": 1.0, 31 | } 32 | 33 | @staticmethod 34 | def assert_allclose_loose(*args, **kwargs): 35 | return np.testing.assert_allclose(*args, **kwargs, rtol=0.01, atol=0.01) 36 | 37 | @staticmethod 38 | def assert_allclose_tight(*args, **kwargs): 39 | # For when results are supposed to be the same, but might be slightly 40 | # different due to the convergence criterion and/or numerical 41 | # inaccuries. 42 | return np.testing.assert_allclose(*args, **kwargs, rtol=1e-5, atol=1e-5) 43 | 44 | def test_flush_doors_closed(self): 45 | init_sal = 15.0 46 | 47 | c = ZSFUnsteady(init_sal, 0.0, **self.parameters) 48 | 49 | # Sanity check when flushing with no discharge 50 | duration = 1000.0 51 | c.step_flush_doors_closed(duration) 52 | self.assert_allclose_tight(c.state["salinity_lock"], init_sal) 53 | 54 | # Flushing with 1.0 m3/s for 0.0 seconds 55 | duration = 0.0 56 | c.step_flush_doors_closed( 57 | duration, flushing_discharge_low_tide=1.0, flushing_discharge_high_tide=1.0 58 | ) 59 | self.assert_allclose_tight(c.state["salinity_lock"], init_sal) 60 | 61 | # Continue flushing with 1.0 m3/s, but for an insanely long amount of time 62 | duration = 1e9 63 | c.step_flush_doors_closed(duration) 64 | self.assert_allclose_tight(c.state["salinity_lock"], self.parameters["salinity_lake"]) 65 | -------------------------------------------------------------------------------- /examples/python/phase_multiple_lockages.py: -------------------------------------------------------------------------------- 1 | import pprint 2 | 3 | import pandas as pd 4 | 5 | import pyzsf 6 | 7 | 8 | lock_parameters = { 9 | "lock_length": 300.0, 10 | "lock_width": 25.0, 11 | "lock_bottom": -7.0, 12 | } 13 | 14 | constant_boundary_conditions = { 15 | "head_lake": 0.0, 16 | "temperature_lake": 15.0, 17 | "temperature_sea": 15.0, 18 | } 19 | 20 | mitigation_parameters = { 21 | "density_current_factor_lake": 0.25, 22 | "density_current_factor_sea": 0.25, 23 | "distance_door_bubble_screen_lake": 10.0, 24 | "distance_door_bubble_screen_sea": 10.0, 25 | "flushing_discharge_high_tide": 0.0, 26 | "flushing_discharge_low_tide": 0.0, 27 | "sill_height_lake": 0.5, 28 | } 29 | 30 | # Initialize the lock 31 | z = pyzsf.ZSFUnsteady( 32 | 15.0, 0.0, **lock_parameters, **constant_boundary_conditions, **mitigation_parameters 33 | ) 34 | 35 | # Read the lockages from a file 36 | df_lockages = pd.read_csv("lockages.csv", index_col=0) 37 | lockages = list(df_lockages.to_dict("records")) 38 | 39 | # Go through all lockages 40 | all_results = [] 41 | 42 | for parameters in lockages: 43 | routine = int(parameters.pop("routine")) 44 | t_open_lake = parameters.pop("t_open_lake") 45 | t_open_sea = parameters.pop("t_open_sea") 46 | t_level = parameters.pop("t_level") 47 | t_flushing = parameters.pop("t_flushing") 48 | 49 | parameters["ship_volume_sea_to_lake"] = 0.0 50 | parameters["ship_volume_lake_to_sea"] = 0.0 51 | 52 | if routine == 1: 53 | assert t_level > 0 54 | results = z.step_phase_1(t_level, **parameters) 55 | elif routine == 2: 56 | results = z.step_phase_2(t_open_lake, **parameters) 57 | elif routine == 3: 58 | assert t_level > 0 59 | results = z.step_phase_3(t_level, **parameters) 60 | elif routine == 4: 61 | results = z.step_phase_4(t_open_sea, **parameters) 62 | elif routine in {-2, -4}: 63 | results = z.step_flush_doors_closed(t_flushing, **parameters) 64 | else: 65 | raise Exception(f"Unknown routine '{routine}'") 66 | 67 | all_results.append(results) 68 | 69 | # Aggregate results 70 | duration = 60 * 24 * 3600 # 60 days 71 | 72 | overall_results = {} 73 | overall_mass_to_sea = 0.0 74 | overall_mass_to_lake = 0.0 75 | 76 | for results in all_results: 77 | for k, v in results.items(): 78 | if k.startswith(("volume_", "mass_")): 79 | overall_results[k] = overall_results.get(k, 0.0) + v 80 | 81 | overall_mass_to_sea += results["volume_to_sea"] * results["salinity_to_sea"] 82 | overall_mass_to_lake += results["volume_to_lake"] * results["salinity_to_lake"] 83 | 84 | overall_results["salinity_to_sea"] = overall_mass_to_sea / overall_results["volume_to_sea"] 85 | overall_results["salinity_to_lake"] = overall_mass_to_lake / overall_results["volume_to_lake"] 86 | 87 | overall_discharges = {} 88 | for k, v in overall_results.items(): 89 | if k.startswith("volume_"): 90 | overall_discharges[f"discharge_{k[7:]}"] = v / duration 91 | overall_results.update(overall_discharges) 92 | 93 | assert overall_results.keys() == all_results[0].keys() 94 | 95 | # Log to console 96 | print("Overall results (60 day aggregates and averages):") 97 | pprint.pprint(overall_results) 98 | -------------------------------------------------------------------------------- /docs/theory/introduction.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | Motivation and goals 5 | -------------------- 6 | 7 | The Directorate-General for Public Works and Water Management (Dutch: Rijkswaterstaat), but also other private and public parties around the world in charge of water reserves, would like to calculate the influence of the salt intrusion through shipping locks on the salinity of the fresh water at a reasonable distance from the lock. 8 | For example, to calculate the salinity of the water that farmers use to irrigate their land. 9 | Such calculations should be able to consider a long period of time and area, as the spread and build-up of salt can take months or years. 10 | It is also necessary to be able to consider multiple economical- and climate scenarios, along with the possible measures both on the shipping lock itself and those elsewhere in the system. 11 | For this reason, there is a need to have a fast and compact formulation of the processes on the lock, such that this formulation can be used inside a far-field model that models the spread. 12 | Such a formulation can then also be used to calculate the transport of water and salt through a shipping lock in a stand-alone fashion (i.e. without coupling it to- or incorporating it in a far-field model). 13 | 14 | Approach 15 | -------- 16 | 17 | The chosen approach for the ZSF is to set up the equations that describe the flows into and out of the lock, for all phases of the locking cycle. 18 | Concretely, this means the volumes due leveling, the lock exchange process, ship displacement, and flushing discharges through the lock chamber. 19 | Note that this phase-wise approach is opposed to a fixed time-step approach. 20 | 21 | In case registrations of the door movements are available, one can calculate per locking phase what the transports of water and salt are in that particular phase over both lock heads (salt side and fresh side). 22 | That way one can calculate based on historical registrations what the salt transport has been. 23 | 24 | When no registrations are available, a cycle-averaged transport of salt and water can be calculated based on a few key parameters of the lock operation. 25 | These parameters are then translated into a repeating pattern of locking phases, i.e. when the door is open and how long leveling is supposed to take. 26 | Then, the transports of water and salt per phase can be added up, leading to cycle-averaged values of the transports. 27 | These averaged values can then serve as in a forcing in a far-field model. 28 | 29 | An overview of the concept of the ZSF model is given in figure below. 30 | A more detailed discussion of all the physical quantities involved will be explained later. 31 | What is visible is that the boundary conditions of the model are the temperature, salinity and water level on each side of the lock. 32 | These boundary conditions, together with the geometry and operation of the lock, determine the discharge that go into the lock with the governing salinity, and out of the lock with a salinity in the lock chamber that follows from the locking process. 33 | The flows into the lock chamber are a withdrawal from the approach harbor on each side of the lock, :math:`Q^-_F` and :math:`Q^-_S`, and the flows going out of the lock are discharges to the approach harbors, :math:`Q^+_F` and :math:`Q^+_S`. 34 | 35 | .. figure:: ../images/introduction_model_concept.png 36 | 37 | Schematic overview of the model concept: discharges going in and out on both sides of the lock, with their corresponding salinities. 38 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | project(libzsf) 4 | enable_language(C) 5 | 6 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_BINARY_DIR}) 7 | 8 | ############################################################################## 9 | ################################## Version ################################### 10 | ############################################################################## 11 | # For systems with git installed, find out revision and description. 12 | execute_process(COMMAND git rev-parse HEAD 13 | OUTPUT_VARIABLE git_revision 14 | OUTPUT_STRIP_TRAILING_WHITESPACE 15 | ERROR_QUIET 16 | ) 17 | execute_process(COMMAND git describe --tags --first-parent HEAD 18 | OUTPUT_VARIABLE git_describe 19 | OUTPUT_STRIP_TRAILING_WHITESPACE 20 | ERROR_QUIET 21 | ) 22 | 23 | string(REPLACE "-g" ".g" git_describe "${git_describe}") 24 | string(REPLACE "-dirty" ".dirty" git_describe "${git_describe}") 25 | string(REPLACE "-" "+" git_describe "${git_describe}") 26 | set(PACKAGE_VERSION_FULL "${git_describe}") 27 | 28 | configure_file(config.h.in config.h ESCAPE_QUOTES) 29 | file(WRITE "wrappers/git_describe.txt" "${git_describe}") 30 | 31 | ############################################################################## 32 | ################################## Options ################################### 33 | ############################################################################## 34 | # Default build type is Release 35 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 36 | set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type (default Release)" FORCE) 37 | endif() 38 | 39 | option(USE_FAST_MATH "Enable fast math optimizations" OFF) 40 | if(USE_FAST_MATH) 41 | if (MSVC) 42 | add_compile_options(/fp:fast) 43 | elseif((CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_C_COMPILER_ID MATCHES "GNU")) 44 | add_compile_options(-ffast-math) 45 | endif() 46 | else() 47 | if (MSVC) 48 | add_compile_options(/fp:precise) 49 | endif() 50 | endif() 51 | 52 | option(USE_FAST_TANH "Enable fast tanh approximation" OFF) 53 | if(USE_FAST_TANH) 54 | add_definitions(-DZSF_USE_FAST_TANH) 55 | endif() 56 | 57 | ############################################################################## 58 | ################################## Targets ################################### 59 | ############################################################################## 60 | set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") 61 | 62 | add_library(zsf SHARED src/zsf.c) 63 | 64 | set_target_properties (zsf PROPERTIES 65 | DEFINE_SYMBOL "ZSF_EXPORTS" 66 | OUTPUT_NAME "zsf" 67 | PUBLIC_HEADER "include/zsf.h" 68 | ) 69 | 70 | add_library(zsf-static STATIC src/zsf.c) 71 | 72 | set_target_properties(zsf-static PROPERTIES 73 | COMPILE_DEFINITIONS "ZSF_STATIC" 74 | OUTPUT_NAME "zsf-static" 75 | PUBLIC_HEADER "include/zsf.h" 76 | POSITION_INDEPENDENT_CODE ON 77 | ) 78 | 79 | set(INSTALL_TARGETS zsf zsf-static) 80 | 81 | # We also generate a 32-bits stdcall version for VBA 82 | if(CMAKE_SIZEOF_VOID_P EQUAL 8) 83 | # 64 bits - do nothing. 64 bits office can just use the regular dll 84 | elseif(CMAKE_SIZEOF_VOID_P EQUAL 4) 85 | add_library(zsf-stdcall SHARED src/zsf.c) 86 | 87 | set_target_properties (zsf-stdcall PROPERTIES 88 | DEFINE_SYMBOL "ZSF_EXPORTS" 89 | COMPILE_DEFINITIONS "ZSF_USE_STDCALL" 90 | OUTPUT_NAME "zsf-stdcall" 91 | PUBLIC_HEADER "include/zsf.h" 92 | ) 93 | 94 | set(INSTALL_TARGETS ${INSTALL_TARGETS} zsf-stdcall) 95 | endif() 96 | 97 | install( 98 | TARGETS 99 | ${INSTALL_TARGETS}) 100 | -------------------------------------------------------------------------------- /wrappers/fortran/test.f90: -------------------------------------------------------------------------------- 1 | program test 2 | use, intrinsic :: iso_c_binding, only : c_int 3 | use zsf 4 | implicit none 5 | 6 | ! initialization 7 | type(zsf_param_t) :: p 8 | type(zsf_results_t) :: results 9 | type(zsf_aux_results_t) :: aux_results 10 | type(zsf_phase_state_t) :: state 11 | type(zsf_phase_transports_t) :: transports 12 | integer(c_int) :: err_code 13 | 14 | call zsf_param_default(p) 15 | p%lock_length = 240.0 16 | p%lock_width = 12.0 17 | p%lock_bottom = -4.0 18 | p%num_cycles = 24.0 19 | p%door_time_to_open = 300.0 20 | p%leveling_time = 300.0 21 | p%calibration_coefficient = 1.0 22 | p%symmetry_coefficient = 1.0 23 | p%ship_volume_sea_to_lake = 0.0 24 | p%ship_volume_lake_to_sea = 0.0 25 | p%head_sea = 0.0 26 | p%salinity_sea = 25.0 27 | p%temperature_sea = 15.0 28 | p%head_lake = 0.0 29 | p%salinity_lake = 5.0 30 | p%temperature_lake = 15.0 31 | p%flushing_discharge_high_tide = 0.0 32 | p%flushing_discharge_low_tide = 0.0 33 | p%density_current_factor_sea = 1.0 34 | p%density_current_factor_lake = 1.0 35 | 36 | ! Test is steady state works 37 | err_code = zsf_calc_steady(p, results, aux_results) 38 | 39 | if (err_code > 0) then 40 | write(*, *) 'zsf_calc_steady failed' 41 | write(*, *) zsf_error_msg(err_code) 42 | call exit(1) 43 | endif 44 | 45 | ! Salt load should be about -34.315 46 | write(*, *) 'Steady salt load: ', results%salt_load_lake 47 | 48 | if (results%salt_load_lake < -34.4 .or. results%salt_load_lake > -34.2) then 49 | write(*, *) 'zsf_calc_steady did not give correct results' 50 | call exit(1) 51 | endif 52 | 53 | ! Test is phase-wise calculation works 54 | p%lock_length = 148.0 55 | p%lock_width = 14.0 56 | p%lock_bottom = -4.4 57 | p%head_lake = 0.0 58 | p%salinity_lake = 5.0 59 | p%temperature_lake = 15.0 60 | p%head_sea = 2.0 61 | p%salinity_sea = 25.0 62 | p%temperature_sea = 15.0 63 | p%ship_volume_sea_to_lake = 1000.0 64 | p%ship_volume_lake_to_sea = 1000.0 65 | 66 | err_code = zsf_initialize_state(p, state, 15.0, 0.0) 67 | if (err_code > 0) then 68 | write(*, *) 'zsf_initialize_state failed' 69 | write(*, *) zsf_error_msg(err_code) 70 | call exit(1) 71 | endif 72 | 73 | err_code = zsf_step_phase_1(p, 300.0, state, transports) 74 | if (err_code > 0) then 75 | write(*, *) 'zsf_step_phase_1 failed' 76 | write(*, *) zsf_error_msg(err_code) 77 | call exit(1) 78 | endif 79 | 80 | err_code = zsf_step_phase_2(p, 840.0, state, transports) 81 | if (err_code > 0) then 82 | write(*, *) 'zsf_step_phase_2 failed' 83 | write(*, *) zsf_error_msg(err_code) 84 | call exit(1) 85 | endif 86 | 87 | err_code = zsf_step_phase_3(p, 300.0, state, transports) 88 | if (err_code > 0) then 89 | write(*, *) 'zsf_step_phase_3 failed' 90 | write(*, *) zsf_error_msg(err_code) 91 | call exit(1) 92 | endif 93 | 94 | p%ship_volume_sea_to_lake = 800.0 95 | err_code = zsf_step_phase_4(p, 840.0, state, transports) 96 | if (err_code > 0) then 97 | write(*, *) 'zsf_step_phase_4 failed' 98 | write(*, *) zsf_error_msg(err_code) 99 | call exit(1) 100 | endif 101 | 102 | write(*, *) '' 103 | write(*, *) 'Unsteady results: ' 104 | write(*, *) 'head_lock = ', state%head_lock 105 | write(*, *) 'salinity_lock = ', state%salinity_lock 106 | write(*, *) 'saltmass_lock = ', state%saltmass_lock 107 | write(*, *) 'volume_ship_in_lock = ', state%volume_ship_in_lock 108 | 109 | if (state%salinity_lock < 22.5 .or. state%salinity_lock > 22.7) then 110 | write(*, *) 'Unsteady calculation did not give correct results' 111 | call exit(1) 112 | endif 113 | end program test 114 | -------------------------------------------------------------------------------- /docs/getting-started.rst: -------------------------------------------------------------------------------- 1 | Getting Started 2 | +++++++++++++++ 3 | 4 | Typical end-users should use either the Excel wrapper, or the Python `pyzsf` package, instructions for which are in the respective sections below. 5 | When embedding the ZSF in other software, the easiest way is to download `libzsf` shared or static libraries for Linux or Windows, see :ref:`getstart_clib`. 6 | It is of course also possible to build the Python and C libraries from source, see :ref:`getstart_fromsource`. 7 | 8 | Excel workbook 9 | ============== 10 | 11 | One of the easiest ways of using the ZSF on Windows is to download the Excel workbook from the `releases `_ page on GitLab. 12 | This will download a zip file containing a macro-enabled Excel file, and two DLLs (one for 32-bit systems, and one for 64-bit systems). 13 | These DLLs contain computational routines that are called using Excel macros. 14 | 15 | When opening the workbook, you need to make sure to enable the macros if so prompted, otherwise it will not be possible to calculate anything. 16 | 17 | To test whether the workbook runs as intended, select cells `AH4:BI4` that contain the output of the first row of input, and delete their contents. 18 | 19 | .. thumbnail:: images/getting_started_excel.png 20 | :width: 50% 21 | 22 | Next, with the cells still selected, press the `Run ZSF` button. 23 | If correct, the output that was there should reappear. 24 | For further instructions, it is best to go through the examples. 25 | 26 | Troubleshooting 27 | --------------- 28 | 29 | If nothing happens, there is a chance the macro did not run, because the button is unresponsive. 30 | To test whether this is the case, and check that macros are indeed able to run, go to `File -> Options -> Customize Ribbon` and enable the Developer mode. 31 | 32 | .. thumbnail:: images/getting_started_excel_enable_developer.png 33 | :width: 50% 34 | 35 | With that done, select the Developer tab on the ribbon, and run the macro manually (still with the cells on row 4 selected). 36 | 37 | .. thumbnail:: images/getting_started_excel_run_macro_manually.png 38 | :width: 50% 39 | 40 | Python package 41 | ============== 42 | 43 | The Python package of the ZSF is called `pyzsf`. 44 | Although not required, it is recommended to install it in a virtual environment. 45 | See the `official Python tutorial `_ for more information on how to set up and activate a virtual environment. 46 | 47 | `pyzsf`, including its dependencies, can be installed using the `pip `_ package manager:: 48 | 49 | # Install pyzsf using the pip package manager 50 | pip install pyzsf 51 | 52 | .. note:: 53 | 54 | Pip version 20.0 or higher is required to install `pyzsf`, or it will fail to find a matching distribution for your platform. 55 | If you have an older version, please run ``python -m pip install -U pip`` before installing pyzsf. 56 | 57 | To test whether it works, import pyzsf and call its ``zsf_calc_steady()`` function. 58 | 59 | .. thumbnail:: images/getting_started_pyzsf.png 60 | :width: 50% 61 | 62 | .. _getstart_clib: 63 | 64 | C library 65 | ========= 66 | 67 | Static and dynamic libraries for both Windows and Linux are available on the `releases `_ page on GitLab. 68 | The Linux libraries are built using the `manylinux2010 `_ docker image, and should be therefore be compatible with most versions of Linux. 69 | 70 | Fortran interface 71 | ================= 72 | 73 | A wrapper is provided to easily call the static and dynamic libraries from Fortran. 74 | See the `releases `_ page on GitLab, or download the ``zsf.f90`` interface file directly from the `git tree `_. 75 | 76 | .. _getstart_fromsource: 77 | 78 | From Source 79 | =========== 80 | 81 | The latest libzsf source can be downloaded using `Git `_:: 82 | 83 | # Get libzsf source 84 | git clone https://gitlab.com/deltares/libzsf.git 85 | 86 | Note that `cmake` is needed to build libzsf, and a working Python installation is required to build the pyzsf wrapper. 87 | For more detailed build instructions, it is probably easiest to look at the ``build:windows`` and ``build:linux`` sections in the `.gitlab.yml` file in the root of the source tree. 88 | These instructions are always up to date, and give a concise and clear overview of the steps required to build from source. 89 | -------------------------------------------------------------------------------- /docs/theory/cycle_averaged_flows_salinities.rst: -------------------------------------------------------------------------------- 1 | .. _chapter_cycle_avg_flow_sals: 2 | 3 | Cycle-averaged flows and salinities 4 | =================================== 5 | 6 | Based on the volumes per locking cycle we now can, for each of the locking heads, determine the total transported volumes with their corresponding salinities. 7 | From these volumes the cycle-averaged flows can be determined. 8 | 9 | Fresh side 10 | ---------- 11 | 12 | The combined equation for the fresh side gives the total transport of the entire locking cycle. 13 | This equation is as follows: 14 | 15 | .. math:: 16 | :label: cycleavg_fresh_side_total_mass 17 | 18 | M_F = M_{F,1} + M_{F,2} + M_{F,4} 19 | 20 | Aside from that we have information about the amount of water that is withdrawn from the fresh side 21 | 22 | .. math:: 23 | :label: cycleavg_fresh_side_vol_withdrawn 24 | 25 | V_F^- = V_{Level,LT} + V_{Ship,Up} + V_{U,F} + Q_{flush} \cdot 2 \cdot T_{open} 26 | 27 | and the volume that is discharged to the fresh side 28 | 29 | .. math:: 30 | :label: cycleavg_fresh_side_vol_discharged 31 | 32 | V_F^+ = V_{Level,HT} + V_{U,F} + V_{Ship,Down} 33 | 34 | 35 | By dividing both volumes by the time spent on a total locking cycle, we can determine the cycle-averaged flows. 36 | Each of these flows has a corresponding discharge, and can be connected to cells in a far-field model as a discharge or withdrawal: 37 | 38 | - Withdrawal from the fresh side, with the prevailing salinity :math:`S_F`: 39 | 40 | .. math:: 41 | :label: cycleavg_fresh_side_flow_withdrawn 42 | 43 | Q_F^- = \frac{ V_F^- }{ T_{cycle} } 44 | 45 | - Discharge to the fresh side with a to-be-determined average salinity: 46 | 47 | .. math:: 48 | :label: cycleavg_fresh_side_flow_discharged 49 | 50 | Q_F^+ = \frac{ V_F^+ } { T_{cycle} }; S = S_F^+ 51 | 52 | The average salinity for the water discharged to the fresh side is determined from the mass and volume transports: 53 | 54 | .. math:: 55 | :label: cycleavg_fresh_side_sal_discharged 56 | 57 | S_F^+ = -\frac{ \left( M_F - V_F^- S_F \right) }{ V_F^+ } 58 | 59 | In case of stand-alone application, but also to compare with other locks or salt intrusion measure configurations, it can be useful to express the salt transport as a mass flux: 60 | 61 | .. math:: 62 | :label: cycleavg_fresh_side_mass_flux 63 | 64 | {\dot{M}}_F = \frac{ M_F }{ T_{cycle} } 65 | 66 | Salt side 67 | --------- 68 | 69 | The combined equation for the salt side is: 70 | 71 | .. math:: 72 | :label: cycleavg_salt_side_total_mass 73 | 74 | M_S = M_{S,2} + M_{S,3} + M_{S,4} 75 | 76 | Again, we can write down the volumes going to and from the salt side. For the withdrawal that is: 77 | 78 | .. math:: 79 | :label: cycleavg_salt_side_vol_withdrawn 80 | 81 | V_S^- = V_{Level,HT} + V_{Ship,Down} + V_{U,S,Flush} 82 | 83 | and the volume that is discharged to the salt side 84 | 85 | .. math:: 86 | :label: cycleavg_salt_side_vol_discharged 87 | 88 | V_S^+ = V_{Level,LT} + V_{U,S,Flush} + V_{Ship,Up} + Q_{flush} \cdot 2 \cdot T_{open} 89 | 90 | By dividing both volumes by the time spent on a total locking cycle, we can determine the cycle-averaged flows. 91 | Each of these flows has a corresponding discharge, and can be connected to cells in a far-field model as a discharge or withdrawal: 92 | 93 | - Withdrawal from the salt side, with the prevailing salinity :math:`S_S`: 94 | 95 | .. math:: 96 | :label: cycleavg_salt_side_flow_withdrawn 97 | 98 | Q_S^- = \frac{ V_S^- }{ T_{cycle} } 99 | 100 | - Discharge to the salt side with a to-be-determined average salinity: 101 | 102 | .. math:: 103 | :label: cycleavg_salt_side_flow_discharged 104 | 105 | Q_S^+ = \frac{ V_S^+ } { T_{cycle} }; S = S_S^+ 106 | 107 | The average salinity for the water discharged to the salt side is determined from the mass and volume transports: 108 | 109 | .. math:: 110 | :label: cycleavg_salt_side_sal_discharged 111 | 112 | S_S^+ = \frac{ \left( M_S - V_S^- S_S \right) }{ V_S^+ } 113 | 114 | In case of stand-alone application, but also to compare with other locks or salt intrusion measure configurations, it can be useful to express the salt transport as a mass flux: 115 | 116 | .. math:: 117 | :label: cycleavg_salt_side_mass_flux 118 | 119 | {\dot{M}}_S = \frac{ M_S }{ T_{cycle} } 120 | 121 | For an equilibrium state, with the lock operating with constant operation for long periods of time, it obviously holds that 122 | 123 | .. math:: 124 | :label: cycleavg_salt_side_mass_flux_equilibrium 125 | 126 | {\dot{M}}_S = {\dot{M}}_F 127 | -------------------------------------------------------------------------------- /docs/examples/python/phase-multiple-lockages.rst: -------------------------------------------------------------------------------- 1 | Phase-wise with multiple lockages 2 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 3 | 4 | .. image:: ../../images/Krammersluizen.jpg 5 | 6 | .. :href: https://beeldbank.rws.nl, Rijkswaterstaat 7 | 8 | Overview 9 | -------- 10 | 11 | .. note:: 12 | 13 | This example focuses on performing a phase-wise calculation of many lockages based on an input file. 14 | It assumes basic exposure to the Python interface and phase-wise calculation. 15 | If you are a first-time user of the ZSF, see the :doc:`steady` and :doc:`phase` examples. 16 | 17 | The purpose of this example is to understand the basic steps to perform a phase-wise calculation of many lockages. 18 | The goal is to determine the average transports of water and salt over a 60-day period. 19 | 20 | Initializing the lock 21 | --------------------- 22 | 23 | When initializing the lock with :py:class:`pyzsf.ZSFUnsteady` we pass all parameters that stay constant throughout the 60-day period. 24 | This includes the parameters for salt intrusion measures like the bubble screens. 25 | Note that the head on the lake side is constant, but that the head on the sea side varies. 26 | Furthermore, the salinity on both sides of the lock varies over time as well. 27 | 28 | .. literalinclude:: ../../../examples/python/phase_multiple_lockages.py 29 | :language: python 30 | :lines: 8-31 31 | :lineno-match: 32 | 33 | Reading the input data 34 | ---------------------- 35 | 36 | The lockages over the 60-day period are defined in a `CSV-file `_: 37 | 38 | .. csv-table:: Lockages 39 | :header-rows: 1 40 | 41 | time,head_sea,routine,salinity_lake,salinity_sea,ship_volume_lake_to_sea,ship_volume_sea_to_lake,t_flushing,t_level,t_open_lake,t_open_sea 42 | 2960.0,0.03760156993333333,3,0.8554550242857143,28.529520582857142,,,,300.0,, 43 | 3380.0,0.03760156993333333,4,0.8554550242857143,28.529520582857142,,1884.2,,,,420.0 44 | 3920.0,0.181197112,1,0.8979030931428571,28.558444978571426,,,,240.0,, 45 | 4280.0,0.181197112,2,0.8979030931428571,28.558444978571426,2482.0,,,,1020.0, 46 | 5420.0,0.74484631,3,1.023605347,28.62057304,,,,340.0,, 47 | 48 | We read this CSV file using `pandas `_, and convert it to a list of parameter dictionaries: 49 | 50 | .. literalinclude:: ../../../examples/python/phase_multiple_lockages.py 51 | :language: python 52 | :lines: 33-35 53 | :lineno-match: 54 | 55 | Stepping through all lockages 56 | ----------------------------- 57 | 58 | Just like in the :doc:`phase` example we step through all phases in the locking cycle. 59 | We do this by iterating over all locking-phase entries defined in the input CSV file. 60 | Depending on the respective phase, we pass either the leveling time, the door-open duration, or the duration of flushing (with the doors closed). 61 | 62 | We store the results of every individual locking phase in a list called ``all_results``, to be aggregated later on in the script: 63 | 64 | .. literalinclude:: ../../../examples/python/phase_multiple_lockages.py 65 | :language: python 66 | :lines: 37-65 67 | :lineno-match: 68 | 69 | Aggregating output 70 | ------------------ 71 | 72 | For many cases we would only be interested in what happens on the lake side, e.g. the average salt flux in `kg/s` over a certain period of time. 73 | For illustrative purposes we however aggregate all outputs. 74 | For volumes and mass transports this means summing them. 75 | For discharges and salt fluxes, this means averaging them. 76 | 77 | .. literalinclude:: ../../../examples/python/phase_multiple_lockages.py 78 | :language: python 79 | :lines: 67-91 80 | :lineno-match: 81 | 82 | Finally, the average fluxes are logged to the console 83 | 84 | .. literalinclude:: ../../../examples/python/phase_multiple_lockages.py 85 | :language: python 86 | :lines: 93-95 87 | :lineno-match: 88 | 89 | The output should show something like: 90 | 91 | .. code-block:: text 92 | 93 | Overall results (60 day aggregates and averages): 94 | {'discharge_from_lake': 2.626938120554457, 95 | 'discharge_from_sea': 2.9855208663562096, 96 | 'discharge_to_lake': 2.7409909136666557, 97 | 'discharge_to_sea': 2.872713264556223, 98 | 'mass_transport_lake': -215507325.24071646, 99 | 'mass_transport_sea': -215288300.05404428, 100 | 'salinity_to_lake': 16.334431315958305, 101 | 'salinity_to_sea': 13.297877648040927, 102 | 'volume_from_lake': 13618047.216954306, 103 | 'volume_from_sea': 15476940.17119059, 104 | 'volume_to_lake': 14209296.896447944, 105 | 'volume_to_sea': 14892145.563459458} 106 | 107 | The whole script 108 | ---------------- 109 | 110 | All together, the whole example script is as follows: 111 | 112 | .. literalinclude:: ../../../examples/python/phase_multiple_lockages.py 113 | :language: python 114 | :lineno-match: 115 | -------------------------------------------------------------------------------- /docs/examples/excel/steady.rst: -------------------------------------------------------------------------------- 1 | Steady-state calculation 2 | ~~~~~~~~~~~~~~~~~~~~~~~~ 3 | 4 | .. image:: ../../images/delfzijl1.jpg 5 | 6 | .. :href: https://beeldbank.rws.nl/MediaObject/Details/479988 7 | .. https://beeldbank.rws.nl, Rijkswaterstaat / Harry van Reeken 8 | 9 | Overview 10 | -------- 11 | 12 | The purpose of this example is to understand the basic steps to calculate the salt transports through a shipping lock in steady state operation. 13 | The scenario is the following: A single lock connects a canal to the sea, and the lock is busy during the day but quiet during the night. 14 | We want to know how much salt comes in on average, and figure out ways to reduce the salt intrusion by means of mitigating measures like bubble screens. 15 | 16 | Properties of the lock and its operation 17 | ---------------------------------------- 18 | 19 | The physical dimensions of our single shipping lock are: 20 | 21 | - length: 148 m 22 | - width: 14 m 23 | - bottom: -4.4 mNAP (Dutch Ordnance Datum) 24 | 25 | For the boundary conditions we will assume that the sea level is the same as the canal level, with both being equal to 0.0 mNAP. 26 | The salinity on the salt side is significantly higher than on the canal side. 27 | 28 | - salinities: 5 kg/m³ on the canal side close to the lock, and 25 kg/m³ on the sea side 29 | - head: 0.0 mNAP on both sides 30 | - temperature: 15.0 °C on both sides 31 | 32 | The last step is to derive basic parameters from the locking information. 33 | 34 | - During daytime, the lock operates at a pace of 1.25 cycles per hour (= 30 cycles per 24 hours). 35 | During the night, the lock operates at a pace of 10 cycles per 24 hours. 36 | - It takes 5 minutes to open or close the doors on either side, and also 5 minutes to level. 37 | - There are the same number of ships going from the sea to the canal, and vice-versa. 38 | - The ships going to and from the canal also have an equal displacement of 1000 m³. 39 | 40 | In the first calculation, the lock does not have any sills or bubble screens, nor is there any flushing. 41 | These are of course measures that we will take a look at later in this example to reduce the amount of salt intrusion. 42 | 43 | .. seealso:: 44 | 45 | For an overview of these parameters, and more in-depth discussion on them, see :ref:`sec_c_api_input` and :ref:`sec_procdef`. 46 | 47 | Salt load without measures 48 | -------------------------- 49 | 50 | The next step is to enter all these physical and operational characteristics in the Excel workbook. 51 | As we are assuming constant operation and boundary conditions, we can use the `Steady` tab. 52 | We need a total of two rows, one for daytime operation and one for nighttime. 53 | Entering the values above into the Excel sheet should result in a something like shown in the image below. 54 | Note that some field are not used (yet); they will be discussed below in detail. 55 | 56 | .. thumbnail:: ../../images/examples_excel_steady_input.png 57 | :width: 50% 58 | 59 | The next step is to select (at least) one cell on each of the rows you want to calculate, and press the `Run ZSF` button in the top-left. 60 | The output columns in the sheet should then take on values similar to those shown in the image below. 61 | 62 | .. thumbnail:: ../../images/examples_excel_steady_output.png 63 | :width: 50% 64 | 65 | From these results we can see that there is a salt load of almost 37 kg/s during daytime, and 19 kg/s during nighttime. 66 | 67 | Comparing salt intrusion measures 68 | --------------------------------- 69 | 70 | The maximum allowable salt load has been determined to be 12 kg/s, so the salt load during daytime and nighttime are currently not acceptable. 71 | The ZSF can help compare various salt intrusion measures that can be taken to reduce the salt load to acceptable levels. 72 | For this particular lock sills are not a feasible option, but bubble screens and flushing discharges could be. 73 | 74 | A typical maximally efficient bubble screen can reduce the pace of the lock exchange to about 25%. 75 | If we fill in 0.25 at both `η Lake` and `η Sea`, the salt load during daytime is reduced to about 10 kg/s. 76 | However, the salt load at nighttime is reduced much less. 77 | With a value of about 13.5 kg/s it is now even higher than that during the daytime, even though there are fewer ships passing through the lock. 78 | 79 | .. thumbnail:: ../../images/examples_excel_steady_etacolumns.png 80 | :width: 50% 81 | 82 | Bubble screens are only effective if the doors are closed well before the (reduced) lock is reduced. 83 | With only 2 to 3 locking cycles during the night, the doors are open for more than an hour at a time, see the output columns `Time Door Lake Open` and `Time Door Sea Open`. 84 | 85 | .. thumbnail:: ../../images/examples_excel_steady_timedoorcolumns.png 86 | :width: 50% 87 | 88 | If we can tell the lock operator to close the doors right after ships have finished sailing out, we can reduce the salt intrusion significantly. 89 | To reduce these door-open durations, we can use `Calibration Factor`. 90 | If we know that the doors are open about 20 minutes at a time during the night, we can fill in a value of approximately 0.3 here to reduce the current duration of about an hour with. 91 | Recalculating with this will give a salt load of about 4 kg/s during the night, which is acceptable. 92 | -------------------------------------------------------------------------------- /wrappers/python/src/_pyzsf_build.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from cffi import FFI 4 | 5 | 6 | ffibuilder = FFI() 7 | 8 | ffibuilder.cdef( 9 | """ 10 | typedef struct zsf_param_t { 11 | double lock_length; 12 | double lock_width; 13 | double lock_bottom; 14 | double num_cycles; 15 | double door_time_to_open; 16 | double leveling_time; 17 | double calibration_coefficient; 18 | double symmetry_coefficient; 19 | double ship_volume_sea_to_lake; 20 | double ship_volume_lake_to_sea; 21 | double salinity_lock; 22 | double head_sea; 23 | double salinity_sea; 24 | double temperature_sea; 25 | double head_lake; 26 | double salinity_lake; 27 | double temperature_lake; 28 | double flushing_discharge_high_tide; 29 | double flushing_discharge_low_tide; 30 | double density_current_factor_sea; 31 | double density_current_factor_lake; 32 | double distance_door_bubble_screen_sea; 33 | double distance_door_bubble_screen_lake; 34 | double sill_height_sea; 35 | double sill_height_lake; 36 | double rtol; 37 | double atol; 38 | } zsf_param_t; 39 | 40 | typedef struct zsf_results_t { 41 | double mass_transport_lake; 42 | double salt_load_lake; 43 | double discharge_from_lake; 44 | double discharge_to_lake; 45 | double salinity_to_lake; 46 | 47 | double mass_transport_sea; 48 | double salt_load_sea; 49 | double discharge_from_sea; 50 | double discharge_to_sea; 51 | double salinity_to_sea; 52 | } zsf_results_t; 53 | 54 | typedef struct zsf_phase_state_t { 55 | double salinity_lock; 56 | double saltmass_lock; 57 | double head_lock; 58 | double volume_ship_in_lock; 59 | } zsf_phase_state_t; 60 | 61 | typedef struct zsf_phase_transports_t { 62 | double mass_transport_lake; 63 | double volume_from_lake; 64 | double volume_to_lake; 65 | double discharge_from_lake; 66 | double discharge_to_lake; 67 | double salinity_to_lake; 68 | 69 | double mass_transport_sea; 70 | double volume_from_sea; 71 | double volume_to_sea; 72 | double discharge_from_sea; 73 | double discharge_to_sea; 74 | double salinity_to_sea; 75 | } zsf_phase_transports_t; 76 | 77 | typedef struct zsf_aux_results_t { 78 | double z_fraction; 79 | double dimensionless_door_open_time; 80 | double volume_to_lake; 81 | double volume_from_lake; 82 | double volume_to_sea; 83 | double volume_from_sea; 84 | double volume_lock_at_lake; 85 | double volume_lock_at_sea; 86 | double t_cycle; 87 | double t_open; 88 | double t_open_lake; 89 | double t_open_sea; 90 | double salinity_lock_1; 91 | double salinity_lock_2; 92 | double salinity_lock_3; 93 | double salinity_lock_4; 94 | zsf_phase_transports_t transports_phase_1; 95 | zsf_phase_transports_t transports_phase_2; 96 | zsf_phase_transports_t transports_phase_3; 97 | zsf_phase_transports_t transports_phase_4; 98 | } zsf_aux_results_t; 99 | 100 | int zsf_initialize_state(const zsf_param_t *p, zsf_phase_state_t *state, 101 | double salinity_lock, double head_lock); 102 | 103 | int zsf_step_phase_1(const zsf_param_t *p, double t_level, 104 | zsf_phase_state_t *state, 105 | zsf_phase_transports_t *results); 106 | 107 | int zsf_step_phase_2(const zsf_param_t *p, double t_open_lake, 108 | zsf_phase_state_t *state, 109 | zsf_phase_transports_t *results); 110 | 111 | int zsf_step_phase_3(const zsf_param_t *p, double t_level, 112 | zsf_phase_state_t *state, 113 | zsf_phase_transports_t *results); 114 | 115 | int zsf_step_phase_4(const zsf_param_t *p, double t_open_sea, 116 | zsf_phase_state_t *state, 117 | zsf_phase_transports_t *results); 118 | 119 | int zsf_step_flush_doors_closed(const zsf_param_t *p, 120 | double t_flushing, 121 | zsf_phase_state_t *state, 122 | zsf_phase_transports_t *results); 123 | 124 | void zsf_param_default(zsf_param_t *p); 125 | 126 | int zsf_calc_steady(const zsf_param_t *p, zsf_results_t *results, 127 | zsf_aux_results_t *aux_results); 128 | 129 | const char * zsf_error_msg(int code); 130 | 131 | const char * zsf_version(); 132 | """ 133 | ) 134 | 135 | if os.name == "posix": 136 | extra_compile_args = [] 137 | else: 138 | extra_compile_args = ["/MD"] 139 | 140 | ffibuilder.set_source( 141 | "pyzsf._zsf_cffi", 142 | '#include "zsf.h"', 143 | libraries=["zsf-static"], 144 | define_macros=[("ZSF_STATIC", None), ("Py_LIMITED_API", None)], 145 | py_limited_api=True, 146 | extra_compile_args=extra_compile_args, 147 | ) 148 | 149 | 150 | if __name__ == "__main__": 151 | ffibuilder.compile(verbose=True) 152 | -------------------------------------------------------------------------------- /docs/examples/excel/phase.rst: -------------------------------------------------------------------------------- 1 | Phase-wise calculation 2 | ~~~~~~~~~~~~~~~~~~~~~~ 3 | 4 | .. image:: ../../images/delfzijl2.jpg 5 | 6 | .. :href: https://beeldbank.rws.nl/MediaObject/Details/479953 7 | .. https://beeldbank.rws.nl, Rijkswaterstaat / Harry van Reeken 8 | 9 | Overview 10 | -------- 11 | 12 | .. note:: 13 | 14 | This example focuses on performing a phase-wise calculation of salt-intrusion through a shipping lock. 15 | It assumes basic exposure to the Excel interface. 16 | If you are a first-time user of the ZSF, see the :doc:`steady` example. 17 | 18 | The purpose of this example is to understand the basic steps to perform a phase-wise calculation of the salt intrusion through a shipping lock. 19 | The scenario is similar to that of the :doc:`steady` example, in that there is a single lock connecting a canal to the sea. 20 | The differences with the steady state example are: 21 | 22 | - there is a head difference now, with the sea at 2 mNAP 23 | - we are only going to calculate one full locking cycle during the daytime operation 24 | 25 | Different phases and routines 26 | ----------------------------- 27 | 28 | There are a few phases the lock can go through, each having associated salt transports. 29 | See :numref:`chapter_eq_per_lock_phase` in the theory for more explanation on what each phase entails. 30 | The term `routines` refers to the actual functions that are called, and their naming mostly corresponds to that of the phases. 31 | The only major difference is that there is an initialization routine **0** to set the starting state, but no such phase as there are no transports yet. 32 | 33 | .. seealso:: 34 | 35 | For an overview of phases and how the transports are determined, see :ref:`chapter_eq_per_lock_phase`. 36 | 37 | Initializing the lock 38 | --------------------- 39 | 40 | First we need to initialize the lock with a certain salinity and head. 41 | The dimensions of the lock are equal to that of the :doc:`steady` example. 42 | You can therefore copy the row of input parameters to the first calculation row in the `Phase` worksheet. 43 | There are a few parameters that are no longer needed, because we will set them explicitly ourselves. 44 | These input parameters that needed for steady state calculation, but not needed for phase-wise calculation, can be spotted by the lack of shading in the second row. 45 | Instead, we will have to enter values in the columns shaded blue. 46 | We set the head of the sea to `2.0 mNAP`, and set the initial head and salinity inside the lock chamber to `0.0 mNAP` and `15 kg/m³` respectively. 47 | 48 | .. thumbnail:: ../../images/examples_excel_phase_diffcolumns.png 49 | :width: 50% 50 | 51 | Next, we make sure that the `routine` column is set to **0**. 52 | Then, just like with the :doc:`steady` example, we can select one or more cells on this row and press `Run ZSF`. 53 | If all goes well, the output columns will then show the following results 54 | 55 | .. thumbnail:: ../../images/examples_excel_phase_initialize_output.png 56 | :width: 30% 57 | 58 | .. note:: 59 | 60 | The lock is always initialized empty, i.e., without a ship in it. 61 | 62 | Leveling to the lake side 63 | ------------------------- 64 | 65 | The next step is to level the lock to the lake side. 66 | The lock was already initialized to the head of the lake side, so we expect to see no transports in this phase. 67 | Copy the input of the first row to the second row., and set 68 | 69 | - the leveling time to 300 seconds 70 | - the routine to **1** 71 | 72 | Now press the `Run ZSF` button. 73 | Note that if you had any remaining values in the `Initialize State` columns, these values will be cleared as they are not needed. 74 | Check that the output columns have zero transport of both water and salt. 75 | 76 | Opening the door to the lake side 77 | --------------------------------- 78 | 79 | With the lock leveled to the lake side, the doors can now be opened. 80 | 81 | .. important:: 82 | 83 | Make sure that the leveling routines (**1** and **3**) have matching heads for the boundary conditions as the subsequent door-open routines (**2** and **4** respectively). If this is not the case, an exception is raised stating this requirement. 84 | 85 | Once again, copy the lock dimensions and other inputs to a new row below the two already existing ones. 86 | Remove the leveling time, and set the following parameters: 87 | 88 | - `volume ship down` (lake to sea) to 1000.0 m³ 89 | - `door open time` on the lake side to 840 seconds 90 | - the routine to **2** 91 | 92 | 93 | .. thumbnail:: ../../images/examples_excel_phase_routine_2_inputs.png 94 | :width: 50% 95 | 96 | Press the `Run ZSF` button, and if all is correct the outputs should be similar to the following image: 97 | 98 | .. thumbnail:: ../../images/examples_excel_phase_routine_2_outputs.png 99 | :width: 50% 100 | 101 | The last columns show the state of the lock, and now indicate that there is a ship in the lock chamber. 102 | 103 | Leveling to the sea side 104 | ------------------------ 105 | 106 | The next step is to level the lock to the sea side. 107 | The instructions are equal to those of leveling to the lake side, except that you should set the routine to **3** instead of **1**. 108 | Press the `Run ZSF` button, and inspect the output. 109 | 110 | Opening the door to the sea side 111 | -------------------------------- 112 | 113 | The last step is to open the doors to the sea side, and let the ship sail out and a new ship sail in. 114 | Copy the last row to a new one, and set: 115 | 116 | - `volume ship up` (sea to lake) to 800.0 m³ 117 | - `door open time` on the sea side to 840 seconds 118 | - the routine to **4** 119 | 120 | The inputs should look as follows: 121 | 122 | .. thumbnail:: ../../images/examples_excel_phase_routine_4_inputs.png 123 | :width: 50% 124 | 125 | After pressing `Run ZSF`, the output should like like: 126 | 127 | .. thumbnail:: ../../images/examples_excel_phase_routine_4_outputs.png 128 | :width: 50% 129 | 130 | Note that the volume of ship inside the lock chamber has changed from 1000 m³ to 800.0 m³. 131 | 132 | Calculating more lockages 133 | ------------------------- 134 | 135 | One can repeat the above process, again adding a row for leveling to the lake side next. 136 | If the water levels and salinities on the lake and/or sea side are changing, you can change these parameters accordingly. 137 | Typically, the water level is set to the average water level during the door-open phase, with the preceding leveling phase also leveling to said water level. 138 | It quickly becomes rather tedious and error-prone to calculate many lockages this way using Excel, especially if a lot of preprocessing is involved to get the parameters per locking phase and the source data is not set in stone. 139 | Depending on your experience with Excel and Python, it might then be easier to use the Python wrapper to do these types of calculations, see the Python :doc:`../python/phase` example. 140 | -------------------------------------------------------------------------------- /docs/theory/numerical_approach_cycle_averaged.rst: -------------------------------------------------------------------------------- 1 | Numerical approach cycle-averaged values 2 | ======================================== 3 | 4 | The description in Chapter :ref:`chapter_eq_per_lock_phase` of how the transports and how the salinity in the lock chamber changes from phase to phase, also gives a recipe for calculating the cycle-averaged values of volumes and salinities. 5 | Namely, we can iteratively calculate our way through the locking cycle until the calculated value no longer change significantly. 6 | From these steady state values the cycle-averaged flows (discharges and withdrawals) can be determined, together with the salinities (see :numref:`sec_numapproach_iterative`). 7 | 8 | An alternative approach is setting up a system of equations (see :numref:`sec_numapproach_syseq`). 9 | From Chapter :ref:`chapter_eq_per_lock_phase` and :ref:`chapter_cycle_avg_flow_sals` the essential equations can be selected, that together form a system of equations with unknowns. 10 | Solving this system of equations than resolves the unknowns that are necessary to calculate the cycle-averaged flows and salinities. 11 | 12 | .. _sec_numapproach_iterative: 13 | 14 | Iteratively calculating the locking cycle 15 | ----------------------------------------- 16 | 17 | In Chapter :ref:`chapter_cycle_avg_flow_sals` the expression for the flows (discharges and withdrawals) have been set up, but a few unknowns remain. 18 | These values arise in the locking cycle, as described in Chapter :ref:`chapter_eq_per_lock_phase`. 19 | 20 | This cycle can then be considered iterative process: for fixed boundary conditions and after enough cycles, eventually these unknowns will converge to their respective values. 21 | To enter this iterative process, e.g. starting at LT 1 / HT 1, we only need an initial guess for the salinity of the lock chamber. 22 | Obviously this guess has to be higher than the salinity of the fresh side, and lower than that of the salt side. 23 | A good starting point would then be the average of the salinities of the boundary conditions. 24 | 25 | In case of calculating through time for varying boundary conditions (e.g. a tide on the sea side, or a time-varying operation of the lock), the converged lock chamber salinity of the previous time step can be chosen as the initial guess. 26 | For slowly changing boundary conditions, the previous chamber salinity is a reasonable estimate. 27 | The closer the guess to the eventual solution, the fewer iterations are needed to converge. 28 | 29 | The numerical approach for determining cycle-averaged values then consists of two steps: 30 | 31 | 1. Iteratively determining the unknown values 32 | 2. Calculate the discharges :math:`Q_F^+` with :math:`S_F^+` and :math:`Q_S^+` with :math:`S_S^+`, and the withdrawals :math:`Q_F^-` and :math:`Q_S^-`. 33 | 34 | .. _sec_numapproach_syseq: 35 | 36 | System of equations 37 | ------------------- 38 | 39 | An alternative to the iterative approach as described above is setting up a system of equations in which the number of equations is equal to the number of unknowns. 40 | From the previous chapters the relevant equations can be selected, each with a number of unknowns. 41 | (The unknowns in this case are all values that cannot be directly calculated from the boundary conditions or input). 42 | Not all equations are linear, so it is necessary to repeatedly solve a linearized system of equations until convergence. 43 | In practice, this way of solving has very little advantage over calculating iteratively as described in :ref:`sec_numapproach_iterative`. 44 | We therefore do not elaborate further on this approach. 45 | 46 | Overview of output 47 | ------------------ 48 | 49 | By the formulation the steady state values of the following quantities are calculated, all as function of time: 50 | 51 | :math:`M_F`, :math:`{\dot{M}}_F`, :math:`Q_F^-`, :math:`Q_F^+`, :math:`S_F^+`, :math:`M_S`, :math:`{\dot{M}}_S`, :math:`Q_S^-`, :math:`Q_S^+`, :math:`S_S^+` 52 | 53 | Getting to these parameters is what the formulation was designed for: the mass transports (per cycle) and the fluxes and flows (cycle-averaged) that enter and exit the lock on both sides. 54 | Aside from that there are a few other parameters that can help interpret the output. 55 | These can be geometric parameters, like the volume of the lock chamber, or operational parameters like door-open times. 56 | It is also possible to export the cycle-averaged transports per locking phase. 57 | 58 | Aside from that, a few other useful parameters can be determined. 59 | These parameters can help compare the ZSF to more naive methods of determined the salt load, or with other theoretical or experimental relations. 60 | 61 | Dimensionless salt transport 62 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 63 | 64 | For the dimensionless salt transport over the lock per cycle, we use the parameter :math:`Z_{fraction}`. 65 | This parameter is defined as a factor on the lock chamber volume times the difference in salinity between the boundary conditions: 66 | 67 | .. math:: 68 | :label: numapproach_z_fraction_mass 69 | 70 | M = Z_{fraction} V_L \cdot \Delta S 71 | 72 | As such, :math:`Z_{fraction}` indicates what fraction of the lock chamber, in regular locking operation, exchanges and contributes to the salt transport. 73 | 74 | In the process of calculating transports we get transports for both lock heads. 75 | Aside from that, the volume of the lock chamber, due to a difference in water level on both sides, is not always equal. 76 | Therefore, we have to use average values for these quantities. 77 | From this, :math:`Z_{fraction}` can be written as: 78 | 79 | .. math:: 80 | :label: numapproach_z_fraction_explicit 81 | 82 | Z_{fraction} = \frac{ \overline{M} }{ \overline{V_L} \cdot \left(S_S - S_F \right) } = \frac{ 0.5 \cdot \left( M_F + M_S \right) }{0.5 \cdot \left( V_{L,F} + V_{L,S} \right) \cdot \left( S_S - S_F \right) } 83 | 84 | Dimensionless door-open time 85 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 86 | 87 | The dimensionless door-open time is :math:`T_{LE} / T_{open}`. In the calculations the value of :math:`T_{LE}` is determined per lock head, with the prevailing salinity difference. 88 | With that, the value of :math:`T_{LE}` is not a direct function of the input, but is dependent on the calculations. 89 | To get to a :math:`T_{LE}` that is only determined by input (boundary conditions and geometry), we define a variant: :math:`T_{LE,FS}`. 90 | This quantity is based solely on the salinity difference over the lock. 91 | This parameter is not used in the calculations, but is sometimes used in determining auxiliary outputs, e.g. of the dimensionless door-open time :math:`T_{LE} / T_{open}`: 92 | 93 | .. math:: 94 | :label: numapproach_dimless_dooropentime_ci 95 | 96 | c_{i,FS} = \frac{1}{2} \sqrt{ g^\prime \overline{H} } = \frac{1}{2} \sqrt{ g \frac{\Delta \rho}{\overline{\rho}} \overline{H} } \approx \frac{1}{2} \sqrt{ \frac{0.8 \left( S_S - S_F \right) }{ \overline{\rho}_{FS} } \left( \frac{H_F + H_S}{2} \right)} 97 | 98 | .. math:: 99 | :label: numapproach_dimless_dooropentime 100 | 101 | T_{LE,FS} = \frac{ 2L }{ c_{i,FS} } 102 | -------------------------------------------------------------------------------- /docs/examples/python/steady.rst: -------------------------------------------------------------------------------- 1 | Steady-state calculation 2 | ~~~~~~~~~~~~~~~~~~~~~~~~ 3 | 4 | .. image:: ../../images/delfzijl1.jpg 5 | 6 | .. :href: https://beeldbank.rws.nl/MediaObject/Details/479988 7 | .. https://beeldbank.rws.nl, Rijkswaterstaat / Harry van Reeken 8 | 9 | Overview 10 | -------- 11 | 12 | The purpose of this example is to understand the basic steps to calculate the salt transports through a shipping lock in a steady state operation. 13 | The scenario is the following: A single lock connects a canal to the sea, and the lock is busy during the day but quiet during the night. 14 | We want to know how much salt comes in on average, and figure out ways to reduce the salt intrusion by means of mitigating measures like bubble screens. 15 | 16 | Properties of the lock and its operation 17 | ---------------------------------------- 18 | 19 | The physical dimensions of our single shipping lock are: 20 | 21 | - length: 148 m 22 | - width: 14 m 23 | - bottom: -4.4 mNAP (Dutch Ordnance Datum) 24 | 25 | For the boundary conditions we will assume that the sea level is the same as the canal level, with both being equal to 0.0 mNAP. 26 | The salinity on the salt side is significantly higher than on the canal side. 27 | 28 | - salinities: 5 kg/m³ on the canal side close to the lock, and 25 kg/m³ on the sea side 29 | - head: 0.0 mNAP on both sides 30 | - temperature: 15.0 °C on both sides 31 | 32 | The last step is to derive basic parameters from the locking information. 33 | 34 | - During daytime, the lock operates at a pace of 1.25 cycles per hour (= 30 cycles per 24 hours). 35 | During the night, the lock operates at a pace of 10 cycles per 24 hours. 36 | - It takes 5 minutes to open or close the doors on either side, and also 5 minutes to level. 37 | - There are the same number of ships going from the sea to the canal, and vice-versa. 38 | - The ships going to and from the canal also have an equal displacement of 1000 m³. 39 | 40 | In the first calculation, the lock does not have any sills or bubble screens, nor is there any flushing. 41 | These are of course measures that we will take a look at later in this example to reduce the amount of salt intrusion. 42 | 43 | .. seealso:: 44 | 45 | For an overview of these parameters, and more in-depth discussion on them, see :ref:`sec_c_api_input` and :ref:`sec_procdef`. 46 | 47 | Salt load without measures 48 | -------------------------- 49 | 50 | The first step in your Python file or Jupyter Notebook is to import ``pyzsf``: 51 | 52 | .. literalinclude:: ../../../examples/python/steady.py 53 | :language: python 54 | :lines: 1 55 | :lineno-match: 56 | 57 | The next step is to enter all these physical and operational characteristics. 58 | For readability, it can make sense to group the parameters, as can be seen in the code: 59 | 60 | .. literalinclude:: ../../../examples/python/steady.py 61 | :language: python 62 | :lines: 4-25 63 | :lineno-match: 64 | 65 | We can merge all dictionaries into one set for the daytime, and one set for the nighttime parameters. 66 | This makes passing the arguments later on a bit shorter and easier to understand. 67 | 68 | .. literalinclude:: ../../../examples/python/steady.py 69 | :language: python 70 | :lines: 27-28 71 | :lineno-match: 72 | 73 | The next step is to actually calculate the salt flux to the lake during day- and nighttime. 74 | As we are assuming constant operation and boundary conditions, we can use the :py:func:`pyzsf.zsf_calc_steady` function. 75 | We log this salt flux to the console. 76 | 77 | .. literalinclude:: ../../../examples/python/steady.py 78 | :language: python 79 | :lines: 30-36 80 | :lineno-match: 81 | 82 | The console output of these lines is as follows: 83 | 84 | .. code-block:: text 85 | 86 | No measures: 87 | Day = 36.8 kg/s 88 | Night = 18.8 kg/s 89 | 90 | Comparing salt intrusion measures 91 | --------------------------------- 92 | 93 | The maximum allowable salt load has been determined to be 12 kg/s, so the salt load during daytime and nighttime are currently not acceptable. 94 | The ZSF can help compare various salt intrusion measures that can be taken to reduce the salt load to acceptable levels. 95 | For this particular lock sills are not a feasible option, but bubble screens and flushing discharges could be. 96 | 97 | A typical maximally efficient bubble screen can reduce the pace of the lock exchange to about 25%. 98 | We define a new dictionary where we set the density current factor to this percentage on both sides of the lock. 99 | 100 | .. literalinclude:: ../../../examples/python/steady.py 101 | :language: python 102 | :lines: 38-42 103 | :lineno-match: 104 | 105 | We then call `pyzsf` again for both day- and nighttime operation, and pass these additional bubble screen parameters: 106 | 107 | .. literalinclude:: ../../../examples/python/steady.py 108 | :language: python 109 | :lines: 44-49 110 | :lineno-match: 111 | 112 | The console output of these lines is as follows: 113 | 114 | .. code-block:: text 115 | 116 | Bubble screen (25%): 117 | Day = 9.8 kg/s 118 | Night = 13.4 kg/s 119 | 120 | The salt load during daytime is reduced to about 10 kg/s with these bubble screens. 121 | However, the salt load at nighttime is reduced by much less. 122 | With a value of about 13.5 kg/s it is now even higher than that during the daytime, even though there are fewer ships passing through the lock. 123 | 124 | Bubble screens are only effective if the doors are closed well before the (reduced) lock is reduced. 125 | With only 2 to 3 locking cycles during the night, the doors are open for more than an hour at a time. 126 | We can get these calculated door open times by also requesting the auxiliary results. 127 | This can be done by setting the first positional argument to :py:func:`pyzsf.zsf_calc_steady` to ``True``. 128 | 129 | .. literalinclude:: ../../../examples/python/steady.py 130 | :language: python 131 | :lines: 51-56 132 | :lineno-match: 133 | 134 | The console output of these lines is as follows: 135 | 136 | .. code-block:: text 137 | 138 | Door open times at night: 139 | t_open = 3720.0 140 | t_open_lake = 3720.0 141 | t_open_sea = 3720.0 142 | 143 | If we can tell the lock operator to close the doors right after ships have finished sailing out, we can reduce the salt intrusion significantly. 144 | To reduce these door-open durations, we can use the :c:struct:`zsf_param_t.calibration_coefficient`. 145 | If we know that the doors are open about 20 minutes at a time during the night, we can fill in a value of approximately 0.3 here to reduce the current duration of about an hour with. 146 | 147 | .. literalinclude:: ../../../examples/python/steady.py 148 | :language: python 149 | :lines: 58-63 150 | :lineno-match: 151 | 152 | This gives us a salt load of about 4 kg/s during the night, which is acceptable: 153 | 154 | .. code-block:: text 155 | 156 | Bubble screen (25%), and close doors sooner: 157 | Night = 4.1 kg/s 158 | 159 | The whole script 160 | ---------------- 161 | 162 | All together, the whole example script is as follows: 163 | 164 | .. literalinclude:: ../../../examples/python/steady.py 165 | :language: python 166 | :lineno-match: 167 | -------------------------------------------------------------------------------- /include/zsf.h: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * zsf.h: zsf public header 3 | *****************************************************************************/ 4 | 5 | // We want to be compatible with as many languages as possible. Most compilers 6 | // do 8-byte alignment, but VBA wants structs packed to 4-byte boundaries. 7 | // Other languages have different assumptions. We try to keep everything 8 | // packed at 8-bytes ourselves, by only using 8-byte types. 9 | 10 | #ifndef ZSF_ZSF_H 11 | #define ZSF_ZSF_H 12 | 13 | #if defined(_WIN32) 14 | # if defined ZSF_STATIC 15 | # define ZSF_EXPORT 16 | # elif defined ZSF_EXPORTS 17 | # define ZSF_EXPORT __declspec(dllexport) 18 | # else 19 | # define ZSF_EXPORT __declspec(dllimport) 20 | # endif 21 | #elif defined(__CYGWIN__) 22 | # define ZSF_EXPORT 23 | #else 24 | # if (defined __GNUC__ && __GNUC__ >= 4) || defined __INTEL_COMPILER 25 | # define ZSF_EXPORT __attribute__((visibility("default"))) 26 | # else 27 | # define ZSF_EXPORT 28 | # endif 29 | #endif 30 | 31 | #if (defined ZSF_USE_STDCALL) && (defined _WIN32) 32 | # define ZSF_CALLCONV __stdcall 33 | #else 34 | # define ZSF_CALLCONV 35 | #endif 36 | 37 | // A custom value to signify "not specified" 38 | #define ZSF_NAN -999.0 39 | 40 | #ifdef __cplusplus 41 | extern "C" { 42 | #endif 43 | 44 | typedef struct zsf_param_t { 45 | double lock_length; 46 | double lock_width; 47 | double lock_bottom; 48 | double num_cycles; 49 | double door_time_to_open; 50 | double leveling_time; 51 | double calibration_coefficient; 52 | double symmetry_coefficient; 53 | double ship_volume_sea_to_lake; 54 | double ship_volume_lake_to_sea; 55 | double salinity_lock; 56 | double head_sea; 57 | double salinity_sea; 58 | double temperature_sea; 59 | double head_lake; 60 | double salinity_lake; 61 | double temperature_lake; 62 | double flushing_discharge_high_tide; 63 | double flushing_discharge_low_tide; 64 | double density_current_factor_sea; 65 | double density_current_factor_lake; 66 | double distance_door_bubble_screen_sea; 67 | double distance_door_bubble_screen_lake; 68 | double sill_height_sea; 69 | double sill_height_lake; 70 | double rtol; 71 | double atol; 72 | } zsf_param_t; 73 | 74 | typedef struct zsf_results_t { 75 | double mass_transport_lake; 76 | double salt_load_lake; 77 | double discharge_from_lake; 78 | double discharge_to_lake; 79 | double salinity_to_lake; 80 | 81 | double mass_transport_sea; 82 | double salt_load_sea; 83 | double discharge_from_sea; 84 | double discharge_to_sea; 85 | double salinity_to_sea; 86 | } zsf_results_t; 87 | 88 | /* Structs when stepping through phases explicitly, i.e. not looping until steady */ 89 | typedef struct zsf_phase_state_t { 90 | double salinity_lock; 91 | double saltmass_lock; 92 | double head_lock; 93 | double volume_ship_in_lock; 94 | } zsf_phase_state_t; 95 | 96 | /* Per phase we calculate the mass transports and volume transports over the 97 | lock gates/openings. A positive values means "from lake to lock" or "from lock 98 | to sea". */ 99 | typedef struct zsf_phase_transports_t { 100 | double mass_transport_lake; 101 | double volume_from_lake; 102 | double volume_to_lake; 103 | double discharge_from_lake; 104 | double discharge_to_lake; 105 | double salinity_to_lake; 106 | 107 | double mass_transport_sea; 108 | double volume_from_sea; 109 | double volume_to_sea; 110 | double discharge_from_sea; 111 | double discharge_to_sea; 112 | double salinity_to_sea; 113 | } zsf_phase_transports_t; 114 | 115 | typedef struct zsf_aux_results_t { 116 | double z_fraction; 117 | double dimensionless_door_open_time; 118 | double volume_to_lake; 119 | double volume_from_lake; 120 | double volume_to_sea; 121 | double volume_from_sea; 122 | double volume_lock_at_lake; 123 | double volume_lock_at_sea; 124 | double t_cycle; 125 | double t_open; 126 | double t_open_lake; 127 | double t_open_sea; 128 | double salinity_lock_1; 129 | double salinity_lock_2; 130 | double salinity_lock_3; 131 | double salinity_lock_4; 132 | zsf_phase_transports_t transports_phase_1; 133 | zsf_phase_transports_t transports_phase_2; 134 | zsf_phase_transports_t transports_phase_3; 135 | zsf_phase_transports_t transports_phase_4; 136 | } zsf_aux_results_t; 137 | 138 | /* zsf_initialize_state: 139 | * fill zsf_state_t with an initial condition for an empty (no ships) lock */ 140 | ZSF_EXPORT int ZSF_CALLCONV zsf_initialize_state(const zsf_param_t *p, zsf_phase_state_t *state, 141 | double salinity_lock, double head_lock); 142 | 143 | /* zsf_step_phase_1: 144 | * Perform step 1: levelling to lake side */ 145 | ZSF_EXPORT int ZSF_CALLCONV zsf_step_phase_1(const zsf_param_t *p, double t_level, 146 | zsf_phase_state_t *state, 147 | zsf_phase_transports_t *results); 148 | 149 | /* zsf_step_phase_2: 150 | * Perform step 1: door open to lake side (ships out, lock exchange + flushing, ships in) */ 151 | ZSF_EXPORT int ZSF_CALLCONV zsf_step_phase_2(const zsf_param_t *p, double t_open_lake, 152 | zsf_phase_state_t *state, 153 | zsf_phase_transports_t *results); 154 | 155 | /* zsf_step_phase_3: 156 | * Perform step 1: levelling to sea side */ 157 | ZSF_EXPORT int ZSF_CALLCONV zsf_step_phase_3(const zsf_param_t *p, double t_level, 158 | zsf_phase_state_t *state, 159 | zsf_phase_transports_t *results); 160 | 161 | /* zsf_step_phase_4: 162 | * Perform step 1: door open to sea side (ships out, lock exchange + flushing, ships in) */ 163 | ZSF_EXPORT int ZSF_CALLCONV zsf_step_phase_4(const zsf_param_t *p, double t_open_sea, 164 | zsf_phase_state_t *state, 165 | zsf_phase_transports_t *results); 166 | 167 | /* zsf_step_flush_doors_closed: 168 | * Doors closed, but still flushing. */ 169 | ZSF_EXPORT int ZSF_CALLCONV zsf_step_flush_doors_closed(const zsf_param_t *p, double t_flushing, 170 | zsf_phase_state_t *state, 171 | zsf_phase_transports_t *results); 172 | 173 | /* zsf_param_default: 174 | * fill zsf_param_t with default values */ 175 | ZSF_EXPORT void ZSF_CALLCONV zsf_param_default(zsf_param_t *p); 176 | 177 | /* zsf_calc_steady: 178 | * calculate the salt intrusion for a set of parameters, assuming steady operation*/ 179 | ZSF_EXPORT int ZSF_CALLCONV zsf_calc_steady(const zsf_param_t *p, zsf_results_t *results, 180 | zsf_aux_results_t *aux_results); 181 | /* zsf_error_msg: 182 | * Get error messeage corresponding to error code */ 183 | ZSF_EXPORT const char *ZSF_CALLCONV zsf_error_msg(int code); 184 | 185 | /* zsf_version: 186 | * Get version string */ 187 | ZSF_EXPORT const char *ZSF_CALLCONV zsf_version(); 188 | 189 | #ifdef __cplusplus 190 | } 191 | #endif 192 | 193 | #endif 194 | -------------------------------------------------------------------------------- /docs/examples/python/phase.rst: -------------------------------------------------------------------------------- 1 | Phase-wise calculation 2 | ~~~~~~~~~~~~~~~~~~~~~~ 3 | 4 | .. image:: ../../images/delfzijl2.jpg 5 | 6 | .. :href: https://beeldbank.rws.nl/MediaObject/Details/479953 7 | .. https://beeldbank.rws.nl, Rijkswaterstaat / Harry van Reeken 8 | 9 | Overview 10 | -------- 11 | 12 | .. note:: 13 | 14 | This example focuses on performing a phase-wise calculation of salt-intrusion through a shipping lock. 15 | It assumes basic exposure to the Python interface. 16 | If you are a first-time user of the ZSF, see the :doc:`steady` example. 17 | 18 | The purpose of this example is to understand the basic steps to perform a phase-wise calculation of the salt intrusion through a shipping lock. 19 | The scenario is similar to that of the :doc:`steady` example, in that there is a single lock connecting a canal to the sea. 20 | The differences with the steady state example are: 21 | 22 | - there is a head difference now, with the sea at 2 mNAP 23 | - we are only going to calculate one full locking cycle during the daytime operation 24 | 25 | Initializing the lock 26 | --------------------- 27 | 28 | First we need to initialize the lock with a certain salinity and head. 29 | The dimensions of the lock are equal to that of the :doc:`steady` example. 30 | The boundary conditions are also equal, except that the ``head_sea`` is 2 mNAP. 31 | The operational parameters differ more, as the two steady-state operation parameters ``num_cycles``, ``leveling_time`` and ``door_time_to_open`` are removed. 32 | 33 | .. literalinclude:: ../../../examples/python/phase.py 34 | :language: python 35 | :lines: 1-24 36 | :lineno-match: 37 | 38 | The next step is to initialize a :py:class:`pyzsf.ZSFUnsteady` instance, with an initial salinity of 15 kg/m³ and head of 0.0 mNAP. 39 | We also directly pass all other parameters to the constructor. 40 | These updated parameters are stored in the instance, and we do not need to specify them again when calling the method to level or open the doors (contrary to the Excel interface), unless we want to change the value of one of them of course. 41 | 42 | .. literalinclude:: ../../../examples/python/phase.py 43 | :language: python 44 | :lines: 28-29 45 | :lineno-match: 46 | 47 | The state of the lock after initialization is logged to the console with the ``pprint.pprint`` statements: 48 | 49 | .. code-block:: text 50 | 51 | State after initialization 52 | {'head_lock': 0.0, 53 | 'salinity_lock': 15.0, 54 | 'saltmass_lock': 136752.00000000003, 55 | 'volume_ship_in_lock': 0.0} 56 | 57 | .. note:: 58 | 59 | The lock is always initialized empty, i.e., without a ship in it. 60 | 61 | Leveling to the lake side 62 | ------------------------- 63 | 64 | The next step is to level the lock to the lake side, which is Phase 1. 65 | See :numref:`chapter_eq_per_lock_phase` in the theory for more explanation on the phases in a locking cycle. 66 | The lock was already initialized to the head of the lake side, so we expect to see no transports in this phase. 67 | We call :py:class:`pyzsf.ZSFUnsteady.zsf_step_phase_1`, which needs the leveling time as an argument. 68 | 69 | .. literalinclude:: ../../../examples/python/phase.py 70 | :language: python 71 | :lines: 33 72 | :lineno-match: 73 | 74 | The results of this method call, and the resulting state of the lock are printed to the console: 75 | 76 | .. code-block:: text 77 | 78 | Phase 1: 79 | ******** 80 | Transports: 81 | {'discharge_from_lake': 0.0, 82 | 'discharge_from_sea': 0.0, 83 | 'discharge_to_lake': 0.0, 84 | 'discharge_to_sea': 0.0, 85 | 'mass_transport_lake': 0.0, 86 | 'mass_transport_sea': 0.0, 87 | 'salinity_to_lake': 15.0, 88 | 'salinity_to_sea': 15.0, 89 | 'volume_from_lake': 0.0, 90 | 'volume_from_sea': 0.0, 91 | 'volume_to_lake': 0.0, 92 | 'volume_to_sea': 0.0} 93 | State: 94 | {'head_lock': 0.0, 95 | 'salinity_lock': 15.000000000000002, 96 | 'saltmass_lock': 136752.00000000003, 97 | 'volume_ship_in_lock': 0.0} 98 | 99 | Note that the console output shows zero transport of both water and salt. 100 | Also note that state of the lock after this phase is equal (barring rounding errors) to the state after initialization. 101 | 102 | Opening the door to the lake side 103 | --------------------------------- 104 | 105 | With the lock leveled to the lake side, the doors can now be opened. 106 | 107 | .. important:: 108 | 109 | Make sure that the leveling methods (**step_phase_1** and **step_phase_3**) have matching heads for the boundary conditions as the subsequent door-open methods (**step_phase_2** and **step_phase_4** respectively). If this is not the case, an exception is raised stating this requirement. 110 | 111 | To open the doors, we call :py:class:`pyzsf.ZSFUnsteady.zsf_step_phase_2`: 112 | 113 | - `volume ship down` (lake to sea) is the default of 1000.0 m³. 114 | We therefore do not need to pass this argument. 115 | - `door open time` on the lake side to 840 seconds. 116 | 117 | .. literalinclude:: ../../../examples/python/phase.py 118 | :language: python 119 | :lines: 40 120 | :lineno-match: 121 | 122 | The transports are logged to the console. 123 | Note that there are no transports to the sea in this phase, as we would expect without any flushing discharge. 124 | 125 | Leveling to the sea side 126 | ------------------------ 127 | 128 | The next step is to level the lock to the sea side. 129 | The instructions are equal to those of leveling to the lake side, except that we call :py:class:`pyzsf.ZSFUnsteady.zsf_step_phase_3`. 130 | 131 | .. literalinclude:: ../../../examples/python/phase.py 132 | :language: python 133 | :lines: 47 134 | :lineno-match: 135 | 136 | Opening the door to the sea side 137 | -------------------------------- 138 | 139 | The last step is to open the doors to the sea side, and let the ship sail out and a new ship sail in. 140 | This time we will also change the displacement of the ship that enters: 141 | 142 | - override the `volume ship up` (sea to lake) to 800.0 m³ 143 | - `door open time` on the sea side to 840 seconds 144 | 145 | .. literalinclude:: ../../../examples/python/phase.py 146 | :language: python 147 | :lines: 54 148 | :lineno-match: 149 | 150 | Check the state output that is logged to the console after this phase: 151 | 152 | .. code-block:: text 153 | 154 | {'head_lock': 2.0, 155 | 'salinity_lock': 22.612960757739405, 156 | 'saltmass_lock': 281775.5814100392, 157 | 'volume_ship_in_lock': 800.0} 158 | 159 | Note that the volume of ship inside the lock chamber has changed from 1000 m³ to 800.0 m³. 160 | 161 | Calculating more lockages 162 | ------------------------- 163 | 164 | One can repeat the above process, again adding a method call to :py:class:`pyzsf.ZSFUnsteady.zsf_step_phase_1` for leveling to the lake side next. 165 | If the water levels and salinities on the lake and/or sea side are changing, you can change these parameters accordingly for each method call. 166 | Typically, the water level is set to the average water level during the door-open phase, with the preceding leveling phase also leveling to said water level. 167 | It quickly becomes rather tedious and error-prone to calculate many lockages this way, and it is better to write a loop over some input data. 168 | For an example of this, see :doc:`phase-multiple-lockages` example. 169 | 170 | The whole script 171 | ---------------- 172 | 173 | All together, the whole example script is as follows: 174 | 175 | .. literalinclude:: ../../../examples/python/phase.py 176 | :language: python 177 | :lineno-match: 178 | -------------------------------------------------------------------------------- /COPYING.LESSER: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /wrappers/python/src/pyzsf/pyzsf.py: -------------------------------------------------------------------------------- 1 | from typing import Dict 2 | 3 | from ._zsf_cffi import ffi, lib 4 | 5 | 6 | def _struct_to_dict(struct): 7 | d = {} 8 | 9 | for k in dir(struct): 10 | d[k] = getattr(struct, k) 11 | 12 | if not isinstance(d[k], (float, int, str)) and ffi.typeof(d[k]).cname.startswith("struct "): 13 | d[k] = _struct_to_dict(d[k]) 14 | 15 | return d 16 | 17 | 18 | def _zsf_error_message(code): 19 | return ffi.string(lib.zsf_error_msg(code)).decode("utf-8") 20 | 21 | 22 | def _zsf_version(): 23 | return ffi.string(lib.zsf_version()).decode("utf-8") 24 | 25 | 26 | def zsf_calc_steady(auxiliary_results: bool = False, **parameters: float) -> Dict[str, float]: 27 | """ 28 | Calculate the salt intrusion for a set of parameters, assuming steady 29 | operation. 30 | 31 | :param auxiliary_results: Whether or not to calculate and output auxiliary 32 | results. See :c:struct:`zsf_aux_results_t`. 33 | :param kwargs: Any parameters that should be changed versus the default. 34 | See also :c:struct:`zsf_param_t` for an overview of the parameters. 35 | 36 | :returns: A dictionary containing the cycle averaged salt fluxes and 37 | discharges (see :c:struct:`zsf_results_t`). Also outputs values in 38 | :c:struct:`zsf_aux_results_t` if ``auxiliary_results`` is `True`. 39 | """ 40 | param_t = ffi.new("zsf_param_t *") 41 | 42 | # Check input parameters 43 | param_names = set(dir(param_t)) 44 | for p in parameters: 45 | if p not in param_names: 46 | raise TypeError(f"No such parameter '{p}'") 47 | 48 | # Set default values 49 | lib.zsf_param_default(param_t) 50 | 51 | # Set parameter values based on keyword arguments 52 | for p, v in parameters.items(): 53 | setattr(param_t, p, v) 54 | 55 | # Get results 56 | results_t = ffi.new("zsf_results_t *") 57 | if auxiliary_results: 58 | aux_results_t = ffi.new("zsf_aux_results_t *") 59 | else: 60 | aux_results_t = ffi.NULL 61 | assert len(dir(aux_results_t)) == 0 62 | 63 | err = lib.zsf_calc_steady(param_t, results_t, aux_results_t) 64 | 65 | if err: 66 | raise RuntimeError(_zsf_error_message(err)) 67 | 68 | # Reformat results into a dictionary and return 69 | return {**_struct_to_dict(results_t), **_struct_to_dict(aux_results_t)} 70 | 71 | 72 | class ZSFUnsteady: 73 | """ 74 | A class to calculate a lock in phase-wise fashion. 75 | """ 76 | 77 | def __init__(self, sal_lock, head_lock, **parameters: float): 78 | 79 | self._param_t = ffi.new("zsf_param_t *") 80 | self._state_t = ffi.new("zsf_phase_state_t *") 81 | # We can reuse the same object for results, 82 | # as we convert it to a dictionary before returning 83 | self._results_t = ffi.new("zsf_phase_transports_t *") 84 | 85 | self._param_t_names = set(dir(self._param_t)) 86 | 87 | # Set default values 88 | lib.zsf_param_default(self._param_t) 89 | 90 | # Set user parameters 91 | self._set_parameters(**parameters) 92 | 93 | # Initialize the state 94 | lib.zsf_initialize_state(self._param_t, self._state_t, sal_lock, head_lock) 95 | 96 | def _set_parameters(self, **parameters: float): 97 | for p, v in parameters.items(): 98 | if p not in self._param_t_names: 99 | raise TypeError(f"No such parameter '{p}'") 100 | else: 101 | setattr(self._param_t, p, v) 102 | 103 | def step_phase_1(self, t_level, **parameters: float) -> Dict[str, float]: 104 | """ 105 | Level the lock to lake side. See also :c:func:`zsf_step_phase_1` . 106 | 107 | :param t_level: Duration of the leveling in seconds. 108 | :param parameters: Any parameters that should be changed before 109 | performing this step. Note that these changes persist. 110 | 111 | :returns: The salt and water transports in this phase. 112 | See also :c:struct:`zsf_phase_transports_t`. 113 | """ 114 | 115 | self._set_parameters(**parameters) 116 | 117 | err = lib.zsf_step_phase_1(self._param_t, t_level, self._state_t, self._results_t) 118 | if err: 119 | raise RuntimeError(_zsf_error_message(err)) 120 | 121 | return _struct_to_dict(self._results_t) 122 | 123 | def step_phase_2(self, t_open_lake: float, **parameters: float) -> Dict[str, float]: 124 | """ 125 | Open the door on lake side. See also :c:func:`zsf_step_phase_2` . 126 | 127 | :param t_open_lake: Duration the door is open in seconds. 128 | :param parameters: Any parameters that should be changed before 129 | performing this step. Note that these changes persist. 130 | 131 | :returns: The salt and water transports in this phase. 132 | See also :c:struct:`zsf_phase_transports_t`. 133 | 134 | .. note: This function assumes that the lock is already at lake level before opening. 135 | """ 136 | 137 | self._set_parameters(**parameters) 138 | 139 | err = lib.zsf_step_phase_2(self._param_t, t_open_lake, self._state_t, self._results_t) 140 | if err: 141 | raise RuntimeError(_zsf_error_message(err)) 142 | 143 | return _struct_to_dict(self._results_t) 144 | 145 | def step_phase_3(self, t_level, **parameters: float) -> Dict[str, float]: 146 | """ 147 | Level the lock to sea side. See also :c:func:`zsf_step_phase_3` . 148 | 149 | :param t_level: Duration of the leveling in seconds. 150 | :param parameters: Any parameters that should be changed before 151 | performing this step. Note that these changes persist. 152 | 153 | :returns: The salt and water transports in this phase. 154 | See also :c:struct:`zsf_phase_transports_t`. 155 | """ 156 | 157 | self._set_parameters(**parameters) 158 | 159 | err = lib.zsf_step_phase_3(self._param_t, t_level, self._state_t, self._results_t) 160 | if err: 161 | raise RuntimeError(_zsf_error_message(err)) 162 | 163 | return _struct_to_dict(self._results_t) 164 | 165 | def step_phase_4(self, t_open_sea: float, **parameters: float) -> Dict[str, float]: 166 | """ 167 | Open the door on sea side. See also :c:func:`zsf_step_phase_4` . 168 | 169 | :param t_open_sea: Duration the door is open in seconds. 170 | :param parameters: Any parameters that should be changed before 171 | performing this step. Note that these changes persist. 172 | 173 | :returns: The salt and water transports in this phase. 174 | See also :c:struct:`zsf_phase_transports_t`. 175 | 176 | .. note: This function assumes that the lock is already at sea level before opening. 177 | """ 178 | 179 | self._set_parameters(**parameters) 180 | 181 | err = lib.zsf_step_phase_4(self._param_t, t_open_sea, self._state_t, self._results_t) 182 | if err: 183 | raise RuntimeError(_zsf_error_message(err)) 184 | 185 | return _struct_to_dict(self._results_t) 186 | 187 | def step_flush_doors_closed(self, t_flushing: float, **parameters: float) -> Dict[str, float]: 188 | """ 189 | Open the door on sea side. See also :c:func:`zsf_step_flush_doors_closed` . 190 | 191 | :param t_flushing: Duration of flushing is open in seconds. 192 | :param parameters: Any parameters that should be changed before 193 | performing this step. Note that these changes persist. 194 | 195 | :returns: The salt and water transports in this phase. 196 | See also :c:struct:`zsf_phase_transports_t`. 197 | 198 | .. note: This function makes no assumption on the lock level with respect to 199 | the sea or lake levels. 200 | """ 201 | 202 | self._set_parameters(**parameters) 203 | 204 | err = lib.zsf_step_flush_doors_closed( 205 | self._param_t, t_flushing, self._state_t, self._results_t 206 | ) 207 | if err: 208 | raise RuntimeError(_zsf_error_message(err)) 209 | 210 | return _struct_to_dict(self._results_t) 211 | 212 | @property 213 | def state(self) -> Dict[str, float]: 214 | """ 215 | Get the state of the lock, see also :c:struct:`zsf_phase_state_t`. 216 | 217 | Note that this is a read-only property, and any changes made to the 218 | dictionary returned by this property do not persist. 219 | """ 220 | 221 | return _struct_to_dict(self._state_t) 222 | -------------------------------------------------------------------------------- /wrappers/fortran/zsf.f90: -------------------------------------------------------------------------------- 1 | module zsf 2 | use, intrinsic :: iso_c_binding, only : c_char, c_double, c_f_pointer, c_int, c_ptr, c_size_t 3 | implicit none 4 | 5 | type, bind(C) :: zsf_param_t 6 | real(c_double) :: lock_length 7 | real(c_double) :: lock_width 8 | real(c_double) :: lock_bottom 9 | real(c_double) :: num_cycles 10 | real(c_double) :: door_time_to_open 11 | real(c_double) :: leveling_time 12 | real(c_double) :: calibration_coefficient 13 | real(c_double) :: symmetry_coefficient 14 | real(c_double) :: ship_volume_sea_to_lake 15 | real(c_double) :: ship_volume_lake_to_sea 16 | real(c_double) :: salinity_lock 17 | real(c_double) :: head_sea 18 | real(c_double) :: salinity_sea 19 | real(c_double) :: temperature_sea 20 | real(c_double) :: head_lake 21 | real(c_double) :: salinity_lake 22 | real(c_double) :: temperature_lake 23 | real(c_double) :: flushing_discharge_high_tide 24 | real(c_double) :: flushing_discharge_low_tide 25 | real(c_double) :: density_current_factor_sea 26 | real(c_double) :: density_current_factor_lake 27 | real(c_double) :: distance_door_bubble_screen_sea 28 | real(c_double) :: distance_door_bubble_screen_lake 29 | real(c_double) :: sill_height_sea 30 | real(c_double) :: sill_height_lake 31 | real(c_double) :: rtol 32 | real(c_double) :: atol 33 | end type zsf_param_t 34 | 35 | type, bind(C) :: zsf_results_t 36 | real(c_double) :: mass_transport_lake 37 | real(c_double) :: salt_load_lake 38 | real(c_double) :: discharge_from_lake 39 | real(c_double) :: discharge_to_lake 40 | real(c_double) :: salinity_to_lake 41 | 42 | real(c_double) :: mass_transport_sea 43 | real(c_double) :: salt_load_sea 44 | real(c_double) :: discharge_from_sea 45 | real(c_double) :: discharge_to_sea 46 | real(c_double) :: salinity_to_sea 47 | end type zsf_results_t 48 | 49 | type, bind(C) :: zsf_phase_state_t 50 | real(c_double) :: salinity_lock 51 | real(c_double) :: saltmass_lock 52 | real(c_double) :: head_lock 53 | real(c_double) :: volume_ship_in_lock 54 | end type zsf_phase_state_t 55 | 56 | type, bind(C) :: zsf_phase_transports_t 57 | real(c_double) :: mass_transport_lake 58 | real(c_double) :: volume_from_lake 59 | real(c_double) :: volume_to_lake 60 | real(c_double) :: discharge_from_lake 61 | real(c_double) :: discharge_to_lake 62 | real(c_double) :: salinity_to_lake 63 | 64 | real(c_double) :: mass_transport_sea 65 | real(c_double) :: volume_from_sea 66 | real(c_double) :: volume_to_sea 67 | real(c_double) :: discharge_from_sea 68 | real(c_double) :: discharge_to_sea 69 | real(c_double) :: salinity_to_sea 70 | end type zsf_phase_transports_t 71 | 72 | type, bind(C) :: zsf_aux_results_t 73 | real(c_double) :: z_fraction 74 | real(c_double) :: dimensionless_door_open_time 75 | real(c_double) :: volume_to_lake 76 | real(c_double) :: volume_from_lake 77 | real(c_double) :: volume_to_sea 78 | real(c_double) :: volume_from_sea 79 | real(c_double) :: volume_lock_at_lake 80 | real(c_double) :: volume_lock_at_sea 81 | real(c_double) :: t_cycle 82 | real(c_double) :: t_open 83 | real(c_double) :: t_open_lake 84 | real(c_double) :: t_open_sea 85 | real(c_double) :: salinity_lock_1 86 | real(c_double) :: salinity_lock_2 87 | real(c_double) :: salinity_lock_3 88 | real(c_double) :: salinity_lock_4 89 | type(zsf_phase_transports_t) :: transports_phase_1 90 | type(zsf_phase_transports_t) :: transports_phase_2 91 | type(zsf_phase_transports_t) :: transports_phase_3 92 | type(zsf_phase_transports_t) :: transports_phase_4 93 | end type zsf_aux_results_t 94 | 95 | interface 96 | integer(c_int) function zsf_initialize_state(p, state, salinity_lock, head_lock) bind(C, name='zsf_initialize_state') 97 | import c_int, zsf_param_t, zsf_phase_state_t, c_double 98 | type(zsf_param_t), intent(in) :: p 99 | type(zsf_phase_state_t), intent(inout) :: state 100 | real(c_double), intent(in), value :: salinity_lock 101 | real(c_double), intent(in), value :: head_lock 102 | end function zsf_initialize_state 103 | 104 | integer(c_int) function zsf_step_phase_1(p, t_level, state, results) bind(C, name='zsf_step_phase_1') 105 | import c_int, zsf_param_t, c_double, zsf_phase_state_t, zsf_phase_transports_t 106 | type(zsf_param_t), intent(in) :: p 107 | real(c_double), intent(in), value :: t_level 108 | type(zsf_phase_state_t), intent(inout) :: state 109 | type(zsf_phase_transports_t), intent(inout) :: results 110 | end function zsf_step_phase_1 111 | 112 | integer(c_int) function zsf_step_phase_2(p, t_open_lake, state, results) bind(C, name='zsf_step_phase_2') 113 | import c_int, zsf_param_t, c_double, zsf_phase_state_t, zsf_phase_transports_t 114 | type(zsf_param_t), intent(in) :: p 115 | real(c_double), intent(in), value :: t_open_lake 116 | type(zsf_phase_state_t), intent(inout) :: state 117 | type(zsf_phase_transports_t), intent(inout) :: results 118 | end function zsf_step_phase_2 119 | 120 | integer(c_int) function zsf_step_phase_3(p, t_level, state, results) bind(C, name='zsf_step_phase_3') 121 | import c_int, zsf_param_t, c_double, zsf_phase_state_t, zsf_phase_transports_t 122 | type(zsf_param_t), intent(in) :: p 123 | real(c_double), intent(in), value :: t_level 124 | type(zsf_phase_state_t), intent(inout) :: state 125 | type(zsf_phase_transports_t), intent(inout) :: results 126 | end function zsf_step_phase_3 127 | 128 | integer(c_int) function zsf_step_phase_4(p, t_open_sea, state, results) bind(C, name='zsf_step_phase_4') 129 | import c_int, zsf_param_t, c_double, zsf_phase_state_t, zsf_phase_transports_t 130 | type(zsf_param_t), intent(in) :: p 131 | real(c_double), intent(in), value :: t_open_sea 132 | type(zsf_phase_state_t), intent(inout) :: state 133 | type(zsf_phase_transports_t), intent(inout) :: results 134 | end function zsf_step_phase_4 135 | 136 | integer(c_int) function zsf_step_flush_doors_closed(p, t_flushing, state, results) bind(C, name='zsf_step_flush_doors_closed') 137 | import c_int, zsf_param_t, c_double, zsf_phase_state_t, zsf_phase_transports_t 138 | type(zsf_param_t), intent(in) :: p 139 | real(c_double), intent(in), value :: t_flushing 140 | type(zsf_phase_state_t), intent(inout) :: state 141 | type(zsf_phase_transports_t), intent(inout) :: results 142 | end function zsf_step_flush_doors_closed 143 | 144 | subroutine zsf_param_default(p) bind(C, name='zsf_param_default') 145 | import zsf_param_t 146 | type(zsf_param_t), intent(inout) :: p 147 | end subroutine zsf_param_default 148 | 149 | integer(c_int) function zsf_calc_steady(p, results, aux_results) bind(C, name='zsf_calc_steady') 150 | import c_int, zsf_param_t, zsf_results_t, zsf_aux_results_t 151 | type(zsf_param_t), intent(in) :: p 152 | type(zsf_results_t), intent(inout) :: results 153 | type(zsf_aux_results_t), intent(inout) :: aux_results 154 | end function zsf_calc_steady 155 | 156 | type(c_ptr) function zsf_error_msg__raw(code) bind(C, name='zsf_error_msg') 157 | import c_int, c_ptr 158 | integer(c_int), intent(in), value :: code 159 | end function zsf_error_msg__raw 160 | 161 | type(c_ptr) function zsf_version__raw() bind(C, name='zsf_version') 162 | import c_ptr 163 | end function zsf_version__raw 164 | 165 | integer(c_size_t) function c_strlen(s) bind(c, name="strlen") 166 | import c_size_t, c_ptr 167 | type(c_ptr), intent(in), value :: s 168 | end function 169 | end interface 170 | 171 | contains 172 | 173 | ! See https://fortran-lang.discourse.group/t/iso-c-binding-interface-to-a-c-function-returning-a-string/527/14 174 | function zsf_version() result(str) 175 | character(:, c_char), allocatable :: str 176 | type(c_ptr) :: cstr 177 | integer(c_size_t) :: n 178 | 179 | cstr = zsf_version__raw() 180 | n = c_strlen(cstr) 181 | allocate(character(len=n, kind=c_char) :: str) 182 | block 183 | character(len=n, kind=c_char), pointer :: s 184 | call c_f_pointer(cstr, s) ! Recovers a view of the C string 185 | str = s ! Copies the string contents 186 | end block 187 | ! Note that we do not have to free the returned string, 188 | ! as it was not dynamically allocated. 189 | end function zsf_version 190 | 191 | function zsf_error_msg(code) result(str) 192 | integer(c_int), intent(in) :: code 193 | character(:, c_char), allocatable :: str 194 | type(c_ptr) :: cstr 195 | integer(c_size_t) :: n 196 | 197 | cstr = zsf_error_msg__raw(code) 198 | n = c_strlen(cstr) 199 | allocate(character(len=n, kind=c_char) :: str) 200 | block 201 | character(len=n, kind=c_char), pointer :: s 202 | call c_f_pointer(cstr, s) ! Recovers a view of the C string 203 | str = s ! Copies the string contents 204 | end block 205 | ! Note that we do not have to free the returned string, 206 | ! as it was not dynamically allocated. 207 | end function zsf_error_msg 208 | end module 209 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - 'master' 8 | - 'maintenance/*' 9 | tags: 10 | - 'v[0-9].*' 11 | schedule: 12 | # Every Wednesday at 6:36 UTC 13 | - cron: "36 6 * * 3" 14 | 15 | jobs: 16 | style-c: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Install and update dependencies 21 | run: | 22 | sudo apt-get update 23 | sudo apt-get install -y clang-format git 24 | - name: Check C code formatting 25 | run: | 26 | clang-format -i src/* include/* 27 | git diff --exit-code 28 | 29 | style-python: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v3 33 | - uses: actions/setup-python@v4 34 | with: 35 | python-version: '3.9' 36 | - name: Install dependencies 37 | run: | 38 | pip install tox 39 | cd wrappers/python 40 | - name: Check Python code formatting 41 | run: | 42 | tox -vv -e flake8,black 43 | 44 | build-linux: 45 | needs: 46 | - style-c 47 | - style-python 48 | runs-on: ubuntu-latest 49 | container: 50 | # Things like actions/checkout and actions/upload-artifact start to fall 51 | # apart if we use an older image. 52 | image: quay.io/pypa/manylinux2014_x86_64 53 | env: 54 | CC: gcc 55 | CXX: g++ 56 | steps: 57 | - uses: actions/checkout@v3 58 | - name: Get history and tags for SCM versioning to work 59 | run: | 60 | git config --global --add safe.directory '*' 61 | git fetch --prune --unshallow 62 | git fetch --depth=1 origin +refs/tags/*:refs/tags/* 63 | - name: Describe version 64 | run: | 65 | git describe --tags --first-parent HEAD 66 | - name: Build C Library 67 | # GLIBC 2.31 broke compatibility in that a.o. __exp_finite is no 68 | # longer defined, i.e. building with -ffast-math on the manylinux2014 69 | # image means it won't run on modern platforms. We therefore disable 70 | # this for Linux wheels. 71 | # See e.g. https://stackoverflow.com/questions/63261220/link-errors-with-ffast-math-ffinite-math-only-and-glibc-2-31 72 | run: | 73 | mkdir build 74 | cd build 75 | cmake -DUSE_FAST_MATH=OFF -DUSE_FAST_TANH=ON -DCMAKE_INSTALL_PREFIX=../dist -DCMAKE_BUILD_TYPE=Release .. 76 | make -j4 install 77 | - name: Build Python Wheel 78 | run: | 79 | cd wrappers/python 80 | /opt/python/cp37-cp37m/bin/python setup.py build_ext --library-dirs=../../dist/lib --include-dirs=../../include bdist_wheel --py-limited-api=cp37 81 | auditwheel repair dist/* 82 | # Upload the artifacts 83 | - uses: actions/upload-artifact@v3 84 | with: 85 | name: c-build-linux 86 | path: dist/ 87 | retention-days: 5 88 | - uses: actions/upload-artifact@v3 89 | with: 90 | name: python-build-linux 91 | path: wrappers/python/wheelhouse/ 92 | retention-days: 5 93 | - uses: actions/upload-artifact@v3 94 | with: 95 | name: fortran-interface 96 | path: wrappers/fortran/zsf.f90 97 | retention-days: 5 98 | 99 | build-windows: 100 | runs-on: windows-latest 101 | defaults: 102 | run: 103 | shell: powershell 104 | needs: 105 | - style-c 106 | - style-python 107 | steps: 108 | - uses: actions/checkout@v3 109 | - name: Get history and tags for SCM versioning to work 110 | run: | 111 | git fetch --prune --unshallow 112 | git fetch --depth=1 origin +refs/tags/*:refs/tags/* 113 | - name: Describe version 114 | run: git describe --tags --first-parent HEAD 115 | - name: Install 32-bit Python 116 | uses: actions/setup-python@v4 117 | id: python_x86 118 | with: 119 | python-version: "3.7" 120 | architecture: x86 121 | - name: Install 64-bit Python 122 | uses: actions/setup-python@v4 123 | id: python_x64 124 | with: 125 | python-version: "3.7" 126 | architecture: x64 127 | - name: Build C library (32-bit and 64-bit) 128 | run: | 129 | cmake -G "Visual Studio 17 2022" -A Win32 -S . -B "build32" -DUSE_FAST_MATH=ON -DUSE_FAST_TANH=ON -DCMAKE_INSTALL_PREFIX:FILEPATH=../dist32 130 | cmake --build build32 --config Release --target Install 131 | cmake -G "Visual Studio 17 2022" -A x64 -S . -B "build64" -DUSE_FAST_MATH=ON -DUSE_FAST_TANH=ON -DCMAKE_INSTALL_PREFIX:FILEPATH=../dist64 132 | cmake --build build64 --config Release --target Install 133 | - name: Install Python dependencies 134 | run: | 135 | & "${{ steps.python_x86.outputs.python-path }}" -m pip install wheel>=0.35.0 136 | & "${{ steps.python_x64.outputs.python-path }}" -m pip install wheel>=0.35.0 137 | - name: Build Python Wheels 138 | run: | 139 | cd wrappers/python 140 | & "${{ steps.python_x86.outputs.python-path }}" setup.py build_ext --library-dirs=../../dist32/lib --include-dirs=../../include bdist_wheel --py-limited-api=cp37 141 | & "${{ steps.python_x64.outputs.python-path }}" setup.py build_ext --library-dirs=../../dist64/lib --include-dirs=../../include bdist_wheel --py-limited-api=cp37 142 | # Upload the artifacts 143 | - uses: actions/upload-artifact@v3 144 | with: 145 | name: c-build-windows-x86 146 | path: dist32/ 147 | retention-days: 5 148 | - uses: actions/upload-artifact@v3 149 | with: 150 | name: c-build-windows-x64 151 | path: dist64/ 152 | retention-days: 5 153 | - uses: actions/upload-artifact@v3 154 | with: 155 | name: python-build-windows-x86 156 | path: wrappers/python/dist/*win32.whl 157 | retention-days: 5 158 | - uses: actions/upload-artifact@v3 159 | with: 160 | name: python-build-windows-x64 161 | path: wrappers/python/dist/*amd64.whl 162 | retention-days: 5 163 | 164 | package-excel: 165 | needs: build-windows 166 | runs-on: ubuntu-latest 167 | steps: 168 | - uses: actions/checkout@v3 169 | - uses: actions/download-artifact@v3 170 | with: 171 | path: artifacts/ 172 | - name: Package Excel 173 | run: | 174 | mkdir excel-build 175 | cp artifacts/c-build-windows-x64/bin/zsf.dll excel-build 176 | cp artifacts/c-build-windows-x86/bin/zsf-stdcall.dll excel-build 177 | cp wrappers/excel/zsf.xlsm excel-build 178 | - uses: actions/upload-artifact@v3 179 | with: 180 | name: excel-build 181 | path: excel-build 182 | retention-days: 5 183 | 184 | test-python: 185 | needs: build-linux 186 | runs-on: ubuntu-latest 187 | strategy: 188 | matrix: 189 | python-version: ['3.7', '3.10', '3.11'] 190 | steps: 191 | - uses: actions/checkout@v3 192 | - uses: actions/setup-python@v4 193 | with: 194 | python-version: ${{ matrix.python-version }} 195 | - uses: actions/download-artifact@v3 196 | with: 197 | name: python-build-linux 198 | path: wrappers/python/wheelhouse/ 199 | - name: Install Tox 200 | run: pip install tox 201 | - name: Run tests 202 | run: | 203 | cd wrappers/python 204 | tox -vv --installpkg wheelhouse/*.whl 205 | env: 206 | TOXENV: py 207 | 208 | test-fortran: 209 | needs: build-linux 210 | runs-on: ubuntu-latest 211 | steps: 212 | - uses: actions/checkout@v3 213 | - uses: actions/download-artifact@v3 214 | with: 215 | name: c-build-linux 216 | path: dist/ 217 | - name: Install gfortran 218 | run: | 219 | sudo apt-get update 220 | sudo apt-get install gfortran 221 | - name: Run tests 222 | run: | 223 | cd wrappers/fortran 224 | gfortran -fdefault-real-8 -o test zsf.f90 test.f90 ../../dist/lib/libzsf-static.a 225 | ./test 226 | 227 | deploy-pypi: 228 | needs: 229 | - test-fortran 230 | - test-python 231 | - package-excel 232 | runs-on: ubuntu-latest 233 | if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }} 234 | steps: 235 | - uses: actions/checkout@v3 236 | - uses: actions/download-artifact@v3 237 | with: 238 | path: artifacts/ 239 | - uses: actions/setup-python@v4 240 | - run: | 241 | python -m pip install --upgrade pip 242 | pip install setuptools wheel twine 243 | - env: 244 | TWINE_USERNAME: __token__ 245 | TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} 246 | run: | 247 | twine upload artifacts/python-build-*/* 248 | 249 | deploy-github-release: 250 | needs: 251 | - test-fortran 252 | - test-python 253 | - package-excel 254 | runs-on: ubuntu-latest 255 | if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }} 256 | permissions: 257 | contents: write 258 | steps: 259 | - uses: actions/download-artifact@v3 260 | with: 261 | path: artifacts/ 262 | - run: | 263 | cd artifacts/excel-build && zip ../../zsf-excel-${{ github.ref_name }}.zip * 264 | - run: | 265 | cd artifacts/c-build-linux && tar zcf ../../libzsf-${{ github.ref_name }}-linux-amd64.tar.gz * 266 | - run: | 267 | cd artifacts/c-build-windows-x64 && zip -r ../../libzsf-${{ github.ref_name }}-windows-amd64.zip * 268 | - run: | 269 | cd artifacts/c-build-windows-x86 && zip -r ../../libzsf-${{ github.ref_name }}-windows-386.zip * 270 | - run: | 271 | cd artifacts/fortran-interface && zip -r ../../libzsf-fortran-interface-${{ github.ref_name }}.zip * 272 | - uses: ncipollo/release-action@v1 273 | with: 274 | artifacts: "*.zip,*.tar.gz,artifacts/python-build-*/*.whl" 275 | generateReleaseNotes: true 276 | draft: true 277 | -------------------------------------------------------------------------------- /docs/api/c-api.rst: -------------------------------------------------------------------------------- 1 | C API 2 | ===== 3 | 4 | Structures 5 | ---------- 6 | 7 | .. _sec_c_api_input: 8 | 9 | Input 10 | ^^^^^ 11 | 12 | .. c:struct:: zsf_param_t 13 | 14 | The parameter structure both for phase-wise calculation, and for steady state calculation. 15 | 16 | .. c:var:: double lock_length 17 | 18 | The length of the lock in meters. 19 | 20 | .. c:var:: double lock_width 21 | 22 | The width of the lock in meters. 23 | 24 | .. c:var:: double lock_bottom 25 | 26 | The bottom of the lock in meters with respect to datum (e.g. mNAP). 27 | 28 | .. c:var:: double num_cycles 29 | 30 | (Steady) The number of cycles (leveling from lake to sea and back again) the 31 | lock makes in a day. 32 | 33 | .. c:var:: double door_time_to_open 34 | 35 | (Steady) The time it takes for the door to go from fully closed to fully 36 | (open in seconds. 37 | 38 | .. c:var:: double leveling_time 39 | 40 | (Steady) The average time it takes to level the lock to the sea or lake level in seconds. 41 | 42 | .. c:var:: double calibration_coefficient 43 | 44 | (Steady) The calibration coefficient on the time that the door is open. 45 | 46 | .. c:var:: double symmetry_coefficient 47 | 48 | (Steady) The imbalance between the lake door and right door being open, should be in the range (0, 2). 49 | A value of 1.0 means that the lake and sea side door are open equally long. 50 | 51 | .. c:var:: double ship_volume_sea_to_lake 52 | 53 | The water displacement of ships going from the sea to the lake in :math:`m^3`. 54 | 55 | .. c:var:: double ship_volume_lake_to_sea 56 | 57 | The water displacement of ships going from the sea to the lake in :math:`m^3`. 58 | 59 | .. c:var:: double salinity_lock 60 | 61 | The (initial) salinity of the lock in :math:`kg/m^3`. 62 | 63 | .. c:var:: double head_sea 64 | 65 | The head of the sea in meters with respect to datum (e.g. mNAP). 66 | 67 | .. c:var:: double salinity_sea 68 | 69 | The salinity of the sea in :math:`kg/m^3`. 70 | 71 | .. c:var:: double temperature_sea 72 | 73 | The temperature of the sea in degrees Celcius. 74 | 75 | .. c:var:: double head_lake 76 | 77 | The head of the lake in meters with respect to datum (e.g. mNAP). 78 | 79 | .. c:var:: double salinity_lake 80 | 81 | The salinity of the lake in :math:`kg/m^3`. 82 | 83 | .. c:var:: double temperature_lake 84 | 85 | The temperature of the lake in degrees Celcius. 86 | 87 | .. c:var:: double flushing_discharge_high_tide 88 | 89 | The flushing discharge in :math:`m3/s` when the sea level is higher than (or equal to) lake level. 90 | 91 | .. c:var:: double flushing_discharge_low_tide 92 | 93 | The flushing discharge in :math:`m3/s` when the lake level is strictly below the sea level. 94 | 95 | .. c:var:: double density_current_factor_sea 96 | 97 | The factor by which to multiply the velocity of the density current on the sea side. 98 | 99 | .. c:var:: double density_current_factor_lake 100 | 101 | The factor by which to multiply the velocity of the density current on the lake side. 102 | 103 | .. c:var:: double distance_door_bubble_screen_sea 104 | 105 | Distance of the bubble screen on the lake side to the door in meters. 106 | Positive values mean that the screen is outside the lock. 107 | Negative values mean that the screen is inside the lock. 108 | 109 | .. c:var:: double distance_door_bubble_screen_lake 110 | 111 | Distance of the bubble screen on the lake side to the door in meters. 112 | Positive values mean that the screen is outside the lock. 113 | Negative values mean that the screen is inside the lock. 114 | 115 | .. c:var:: double sill_height_sea 116 | 117 | Sill height on the sea side in meters above the bottom of the lock. 118 | 119 | .. c:var:: double sill_height_lake 120 | 121 | Sill height on the lake side in meters above the bottom of the lock. 122 | 123 | .. c:var:: double rtol 124 | 125 | (Steady) The relative tolerance of the salinity in the lock after phase 4 to determine whether convergence has been reached. 126 | 127 | .. c:var:: double atol 128 | 129 | (Steady) The absolute tolerance of the salinity in the lock after phase 4 to determine whether convergence has been reached. 130 | 131 | 132 | Steady state output 133 | ^^^^^^^^^^^^^^^^^^^ 134 | 135 | These structures are output by :c:func:`zsf_calc_steady`. 136 | Note that :c:struct:`zsf_aux_results_t` is an optional output of this function, and is not output/calculated by default. 137 | 138 | .. c:struct:: zsf_results_t 139 | 140 | For mass and salt transport the definition is such that positive values are in the direction lake → lock → sea. 141 | Negative values mean that there is a net withdrawal of salt from the sea or net salt load on the lake. 142 | 143 | .. c:var:: double mass_transport_lake 144 | 145 | The mass transport of salt over the lake head in :math:`kg`. 146 | 147 | .. c:var:: double salt_load_lake 148 | 149 | The average salt transport to the lake :math:`kg/s`. 150 | 151 | .. c:var:: double discharge_from_lake 152 | 153 | The average discharge from the lake to the lock in :math:`m3/s`. 154 | 155 | .. c:var:: double discharge_to_lake 156 | 157 | The average discharge from the lock to the lake in :math:`m3/s`. 158 | 159 | .. c:var:: double salinity_to_lake 160 | 161 | The average salinity of the water going from the lock to the lake in :math:`kg/m^3`. 162 | 163 | .. c:var:: double mass_transport_sea 164 | 165 | The mass transport of salt over the sea head in :math:`kg/m^3`. 166 | 167 | .. c:var:: double salt_load_sea 168 | 169 | The average salt transport to the sea :math:`kg/s`. 170 | 171 | .. c:var:: double discharge_from_sea 172 | 173 | The average discharge from the sea to the lock in :math:`m3/s`. 174 | 175 | .. c:var:: double discharge_to_sea 176 | 177 | The average discharge from the lock to the sea in :math:`m3/s`. 178 | 179 | .. c:var:: double salinity_to_sea 180 | 181 | The average salinity of the water going from the lock to the sea in :math:`kg/m^3`. 182 | 183 | 184 | .. c:struct:: zsf_aux_results_t 185 | 186 | Additional results that can be calculated for steady state operation. 187 | 188 | .. c:var:: double z_fraction 189 | 190 | The dimensionless salt transport per cycle. 191 | It is defined as a factor on the lock volume times the difference in salinity between the lake and sea side. 192 | 193 | .. math:: 194 | 195 | M = Z_{fraction} V_{lock} \cdot \Delta S 196 | 197 | This way it represents what part of the lock, in the regular locking process, is exchanged by the density current and contributes to the salt transport. 198 | 199 | Because the salt transports are per head, and the volume of the lock is not always equal on sea and lake side, average values are used for these. 200 | 201 | .. math:: 202 | 203 | Z_{fraction}= \frac{\overline M}{\overline V_{lock} \cdot \left( S_{sea} - S_{lake} \right) } = \frac{0.5 \cdot \left( M_{lake} + M_{sea} \right) }{0.5 \cdot \left( V_{lock,lake} + V_{lock,sea} \right) \cdot \left( S_{sea} - S_{lake} \right) } 204 | 205 | .. c:var:: double dimensionless_door_open_time 206 | 207 | The dimensionless door open time is :math:`T_{LE} / T_{open}`. 208 | In this calculation the value for :math:`T_{LE}` is calculated per lock head, with the corresponding salinity difference. 209 | That means that :math:`T_{LE}` is not just a function of the input, but is dependent on the calculation routines, and there are two values (one for each lock head). 210 | To get to a single :math:`T_{LE}` that is determined by the geometry and boundary conditions, we define a variant of :math:`T_{LE,lake-sea}` based on the density difference over the lock. 211 | 212 | .. math:: 213 | 214 | c_{i,lake-sea} = \frac{1}{2} \sqrt{g' \overline H } = \frac{1}{2} \sqrt{ g \frac{\Delta \rho}{\bar \rho} \overline H } \approx \frac{1}{2} \sqrt{g \frac{0.8 \left( S_{sea} - S_{lake} \right) }{{\bar \rho}_{lake-sea}} \left( {\frac{{H_{lake} + H_{sea}}}{2}} \right)} 215 | 216 | .. math:: 217 | 218 | T_{LE,lake-sea} = \frac{2L}{c_{i,lake-sea}} 219 | 220 | .. c:var:: double volume_to_lake 221 | 222 | The volume that is discharged to the lake per locking cycle in :math:`m^3`. 223 | 224 | .. c:var:: double volume_from_lake 225 | 226 | The volume that is withdrawn from the lake per locking cycle in :math:`m^3`. 227 | 228 | .. c:var:: double volume_to_sea 229 | 230 | The volume that is discharged to the sea per locking cycle in :math:`m^3`. 231 | 232 | .. c:var:: double volume_from_sea 233 | 234 | The volume that is withdrawn from the sea per locking cycle in :math:`m^3`. 235 | 236 | .. c:var:: double volume_lock_at_lake 237 | 238 | The volume of the lock when it is at sea level in :math:`m^3`. 239 | 240 | .. c:var:: double volume_lock_at_sea 241 | 242 | The volume of the lock when it is at sea level in :math:`m^3`. 243 | 244 | .. c:var:: double t_cycle 245 | 246 | The time it takes to complete one locking cycle in seconds. 247 | 248 | .. c:var:: double t_open 249 | 250 | The average value of the door open time on the lake and sea side, i.e. the average of :c:struct:`zsf_aux_results_t.t_open_lake` and :c:struct:`zsf_aux_results_t.t_open_sea`. 251 | 252 | .. c:var:: double t_open_lake 253 | 254 | The time the door is open on the lake side per locking cycle in seconds. 255 | 256 | .. c:var:: double t_open_sea 257 | 258 | The time the door is open on the sea side per locking cycle in seconds. 259 | 260 | .. c:var:: double salinity_lock_1 261 | 262 | The salinity of the lock after phase 1 in :math:`kg/m^3`. 263 | 264 | .. c:var:: double salinity_lock_2 265 | 266 | The salinity of the lock after phase 2 in :math:`kg/m^3`. 267 | 268 | .. c:var:: double salinity_lock_3 269 | 270 | The salinity of the lock after phase 3 in :math:`kg/m^3`. 271 | 272 | .. c:var:: double salinity_lock_4 273 | 274 | The salinity of the lock after phase 4 in :math:`kg/m^3`. 275 | 276 | .. c:var:: zsf_phase_transports_t transports_phase_1 277 | 278 | The phase transports in phase 1. See :c:struct:`zsf_phase_transports_t` 279 | 280 | .. c:var:: zsf_phase_transports_t transports_phase_2 281 | 282 | The phase transports in phase 2. See :c:struct:`zsf_phase_transports_t` 283 | 284 | .. c:var:: zsf_phase_transports_t transports_phase_3 285 | 286 | The phase transports in phase 3. See :c:struct:`zsf_phase_transports_t` 287 | 288 | .. c:var:: zsf_phase_transports_t transports_phase_4 289 | 290 | The phase transports in phase 4. See :c:struct:`zsf_phase_transports_t` 291 | 292 | 293 | Phase-wise output 294 | ^^^^^^^^^^^^^^^^^ 295 | 296 | .. c:struct:: zsf_phase_state_t 297 | 298 | The state of the lock. 299 | Note that some of these values are redundant, but it is faster to store them than recalculate them every time. 300 | 301 | .. c:var:: double salinity_lock 302 | 303 | The salinity of the water in the lock in :math:`kg/m^3`. 304 | 305 | .. c:var:: double saltmass_lock 306 | 307 | The amount of salt in the lock in :math:`kg`. 308 | 309 | .. c:var:: double head_lock 310 | 311 | The head of the lock in meters with respect to datum (e.g. mNAP). 312 | 313 | .. c:var:: double volume_ship_in_lock 314 | 315 | The water displacement of a ship inside the lock in :math:`m^3`. 316 | 317 | 318 | .. c:struct:: zsf_phase_transports_t 319 | 320 | For mass and salt transport the definition is such that positive values are in the direction lake → lock → sea. 321 | Negative values mean that there is a net withdrawal of salt from the sea or net salt load on the lake. 322 | 323 | .. c:var:: double mass_transport_lake 324 | 325 | The mass transport of salt over the lake head in :math:`kg`. 326 | 327 | .. c:var:: double volume_from_lake 328 | 329 | The volume of water that goes from the lake to the lock in :math:`m^3`. 330 | 331 | .. c:var:: double volume_to_lake 332 | 333 | The volume of water that goes from the the lock to the lake in :math:`m^3`. 334 | 335 | .. c:var:: double discharge_from_lake 336 | 337 | The average discharge of water going from the lake to the lock in :math:`m3/s`. 338 | 339 | .. c:var:: double discharge_to_lake 340 | 341 | The average discharge of water going from the lock to the lake in :math:`m3/s`. 342 | 343 | .. c:var:: double salinity_to_lake 344 | 345 | The average salinity of the water going from the lock to the lake in :math:`kg/m^3`. 346 | 347 | .. c:var:: double mass_transport_sea 348 | 349 | The mass transport of salt over the sea head in :math:`kg`. 350 | 351 | .. c:var:: double volume_from_sea 352 | 353 | The volume of water that goes from the sea to the lock in :math:`m^3`. 354 | 355 | .. c:var:: double volume_to_sea 356 | 357 | The volume of water that goes from the the lock to the sea in :math:`m^3`. 358 | 359 | .. c:var:: double discharge_from_sea 360 | 361 | The average discharge of water going from the sea to the lock in :math:`m3/s`. 362 | 363 | .. c:var:: double discharge_to_sea 364 | 365 | The average discharge of water going from the lock to the sea in :math:`m3/s`. 366 | 367 | .. c:var:: double salinity_to_sea 368 | 369 | The average salinity of the water going from the lock to the sea in :math:`kg/m^3`. 370 | 371 | 372 | Functions 373 | --------- 374 | 375 | .. c:function:: int zsf_initialize_state(const zsf_param_t *p, zsf_phase_state_t *state, double salinity_lock, double head_lock) 376 | 377 | Fill the state with an initial condition for an empty (no ships) lock. 378 | 379 | .. c:function:: int zsf_step_phase_1(const zsf_param_t *p, double t_level, zsf_phase_state_t *state, zsf_phase_transports_t *results) 380 | 381 | Perform step 1: leveling to lake side 382 | 383 | .. c:function:: int zsf_step_phase_2(const zsf_param_t *p, double t_open_lake, zsf_phase_state_t *state, zsf_phase_transports_t *results) 384 | 385 | Perform step 2: door open to lake side: 386 | 387 | - Ships leaving the lock chamber 388 | - Lock exchange with or without flushing 389 | - Ships entering the lock chamber 390 | 391 | .. c:function:: int zsf_step_phase_3(const zsf_param_t *p, double t_level, zsf_phase_state_t *state, zsf_phase_transports_t *results) 392 | 393 | Perform step 3: leveling to sea side 394 | 395 | .. c:function:: int zsf_step_phase_4(const zsf_param_t *p, double t_open_sea, zsf_phase_state_t *state, zsf_phase_transports_t *results) 396 | 397 | Perform step 4: door open to sea side: 398 | 399 | - Ships leaving the lock chamber 400 | - Lock exchange with or without flushing 401 | - Ships entering the lock chamber 402 | 403 | .. c:function:: int zsf_step_flush_doors_closed(const zsf_param_t *p, double t_flushing, zsf_phase_state_t *state, zsf_phase_transports_t *results) 404 | 405 | Flush the lock with the doors closed. 406 | 407 | .. c:function:: void zsf_param_default(zsf_param_t *p) 408 | 409 | Fill a :c:struct:`zsf_param_t` with default values. 410 | 411 | .. c:function:: int zsf_calc_steady(const zsf_param_t *p, zsf_results_t *results, zsf_aux_results_t *aux_results) 412 | 413 | Calculate the salt intrusion for a set of parameters, assuming steady operation. 414 | 415 | .. c:function:: const char * zsf_error_msg(int code) 416 | 417 | Get error message corresponding to error code. 418 | 419 | .. c:function:: const char * zsf_version() 420 | 421 | Get version string. 422 | -------------------------------------------------------------------------------- /wrappers/python/tests/test_steady.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import numpy as np 4 | 5 | from pyzsf import zsf_calc_steady 6 | 7 | 8 | class TestSaltLoadSteady(unittest.TestCase): 9 | def setUp(self): 10 | self.parameters = { 11 | "lock_length": 240.0, 12 | "lock_width": 12.0, 13 | "lock_bottom": -4.0, 14 | "num_cycles": 24.0, 15 | "door_time_to_open": 300.0, 16 | "leveling_time": 300.0, 17 | "calibration_coefficient": 1.0, 18 | "symmetry_coefficient": 1.0, 19 | "ship_volume_sea_to_lake": 0.0, 20 | "ship_volume_lake_to_sea": 0.0, 21 | "head_sea": 0.0, 22 | "salinity_sea": 25.0, 23 | "temperature_sea": 15.0, 24 | "head_lake": 0.0, 25 | "salinity_lake": 5.0, 26 | "temperature_lake": 15.0, 27 | "flushing_discharge_high_tide": 0.0, 28 | "flushing_discharge_low_tide": 0.0, 29 | "density_current_factor_sea": 1.0, 30 | "density_current_factor_lake": 1.0, 31 | } 32 | 33 | self.reference_results = zsf_calc_steady(**self.parameters) 34 | self.reference_load = self.reference_results["salt_load_lake"] 35 | 36 | @staticmethod 37 | def assert_allclose_loose(*args, **kwargs): 38 | return np.testing.assert_allclose(*args, **kwargs, rtol=0.01, atol=0.01) 39 | 40 | @staticmethod 41 | def assert_allclose_tight(*args, **kwargs): 42 | # For when results are supposed to be the same, but might be slightly 43 | # different due to the convergence criterion and/or numerical 44 | # inaccuries. 45 | return np.testing.assert_allclose(*args, **kwargs, rtol=1e-5, atol=1e-5) 46 | 47 | def test_reference_load(self): 48 | self.assert_allclose_loose(zsf_calc_steady(**self.parameters)["salt_load_lake"], -34.315) 49 | 50 | def test_deeper_lock(self): 51 | sl_high_head = zsf_calc_steady(**dict(self.parameters, head_lake=4.0, head_sea=4.0,))[ 52 | "salt_load_lake" 53 | ] 54 | 55 | sl_low_bottom = zsf_calc_steady(**dict(self.parameters, lock_bottom=-8.0,))[ 56 | "salt_load_lake" 57 | ] 58 | 59 | sl_higher_bottom = zsf_calc_steady(**dict(self.parameters, lock_bottom=-2.0,))[ 60 | "salt_load_lake" 61 | ] 62 | 63 | # Comparison checks 64 | self.assertLess(-sl_higher_bottom, -self.reference_load) 65 | self.assertGreater(-sl_low_bottom, -sl_higher_bottom) 66 | self.assert_allclose_tight(sl_high_head, sl_low_bottom) 67 | 68 | # Check values against known good values 69 | self.assert_allclose_loose(sl_high_head, -97.346) 70 | 71 | self.assert_allclose_loose(sl_low_bottom, -97.346) 72 | 73 | self.assert_allclose_loose(sl_higher_bottom, -11.138) 74 | 75 | def test_salinity_lake_sea(self): 76 | sl_sal_gap_smaller = zsf_calc_steady( 77 | **dict(self.parameters, salinity_lake=10.0, salinity_sea=20.0,) 78 | )["salt_load_lake"] 79 | 80 | sl_sal_gap_wider = zsf_calc_steady( 81 | **dict(self.parameters, salinity_lake=0.0, salinity_sea=30.0,) 82 | )["salt_load_lake"] 83 | 84 | # Comparison checks 85 | self.assertGreater(-self.reference_load, -sl_sal_gap_smaller) 86 | self.assertGreater(-sl_sal_gap_wider, -self.reference_load) 87 | 88 | # Check values against known good values 89 | self.assert_allclose_loose(sl_sal_gap_smaller, -11.138) 90 | 91 | self.assert_allclose_loose(sl_sal_gap_wider, -64.234) 92 | 93 | def test_lock_dimensions(self): 94 | sl_lock_longer = zsf_calc_steady(**dict(self.parameters, lock_length=480.0,))[ 95 | "salt_load_lake" 96 | ] 97 | 98 | sl_lock_wider = zsf_calc_steady(**dict(self.parameters, lock_width=24.0,))["salt_load_lake"] 99 | 100 | # Comparison checks 101 | # NOTE: A longer lock does not imply more salt intrusion. A deeper or 102 | # wider one does. 103 | self.assertGreater(-sl_lock_wider, -self.reference_load) 104 | 105 | # Check values against known good values 106 | self.assert_allclose_loose(sl_lock_longer, -28.806) 107 | 108 | self.assert_allclose_loose(sl_lock_wider, -68.632) 109 | 110 | def test_num_cycles(self): 111 | num_cycles_to_sl = [ 112 | (54.0, -9.155), 113 | (43.0, -16.083), 114 | (36.0, -21.604), 115 | (28.0, -29.721), 116 | (20.0, -37.758), 117 | (16.0, -37.176), 118 | (12.0, -31.042), 119 | (8.0, -21.312), 120 | ] 121 | 122 | for num_cycles, sl_ref in num_cycles_to_sl: 123 | result = zsf_calc_steady(**dict(self.parameters, num_cycles=num_cycles,)) 124 | 125 | self.assert_allclose_loose( 126 | result["salt_load_lake"], sl_ref, err_msg=f"num_cycles: {num_cycles}" 127 | ) 128 | 129 | def test_quicker_door_level(self): 130 | sl_door_quick_open = zsf_calc_steady(**dict(self.parameters, door_time_to_open=0.0,))[ 131 | "salt_load_lake" 132 | ] 133 | 134 | sl_door_quick_level = zsf_calc_steady(**dict(self.parameters, leveling_time=0.0,))[ 135 | "salt_load_lake" 136 | ] 137 | 138 | # Comparison checks 139 | self.assertGreater(-sl_door_quick_open, -self.reference_load) 140 | self.assert_allclose_tight(sl_door_quick_open, sl_door_quick_level) 141 | 142 | # Check values against known good values 143 | self.assert_allclose_loose(sl_door_quick_open, -43.679) 144 | 145 | self.assert_allclose_loose(sl_door_quick_level, -43.679) 146 | 147 | def test_calibration_factor(self): 148 | # TODO: Why do we also change num_cycles 14.4? 149 | sl_ref = zsf_calc_steady(**dict(self.parameters, num_cycles=14.4,))["salt_load_lake"] 150 | 151 | sl_calibration_fac = zsf_calc_steady( 152 | **dict(self.parameters, num_cycles=14.4, calibration_coefficient=0.5,) 153 | )["salt_load_lake"] 154 | 155 | # Comparison checks 156 | self.assertGreater(-sl_ref, -sl_calibration_fac) 157 | 158 | # Check values against known good values 159 | self.assert_allclose_loose(sl_calibration_fac, -20.590) 160 | 161 | def test_symmetry_coefficient(self): 162 | results_sym_0_5 = zsf_calc_steady(**dict(self.parameters, symmetry_coefficient=0.5,)) 163 | 164 | results_sym_1_5 = zsf_calc_steady(**dict(self.parameters, symmetry_coefficient=1.5,)) 165 | 166 | sl_sym_0_5 = results_sym_0_5["salt_load_lake"] 167 | sl_sym_1_5 = results_sym_1_5["salt_load_lake"] 168 | 169 | disch_to_lake_sym_0_5 = results_sym_0_5["discharge_to_lake"] 170 | disch_to_sea_sym_0_5 = results_sym_0_5["discharge_to_sea"] 171 | disch_to_lake_sym_1_5 = results_sym_1_5["discharge_to_lake"] 172 | disch_to_sea_sym_1_5 = results_sym_1_5["discharge_to_sea"] 173 | 174 | # Comparison checks 175 | # Any asymmetry in door open times results in a lower mass load 176 | self.assertGreater(-self.reference_load, -sl_sym_0_5) 177 | # Shorter opened lake door -> volume exchanged in lock exchange is 178 | # lower -> cycle-averaged discharge is lower. 179 | self.assertLess(disch_to_lake_sym_0_5, self.reference_results["discharge_to_lake"]) 180 | # It does not matter what direction the asymmetry is for the mass load 181 | # (when not flushing). Discharges flip side. 182 | self.assert_allclose_tight( 183 | sl_sym_0_5, sl_sym_1_5, 184 | ) 185 | self.assert_allclose_tight( 186 | disch_to_lake_sym_0_5, disch_to_sea_sym_1_5, 187 | ) 188 | self.assert_allclose_tight( 189 | disch_to_sea_sym_0_5, disch_to_lake_sym_1_5, 190 | ) 191 | 192 | # Check values against known good values 193 | self.assert_allclose_loose(sl_sym_0_5, -24.707) 194 | 195 | self.assert_allclose_loose(sl_sym_1_5, -24.707) 196 | 197 | def test_ship_water_deplacement(self): 198 | results_ship_sea_to_lake = zsf_calc_steady( 199 | **dict(self.parameters, ship_volume_sea_to_lake=5000.0,) 200 | ) 201 | sl_ship_sea_to_lake = results_ship_sea_to_lake["salt_load_lake"] 202 | 203 | results_ship_lake_to_sea = zsf_calc_steady( 204 | **dict(self.parameters, ship_volume_lake_to_sea=5000.0,) 205 | ) 206 | sl_ship_lake_to_sea = results_ship_lake_to_sea["salt_load_lake"] 207 | 208 | results_ship_both = zsf_calc_steady( 209 | **dict(self.parameters, ship_volume_sea_to_lake=5000.0, ship_volume_lake_to_sea=5000.0,) 210 | ) 211 | sl_ship_both = results_ship_both["salt_load_lake"] 212 | 213 | # Comparison checks 214 | self.assertGreater(-self.reference_load, -sl_ship_sea_to_lake) 215 | self.assertGreater(-sl_ship_lake_to_sea, -sl_ship_both) 216 | self.assertGreater(-sl_ship_both, -sl_ship_sea_to_lake) 217 | 218 | # A ship going from sea to lake means a movement of water in the 219 | # opposite (lake to sea) direction 220 | self.assertGreater( 221 | results_ship_sea_to_lake["discharge_from_lake"], 222 | self.reference_results["discharge_from_lake"], 223 | ) 224 | self.assertGreater( 225 | results_ship_lake_to_sea["discharge_from_sea"], 226 | self.reference_results["discharge_from_sea"], 227 | ) 228 | 229 | # Discharges are flipped when ships move in opposite direction 230 | self.assert_allclose_tight( 231 | results_ship_sea_to_lake["discharge_from_lake"], 232 | results_ship_lake_to_sea["discharge_from_sea"], 233 | ) 234 | self.assert_allclose_tight( 235 | results_ship_sea_to_lake["discharge_to_lake"], 236 | results_ship_lake_to_sea["discharge_to_sea"], 237 | ) 238 | self.assert_allclose_tight( 239 | results_ship_sea_to_lake["discharge_from_sea"], 240 | results_ship_lake_to_sea["discharge_from_lake"], 241 | ) 242 | self.assert_allclose_tight( 243 | results_ship_sea_to_lake["discharge_to_sea"], 244 | results_ship_lake_to_sea["discharge_to_lake"], 245 | ) 246 | 247 | # Check values against known good values 248 | self.assert_allclose_loose(results_ship_sea_to_lake["salt_load_lake"], -8.846) 249 | 250 | self.assert_allclose_loose(results_ship_lake_to_sea["salt_load_lake"], -50.513) 251 | 252 | self.assert_allclose_loose(results_ship_both["salt_load_lake"], -22.373) 253 | 254 | def test_bubble_screen(self): 255 | sl_bubble_50 = zsf_calc_steady( 256 | **dict( 257 | self.parameters, density_current_factor_sea=0.5, density_current_factor_lake=0.5, 258 | ) 259 | )["salt_load_lake"] 260 | 261 | sl_bubble_30 = zsf_calc_steady( 262 | **dict( 263 | self.parameters, density_current_factor_sea=0.25, density_current_factor_lake=0.25, 264 | ) 265 | )["salt_load_lake"] 266 | 267 | # Comparison checks 268 | self.assertGreater(-self.reference_load, -sl_bubble_50) 269 | self.assertGreater(-sl_bubble_50, -sl_bubble_30) 270 | 271 | # Check values against known good values 272 | self.assert_allclose_loose(sl_bubble_50, -14.403) 273 | 274 | self.assert_allclose_loose(sl_bubble_30, -6.369) 275 | 276 | def test_flushing_equal_head(self): 277 | results_flushing_lw = zsf_calc_steady( 278 | **dict(self.parameters, flushing_discharge_low_tide=1.0,) 279 | ) 280 | 281 | results_flushing_hw = zsf_calc_steady( 282 | **dict(self.parameters, flushing_discharge_high_tide=1.0,) 283 | ) 284 | 285 | # Comparison checks 286 | # Equal water level dictates that it is _high_ tide (convention). 287 | # There is therefore no flushing in the low tide test. 288 | self.assert_allclose_tight(results_flushing_lw["salt_load_lake"], self.reference_load) 289 | self.assertGreater(-self.reference_load, -results_flushing_hw["salt_load_lake"]) 290 | 291 | # Check values against known good values 292 | self.assert_allclose_loose(results_flushing_lw["salt_load_lake"], -34.316) 293 | 294 | self.assert_allclose_loose(results_flushing_hw["salt_load_lake"], -25.035) 295 | 296 | def test_low_high_tide(self): 297 | results_low_tide = zsf_calc_steady(**dict(self.parameters, head_sea=-2.0,)) 298 | 299 | results_high_tide = zsf_calc_steady(**dict(self.parameters, head_sea=2.0,)) 300 | 301 | results_flushing_low_tide = zsf_calc_steady( 302 | **dict(self.parameters, head_sea=-2.0, flushing_discharge_low_tide=1.0,) 303 | ) 304 | 305 | results_flushing_high_tide = zsf_calc_steady( 306 | **dict(self.parameters, head_sea=2.0, flushing_discharge_high_tide=1.0,) 307 | ) 308 | 309 | # Comparison checks 310 | self.assertGreater(-self.reference_load, -results_low_tide["salt_load_lake"]) 311 | self.assertGreater(-results_high_tide["salt_load_lake"], -self.reference_load) 312 | 313 | self.assertGreater( 314 | -results_high_tide["salt_load_lake"], -results_flushing_high_tide["salt_load_lake"] 315 | ) 316 | self.assertGreater( 317 | -results_low_tide["salt_load_lake"], -results_flushing_low_tide["salt_load_lake"] 318 | ) 319 | 320 | self.assertGreater( 321 | results_low_tide["discharge_from_lake"], self.reference_results["discharge_from_lake"] 322 | ) 323 | self.assertGreater( 324 | results_high_tide["discharge_from_sea"], self.reference_results["discharge_from_sea"] 325 | ) 326 | 327 | # Check values against known good values 328 | self.assert_allclose_loose(results_low_tide["salt_load_lake"], -2.189) 329 | 330 | self.assert_allclose_loose(results_high_tide["salt_load_lake"], -73.995) 331 | 332 | self.assert_allclose_loose(results_flushing_low_tide["salt_load_lake"], 4.587) 333 | 334 | self.assert_allclose_loose(results_flushing_high_tide["salt_load_lake"], -63.516) 335 | 336 | def test_sill(self): 337 | sl_sill_sea = zsf_calc_steady(**dict(self.parameters, sill_height_sea=1.0,))[ 338 | "salt_load_lake" 339 | ] 340 | 341 | sl_sill_lake = zsf_calc_steady(**dict(self.parameters, sill_height_lake=1.0,))[ 342 | "salt_load_lake" 343 | ] 344 | 345 | # Comparison checks 346 | self.assertGreater(-self.reference_load, -sl_sill_sea) 347 | self.assertGreater(-sl_sill_sea, -sl_sill_lake) 348 | 349 | # Check values against known good values 350 | self.assert_allclose_loose(sl_sill_sea, -32.043) 351 | 352 | self.assert_allclose_loose(sl_sill_lake, -26.126) 353 | 354 | def test_distance_door_bubble_screen(self): 355 | base_params = dict( 356 | self.parameters, density_current_factor_sea=0.25, density_current_factor_lake=0.25 357 | ) 358 | 359 | sl_bubble_base = zsf_calc_steady(**dict(base_params,))["salt_load_lake"] 360 | 361 | sl_bubble_distance_sea = zsf_calc_steady( 362 | **dict(base_params, distance_door_bubble_screen_sea=4.0,) 363 | )["salt_load_lake"] 364 | 365 | sl_bubble_distance_lake = zsf_calc_steady( 366 | **dict(base_params, distance_door_bubble_screen_lake=4.0,) 367 | )["salt_load_lake"] 368 | 369 | # Comparison checks 370 | self.assertGreater(-self.reference_load, -sl_bubble_base) 371 | self.assertGreater(-sl_bubble_distance_sea, -sl_bubble_base) 372 | self.assertGreater(-sl_bubble_distance_lake, -sl_bubble_base) 373 | 374 | # Values are not _exactly_ equal due to differences in computation order, 375 | # but because the effect is the same they should be very close 376 | self.assertNotEqual(sl_bubble_distance_lake, sl_bubble_distance_sea) 377 | self.assert_allclose_loose(sl_bubble_distance_lake, sl_bubble_distance_sea) 378 | 379 | # Check values against known good values 380 | self.assert_allclose_loose(sl_bubble_distance_sea, -6.467) 381 | self.assert_allclose_loose(sl_bubble_distance_lake, -6.467) 382 | -------------------------------------------------------------------------------- /docs/theory/equations_per_locking_phase.rst: -------------------------------------------------------------------------------- 1 | .. _chapter_eq_per_lock_phase: 2 | 3 | Equations per locking phase 4 | =========================== 5 | 6 | Below we discuss, per locking phase, the equations for the mass transport of salt, expressed in terms of volumes with certain salinity. 7 | Eventually the transports over the entire locking cycle can then be determined by aggregating the transports per phase. 8 | 9 | Because we assume cyclic operation of the lock, the initial condition of the lock (e.g. salinity) are equal to the conditions at the end of the cycle. 10 | 11 | Phase 1: Leveling to fresh side 12 | ------------------------------- 13 | 14 | The leveling takes place on the fresh-side head. 15 | There are no transports over the sea-side head. 16 | 17 | .. figure:: ../images/lockphase_1.png 18 | 19 | Schematic overview of leveling to the fresh side during low and high tide 20 | 21 | The salt transport due to leveling at low tide (LT) can be described as: 22 | 23 | .. math:: 24 | :label: eqphase_1_lt_mass 25 | 26 | LT: M_{F,LT1} = V_{level,LT} S_F 27 | 28 | Similarly for high tide: 29 | 30 | .. math:: 31 | :label: eqphase_1_ht_mass 32 | 33 | HT: M_{F,HT1} = -V_{level,HT} S_{L,S} 34 | 35 | By definition (see :eq:`calctransvol_level_definitions_lt` and :eq:`calctransvol_level_definitions_ht`) either :math:`V_{level,LT}` or :math:`V_{level,HT}` is zero, we can sum them up into one single equation: 36 | 37 | .. math:: 38 | :label: eqphase_1_mass 39 | 40 | M_{F,1} = V_{level,LT} S_F - V_{level,HT} S_{L,S} 41 | 42 | When leveling at low tide, the average salinity of the water in lock chamber drops because fresh water is let in. 43 | To calculate this new salinity, we have to take into account the water displacement of ships present in the lock chamber: 44 | 45 | .. math:: 46 | :label: eqphase_1_lt_sal_after_level 47 | 48 | LT: S_{L,S,Lev} = \frac{ S_{L,S} \left( V_{L,S}-V_{Ship,Up} \right) + S_F V_{Lev,LT}} {V_{L,F} - V_{Ship,Up}} 49 | 50 | In the situation around high tide, water is extracted from the lock chamber to lower the level, which does not change the salinity of the lock chamber: 51 | 52 | .. math:: 53 | :label: eqphase_1_ht_sal_after_level 54 | 55 | HT: S_{L,F,Lev} = S_{L,S} 56 | 57 | With this, we can write :eq:`eqphase_1_mass` as follows: 58 | 59 | .. math:: 60 | :label: eqphase_1_mass_alternate 61 | 62 | M_{F,1} = V_{level,LT} S_F - V_{level,HT} S_{L,S,Lev} 63 | 64 | (The salinity in the lock chamber after Phase 1, :math:`S_{L,S,Lev}` can also be written as :math:`S_{L,1}`.) 65 | 66 | The phase-averaged discharges to and withdrawals from the fresh side are then as follows (note that there are no flows on the salt side): 67 | 68 | - withdrawal from the fresh side, with the prevailing salinity :math:`S_F`: 69 | 70 | .. math:: 71 | :label: eqphase_1_vol_withdrawn 72 | 73 | V_{F,1}^- = V_{Lev,LT} 74 | 75 | .. math:: 76 | :label: eqphase_1_flow_withdrawn 77 | 78 | Q_{F,1}^- = \frac{V_{F,1}^-}{T_{Lev}} 79 | 80 | - discharge to the fresh side with salinity :math:`S_{L,S}`: 81 | 82 | .. math:: 83 | :label: eqphase_1_vol_discharged 84 | 85 | V_{F,1}^+ = V_{Lev,HT} 86 | 87 | .. math:: 88 | :label: eqphase_1_flow_discharged 89 | 90 | Q_{F,1}^+ = \frac{V_{F,1}^+}{T_{Lev}} 91 | 92 | 93 | .. _sec_eqphase_phase2: 94 | 95 | Phase 2: Door open on fresh side 96 | -------------------------------- 97 | 98 | The figure below illustrates that in principle there are no differences between low tide and high tide. 99 | In case of flushing through the lock, there is also a transport over the sea-side head. 100 | 101 | .. figure:: ../images/lockphase_2.png 102 | 103 | Schematic overview of flows when door is open on the fresh side during low and high tide 104 | 105 | While the doors are open on one side there are various processes that take place that contribute to the transport of salt over the opened lock head. 106 | These processes are: 107 | 108 | 1. Salt transport due to ships exiting the lock chamber 109 | 2. Salt transports due to the lock exchange (with or without flushing) 110 | 3. Salt transport due to ships entering the lock chamber 111 | 112 | If the transports due to these processes are independently calculated before adding them up, there is a possibility of the salt transport being too high. 113 | This would result in a salinity in the lock chamber that is lower than the fresh side, or higher than the salt side. 114 | To prevent this from happening, Phase 2 has been divided into three subphases, corresponding to the list above. 115 | Each of these subphases leads to a new intermediate salinity of the lock chamber: 116 | 117 | 1: Salt transport due to ships exiting the lock 118 | 119 | .. math:: 120 | :label: eqphase_2_subphase_1_mass 121 | 122 | M_{F,2a} = V_{Ship,Up} S_F 123 | 124 | .. math:: 125 | :label: eqphase_2_subphase_1_sal 126 | 127 | S_{L,2a} = \frac{ S_{L,S,Lev} \left( V_{L,F} - V_{Ship,Up} \right) + M_{F,2a} }{ V_{L,F} } 128 | 129 | 2. Salt transport due to lock exchange (with or without flushing) 130 | 131 | Contribution of lock exchange: 132 | 133 | .. math:: 134 | :label: eqphase_2_subphase_2_mass_le 135 | 136 | M_{F,2b,LE} = V_{U,F} S_F - V_{U,F} S_{L,2a} 137 | 138 | With :math:`V_{U,F}` as determined in :eq:`flushing_superpos_lake_volume_le`. 139 | 140 | Contribution of flushing over fresh-side head: 141 | 142 | .. math:: 143 | :label: eqphase_2_subphase_2_fresh_side_vol_flush 144 | 145 | V_{flush} = Q_{flush} T_{open,F} 146 | 147 | .. math:: 148 | :label: eqphase_2_subphase_2_fresh_side_mass_flush 149 | 150 | M_{F,2b,flush} = V_{flush} S_F 151 | 152 | Contribution of flushing over the salt-side head. 153 | When flushing for such a long time that the lock chamber's salinity reaches that of the fresh side, the salinity of the water going to the sea side changes accordingly: 154 | 155 | .. math:: 156 | :label: eqphase_2_subphase_2_salt_side_vol_flush 157 | 158 | V_{flush,max} = V_{L,F} - V_{U,F} 159 | 160 | .. math:: 161 | :label: eqphase_2_subphase_2_salt_side_mass_flush 162 | 163 | M_{S,2b,flush} = min \left( V_{flush}, V_{flush,max} \right) S_{L,2a} + max \left( V_{flush}-V_{flush,max},0 \right) S_F 164 | 165 | The new salinity at the end of this subphase then is: 166 | 167 | .. math:: 168 | :label: eqphase_2_subphase_2_sal 169 | 170 | S_{L,2b} = \frac{ S_{L,2a} V_{L,F} + M_{F,2b,LE} + M_{F,2b,flush} - M_{Z,2b,flush} }{ V_{L,F} } 171 | 172 | 3. Salt transport due to ships entering the lock 173 | 174 | .. math:: 175 | :label: eqphase_2_subphase_3_mass 176 | 177 | M_{F,2c} = -V_{Ship,Down} S_{L,2b} 178 | 179 | Phase 2: Total transports 180 | ------------------------- 181 | 182 | The total transport of salt over the fresh-side head in this Phase is the sum of the transports of each subphase: 183 | 184 | .. math:: 185 | :label: eqphase_2_mass_fresh_side 186 | 187 | M_{F,2} = M_{F,2a} + M_{F,2b,flush} + M_{F,2b,LE} + M_{F,2c} 188 | 189 | In case of a non-zero flushing discharge, there is also a transport over the salt-side head: 190 | 191 | .. math:: 192 | :label: eqphase_2_mass_salt_side 193 | 194 | M_{S,2}=M_{S,2b,flush} 195 | 196 | The resulting salinity in the lock is then: 197 | 198 | .. math:: 199 | :label: eqphase_2_sal 200 | 201 | S_{L,F} = \frac{ S_{L,S,Lev} \left( V_{L,F} - V_{Ship,Up} \right) + M_{M,2} - M_{S,2} }{ \left( V_{L,F} - V_{Ship,Down} \right) } 202 | 203 | (The salinity in the lock after Phase 2, :math:`S_{L,F}`, can also be written as :math:`S_{L,2}`.) 204 | 205 | The phase-averaged discharges to and withdrawals from the fresh and salt side are then as follows: 206 | 207 | - withdrawal from the fresh side, with the prevailing salinity :math:`S_F`: 208 | 209 | .. math:: 210 | :label: eqphase_2_vol_withdrawn_fresh_side 211 | 212 | V_{F,2}^- = V_{Ship,Up} + V_{U,F} + V_{flush} 213 | 214 | .. math:: 215 | :label: eqphase_2_flow_withdrawn_fresh_side 216 | 217 | Q_{F,2}^- = \frac{ V_{F,2}^- }{ T_{open,F} } 218 | 219 | - discharge to the fresh side with salinity :math:`S_{F,2}^+`: 220 | 221 | .. math:: 222 | :label: eqphase_2_vol_discharged_fresh_side 223 | 224 | V_{F,2}^+ = V_{Ship,Down} + V_{U,F} 225 | 226 | .. math:: 227 | :label: eqphase_2_flow_discharged_fresh_side 228 | 229 | Q_{F,2}^+ = \frac{ V_{F,2}^+ }{ T_{open,F} } 230 | 231 | .. math:: 232 | :label: eqphase_2_sal_discharged_fresh_side 233 | 234 | S_{F,2}^+ = -\frac{ M_{F,2} - V_{F,2}^- \cdot S_F }{ V_{F,2}^+ } 235 | 236 | - there is no withdrawal from the salt side in this Phase 237 | 238 | - discharge to the salt side with average salinity :math:`S_S^+` 239 | 240 | .. math:: 241 | :label: eqphase_2_vol_discharged_salt_side 242 | 243 | V_{S,2}^+ = Q_{flush} T_{open,F} 244 | 245 | .. math:: 246 | :label: eqphase_2_flow_discharged_salt_side 247 | 248 | Q_{S,2}^+ = Q_{flush} 249 | 250 | .. math:: 251 | :label: eqphase_2_sal_discharged_salt_side 252 | 253 | S_{S,2}^+ = \frac{ M_{S,2} }{ V_{S,2}^+ } 254 | 255 | Phase 3: Leveling to salt side 256 | ------------------------------ 257 | 258 | The leveling takes place on the sea-side head. 259 | There are no transports over the fresh-side head. 260 | 261 | .. figure:: ../images/lockphase_3.png 262 | 263 | Schematic overview of leveling to the salt side during low and high tide 264 | 265 | Just like in Phase LT 1 and HT 1 it holds that by definition either :math:`V_{level,LT}` or :math:`V_{level,HT}` is zero. 266 | Therefore we can sum the equations for both tidal phases up into a single one: 267 | 268 | .. math:: 269 | :label: eqphase_3_mass 270 | 271 | M_{F,3} = V_{level,LT} S_{L,F} - V_{level,HT} S_{S} 272 | 273 | In the situation around low tide, water is extracted from the lock chamber to lower the level, which does not change the salinity of the lock chamber: 274 | 275 | .. math:: 276 | :label: eqphase_3_lt_sal_after_level 277 | 278 | LT: S_{L,F,Lev} = S_{L,F} 279 | 280 | When leveling at high tide, the average salinity of the water in lock chamber rises because salt water is let in. 281 | To calculate this new salinity, we have to take into account the water displacement of ships present in the lock chamber: 282 | 283 | .. math:: 284 | :label: eqphase_3_ht_sal_after_level 285 | 286 | HT: S_{L,F,Lev} = \frac{ S_{L,F} \left( V_{L,F}-V_{Ship,Down} \right) + S_S V_{Lev,HT}} { V_{L,S} - V_{Ship,Down}} 287 | 288 | With this, we can write :eq:`eqphase_3_mass` as follows: 289 | 290 | .. math:: 291 | :label: eqphase_3_mass_alternate 292 | 293 | M_{S,3} = V_{level,LT} S_{L,F,Lev} - V_{level,HT} S_S 294 | 295 | (The salinity in the lock chamber after Phase 3, :math:`S_{L,F,Lev}` can also be written as :math:`S_{L,3}`.) 296 | 297 | The phase-averaged discharges to and withdrawals from the salt side are then as follows (note that there are no flows on the fresh side): 298 | 299 | - withdrawal from the salt side, with the prevailing salinity :math:`S_S`: 300 | 301 | .. math:: 302 | :label: eqphase_3_vol_withdrawn 303 | 304 | V_{S,3}^- = V_{Lev,HT} 305 | 306 | .. math:: 307 | :label: eqphase_3_flow_withdrawn 308 | 309 | Q_{S,3}^- = \frac{V_{S,3}^-}{T_{Lev}} 310 | 311 | - discharge to the salt side with salinity :math:`S_{L,F}`: 312 | 313 | .. math:: 314 | :label: eqphase_3_vol_discharged 315 | 316 | V_{S,3}^+ = V_{Lev,LT} 317 | 318 | .. math:: 319 | :label: eqphase_3_flow_discharged 320 | 321 | Q_{S,3}^+ = \frac{V_{S,3}^+}{T_{Lev}} 322 | 323 | Phase 4: Door open on salt side 324 | ------------------------------- 325 | 326 | The figure below illustrates that in principle there are no differences between low tide and high tide. 327 | In case of flushing through the lock, there is also a transport over the fresh-side head. 328 | 329 | .. figure:: ../images/lockphase_4.png 330 | 331 | Schematic overview of flows when door is open on the salt side during low and high tide 332 | 333 | While the doors are open on one side there are various processes that take place that contribute to the transport of salt over the opened lock head. 334 | These processes are: 335 | 336 | 1. Salt transport due to ships exiting the lock chamber 337 | 2. Salt transports due to the lock exchange (with or without flushing) 338 | 3. Salt transport due to ships entering the lock chamber 339 | 340 | If the transports due to these processes are independently calculated before adding them up, there is a possibility of the salt transport being too high. 341 | This would result in a salinity in the lock chamber that is lower than the fresh side, or higher than the salt side. 342 | To prevent this from happening, Phase 4 (just like Phase 2) has been divided into three subphases, corresponding to the list above. 343 | Each of these subphases leads to a new intermediate salinity of the lock chamber: 344 | 345 | 1: Salt transport due to ships exiting the lock 346 | 347 | .. math:: 348 | :label: eqphase_4_subphase_1_mass 349 | 350 | M_{S,4a} = -V_{Ship,Down} S_S 351 | 352 | .. math:: 353 | :label: eqphase_4_subphase_1_sal 354 | 355 | S_{L,4a} = \frac{ S_{L,F,Lev} \left( V_{L,S} - V_{Ship,Down} \right) + M_{S,4a} }{ V_{L,S} } 356 | 357 | 2. Salt transport due to lock exchange (with or without flushing) 358 | 359 | Contribution of lock exchange: 360 | 361 | .. math:: 362 | :label: eqphase_4_subphase_2_mass_le 363 | 364 | M_{S,4b,LE} = V_{U,S} S_{L,4a} - V_{U,S} S_S 365 | 366 | With :math:`V_{U,S}` as determined in :eq:`flushing_superpos_salt_side_vol_u`. 367 | 368 | Contribution of flushing over fresh-side head: 369 | 370 | .. math:: 371 | :label: eqphase_4_subphase_2_fresh_side_vol_flush 372 | 373 | V_{flush} = Q_{flush} T_{open,S} 374 | 375 | .. math:: 376 | :label: eqphase_4_subphase_2_fresh_side_mass_flush 377 | 378 | M_{F,4b,flush} = V_{flush} S_F 379 | 380 | Contribution of flushing over the salt-side head. 381 | When flushing for a long, an equilibrium situation arises as described in :numref:`sec_procdef_flushing_salt_side`. Furthermore, the salinity of the flushing discharge going to the salt side changes from that of the (initial salinity of the) lock chamber to that of the fresh side. 382 | 383 | .. math:: 384 | :label: eqphase_4_subphase_2_salt_side_vol_flush 385 | 386 | V_{flush,max} = V_{L,S} - V_{U,S} 387 | 388 | .. math:: 389 | :label: eqphase_4_subphase_2_salt_side_mass_flush 390 | 391 | M_{S,4b,flush} = min \left( V_{flush}, V_{flush,max} \right) S_{L,4a} + max \left( V_{flush}-V_{flush,max},0 \right) S_F 392 | 393 | The new salinity at the end of this subphase then is: 394 | 395 | .. math:: 396 | :label: eqphase_4_subphase_2_sal 397 | 398 | S_{L,4b} = \frac{ S_{L,4a} V_{L,S} + M_{S,4b,LE} + M_{F,4b,flush} - M_{Z,4b,flush} }{ V_{L,S} } 399 | 400 | 3. Salt transport due to ships entering the lock 401 | 402 | .. math:: 403 | :label: eqphase_4_subphase_3_mass 404 | 405 | M_{S,4c} = V_{Ship,Up} S_{L,4b} 406 | 407 | Phase 4: Total transports 408 | ------------------------- 409 | 410 | The total transport of salt over the salt-side head in this Phase is the sum of the transports of each subphase: 411 | 412 | .. math:: 413 | :label: eqphase_4_mass_salt_side 414 | 415 | M_{S,4} = M_{S,4a} + M_{S,4b,flush} + M_{S,4b,LE} + M_{S,4c} 416 | 417 | In case of a non-zero flushing discharge, there is also a transport over the fresh-side head: 418 | 419 | .. math:: 420 | :label: eqphase_4_mass_fresh_side 421 | 422 | M_{F,4} = M_{F,4b,flush} 423 | 424 | The resulting salinity in the lock is then: 425 | 426 | .. math:: 427 | :label: eqphase_4_sal 428 | 429 | S_{L,S} = \frac{ S_{L,F,Lev} \left( V_{L,S} - V_{Ship,Down} \right) + M_{M,4} - M_{S,4} }{ \left( V_{L,S} - V_{Ship,Up} \right) } 430 | 431 | (The salinity in the lock after Phase 4, :math:`S_{L,S}`, can also be written as :math:`S_{L,4}`.) 432 | 433 | The phase-averaged discharges to and withdrawals from the fresh and salt side are then as follows: 434 | 435 | - withdrawal from the salt side, with the prevailing salinity :math:`S_S`: 436 | 437 | .. math:: 438 | :label: eqphase_4_vol_withdrawn_salt_side 439 | 440 | V_{S,4}^- = V_{Ship,Down} + V_{U,S} 441 | 442 | .. math:: 443 | :label: eqphase_4_flow_withdrawn_salt_side 444 | 445 | Q_{S,4}^- = \frac{ V_{S,4}^- }{ T_{open,S} } 446 | 447 | - discharge to the salt side with salinity :math:`S_{S,4}^+`: 448 | 449 | .. math:: 450 | :label: eqphase_4_vol_discharged_salt_side 451 | 452 | V_{S,4}^+ = V_{Ship,Up} + V_{U,S} + V_{flush} 453 | 454 | .. math:: 455 | :label: eqphase_4_flow_discharged_salt_side 456 | 457 | Q_{S,4}^+ = \frac{ V_{S,4}^+ }{ T_{open,S} } 458 | 459 | .. math:: 460 | :label: eqphase_4_sal_discharged_salt_side 461 | 462 | S_{S,4}^+ = \frac{ M_{S,4} - V_{S,4}^- \cdot S_S }{ V_{S,4}^+ } 463 | 464 | - withdrawal from the fresh side, with the prevailing salinity :math:`S_F`: 465 | 466 | .. math:: 467 | :label: eqphase_4_vol_withdrawn_fresh_side 468 | 469 | V_{F,4}^- = Q_{flush} T_{open,S} 470 | 471 | .. math:: 472 | :label: eqphase_4_flow_withdrawn_fresh_side 473 | 474 | Q_{F,4}^- = Q_{flush} 475 | 476 | - there is no discharge to the lake side in this Phase 477 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------