├── .gitattributes ├── requirements.txt ├── .travis.yml ├── setup.cfg ├── .gitignore ├── scripts ├── profile_package ├── to_conda ├── faster_timelapse └── pypi_to_conda ├── mattflow ├── __init__.py ├── dat_writer.py ├── mattflow_cmaps.py ├── bcmanager.py ├── __main__.py ├── logger.py ├── config.py ├── utils.py ├── initializer.py ├── mattflow_solver.py ├── flux.py ├── mattflow_post.py └── mattflow_test.py ├── meta.yaml ├── setup.py ├── README.md ├── COPYING └── bin └── mattflow_profile.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | * linguist-vendored 2 | *.py linguist-vendored=false 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click==7.0 2 | numba==0.51.2 3 | numpy==1.22.0 4 | matplotlib==3.3.3 5 | joblib==1.2.0 6 | pytest==6.2.4 7 | pytest-cov==2.12.1 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - 3.7 5 | 6 | install: 7 | - pip install -r requirements.txt 8 | 9 | script: 10 | pytest --cov-report term --cov=mattflow/ 11 | 12 | after_success: 13 | - bash <(curl https://codecov.io/bash) -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [coverage:run] 2 | omit = 3 | mattflow/__init__.py 4 | mattflow/__main__.py 5 | mattflow/mattflow_cmaps.py 6 | mattflow/mattflow_post.py 7 | mattflow/dat_writer.py 8 | mattflow/logger.py 9 | 10 | [flake8] 11 | exclude = 12 | .git, 13 | __pycache__, 14 | build, 15 | dist, 16 | max-line-length = 79 17 | max-complexity = 10 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !*.* 3 | !*/ 4 | 5 | *.exe 6 | *.o 7 | *.so 8 | *.dll 9 | 10 | *.zip 11 | *.rar 12 | *.tar 13 | *.7z 14 | *.jar 15 | *.iso 16 | *.gz 17 | *.dmgd 18 | 19 | *.doc 20 | *.docx 21 | *.log 22 | *.dat 23 | *.csv 24 | *.npy 25 | 26 | *.mp4 27 | *.gif 28 | *.avi 29 | *.flv 30 | *.mpeg 31 | *.mpg 32 | 33 | *.png 34 | 35 | **/.vscode* 36 | **/__pycache__/* 37 | **/.ipynb_checkpoints/* 38 | **/*.lprof 39 | 40 | **/build/* 41 | **/dist/* 42 | **/*.egg-info/* 43 | **/*.egg 44 | **/extras/* 45 | .hidden 46 | .coverage* 47 | -------------------------------------------------------------------------------- /scripts/profile_package: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # script: profile_package 3 | # author: Athanasios Mattas 4 | # ------------------------- 5 | # Profiles a python package, using rkern's line_profiler. 6 | # [repo](https://github.com/pyutils/line_profiler) 7 | # 8 | # You can pass 1 argument with the name of the package. 9 | # 10 | # Prerequisites: 11 | # 1. $ pip install line_profiler 12 | # 2. Manually planting @profile above each function to be profiled. 13 | # 14 | # Examples: 15 | # $ source profile_package 16 | # $ source profile_package 17 | if [ $# -eq 0 ]; then 18 | # default 19 | pkg="mattflow" 20 | else 21 | pkg=$1 22 | fi 23 | 24 | if [ -d $PWD/$pkg ]; then 25 | export PYTHONPATH="${PYTHONPATH}:${PWD}/${pkg}" 26 | kernprof -lv ${pkg}/__main__.py.lprof > "${pkg}_profile.txt" 27 | else 28 | cwd=$PWD 29 | cd .. 30 | export PYTHONPATH="${PYTHONPATH}:${PWD}/${pkg}" 31 | kernprof -lv ${pkg}/__main__.py > "${pkg}_profile.txt" 32 | cd $cwd 33 | fi 34 | 35 | -------------------------------------------------------------------------------- /mattflow/__init__.py: -------------------------------------------------------------------------------- 1 | # __init__.py is part of MattFlow 2 | # 3 | # MattFlow is free software; you may redistribute it and/or modify it 4 | # under the terms of the GNU General Public License as published by the 5 | # Free Software Foundation, either version 3 of the License, or (at your 6 | # option) any later version. You should have received a copy of the GNU 7 | # General Public License along with this program. If not, see 8 | # . 9 | # 10 | # (C) 2019 Athanasios Mattas 11 | # ====================================================================== 12 | """Special project variables""" 13 | 14 | 15 | __name__ = 'mattflow' 16 | __version__ = '1.4.3' 17 | __author__ = 'Athanasios Mattas' 18 | __author_email__ = 'thanasismatt@gmail.com' 19 | __description__ = 'A CFD python package for the Shallow Water Equations' 20 | __url__ = 'https://github.com/ThanasisMattas/mattflow.git' 21 | __license__ = 'GNU General Public License v3' 22 | __copyright__ = 'Copyright 2019 Athanasios Mattas' 23 | -------------------------------------------------------------------------------- /meta.yaml: -------------------------------------------------------------------------------- 1 | {% set name = "mattflow" %} 2 | {% set version = "1.4.3" %} 3 | 4 | package: 5 | name: "{{ name|lower }}" 6 | version: "{{ version }}" 7 | 8 | source: 9 | git_url: https://github.com/ThanasisMattas/mattflow.git 10 | 11 | build: 12 | noarch: python 13 | number: 0 14 | script: python setup.py install --single-version-externally-managed --record=record.txt 15 | entry_points: 16 | - mattflow=mattflow.__main__:main 17 | include_recipe: False 18 | 19 | requirements: 20 | host: 21 | - python >=3.6 22 | - pip 23 | run: 24 | - python >=3.6 25 | - click >=7.0 26 | - numpy >=1.18.5 27 | - matplotlib >=3.3.1 28 | - joblib >=0.13.2 29 | - numba >=0.51.2 30 | 31 | about: 32 | home: https://github.com/ThanasisMattas/mattflow.git 33 | license: GNU General Public License v3 or later (LGPLv3+) 34 | license_family: LGPL 35 | license_file: COPYING 36 | summary: A CFD python package for the Shallow Water Equations 37 | 38 | extra: 39 | maintainers: 40 | - ThanasisMattas -------------------------------------------------------------------------------- /scripts/to_conda: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # script: to_conda 3 | # ------------------------ 4 | # Uploads a package to the anaconda cloud, using as recipe a meta.yaml file. 5 | # NOTE: Currently meta.yaml lies in the repository directory. 6 | # 7 | # How to install prerequisites: 8 | # $ conda install conda-build anaconda-client -y 9 | # 10 | # Examples: 11 | # $ anaconda login 12 | # $ source to_conda 13 | 14 | python_versions=(3.7 3.8 3.9) 15 | 16 | mkdir build 17 | cd build 18 | 19 | # Build for all specified python versions. 20 | # conda build ../../. --no-test 21 | for v in ${python_versions[@]}; do 22 | conda build --python $v ../../. --no-test 23 | done 24 | 25 | # Delete ouput_dir, if it exists, not to accidentally upload stuff. 26 | if [ -d /output_dir ]; then 27 | rm -rf /output_dir 28 | fi 29 | 30 | # Convert the package for all platforms if it is not already a noarch version. 31 | output_tar=$(conda build ../../. --output) 32 | if [[ $output_tar != *"noarch"* ]]; then 33 | conda convert --platform all $output_tar -o output_dir/ 34 | 35 | # Upload platformwise packages to anaconda cloud 36 | for arch_dir in output_dir/*; do 37 | for arch_tar in $arch_dir/*.tar.bz2; do 38 | anaconda upload $arch_tar 39 | done 40 | done 41 | fi 42 | 43 | # clean the tars created 44 | conda build purge 45 | # Remove the build folder 46 | cd ../ 47 | rm -rf build/ 48 | echo "The package was successfully uploaded to the anaconda cloud." 49 | -------------------------------------------------------------------------------- /scripts/faster_timelapse: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # script: faster_timelapse 3 | # ------------------------ 4 | # This script uses ffmepg to read an .mp4 timelapse (first encountered) at the 5 | # current directory and creates a new one, that rans faster. It also outputs a 6 | # light gif. 7 | # 8 | # You can pass 1 argument at the range of 0.0 - 1.0, selecting the duration of 9 | # the new video as a portion of the duration of the old video (default: 0.6). 10 | # 11 | # examples: 12 | # $ source faster_timelapse 13 | # $ source faster_timelapse 0.5 14 | 15 | # check if an argument is passed: portion of previous video duration (0.0 - 1.0) 16 | if [ $# -eq 0 ]; then 17 | # default 18 | portion=0.6 19 | else 20 | portion=$1 21 | fi 22 | 23 | # get file name 24 | mp4s=( ./*.mp4 ) 25 | old_file_name="${mp4s[0]}" 26 | 27 | # check if there are any .mp4's 28 | if [ ${mp4s[0]: -5} == "*.mp4" ]; then 29 | echo "No .mp4's here" 30 | fi 31 | 32 | # get file name without extension and its extension 33 | filename="${old_file_name%.*}" 34 | extension="${old_file_name##*.}" 35 | 36 | # create new file name 37 | faster="_faster" 38 | new_file_name="$filename$faster.$extension" 39 | 40 | # create gif name 41 | gif_name="$filename$faster.gif" 42 | 43 | # create faster timelapse with the same quality 44 | ffmpeg -i "$old_file_name" -filter:v "setpts=${portion}*PTS" -q 1 -loglevel panic \ 45 | "$new_file_name" 46 | 47 | # create gif 48 | ffmpeg -i "$new_file_name" -vf \ 49 | "scale=320:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" \ 50 | -loop 0 -loglevel panic "$gif_name" 51 | -------------------------------------------------------------------------------- /scripts/pypi_to_conda: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # script: pypi_to_conda 3 | # ------------------------ 4 | # Uploads a package to the anaconda cloud, if it is already on pypi.org. 5 | # 6 | # You can pass 1 argument with the name of the package, as it is on pypi.org. 7 | # 8 | # How to install prerequisites: 9 | # $ conda install conda-build anaconda-client -y 10 | # 11 | # Examples: 12 | # $ anaconda login 13 | # $ source pypi_to_conda 14 | # $ source pypi_to_conda 15 | # $ source pypi_to_conda " --version " 16 | if [ $# -eq 0 ]; then 17 | # default 18 | pkg="mattflow" 19 | else 20 | pkg=$1 21 | fi 22 | 23 | python_versions=(3.7 3.8) 24 | 25 | mkdir build 26 | cd build 27 | # Generates the meta.yml recipe, drawing all necessary info from the pypi repo. 28 | conda skeleton pypi $pkg 29 | 30 | # Build for all specified python versions. 31 | for v in ${python_versions[@]}; do 32 | conda build --python $v $pkg --no-test 33 | done 34 | 35 | # In case pkg string holds --version, keep only the package name. 36 | pkg=$(echo $pkg | head -n1 | cut -d " " -f1) 37 | 38 | # Delete ouput_dir, if it exists, not to accidentally upload stuff. 39 | if [ -d /output_dir ]; then 40 | rm -rf /output_dir 41 | fi 42 | 43 | # Convert the package for all platforms 44 | output_tar=$(conda build $pkg --no-test --output) 45 | conda convert --platform all $output_tar -o output_dir/ 46 | 47 | # Upload platformwise packages to the anaconda cloud 48 | for arch_dir in output_dir/*; do 49 | for arch_tar in $arch_dir/*.tar.bz2; do 50 | anaconda upload $arch_tar 51 | done 52 | done 53 | 54 | # Clean the tars created 55 | conda build purge 56 | # Remove the build folder 57 | cd ../ 58 | rm -rf build/ 59 | echo "Package $pkg was successfully uploaded to the anaconda cloud." 60 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from setuptools import setup, find_packages 3 | import os 4 | 5 | 6 | REQUIRED = ['click>=7.0', 7 | 'numba>=0.51.2', 8 | 'numpy>=1.18.5', 9 | 'matplotlib>=3.3.1', 10 | 'joblib>=0.13.2'] 11 | 12 | EXTRAS = {'write video': ['ffmpeg']} 13 | 14 | here = os.path.abspath(os.path.dirname(__file__)) 15 | 16 | # pull info from __init__.py 17 | about = {} 18 | with open(os.path.join(here, 'mattflow', '__init__.py'), 'r') as fr: 19 | exec(fr.read(), about) 20 | 21 | # assign long_description with the README.md content 22 | try: 23 | with open(os.path.join(here, 'README.md'), 'r') as fr: 24 | long_description = fr.read() 25 | except FileNotFoundError: 26 | long_description = about['__description__'] 27 | 28 | 29 | setup( 30 | name=about['__name__'], 31 | version=about['__version__'], 32 | author=about['__author__'], 33 | author_email=about['__author_email__'], 34 | license='GNU GPL3', 35 | description=about['__description__'], 36 | long_description=long_description, 37 | long_description_content_type='text/markdown', 38 | url=about['__url__'], 39 | packages=find_packages(exclude=["tests", "*.tests", "*.tests.*", "tests.*"]), 40 | install_requires=REQUIRED, 41 | extras_require=EXTRAS, 42 | include_package_data=True, 43 | classifiers=[ 44 | 'Programming Language :: Python :: 3', 45 | 'Natural Language :: English', 46 | 'License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)', 47 | 'Operating System :: OS Independent', 48 | ], 49 | entry_points={ 50 | "console_scripts": [ 51 | "mattflow=mattflow.__main__:main", 52 | ] 53 | }, 54 | ) 55 | -------------------------------------------------------------------------------- /mattflow/dat_writer.py: -------------------------------------------------------------------------------- 1 | # dat_writer.py is part of MattFlow 2 | # 3 | # MattFlow is free software; you may redistribute it and/or modify it 4 | # under the terms of the GNU General Public License as published by the 5 | # Free Software Foundation, either version 3 of the License, or (at your 6 | # option) any later version. You should have received a copy of the GNU 7 | # General Public License along with this program. If not, see 8 | # . 9 | # 10 | # (C) 2019 Athanasios Mattas 11 | # ====================================================================== 12 | """Saves the step-wise solution at a .dat file.""" 13 | 14 | from mattflow import config as conf, utils 15 | 16 | 17 | def write_dat(h_hist, time, it): 18 | """Writes the solution data into a dat file. 19 | 20 | Args: 21 | h_hist (array) : the 0th state variable, U[0, :, :] 22 | time (float) : current time 23 | it (int) : current iteration 24 | """ 25 | utils.child_dir("data_files") 26 | try: 27 | zeros_left = (4 - len(str(it))) * '0' 28 | file_name = './data_files/solution' + zeros_left + str(it) + '.dat' 29 | fw = open(file_name, 'w') 30 | fw.write('xCells: ' + str(conf.Nx) + '\n' 31 | + 'yCells: ' + str(conf.Ny) + '\n' 32 | + 'ghostCells: ' + str(conf.Ng) + '\n' 33 | + 'time: ' + "{0:.3f}".format(time) + '\n') 34 | 35 | for j in range(len(h_hist)): 36 | for i in range(len(h_hist[0])): 37 | # if-else used for comumn-wise alignment 38 | fw.write( 39 | ("{0:.15f}".format(conf.CX[i + conf.Ng]) 40 | if conf.CX[i + conf.Ng] < 0 41 | else ' ' + "{0:.15f}".format(conf.CX[i + conf.Ng])) + ' ' 42 | 43 | + ("{0:.15f}".format(conf.CY[j + conf.Ng]) 44 | if conf.CY[j + conf.Ng] < 0 45 | else ' ' + "{0:.15f}".format(conf.CY[j + conf.Ng])) + ' ' 46 | 47 | + ("{0:.15f}".format(h_hist[j, i]) 48 | if h_hist[j, i] < 0 49 | else ' ' + "{0:.15f}".format(h_hist[j, i])) + ' ' 50 | # In case the whole U (3D array) is passed and you need 51 | # to write all 3 state variables, use the following: 52 | # + ("{0:.15f}".format(U[0, j, i]) if U[0, j, i] < 0 \ 53 | # else ' ' + "{0:.15f}".format(U[0, j, i])) + ' ' 54 | # + ("{0:.15f}".format(U[1, j, i]) if U[1, j, i] < 0 \ 55 | # else ' ' + "{0:.15f}".format(U[1, j, i])) + ' ' 56 | # + ("{0:.15f}".format(U[2, j, i]) if U[2, j, i] < 0 \ 57 | # else ' ' + "{0:.15f}".format(U[2, j, i])) 58 | + '\n' 59 | ) 60 | fw.close() 61 | except OSError: 62 | print("Unable to create data file") 63 | -------------------------------------------------------------------------------- /mattflow/mattflow_cmaps.py: -------------------------------------------------------------------------------- 1 | # mattflow_cmaps.py is part of MattFlow 2 | # 3 | # MattFlow is free software; you may redistribute it and/or modify it 4 | # under the terms of the GNU General Public License as published by the 5 | # Free Software Foundation, either version 3 of the License, or (at your 6 | # option) any later version. You should have received a copy of the GNU 7 | # General Public License along with this program. If not, see 8 | # . 9 | # 10 | # (C) 2019 Athanasios Mattas 11 | # ====================================================================== 12 | """Creates some color maps (currently unused).""" 13 | 14 | import numpy as np 15 | 16 | from matplotlib.colors import ListedColormap, LinearSegmentedColormap 17 | 18 | 19 | # TODO add LightSource-shade 20 | # unfortunately, when using a cmap instead of a single color, shading is 21 | # disabled 22 | 23 | 24 | def deep_water_cmap(): 25 | """Creates a deep water color-map out of shades of blue, adding 26 | transparency to colors that lie at greater hights (water drops). 27 | """ 28 | # Deep water color-map (darker) 29 | water_colors = np.zeros((256, 4)) 30 | water_colors[:, 0] = np.append(np.zeros(255), [12], 0) / 255 # r 31 | water_colors[:, 1] = np.linspace(71, 164, 256) / 255 # g 32 | water_colors[:, 2] = np.linspace(114, 255, 256) / 255 # b 33 | # Make the last 120 (lighter) colors transparent (a water drop is 34 | # transparent). 35 | water_colors[:, 3] = np.append( 36 | np.ones(136), 37 | np.ones(120) * np.linspace(0.9, 0.3, 120) 38 | ) # a 39 | # Make the last 100 (lighter) colors an interpolation between indices 156 40 | # to 176. 41 | water_colors[156:, 1] \ 42 | = np.linspace(water_colors[156, 1], water_colors[176, 1], 100) 43 | matt_deepwater = ListedColormap(water_colors) 44 | ''' 45 | water_colors = np.array([[ 0, 71, 114, 255], 46 | ... 47 | [12 ,164, 255, 255]]) / 255 48 | ''' 49 | return matt_deepwater 50 | 51 | 52 | def shallow_water_cmap(): 53 | """Creates a shallow water color-map out of shades of blue, adding 54 | transparency to colors that lie at greater hights (water drops). 55 | """ 56 | # Shallow water color-map (lighter) 57 | water_colors = np.zeros((256, 4)) 58 | water_colors[:, 0] = np.append(np.zeros(255), [12], 0) / 255 # r 59 | water_colors[:, 1] = np.linspace(103, 164, 256) / 255 # g 60 | water_colors[:, 2] = np.linspace(165, 255, 256) / 255 # b 61 | # Make the last 120 (lighter) colors transparent (a water drop is 62 | # transparent). 63 | water_colors[:, 3] = np.append( 64 | np.ones(136), 65 | np.ones(120) * np.linspace(0.9, 0.3, 120) 66 | ) # a 67 | matt_shallowwater = ListedColormap(water_colors) 68 | ''' 69 | water_colors = np.array([[ 0, 119, 190, 255], 70 | ... 71 | [12, 164, 255, 255]]) / 255 72 | ''' 73 | return matt_shallowwater 74 | -------------------------------------------------------------------------------- /mattflow/bcmanager.py: -------------------------------------------------------------------------------- 1 | # bcmanager.py is part of MattFlow 2 | # 3 | # MattFlow is free software; you may redistribute it and/or modify it 4 | # under the terms of the GNU General Public License as published by the 5 | # Free Software Foundation, either version 3 of the License, or (at your 6 | # option) any later version. You should have received a copy of the GNU 7 | # General Public License along with this program. If not, see 8 | # . 9 | # 10 | # (C) 2019 Athanasios Mattas 11 | # ====================================================================== 12 | """Handles the boundary conditions.""" 13 | 14 | # x 15 | # 0 1 2 3 4 5 6 7 8 9 16 | # 0 G G G G G G G G G G 17 | # 1 G G G G G G G G G G 18 | # 2 G G - - - - - - G G 19 | # 3 G G - - - - - - G G 20 | # y 4 G G - - - - - - G G 21 | # 5 G G - - - - - - G G 22 | # 6 G G - - - - - - G G 23 | # 7 G G - - - - - - G G 24 | # 8 G G G G G G G G G G 25 | # 9 G G G G G G G G G G 26 | 27 | 28 | import numpy as np 29 | 30 | from mattflow import config as conf 31 | 32 | 33 | def update_ghost_cells(U): 34 | """Implements the boundary conditions. 35 | 36 | reflective boundary conditions: 37 | 38 | + vertical walls (x=X_MIN, x=X_max) 39 | - h : dh/dx = 0 (Neumann) h_0 = h_-1 (1 ghost cell) 40 | h_1 = h_-2 (2 ghost cells) 41 | 42 | - u : u = 0 (Dirichlet) u_0 = -u_-1 (1 ghost cell) 43 | u_1 = -u_-2 (2 ghost cells) 44 | 45 | - v : dv/dx = 0 (Neumann) v_0 = v_-1 (1 ghost cell) 46 | v_1 = v_-2 (2 ghost cells) 47 | 48 | + horizontal walls (y=Y_MIN, y=Y_max) 49 | - h : dh/dy = 0 (Neumann) h_0 = h_-1 (1 ghost cell) 50 | h_1 = h_-2 (2 ghost cells) 51 | 52 | - u : du/dy = 0 (Neumann) u_0 = u_-1 (1 ghost cell) 53 | u_1 = u_-2 (2 ghost cells) 54 | 55 | - v : v = 0 (Dirichlet) v_0 = -v_-1 (1 ghost cell) 56 | v_1 = -v_-2 (2 ghost cells) 57 | 58 | Args: 59 | U (3D array) : the state variables, populating a x,y grid 60 | 61 | Returns: 62 | U 63 | """ 64 | Nx = conf.Nx 65 | Ny = conf.Ny 66 | Ng = conf.Ng 67 | 68 | if conf.BOUNDARY_CONDITIONS == 'reflective': 69 | # left wall (0 <= x < Ng) 70 | U[0, :, :Ng] = np.flip(U[0, :, Ng: 2 * Ng], 1) 71 | U[1, :, :Ng] = - np.flip(U[1, :, Ng: 2 * Ng], 1) 72 | U[2, :, :Ng] = np.flip(U[2, :, Ng: 2 * Ng], 1) 73 | 74 | # right wall (Nx + Ng <= x < Nx + 2Ng) 75 | U[0, :, Nx + Ng: Nx + 2 * Ng] \ 76 | = np.flip(U[0, :, Nx: Nx + Ng], 1) 77 | U[1, :, Nx + Ng: Nx + 2 * Ng] \ 78 | = - np.flip(U[1, :, Nx: Nx + Ng], 1) 79 | U[2, :, Nx + Ng: Nx + 2 * Ng] \ 80 | = np.flip(U[2, :, Nx: Nx + Ng], 1) 81 | 82 | # top wall (0 <= y < Ng) 83 | U[0, :Ng, :] = np.flip(U[0, Ng: 2 * Ng, :], 0) 84 | U[1, :Ng, :] = np.flip(U[1, Ng: 2 * Ng, :], 0) 85 | U[2, :Ng, :] = - np.flip(U[2, Ng: 2 * Ng, :], 0) 86 | 87 | # bottom wall (Ny + Ng <= y < Ny + 2Ng) 88 | U[0, Ny + Ng: Ny + 2 * Ng, :] \ 89 | = np.flip(U[0, Ny: Ny + Ng, :], 0) 90 | U[1, Ny + Ng: Ny + 2 * Ng, :] \ 91 | = np.flip(U[1, Ny: Ny + Ng, :], 0) 92 | U[2, Ny + Ng: Ny + 2 * Ng, :] \ 93 | = - np.flip(U[2, Ny: Ny + Ng, :], 0) 94 | return U 95 | -------------------------------------------------------------------------------- /mattflow/__main__.py: -------------------------------------------------------------------------------- 1 | # __main__.py is part of MattFlow 2 | # 3 | # MattFlow is free software; you may redistribute it and/or modify it 4 | # under the terms of the GNU General Public License as published by the 5 | # Free Software Foundation, either version 3 of the License, or (at your 6 | # option) any later version. You should have received a copy of the GNU 7 | # General Public License along with this program. If not, see 8 | # . 9 | # 10 | # (C) 2019 Athanasios Mattas 11 | # ====================================================================== 12 | """Executes pre-processing, solution and post-processing.""" 13 | 14 | import os 15 | 16 | import click 17 | 18 | from mattflow import (config as conf, 19 | logger, 20 | mattflow_post, 21 | mattflow_solver, 22 | utils) 23 | from mattflow.utils import time_this 24 | 25 | 26 | def _configure(**kwargs): 27 | conf.MODE = kwargs.get("mode", "drops") 28 | conf.MAX_N_DROPS = kwargs.get("drops", 5) 29 | conf.MAX_ITERS = 90 * conf.MAX_N_DROPS + 150 30 | conf.PLOTTING_STYLE = kwargs.get("style", "wireframe") 31 | conf.ROTATION = kwargs.get("rotation", True) 32 | conf.SHOW_BASIN = kwargs.get("basin", False) 33 | conf.SHOW_ANIMATION = kwargs.get("show", True) 34 | conf.SAVE_ANIMATION = kwargs.get("save", False) 35 | conf.VID_FORMAT = kwargs.get("format", "mp4") 36 | conf.FPS = kwargs.get("fps", 18) 37 | conf.DPI = kwargs.get("dpi", 75) 38 | conf.FIG_HEIGHT = kwargs.get("fig_height", 18) 39 | 40 | if conf.SAVE_ANIMATION: 41 | save_dir = input("save directory: ") 42 | while not os.path.isdir(save_dir): 43 | save_dir = input("Director don't exist.\nsave directory: ") 44 | conf.SAVE_DIR = save_dir 45 | 46 | 47 | @click.command() 48 | @click.option('-m', "--mode", default="drops", show_default=True, 49 | type=click.Choice(["drop", "drops", "rain"], 50 | case_sensitive=False)) 51 | @click.option('-d', "--drops", type=click.INT, default=5, show_default=True, 52 | help="number of drops to generate") 53 | @click.option('-s', "--style", default="wireframe", show_default=True, 54 | type=click.Choice(["water", "contour", "wireframe"], 55 | case_sensitive=False)) 56 | @click.option("--rotation/--no-rotation", default=True, show_default=True, 57 | help="rotate the domain") 58 | @click.option('-b', "--basin", is_flag=True, help="render the fluid basin") 59 | @click.option("--show/--no-show", default=True, show_default=True) 60 | @click.option("--save", is_flag=True) 61 | @click.option("--format", default="mp4", show_default=True, 62 | type=click.Choice(["mp4", "gif"], case_sensitive=False)) 63 | @click.option("--fps", type=click.INT, default=18, show_default=True) 64 | @click.option("--dpi", type=click.INT, default=75, show_default=True) 65 | @click.option("--fig-height", type=click.INT, default=18, show_default=True, 66 | help="figure height (width is 1.618 * height)") 67 | @time_this 68 | def main(**kwargs): 69 | _configure(**kwargs) 70 | # Uncomment this to delete previous log, dat and png files (for debugging). 71 | # utils.delete_prev_runs_data() 72 | 73 | # Pre-processing (mesh construction) 74 | utils.preprocessing(kwargs.get("mode", "drops")) 75 | 76 | # Solution 77 | h_hist, t_hist, U_ds = mattflow_solver.simulate() 78 | 79 | # Post-processing 80 | mattflow_post.animate(h_hist, t_hist) 81 | 82 | 83 | if __name__ == "__main__": 84 | main() 85 | 86 | # Close the log file 87 | logger.close() 88 | -------------------------------------------------------------------------------- /mattflow/logger.py: -------------------------------------------------------------------------------- 1 | # logger.py is part of MattFlow 2 | # 3 | # MattFlow is free software; you may redistribute it and/or modify it 4 | # under the terms of the GNU General Public License as published by the 5 | # Free Software Foundation, either version 3 of the License, or (at your 6 | # option) any later version. You should have received a copy of the GNU 7 | # General Public License along with this program. If not, see 8 | # . 9 | # 10 | # (C) 2019 Athanasios Mattas 11 | # ====================================================================== 12 | """Handles the logging precess.""" 13 | 14 | from datetime import datetime, timedelta 15 | import os 16 | 17 | from mattflow import config as conf 18 | from mattflow import __version__, __author__, __license__ 19 | 20 | 21 | file_name = str(datetime.now())[:19] 22 | # Replace ':' with '-' for compatibility with windows file formating. 23 | file_name = file_name.replace(':', '-').replace(' ', '_') + ".log" 24 | version_msg = 'MattFlow v' + __version__ 25 | author_msg = 'Author: ' + __author__ + ', 2019' 26 | license_msg = __license__ 27 | 28 | 29 | def log(state): 30 | """Appends a state-message at a new line of the log file. 31 | 32 | If a log file does not exist, it creates one, printing the simulation info. 33 | 34 | Args: 35 | state (str) : the string to be logged 36 | """ 37 | if not conf.LOGGING_MODE: 38 | return 39 | open_log = find_open_log() 40 | 41 | if open_log: 42 | # Append the state. 43 | with open(open_log, 'a') as fa: 44 | fa.write(state + '\n') 45 | # Update log name with the current time. 46 | os.rename(open_log, file_name) 47 | else: 48 | fw = open(file_name, 'w') 49 | fw.write( 50 | version_msg + '\n' + author_msg + '\n' + license_msg + '\n' 51 | + len(license_msg) * '-' + '\n' 52 | + str(datetime.now())[:19] + '\n\n' 53 | + 'Configuration of the simulation' + '\n' 54 | + 31 * '-' + '\n' 55 | + 'Number of cells : ' + str(conf.Nx * conf.Ny) + '\n' 56 | + 'Number of ghost cells : ' + str(conf.Ng) + '\n' 57 | + 'Courant number : ' + str(conf.COURANT) + '\n' 58 | + 'Simulation mode : ' + str(conf.MODE) + '\n' 59 | + 'Boundary conditions : ' + str(conf.BOUNDARY_CONDITIONS) + '\n' 60 | + 'Solver type : ' + str(conf.SOLVER_TYPE) + '\n' 61 | + 'Plotting style : ' + str(conf.PLOTTING_STYLE) + '\n\n') 62 | 63 | fw.write(state + '\n') 64 | fw.close() 65 | 66 | 67 | def log_timestep(it, time): 68 | """Appends timestep info to the log file.""" 69 | if not conf.LOGGING_MODE: 70 | return 71 | if (it == 1) | (it % 15 == 0): 72 | log(" iter time") 73 | log(f"{it: >{6}d} {time:0.3f}") 74 | 75 | 76 | def find_open_log(): 77 | """Returns the 1st encountered open log file (False if there isn't one). 78 | 79 | An open log file is a mattflow log that does not end with '_done.log'. 80 | """ 81 | working_dir = os.getcwd() 82 | files_list = [] 83 | files_list = [f for f in os.listdir(working_dir) 84 | if f.endswith(".log") and is_open(f) and is_mattflow_log(f)] 85 | if files_list: 86 | return files_list[0] 87 | return False 88 | 89 | 90 | def close(): 91 | """Closes the log file, appending '_done' to the file name.""" 92 | if not conf.LOGGING_MODE: 93 | return 94 | try: 95 | open_log = find_open_log() 96 | if open_log: 97 | # append blank line 98 | with open(open_log, 'a') as fa: 99 | fa.write('\n') 100 | # append '_done' at the file name 101 | os.rename(open_log, file_name[:-4] + '_done' 102 | + file_name[-4:]) 103 | else: 104 | fw = open(file_name, 'w') 105 | fw.write('Trying to close a log file that does not exist...\n\n') 106 | os.rename(open_log, file_name[:-4] + '_errored' 107 | + file_name[-4:]) 108 | fw.close() 109 | except TypeError: 110 | fw = open(file_name, 'w') 111 | fw.write('Trying to close a log file that does not exist...\n\n') 112 | os.rename(open_log, file_name[:-4] + '_errored' 113 | + file_name[-4:]) 114 | fw.close() 115 | 116 | 117 | def is_open(log_file: str): 118 | """Checks whether a log file object is open or not.""" 119 | if isinstance(log_file, str) and log_file[-9:] != '_done.log': 120 | return True 121 | return False 122 | 123 | 124 | def is_mattflow_log(log_file: str): 125 | """Checks whether a log file was generated from MattFlow or not.""" 126 | with open(log_file, 'r') as fr: 127 | first_word = fr.read(8) 128 | if first_word == 'MattFlow': 129 | return True 130 | return False 131 | 132 | 133 | def log_duration(start, end, process): 134 | """Logs the duration of a process.""" 135 | process_name = { 136 | "main": "Total", 137 | "simulate": "Solution", 138 | "createAnimation": "Post-processing" 139 | } 140 | if process in process_name: 141 | process = process_name[process] 142 | prefix = f"{process.capitalize()} duration" 143 | duration = timedelta(seconds=end - start) 144 | log(f"{prefix:-<30}{duration}"[:40]) 145 | -------------------------------------------------------------------------------- /mattflow/config.py: -------------------------------------------------------------------------------- 1 | # config.py is part of MattFlow 2 | # 3 | # MattFlow is free software; you may redistribute it and/or modify it 4 | # under the terms of the GNU General Public License as published by the 5 | # Free Software Foundation, either version 3 of the License, or (at your 6 | # option) any later version. You should have received a copy of the GNU 7 | # General Public License along with this program. If not, see 8 | # . 9 | # 10 | # (C) 2019 Athanasios Mattas 11 | # ====================================================================== 12 | """Configures the simulation, providing some global constants.""" 13 | 14 | # TODO simple API: configuring options on a small window popup 15 | 16 | # x 17 | # 0 1 2 3 4 5 6 7 8 9 18 | # 0 G G G G G G G G G G 19 | # 1 G G G G G G G G G G 20 | # 2 G G - - - - - - G G 21 | # 3 G G - - - - - - G G 22 | # y 4 G G - - - - - - G G 23 | # 5 G G - - - - - - G G 24 | # 6 G G - - - - - - G G 25 | # 7 G G - - - - - - G G 26 | # 8 G G G G G G G G G G 27 | # 9 G G G G G G G G G G 28 | 29 | import os 30 | 31 | import numpy as np 32 | 33 | 34 | # Select whether a .log file will be generated or not. 35 | LOGGING_MODE = False 36 | DTYPE = np.dtype("float32") 37 | 38 | 39 | # Pre-processing configuration { 40 | # 41 | # Number of cells on x and y axis 42 | Nx = None 43 | Ny = None 44 | 45 | # Number of ghost cells (used for applying Boundary Conditions) 46 | # Depends on the used numerical scheme 47 | Ng = None 48 | 49 | # Domain (basin) limits 50 | MIN_X = None 51 | MAX_X = None 52 | MIN_Y = None 53 | MAX_Y = None 54 | 55 | # Spatial discretization steps (structured/Cartesian mesh) 56 | dx = None 57 | dy = None 58 | 59 | # Cell centers along the x and y axes 60 | CX = None 61 | CY = None 62 | # 63 | # } 64 | 65 | 66 | # Solution configuration { 67 | # 68 | # Ending conditions of the simulation 69 | STOPPING_TIME = 30000 70 | MAX_ITERS = None 71 | 72 | # Saving frames every iters. 73 | # - resulting to a lighter animation (less fps) 74 | # - visualizing and debugging long simulations 75 | # e.g. MAX_ITERS = 10000, FREQ = 500, FRAMES_PER_PERIOD = 25 76 | FRAME_SAVE_FREQ = 3 77 | FRAMES_PER_PERIOD = 1 78 | 79 | # Number of workers for multiprocessing 80 | WORKERS = 1 81 | 82 | # Pre-allocate and dump a binary memmap, used by all the workers. 83 | DUMP_MEMMAP = False 84 | MEMMAP_DIR = os.path.join(os.getcwd(), "flux_memmap") 85 | 86 | # Courant number 87 | # -------------- 88 | # dx * COURANT = 0.015 for a more realistic result, in the current fps range 89 | # 90 | # 91 | # dt_sim = dt * COURANT 92 | # dt_sim = dx / wave_vel * COURANT 93 | # dx * COURANT = dt_sim * wave_vel (= dx_sim?) 94 | # 95 | # where: 96 | # - dt is the time that the slowest wave component (represented by the 97 | # velocity eagenvector with the min eagenvalue at the jacobian of the 98 | # non-conservative form of the vectorized representation of the system) 99 | # takes to travel for dx. 100 | # - dt_sim is the time that the simulation covers dx 101 | # 102 | # More on mattflow_solver._dt() documentation. 103 | COURANT = None 104 | 105 | # Surface level 106 | SURFACE_LEVEL = 1 107 | 108 | # Simulation modes (initialization types) 109 | # --------------------------------------- 110 | # Supported: 111 | # 1. 'drop' : modeled via a gaussian distribution 112 | # 1. 'drops' : configure N_DROPS and ITERS_FOR_NEXT_DROP 113 | # 1. 'rain' : random drops 114 | MODE = 'drops' 115 | 116 | # Default max number of drops 117 | # (It will be overwritten if ITERS_BETWEEN_DROPS_MODE in ["random", "custom"].) 118 | MAX_N_DROPS = None 119 | 120 | # Number of iterations between drops 121 | # ---------------------------------- 122 | # Options: 123 | # 1. 'fixed' : fixed every 105 iters 124 | # 2. 'custom' : periodically selected from a list of 10 custom iter intervals 125 | # 3. 'random' : between 60 - 120 iters 126 | ITERS_BETWEEN_DROPS_MODE = "random" 127 | 128 | FIXED_ITERS_BETWEEN_DROPS = 105 129 | CUSTOM_ITERS_BETWEEN_DROPS = [120, 150, 140, 130, 210, 60, 180, 220, 140, 130] 130 | 131 | RANDOM_DROP_CENTERS = True 132 | # Dimensionless x, y drop centers 133 | DIMLESS_DCX = [0, -0.56, 0.28, -0.25, 0.48, -0.42, 0.90, 0.24, -0.78, 0.84] 134 | DIMLESS_DCY = [0, 0.25, 0.48, -0.24, -0.59, -0.64, 0.05, -0.40, 0.63, -0.40] 135 | DROPS_CX = None 136 | DROPS_CY = None 137 | 138 | # Boundary conditions 139 | # ------------------- 140 | # Supported: 141 | # 1. 'reflective' 142 | BOUNDARY_CONDITIONS = 'reflective' 143 | 144 | # Finite Volume Numerical Methods 145 | # ------------------------------- 146 | # Supported: 147 | # 1. 'Lax-Friedrichs Riemann' : 1st order in time: O(Δt, Δx^2, Δy^2) 148 | # 2. '2-stage Runge-Kutta' : 2nd order in time: O(Δt^2, Δx^2, Δy^2) 149 | # 3. 'MacCormack experimental' : 2nd order in time: O(Δt^2, Δx^2, Δy^2) 150 | SOLVER_TYPE = '2-stage Runge-Kutta' 151 | 152 | # Select whether to save a memmap with the simulation data or not (for ML). 153 | SAVE_DS_FOR_ML = False 154 | # 155 | # } 156 | 157 | 158 | # Post-processing configuration { 159 | # 160 | # Plotting style 161 | # Options: water, contour, wireframe 162 | PLOTTING_STYLE = None 163 | 164 | # frames per sec 165 | FPS = None 166 | # dots per inch 167 | DPI = None 168 | # figure size in inches 169 | FIG_HEIGHT = None 170 | 171 | # FIGSIZE = (FIG_HEIGHT * 1.618, FIG_HEIGHT) 172 | # resolution = figsize * dpi 173 | # -------------------------- 174 | # example: 175 | # figsize = (9.6, 5.4), dpi=200 176 | # resolution: 1920x1080 (1920/200=9.6) 177 | 178 | # Rotate the domain at each frame. 179 | ROTATION = True 180 | 181 | # Render the basin that contains the fluid. 182 | SHOW_BASIN = False 183 | 184 | # Show the animation. 185 | SHOW_ANIMATION = True 186 | 187 | # Save the animation. 188 | SAVE_ANIMATION = False 189 | SAVE_DIR = None 190 | 191 | # Path to ffmpeg 192 | PATH_TO_FFMPEG = '/usr/bin/ffmpeg' 193 | 194 | # Animation video format 195 | # ---------------------- 196 | # Supported: 197 | # 1. 'mp4' 198 | # 2. 'gif' 199 | VID_FORMAT = 'mp4' 200 | 201 | # Writing dat files mode 202 | # ---------------------- 203 | # Select whether dat files are generated or not. 204 | WRITE_DAT = False 205 | # 206 | # } 207 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MattFlow 2 | 3 | ![Conda] ![Build_Status] ![codecov] 4 | 5 |
6 | 7 | A CFD python package for the Shallow Water Equations 8 | 9 | MattFlow simulates the surface of the water after any initial conditions, such as drops or stones falling on. 10 | 11 | 12 | 13 | ___ 14 | 15 | | requirements | os | 16 | | -------------------- | --------- | 17 | | python3 | GNU/Linux | 18 | | click >= 7.0 | Windows | 19 | | joblib >= 0.13.2 | OSX | 20 | | matplotlib >= 3.3.1 | | 21 | | numba >= 0.51.2 | | 22 | | numpy >= 1.18.5 | | 23 | | ffmpeg (optional) | | 24 | 25 | ## Install 26 | 27 | ```bash 28 | $ conda create --name mattflow -y 29 | $ conda activate mattflow 30 | $ conda install -c mattasa mattflow 31 | ``` 32 | 33 | ```bash 34 | $ pip install mattflow 35 | ``` 36 | 37 | ## Usage 38 | 39 | ```bash 40 | $ mattflow [OPTIONS] 41 | ``` 42 | 43 | ```text 44 | Options: 45 | -m, --mode [drop|drops|rain] [default: drops] 46 | -d, --drops INTEGER number of drops to generate [default: 5] 47 | -s, --style [water|contour|wireframe] 48 | [default: wireframe] 49 | --rotation / --no-rotation rotate the domain [default: True] 50 | -b, --basin render the fluid basin 51 | --show / --no-show [default: True] 52 | --save 53 | --format [mp4|gif] [default: mp4] 54 | --fps INTEGER [default: 18] 55 | --dpi INTEGER [default: 75] 56 | --fig-height INTEGER figure height (width is 1.618 * height) 57 | [default: 18] 58 | --help Show this message and exit. 59 | ``` 60 | 61 | ## Shallow Water Equations 62 | 63 | SWE is a simplified CFD problem which models the surface of the water, with the assumption
64 | that the horizontal length scale is much greater than the vertical length scale. 65 | 66 | SWE is a coupled system of 3 hyperbolic partial differential equations, that derive from the
67 | conservation of mass and the conservation of linear momentum (Navier-Stokes) equations, in
68 | case of a horizontal stream bed, with no Coriolis, frictional or viscous forces ([wiki]). 69 | 70 | 71 | 72 | where:
73 | _η_ : height
74 | _u_ : velocity along the x axis
75 | _υ_ : velocity along the y axis
76 | _ρ_ : density
77 | _g_ : gravity acceleration 78 | 79 | ## Structure 80 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ThanasisMattas/mattflow/blob/master/notebooks/mattflow_notebook.ipynb) 81 | 82 | 1. pre-process
83 | structured/cartesian mesh 84 | 2. solution
85 | supported solvers: 86 | - [Lax-Friedrichs] Riemann 87 |    | O(Δt, Δx2, Δy2) 88 | - 2-stage [Runge-Kutta] 89 |         | O(Δt2, Δx2, Δy2) 90 |  | default 91 | - [MacCormack] 92 |           | O(Δt2, Δx2, Δy2) 93 |  | experimental 94 | 3. post-processing
95 | matplotlib animation 96 | 97 | ## Configuration options 98 | 99 | - mesh sizing 100 | - domain sizing 101 | - initial conditions (single drop, multiple drops, rain) 102 | - boundary conditions (currently: reflective) 103 | - solver 104 | - multiprocessing 105 | - plotting style 106 | - animation options 107 | 108 | ## TODO 109 | 110 | 1. GUI 111 | 2. Cython/C++ 112 | 3. Higher order schemes 113 | 4. Source terms 114 | 5. Viscous models 115 | 6. Algorithm that converts every computational second to a real-time second, 116 | modifying the fps at
the post-processing animation, because each 117 | iteration uses a different time-step (CFL condition). 118 | 7. Moving objects inside the domain 119 | 8. 3D 120 | 121 | 122 | ## License 123 | 124 | [GNU General Public License v3.0] 125 |
126 |
127 | 128 | Special thanks to [Marios Mitalidis] for the valuable feedback. 129 | 130 |
131 | 132 | ***Start the flow!*** 133 | 134 | 135 | >(C) 2019, Athanasios Mattas
136 | >thanasismatt@gmail.com 137 | 138 | [//]: # "links" 139 | 140 | [Conda]: 141 | [Build_Status]: 142 | [codecov]: 143 | [Lincense]: 144 | 145 | [wiki]: 146 | [Lax-Friedrichs]: 147 | [Runge-Kutta]: 148 | [Lax-Wendroff]: 149 | [MacCormack]: 150 | [GNU General Public License v3.0]: 151 | [Marios Mitalidis]: -------------------------------------------------------------------------------- /mattflow/utils.py: -------------------------------------------------------------------------------- 1 | # utils.py is part of MattFlow 2 | # 3 | # MattFlow is free software; you may redistribute it and/or modify it 4 | # under the terms of the GNU General Public License as published by the 5 | # Free Software Foundation, either version 3 of the License, or (at your 6 | # option) any later version. You should have received a copy of the GNU 7 | # General Public License along with this program. If not, see 8 | # . 9 | # 10 | # (C) 2019 Athanasios Mattas 11 | # ====================================================================== 12 | """Provides some helper functions.""" 13 | 14 | from datetime import timedelta 15 | from functools import wraps 16 | import os 17 | import random 18 | import shutil 19 | from timeit import default_timer as timer 20 | 21 | import numpy as np 22 | 23 | from mattflow import config as conf, logger 24 | 25 | 26 | def cell_centers(): 27 | """Generates the cell centers along the x and y axes.""" 28 | cx = np.arange(conf.MIN_X + (0.5 - conf.Ng) * conf.dx, 29 | conf.MAX_X + conf.Ng * conf.dx, 30 | conf.dx, 31 | dtype=conf.DTYPE) 32 | cy = np.arange(conf.MIN_Y + (0.5 - conf.Ng) * conf.dy, 33 | conf.MAX_Y + conf.Ng * conf.dy, 34 | conf.dy, 35 | dtype=conf.DTYPE) 36 | return cx, cy 37 | 38 | 39 | def delete_prev_runs_data(): # pragma: no cover 40 | """Deletes all the output files (log, dat, png etc) from previous runs.""" 41 | input("Deleting data from previous runs. Press ENTER to continue...") 42 | working_dir = os.getcwd() 43 | directories = [os.path.join(working_dir, "data_files/"), 44 | os.path.join(working_dir, "session/")] 45 | extensions = (".dat", ".log", ".png", ".gif", ".mp4", ".npy") 46 | for f in os.listdir(working_dir): 47 | if f.endswith(extensions): 48 | os.remove(f) 49 | for dir in directories: 50 | if os.path.isdir(dir): 51 | shutil.rmtree(dir) 52 | 53 | 54 | def U_shape(): 55 | return (3, conf.Nx + 2 * conf.Ng, conf.Ny + 2 * conf.Ng) 56 | 57 | 58 | def ds_shape(): # pragma: no cover 59 | return (conf.MAX_ITERS, 3, conf.Nx, conf.Ny) 60 | 61 | 62 | def delete_memmap(): # pragma: no cover 63 | try: 64 | shutil.rmtree(conf.MEMMAP_DIR) 65 | except FileNotFoundError: 66 | print("Could not clean-up the memmap folder.") 67 | 68 | 69 | def print_duration(start, end, process): 70 | """Prints the duration of a process.""" 71 | process_name = { 72 | "main": "Total", 73 | "simulate": "Solution", 74 | "animate": "Animating" 75 | } 76 | if process in process_name: 77 | process = process_name[process] 78 | prefix = f"{process.capitalize()} duration" 79 | duration = timedelta(seconds=end - start) 80 | print(f"{prefix:-<30}{duration}"[:40]) 81 | 82 | 83 | def child_dir(dirname): # pragma: no cover 84 | """Create a directory under the current working directory.""" 85 | try: 86 | if os.path.isdir(os.path.join(os.getcwd(), dirname)): 87 | pass 88 | else: 89 | os.mkdir(os.path.join(os.getcwd(), dirname)) 90 | except OSError: 91 | print("Unable to create ./" + dirname + " directory") 92 | 93 | 94 | def time_this(f): 95 | """function timer decorator 96 | 97 | - Uses wraps to preserve the metadata of the decorated function 98 | (__name__ and __doc__) 99 | - logs the duration 100 | - prints the duration 101 | 102 | Args: 103 | f(funtion) : the function to be decorated 104 | 105 | Returns: 106 | wrap (callable) : returns the result of the decorated function 107 | """ 108 | assert callable(f) 109 | 110 | @wraps(f) 111 | def wrap(*args, **kwargs): 112 | start = timer() 113 | result = f(*args, **kwargs) 114 | end = timer() 115 | logger.log_duration(start, end, f.__name__) 116 | print_duration(start, end, f.__name__) 117 | return result 118 | return wrap 119 | 120 | 121 | def preprocessing(mode, **kwargs): 122 | """constructs the mesh. 123 | 124 | Args: 125 | mode (str) : one of ["drop", "drops", "rain"] 126 | kwargs (dict) : domain dimensions 127 | 128 | - dx, dy: Spatial discretization steps (structured/Cartesian mesh) 129 | - cy, cy: Cell centers on x and y dimensions 130 | """ 131 | if mode in ["drop", "drops"]: 132 | N = kwargs.pop("N", 100) 133 | max_len = kwargs.pop("max_len", 0.7) 134 | elif mode == "rain": 135 | N = kwargs.pop("N", 200) 136 | max_len = kwargs.pop("max_len", 1.5) 137 | Ny = kwargs.pop("Ny", N) 138 | Nx = kwargs.pop("Nx", N) 139 | Ng = kwargs.pop("Ng", 1) 140 | max_x = kwargs.pop("max_x", max_len) 141 | min_x = kwargs.pop("min_x", -max_len) 142 | max_y = kwargs.pop("max_y", max_len) 143 | min_y = kwargs.pop("min_y", -max_len) 144 | 145 | conf.Nx = Nx 146 | conf.Ny = Ny 147 | conf.Ng = Ng 148 | conf.MAX_X = max_x 149 | conf.MIN_X = min_x 150 | conf.MAX_Y = max_y 151 | conf.MIN_Y = min_y 152 | conf.dx = (max_x - min_x) / Nx 153 | conf.dy = (max_y - min_y) / Ny 154 | conf.CX, conf.CY = cell_centers() 155 | conf.COURANT = min(0.9, 0.015 / min(conf.dx, conf.dy)) 156 | conf.DROPS_CX = [x * max_x for x in conf.DIMLESS_DCX] 157 | conf.DROPS_CY = [y * max_y for y in conf.DIMLESS_DCY] 158 | 159 | 160 | def drop_iters_list(): 161 | """list with the simulation iters at which a drop is going to fall.""" 162 | drop_iters = [0] 163 | iters_cumsum = 0 164 | i = 0 165 | if conf.ITERS_BETWEEN_DROPS_MODE == "custom": 166 | while iters_cumsum <= conf.MAX_ITERS: 167 | iters_cumsum += conf.CUSTOM_ITERS_BETWEEN_DROPS[i % 10] 168 | drop_iters.append(iters_cumsum) 169 | i += 1 170 | elif conf.ITERS_BETWEEN_DROPS_MODE == "random": 171 | while iters_cumsum <= conf.MAX_ITERS: 172 | iters_cumsum += random.randint(60, 120) 173 | drop_iters.append(iters_cumsum) 174 | elif conf.ITERS_BETWEEN_DROPS_MODE == "fixed": 175 | while iters_cumsum <= conf.MAX_ITERS: 176 | iters_cumsum += conf.FIXED_ITERS_BETWEEN_DROPS 177 | drop_iters.append(iters_cumsum) 178 | else: 179 | logger.log("Configure ITERS_BETWEEN_DROPS_MODE | options:" 180 | " 'fixed', 'custom', 'random'") 181 | 182 | # # Remove drop iterations that fall after MAX_ITERS. 183 | # last_drop_idx = np.searchsorted(drop_iters, conf.MAX_ITERS) 184 | # drop_iters = drop_iters[:last_drop_idx] 185 | # # Overwrite max number of drops. 186 | # conf.MAX_N_DROPS = len(drop_iters) 187 | 188 | if conf.SAVE_DS_FOR_ML: 189 | # It is needed to retrieve the new drop frames, because these frames 190 | # cannot be used as labels (the previous frame cannot know when and 191 | # where a new drop will fall). 192 | # ds_shape 193 | dss = ds_shape() 194 | file_name = f"drop_iters_list_{dss[0]}x{dss[1]}x{dss[2]}x{dss[3]}.npy" 195 | np.save(os.path.join(os.getcwd(), file_name), drop_iters) 196 | return drop_iters 197 | -------------------------------------------------------------------------------- /mattflow/initializer.py: -------------------------------------------------------------------------------- 1 | # initializer.py is part of MattFlow 2 | # 3 | # MattFlow is free software; you may redistribute it and/or modify it 4 | # under the terms of the GNU General Public License as published by the 5 | # Free Software Foundation, either version 3 of the License, or (at your 6 | # option) any later version. You should have received a copy of the GNU 7 | # General Public License along with this program. If not, see 8 | # . 9 | # 10 | # (C) 2019 Athanasios Mattas 11 | # ====================================================================== 12 | """Handles the initialization of the simulation.""" 13 | 14 | # x 15 | # 0 1 2 3 4 5 6 7 8 9 16 | # 0 G G G G G G G G G G 17 | # 1 G G G G G G G G G G 18 | # 2 G G - - - - - - G G 19 | # 3 G G - - - - - - G G 20 | # y 4 G G - - - - - - G G 21 | # 5 G G - - - - - - G G 22 | # 6 G G - - - - - - G G 23 | # 7 G G - - - - - - G G 24 | # 8 G G G G G G G G G G 25 | # 9 G G G G G G G G G G 26 | 27 | 28 | import os 29 | from random import randint, uniform 30 | 31 | import numpy as np 32 | from numpy.lib.format import open_memmap 33 | 34 | from mattflow import config as conf, dat_writer, logger, utils 35 | 36 | 37 | def _variance(): 38 | """Returns the drop-variance used at the different simulation modes. 39 | 40 | Use small variance (but > 0.0004) to make the distribution steep and sharp, 41 | for a better representation of a drop. 42 | """ 43 | variance = { 44 | "drop": randint(5, 8) / 10000, 45 | "drops": randint(5, 8) / 10000, 46 | "rain": 0.0002 47 | } 48 | return variance[conf.MODE] 49 | 50 | 51 | def _drop_heights_multiplier(): 52 | """Adjusts the size of the drop, regarding the simulation mode.""" 53 | # multiply with 4 / 3 for a small stone droping 54 | # with 1 / 4 for a water drop with a considerable momentum build 55 | # with 1 / 6 for a soft water drop 56 | if conf.MODE == 'drop' or conf.MODE == 'drops': 57 | factor = randint(6, 12) / 10 58 | elif conf.MODE == 'rain': 59 | factor = 1 / 6 60 | else: 61 | print("Configure MODE | options: 'drop', 'drops', 'rain'") 62 | return factor 63 | 64 | 65 | def _gaussian(variance, drops_count=None): 66 | '''Populates the mesh with a bivariate gaussian distribution of a certain 67 | variance. 68 | 69 | formula: amplitude * np.exp(-exponent) 70 | 71 | Args: 72 | variance (float) : target variance of the distribution 73 | drops_count(int) : drop counter 74 | 75 | Returs: 76 | gaussian_distribution (2D array) 77 | ''' 78 | # random pick of drop center coordinates 79 | # (mean or expectation of the gaussian distribution) 80 | if conf.RANDOM_DROP_CENTERS: 81 | drop_cx = uniform(conf.MIN_X, conf.MAX_X) 82 | drop_cy = uniform(conf.MIN_Y, conf.MAX_Y) 83 | else: 84 | drop_cx = conf.DROPS_CX[drops_count % 10] 85 | drop_cy = conf.DROPS_CY[drops_count % 10] 86 | 87 | # grid of the cell centers 88 | CX, CY = np.meshgrid(conf.CX, conf.CY) 89 | 90 | amplitude = 1 / np.sqrt(2 * np.pi * variance) 91 | exponent = \ 92 | ((CX - drop_cx)**2 + (CY - drop_cy)**2) / (2 * variance) 93 | 94 | gaussian_distribution = amplitude * np.exp(-exponent) 95 | return gaussian_distribution 96 | 97 | 98 | def _drop_heights_correction(drop_heights, divisor=2): 99 | """Subtracts the fluid volume that the drop adds to the domain. 100 | 101 | For a few thousands of iterations the fluid level rises quite subtly, but 102 | after a point the volume adds up to be significant. 103 | 104 | Args: 105 | drop_heights (2D array) : the gaussian distribution modeling the drop 106 | divisor (int) : divides the correction, resulting to smoother 107 | correction steps 108 | 109 | Returns: 110 | drop_correction (float) : the extra fluid volume of the drop, 111 | distributed to the whole domain, divided 112 | by a divisor for a smoother transition 113 | to the next time_step 114 | """ 115 | return drop_heights.sum() / drop_heights.size / divisor 116 | 117 | 118 | def drop(h_hist, drops_count=None): 119 | """Generates a drop. 120 | 121 | Drop is modeled as a bivariate gaussian distribution. 122 | 123 | Args: 124 | h_hist (array) : the 0th state variable, U[0, :, :] 125 | drops_count(int) : drop counter 126 | 127 | Returns: 128 | h_hist(2D array) : drop is added to the input h_hist 129 | """ 130 | variance = _variance() 131 | drop_heights = (_drop_heights_multiplier() 132 | * _gaussian(variance, drops_count)) 133 | drop_correction = _drop_heights_correction(drop_heights) 134 | h_hist += drop_heights - drop_correction 135 | return h_hist 136 | 137 | 138 | def _init_U(): 139 | """Creates and initializes the state-variables 3D matrix, U.""" 140 | cx = conf.CX 141 | cy = conf.CY 142 | U = np.zeros((utils.U_shape()), dtype=conf.DTYPE) 143 | # 1st drop 144 | U[0, :, :] = conf.SURFACE_LEVEL + drop(U[0, :, :], drops_count=1) 145 | # Write a .dat file (default: False) 146 | if conf.WRITE_DAT: 147 | dat_writer.writeDat(U[0, conf.Ng: -conf.Ng, conf.Ng: -conf.Ng], 148 | time=0, it=0) 149 | from mattflow import mattflow_post 150 | mattflow_post.plotFromDat(time=0, it=0) 151 | elif not conf.WRITE_DAT: 152 | pass 153 | else: 154 | print("Configure WRITE_DAT | Options: True, False") 155 | return U 156 | 157 | 158 | def _init_h_hist(U): 159 | """Creates and initializes h_hist, which holds the stepwise height data. 160 | 161 | - holds the states of the fluid for post-processing 162 | - saving frames every iters 163 | """ 164 | # Number of integer divisions with the freq, times the consecutive frames, 165 | # plus the consecutive frames that we can take from the remainder of the 166 | # division. 167 | num_states_to_save = ( 168 | conf.MAX_ITERS 169 | // conf.FRAME_SAVE_FREQ 170 | * conf.FRAMES_PER_PERIOD 171 | + min(conf.MAX_ITERS % conf.FRAME_SAVE_FREQ, conf.FRAMES_PER_PERIOD) 172 | ) 173 | h_hist = np.zeros((num_states_to_save, conf.Nx, conf.Ny), dtype=conf.DTYPE) 174 | h_hist[0] = U[0, conf.Ng: -conf.Ng, conf.Ng: -conf.Ng] 175 | return h_hist 176 | 177 | 178 | def _init_U_ds(U): # pragma: no cover 179 | """Creates and initializes U_ds, which holds stepwise data for ML.""" 180 | dss = utils.ds_shape() 181 | ds_name = f"mattflow_data_{dss[0]}x{dss[1]}x{dss[2]}x{dss[3]}.npy" 182 | U_ds = open_memmap(os.path.join(os.getcwd(), ds_name), 183 | mode='w+', 184 | dtype=conf.DTYPE, 185 | shape=dss) 186 | U_ds[0] = U[:, conf.Ng: - conf.Ng, conf.Ng: - conf.Ng] 187 | return U_ds 188 | 189 | 190 | def initialize(): 191 | """Wrapper that initializes all necessary data structures. 192 | 193 | Returns 194 | U (3D array) : the state-variables-3D-matrix (populating a x,y grid) 195 | - shape: (3, Nx + 2 * Ng, Ny + 2 * Ng) 196 | - U[0] : state varables [h, hu, hv] 197 | - U[1] : y dimention (rows) 198 | - U[2] : x dimention (columns) 199 | h_hist (array) : holds the step-wise height solutions for the 200 | post-processing animation 201 | t_hist (array) : holds the step-wise times for the post- 202 | processing animation 203 | U_ds (memmap) : holds the state-variables 3D matrix data for all 204 | the timesteps 205 | (conf.MAX_ITERS, 3, Nx + 2 * Ng, Ny + 2 * Ng) 206 | """ 207 | logger.log('Initialization...') 208 | 209 | U = _init_U() 210 | h_hist = _init_h_hist(U) 211 | t_hist = np.zeros(len(h_hist), dtype=conf.DTYPE) 212 | if conf.SAVE_DS_FOR_ML: 213 | U_ds = _init_U_ds(U) 214 | else: 215 | U_ds = None 216 | return U, h_hist, t_hist, U_ds 217 | -------------------------------------------------------------------------------- /mattflow/mattflow_solver.py: -------------------------------------------------------------------------------- 1 | # mattflow_solver.py is part of MattFlow 2 | # 3 | # MattFlow is free software; you may redistribute it and/or modify it 4 | # under the terms of the GNU General Public License as published by the 5 | # Free Software Foundation, either version 3 of the License, or (at your 6 | # option) any later version. You should have received a copy of the GNU 7 | # General Public License along with this program. If not, see 8 | # . 9 | # 10 | # (C) 2019 Athanasios Mattas 11 | # ====================================================================== 12 | """Handles the solution of the simulation.""" 13 | 14 | # TODO: implement high order schemes 15 | 16 | import random 17 | 18 | import numpy as np 19 | 20 | from mattflow import (bcmanager, 21 | config as conf, 22 | dat_writer, 23 | flux, 24 | initializer, 25 | logger, 26 | mattflow_post, 27 | utils) 28 | from mattflow.utils import time_this 29 | 30 | 31 | def _solve(U, 32 | delta_t, 33 | it, 34 | drops_count, 35 | drop_its_iterator, 36 | next_drop_it): 37 | """Evaluates the state variables (h, hu, hv) at a new time-step. 38 | 39 | It can be used in a for/while loop, iterating through each time-step. 40 | 41 | Args: 42 | U (3D array) : the state variables, populating a x,y grid 43 | delta_t (float) : time discretization step 44 | it (int) : current iteration 45 | drops_count (int) : number of drops been generated 46 | drop_its_iterator (iterator) 47 | : iterator of the drop_its list (the list with 48 | the iters at which a new drop falls) 49 | next_drop_it (int) : the next iteration at which a new drop will fall 50 | 51 | Returns: 52 | U, drops_count, drop_its_iterator, next_drop_it 53 | """ 54 | # Retrieve the mesh 55 | Ng = conf.Ng 56 | # cx = conf.CX 57 | # cy = conf.CY 58 | cellArea = conf.dx * conf.dy 59 | 60 | # Simulation mode 61 | # --------------- 62 | # 'drop': handled at the initialization 63 | if conf.MODE == 'drop': 64 | pass 65 | 66 | # 'drops': specified number of drops are generated at specified frequency 67 | elif conf.MODE == 'drops': 68 | if conf.ITERS_BETWEEN_DROPS_MODE == "fixed": 69 | drop_condition = ((it % conf.FIXED_ITERS_BETWEEN_DROPS == 0) 70 | and (drops_count < conf.MAX_N_DROPS)) 71 | else: # conf.ITERS_TO_NEXT_DROP_MODE in ["custom", "random"] 72 | drop_condition = ((it == next_drop_it) 73 | and (drops_count < conf.MAX_N_DROPS)) 74 | 75 | if drop_condition: 76 | U[0, :, :] = initializer.drop(U[0, :, :], drops_count + 1) 77 | drops_count += 1 78 | if ((conf.ITERS_BETWEEN_DROPS_MODE in ["custom", "random"]) 79 | and (drops_count < conf.MAX_N_DROPS)): 80 | next_drop_it = next(drop_its_iterator) 81 | 82 | # 'rain': random number of drops are generated at random frequency 83 | elif conf.MODE == 'rain': 84 | if it % random.randrange(1, 15) == 0: 85 | simultaneous_drops = range(random.randrange(1, 2)) 86 | for _ in simultaneous_drops: 87 | U[0, :, :] = initializer.drop(U[0, :, :]) 88 | else: 89 | modes = ['drop', 'drops', 'rain'] 90 | logger.log(f"Configure MODE | options: {modes}") 91 | 92 | # Numerical scheme 93 | # flux.flux() returns the total flux entering and leaving each cell. 94 | if conf.SOLVER_TYPE == 'Lax-Friedrichs Riemann': 95 | U[:, Ng: -Ng, Ng: -Ng] += delta_t / cellArea * flux.flux(U) 96 | elif conf.SOLVER_TYPE == '2-stage Runge-Kutta': 97 | # 1st stage 98 | U_pred = U 99 | U_pred[:, Ng: -Ng, Ng: -Ng] += delta_t / cellArea * flux.flux(U) 100 | 101 | # 2nd stage 102 | U[:, Ng: -Ng, Ng: -Ng] = \ 103 | 0.5 * (U[:, Ng: -Ng, Ng: -Ng] 104 | + U_pred[:, Ng: -Ng, Ng: -Ng] 105 | + delta_t / cellArea * flux.flux(U_pred)) 106 | else: 107 | solver_types = ['Lax-Friedrichs Riemann', '2-stage Runge-Kutta'] 108 | logger.log(f"Configure SOLVER_TYPE | Options: {solver_types}") 109 | return U, drops_count, drop_its_iterator, next_drop_it 110 | 111 | ''' 112 | # Experimenting on the finite differences form of the MacCormack solver. 113 | # TODO somewhere delta_t/dx becomes the greatest eigenvalue of the jacobian 114 | elif conf.SOLVER_TYPE == 'MacCormack experimental': 115 | # 1st step: prediction (FTFS) 116 | U_pred = U 117 | U_pred[:, Ng: -Ng, Ng: -Ng] = U[:, Ng: -Ng, Ng: -Ng] \ 118 | - delta_t / dx * (flux.F(U[:, Ng: -Ng, Ng + 1: Nx + Ng + 1]) \ 119 | - flux.F(U[:, Ng: -Ng, Ng: -Ng])) \ 120 | - delta_t / dy * (flux.G(U[:, Ng + 1: Ny + Ng + 1, Ng: -Ng]) \ 121 | - flux.G(U[:, Ng: -Ng, Ng: -Ng])) 122 | 123 | U_pred = bcmanager.updateGhostCells(U_pred) 124 | delta_t = _dt(U_pred, dx, dy) 125 | 126 | # 2nd step: correction (BTBS) 127 | U[:, Ng: -Ng, Ng: -Ng] \ 128 | = 0.5 * (U[:, Ng: -Ng, Ng: -Ng] + U_pred[:, Ng: -Ng, Ng: -Ng]) \ 129 | - 0.5 * delta_t / dx * (flux.F(U_pred[:, Ng: -Ng, Ng: -Ng]) \ 130 | - flux.F(U_pred[:, Ng: -Ng, Ng - 1: Nx + Ng - 1])) \ 131 | - 0.5 * delta_t / dy * (flux.G(U_pred[:, Ng: -Ng, Ng: -Ng]) \ 132 | - flux.G(U_pred[:, Ng - 1: Ny + Ng - 1, Ng: -Ng])) 133 | ''' 134 | 135 | 136 | def _dt(U, epsilon=1e-4): 137 | """Evaluates the time discretization step of the current iteration. 138 | 139 | The stability condition of the numerical simulation (Known as Courant- 140 | Friedrichs-Lewy or CFL condition) describes that the solution velocity 141 | (dx/dt) has to be greater than the wave velocity. Namely, the simulation 142 | has to run faster than the information, in order to evaluate it. The wave 143 | velocity used is the greatest velocity of the waves that contribute to the 144 | fluid, which is the greatest eagenvalue of the Jacobian matrix df(U)/dU, 145 | along the x axis, and dG(U)/dU, along the y axis, |u| + c and |v| + c, 146 | respectively. 147 | 148 | We equate 149 | 150 | dx/dt = wave_vel => dt = dx / wave_vel 151 | 152 | and the magnitude that the solution velocity is greater than the wave velo- 153 | city is handled by the Courant number (<= 1): 154 | 155 | dt_final = dt * Courant 156 | 157 | The velocity varies at each point of the grid, constituting the velocity 158 | field. So, dt is evaluated at every cell as the mean of the dt's at x and y 159 | directions. Finally, the minimum dt of all cells is returned as the time- 160 | step of the current iteration, ensuring that the CFL condition is met at 161 | all the cells. 162 | 163 | The velocity field is step-wisely changing and, thus, the calculation of dt 164 | is repeated at each iteration, preserving consistency with the CFL conditi- 165 | on. 166 | 167 | If the CFL condition is not met even at one cell, the simulation will not 168 | evaluate correctly the state variables there, resulting to discontinuities 169 | with the neighbor cells. These inconsistencies add up and bleed to nearby 170 | cells and, eventually the simulation (literally) blows up. 171 | 172 | Args: 173 | U (3D array) : the state variables, populating a x,y grid 174 | epsilon (float) : small number added to the denominator, to avoid 175 | dividing by zero (default: 1e-6) 176 | 177 | Returns: 178 | dt (float) : time discretization step 179 | """ 180 | h = U[0] 181 | u = U[1] / (h + epsilon) 182 | v = U[2] / (h + epsilon) 183 | g = 9.81 184 | c = np.sqrt(np.abs(g * h)) 185 | 186 | dt_x = conf.dx / (np.abs(u) + c + epsilon) 187 | dt_y = conf.dy / (np.abs(v) + c + epsilon) 188 | 189 | dt = 1.0 / (1.0 / dt_x + 1.0 / dt_y) 190 | dt_final = np.min(dt) * conf.COURANT 191 | return dt_final 192 | 193 | 194 | @time_this 195 | def simulate(): 196 | time = 0 197 | 198 | U, h_hist, t_hist, U_ds = initializer.initialize() 199 | drops_count = 1 200 | # idx of the frame saved in h_hist 201 | saving_frame_idx = 0 202 | # Counts up to conf.FRAMES_PER_PERIOD (1st frame saved at initialization). 203 | consecutive_frames_counter = 1 204 | 205 | if conf.ITERS_BETWEEN_DROPS_MODE in ["custom", "random"]: 206 | # List with the simulation iterations at which a drop is going to fall 207 | drop_its = utils.drop_iters_list() 208 | # Drop the 0th drop 209 | drop_its_iterator = iter(drop_its[1:]) 210 | # The iteration at which the next drop will fall 211 | try: 212 | next_drop_it = next(drop_its_iterator) 213 | except StopIteration: 214 | drop_its_iterator = None 215 | next_drop_it = None 216 | else: 217 | drop_its_iterator = None 218 | next_drop_it = None 219 | 220 | for it in range(1, conf.MAX_ITERS): 221 | 222 | # Time discretization step (CFL condition) 223 | delta_t = _dt(U) 224 | 225 | # Update current time 226 | time += delta_t 227 | if time > conf.STOPPING_TIME: 228 | break 229 | 230 | # Apply boundary conditions (reflective) 231 | U = bcmanager.update_ghost_cells(U) 232 | 233 | # Numerical iterative scheme 234 | U, drops_count, drop_its_iterator, next_drop_it = _solve( 235 | U=U, 236 | delta_t=delta_t, 237 | it=it, 238 | drops_count=drops_count, 239 | drop_its_iterator=drop_its_iterator, 240 | next_drop_it=next_drop_it 241 | ) 242 | 243 | if conf.WRITE_DAT: 244 | dat_writer.write_dat( 245 | U[0, conf.Ng: conf.Ny + conf.Ng, conf.Ng: conf.Nx + conf.Ng], 246 | time, it 247 | ) 248 | mattflow_post.plot_from_dat(time, it) 249 | elif not conf.WRITE_DAT: 250 | # Append current frame to the list, to be animated at 251 | # post-processing. 252 | if it % conf.FRAME_SAVE_FREQ == 0: 253 | # Zero the counter, when a perfect division occurs. 254 | consecutive_frames_counter = 0 255 | if consecutive_frames_counter < conf.FRAMES_PER_PERIOD: 256 | saving_frame_idx += 1 257 | h_hist[saving_frame_idx] = \ 258 | U[0, conf.Ng: -conf.Ng, conf.Ng: -conf.Ng] 259 | # time * 10 is insertd, because space is scaled about x10. 260 | t_hist[saving_frame_idx] = time * 10 261 | consecutive_frames_counter += 1 262 | if conf.SAVE_DS_FOR_ML: 263 | U_ds[it] = U[:, conf.Ng: -conf.Ng, conf.Ng: -conf.Ng] 264 | else: 265 | logger.log("Configure WRITE_DAT | Options: True, False") 266 | 267 | logger.log_timestep(it, time) 268 | 269 | # Clean-up the memmap 270 | if conf.DUMP_MEMMAP and conf.WORKERS > 1: 271 | utils.delete_memmap() 272 | 273 | return h_hist, t_hist, U_ds 274 | -------------------------------------------------------------------------------- /mattflow/flux.py: -------------------------------------------------------------------------------- 1 | # flux.py is part of MattFlow 2 | # 3 | # MattFlow is free software; you may redistribute it and/or modify it 4 | # under the terms of the GNU General Public License as published by the 5 | # Free Software Foundation, either version 3 of the License, or (at your 6 | # option) any later version. You should have received a copy of the GNU 7 | # General Public License along with this program. If not, see 8 | # . 9 | # 10 | # (C) 2019 Athanasios Mattas 11 | # ====================================================================== 12 | """Evaluates the total flux entering or leaving a cell.""" 13 | 14 | # x 15 | # 0 1 2 3 4 5 6 7 8 9 16 | # 0 G G G G G G G G G G 17 | # 1 G G G G G G G G G G 18 | # 2 G G - - - - - - G G 19 | # 3 G G - - - - - - G G 20 | # y 4 G G - - - - - - G G 21 | # 5 G G - - - - - - G G 22 | # 6 G G - - - - - - G G 23 | # 7 G G - - - - - - G G 24 | # 8 G G G G G G G G G G 25 | # 9 G G G G G G G G G G 26 | # 27 | # _ - - - _ 28 | # _ - - - _ 29 | # 30 | # example: 2 slices (workers) with window of 3, for parallel solving 31 | # (the 'underscore' cells are required by the numerical scheme) 32 | 33 | import os 34 | 35 | from joblib import Parallel, delayed 36 | import numba as nb 37 | import numpy as np 38 | 39 | from mattflow import config as conf 40 | 41 | 42 | @nb.njit(nb.f4()) 43 | def _g(): 44 | return 9.81 45 | 46 | 47 | @nb.njit(nb.f4[:, ::1](nb.f4[:, :, :], nb.i4, nb.i4, nb.b1), nogil=True) 48 | def _max_horizontal_speed(U, Nx, Ng, parallel=True): 49 | """Max horizontal speed between left and right cells for every vertical 50 | interface""" 51 | g = _g() 52 | if parallel: 53 | max_h_speed = np.maximum( 54 | # x dim slicing of left values: 0: -1 55 | np.abs(U[1, Ng: -Ng, 0: -1] / U[0, Ng: -Ng, 0: -1]) 56 | + np.sqrt(g * np.abs(U[0, Ng: -Ng, 0: -1])), 57 | 58 | # x dim slicing of right values: 1: 59 | np.abs(U[1, Ng: -Ng, 1:] / U[0, Ng: -Ng, 1:]) 60 | + np.sqrt(g * np.abs(U[0, Ng: -Ng, 1:])) 61 | ) 62 | else: 63 | max_h_speed = np.maximum( 64 | # x dim slicing of left values: Ng - 1: -Ng 65 | np.abs(U[1, Ng: -Ng, Ng - 1: -Ng] / U[0, Ng: -Ng, Ng - 1: -Ng]) 66 | + np.sqrt(g * np.abs(U[0, Ng: -Ng, Ng - 1: Nx + Ng])), 67 | 68 | # x dim slicing of right values: Ng: Nx + Ng + 1 69 | np.abs(U[1, Ng: -Ng, Ng: Nx + Ng + 1] 70 | / U[0, Ng: -Ng, Ng: Nx + Ng + 1]) 71 | + np.sqrt(g * np.abs(U[0, Ng: -Ng, Ng: Nx + Ng + 1])) 72 | ) 73 | return max_h_speed 74 | 75 | 76 | @nb.njit(nb.f4[:, ::1](nb.f4[:, :, :], nb.i4, nb.i4, nb.b1), nogil=True) 77 | def _max_vertical_speed(U, Ny, Ng, parallel=True): 78 | """Max vertical speed between top and bottom cells for every horizontal 79 | interface""" 80 | g = _g() 81 | if parallel: 82 | x_limit = 1 83 | else: 84 | x_limit = Ng 85 | 86 | max_v_speed = np.maximum( 87 | # y dim slicing of top values: Ng - 1: -Ng 88 | np.abs(U[1, Ng - 1: -Ng, x_limit: -x_limit] 89 | / U[0, Ng - 1: -Ng, x_limit: -x_limit]) 90 | + np.sqrt(g * np.abs(U[0, Ng - 1: -Ng, x_limit: -x_limit])), 91 | 92 | # y dim slicing of bottom values: Ng: Nx + Ng + 1 93 | np.abs(U[1, Ng: Ny + Ng + 1, x_limit: -x_limit] 94 | / U[0, Ng: Ny + Ng + 1, x_limit: -x_limit]) 95 | + np.sqrt(g * np.abs(U[0, Ng: Ny + Ng + 1, x_limit: -x_limit])) 96 | ) 97 | return max_v_speed 98 | 99 | 100 | def _F(U): 101 | """Evaluates the x-dimention-fluxes-vector, F. 102 | 103 | Args: 104 | U (3D array) : the state variables 3D matrix 105 | """ 106 | # h = U[0] 107 | # u = U[1] / h 108 | # v = U[2] / h 109 | 110 | # # 0.5 * g = 0.5 * 9.81 = 4.905 111 | # return np.array([h * u, h * u**2 + 4.905 * h**2, h * u * v]) 112 | return np.array([U[1], 113 | U[1]**2 / U[0] + 4.905 * U[0]**2, 114 | U[1] * U[2] / U[0]]) 115 | 116 | 117 | def _G(U): 118 | """Evaluates the y-dimention-fluxes-vector, G. 119 | 120 | Args: 121 | U (3D array) : the state variables 3D matrix 122 | """ 123 | # h = U[0] 124 | # u = U[1] / h 125 | # v = U[2] / h 126 | 127 | # # 0.5 * g = 0.5 * 9.81 = 4.905 128 | # return np.array([h * v, h * u * v, h * v**2 + 4.905 * h**2]) 129 | return np.array([U[2], 130 | U[1] * U[2] / U[0], 131 | U[2]**2 / U[0] + 4.905 * U[0]**2]) 132 | 133 | 134 | def _horizontal_flux(U, Nx, Ng, dy, maxHorizontalSpeed, parallel=True): 135 | """Lax-Friedrichs scheme (flux is calculated on each vertical interface) 136 | 137 | flux = 0.5 * (F_left + F_right) - 0.5 * maxSpeed * (U_right - U_left) 138 | """ 139 | if parallel: 140 | h_flux = ( 141 | 0.5 * dy 142 | * (_F(U[:, Ng: -Ng, 0: -1]) 143 | + _F(U[:, Ng: -Ng, 1:])) 144 | 145 | - 0.5 * dy * maxHorizontalSpeed 146 | * (U[:, Ng: -Ng, 1:] 147 | - U[:, Ng: -Ng, 0: -1]) 148 | ) 149 | else: 150 | h_flux = ( 151 | 0.5 * dy 152 | * (_F(U[:, Ng: -Ng, Ng - 1: -Ng]) 153 | + _F(U[:, Ng: -Ng, Ng: Nx + Ng + 1])) 154 | 155 | - 0.5 * dy * maxHorizontalSpeed 156 | * (U[:, Ng: -Ng, Ng: Nx + Ng + 1] 157 | - U[:, Ng: -Ng, Ng - 1: -Ng]) 158 | ) 159 | return h_flux 160 | 161 | 162 | def _vertical_flux(U, Ny, Ng, dx, maxVerticalSpeed, parallel=True): 163 | """Lax-Friedrichs scheme (flux is calculated on each horizontal interface) 164 | 165 | flux = 0.5 * (F_top + F_bottom) - 0.5 * maxSpeed * (U_bottom - U_top) 166 | """ 167 | if parallel: 168 | x_limit = 1 169 | else: 170 | x_limit = Ng 171 | 172 | v_flux = ( 173 | 0.5 * dx 174 | * (_G(U[:, Ng - 1: -Ng, x_limit: -x_limit]) 175 | + _G(U[:, Ng: Ny + Ng + 1, x_limit: -x_limit])) 176 | 177 | - 0.5 * dx * maxVerticalSpeed 178 | * (U[:, Ng: Ny + Ng + 1, x_limit: -x_limit] 179 | - U[:, Ng - 1: -Ng, x_limit: -x_limit]) 180 | ) 181 | return v_flux 182 | 183 | 184 | # @profile 185 | def _flux_batch(U, window=None, slicing_obj=None, domain_dims=None, 186 | flux_out=None, idx=None, parallel=True): 187 | """Evaluates the total flux that enters or leaves a cell, using the \ 188 | Lax-Friedrichs scheme. 189 | 190 | It runs on each joblib worker on a slice of the domain. 191 | (The horizontal axis, x, is sliced.) 192 | 193 | Args: 194 | U (3D array) : the state variables 3D matrix 195 | window (int) : the effective width of a slice 196 | (default None, in case of single-processing) 197 | slicing_obj (slice) : slice of the domain (1 + window + 1) 198 | (default None, in case of single-processing) 199 | flux_out (3D array) : container of the results of each worker 200 | (default None, in case of single-processing) 201 | idx (int) : the index in flux_out of the current results 202 | (default None, in case of single-processing) 203 | parallel (bool) : multi or single processing 204 | 205 | Returns: 206 | total_flux (3D array) 207 | """ 208 | Nx = domain_dims["Nx"] 209 | Ny = domain_dims["Ny"] 210 | Ng = domain_dims["Ng"] 211 | dx = domain_dims["dx"] 212 | dy = domain_dims["dy"] 213 | 214 | if parallel: 215 | x_limit = 1 216 | U_batch = U[:, :, slicing_obj] 217 | total_flux = np.zeros(((3, Ny + 2 * Ng, window + 2)), 218 | dtype=conf.DTYPE) 219 | else: 220 | x_limit = Ng 221 | U_batch = U 222 | total_flux = np.zeros(((3, Ny + 2 * Ng, Nx + 2 * Ng)), 223 | dtype=conf.DTYPE) 224 | 225 | # Vertical interfaces - Horizontal flux { 226 | # 227 | # Max horizontal speed between left and right cells for every interface 228 | maxHorizontalSpeed = _max_horizontal_speed(U_batch, Nx, Ng, 229 | parallel=parallel) 230 | 231 | # Lax-Friedrichs scheme 232 | # flux = 0.5 * (F_left + F_right) - 0.5 * maxSpeed * (U_right - U_left) 233 | # flux is calculated on each interface. 234 | horizontalFlux = _horizontal_flux(U_batch, Nx, Ng, dy, maxHorizontalSpeed, 235 | parallel=parallel) 236 | 237 | # horizontalFlux is subtracted from the left and added to the right cells. 238 | if parallel: 239 | total_flux[:, Ng: -Ng, 0: U_batch.shape[2] - 1] -= horizontalFlux 240 | total_flux[:, Ng: -Ng, 1: U_batch.shape[2]] += horizontalFlux 241 | else: 242 | total_flux[:, Ng: -Ng, Ng - 1: -Ng] -= horizontalFlux 243 | total_flux[:, Ng: -Ng, Ng: Nx + Ng + 1] += horizontalFlux 244 | # } 245 | 246 | # Horizontal interfaces - Vertical flux { 247 | # 248 | # Max vertical speed between top and bottom cells for every interface. 249 | # (for the vertical calculations the extra horizontal cells are not needed) 250 | maxVerticalSpeed = _max_vertical_speed(U_batch, Ny, Ng, parallel=parallel) 251 | 252 | # Lax-Friedrichs scheme 253 | # flux = 0.5 * (F_top + F_bottom) - 0.5 * maxSpeed * (U_bottom - U_top) 254 | verticalFlux = _vertical_flux(U_batch, Ny, Ng, dx, maxVerticalSpeed, 255 | parallel=parallel) 256 | 257 | # verticalFlux is subtracted from the top and added to the bottom cells. 258 | if parallel: 259 | total_flux[:, Ng - 1: -Ng, 1: U_batch.shape[2] - 1] -= verticalFlux 260 | total_flux[:, Ng: Ny + Ng + 1, 1: U_batch.shape[2] - 1] += verticalFlux 261 | else: 262 | total_flux[:, Ng - 1: -Ng, Ng: -Ng] -= verticalFlux 263 | total_flux[:, Ng: Ny + Ng + 1, Ng: -Ng] += verticalFlux 264 | # } 265 | 266 | # No need to keep ghost cells --> removes 2*(Nx + Ny) operations stepwise 267 | # Also, 1st and last nodes of each column are removed (they were only 268 | # needed from the numerical scheme, to calculate the other nodes). 269 | if flux_out is not None: 270 | flux_out[idx] = total_flux[:, Ng: -Ng, x_limit: -x_limit] 271 | else: 272 | return total_flux[:, Ng: -Ng, x_limit: -x_limit] 273 | 274 | 275 | def flux(U): 276 | """Evaluates the total flux that enters or leaves a cell, using the Lax- 277 | Friedrichs scheme. 278 | 279 | Args: 280 | U (3D array) : the state variables 3D matrix 281 | 282 | Returns: 283 | total_flux (3D array) 284 | """ 285 | Nx = conf.Nx 286 | Ny = conf.Ny 287 | Ng = conf.Ng 288 | workers = conf.WORKERS 289 | domain_dims = {} 290 | domain_dims["Nx"] = conf.Nx 291 | domain_dims["Ny"] = conf.Ny 292 | domain_dims["Ng"] = conf.Ng 293 | domain_dims["dx"] = conf.dx 294 | domain_dims["dy"] = conf.dy 295 | 296 | # Although joblib.Parallel can work single-processing, passing the whole 297 | # state-matrix directly to _flux_batch() is much faster. 298 | if workers == 1: 299 | return _flux_batch(U, parallel=False, domain_dims=domain_dims) 300 | 301 | # Slice the column dimention, x, to the number of workers. 302 | # (divide-ceil) 303 | window = -(-(Nx + 2 * Ng) // workers) 304 | 305 | # The extra cells at the two ends are required by the numerical scheme. 306 | # 307 | # Example: Ng = 2, window = 30: 308 | # slices = [slice(1, 33, slice(31, 63), slice(61, 63), ...] 309 | slices = [slice(start - 1, start + window + 1) 310 | for start in range(Ng, Nx + 2 * Ng, window)] 311 | 312 | if conf.DUMP_MEMMAP: 313 | # Pre-allocate a writeable shared memory map as a container for the 314 | # results of the parallel computation, shared by all the workers. 315 | try: 316 | os.mkdir(conf.MEMMAP_DIR) 317 | except FileExistsError: 318 | pass 319 | memmap_file = os.path.join(conf.MEMMAP_DIR, "flux_memmap") 320 | flux_out = np.memmap(memmap_file, dtype=np.dtype('float32'), 321 | shape=(len(slices), 3, Ny, window), 322 | mode="w+") 323 | 324 | Parallel(n_jobs=workers)( 325 | delayed(_flux_batch)(U, window, slicing_obj, flux_out, idx) 326 | for idx, slicing_obj in enumerate(slices) 327 | ) 328 | else: 329 | flux_out = np.zeros([len(slices), 3, Ny, window]) 330 | 331 | flux_out = Parallel(n_jobs=workers)( 332 | delayed(_flux_batch)(U, 333 | window, 334 | slicing_obj, 335 | domain_dims=domain_dims) 336 | for slicing_obj in slices 337 | ) 338 | 339 | total_flux = np.concatenate(flux_out, axis=2) 340 | 341 | # If last slice, appended some extra columns that must be left out. 342 | return total_flux[:, :, : Nx] 343 | -------------------------------------------------------------------------------- /mattflow/mattflow_post.py: -------------------------------------------------------------------------------- 1 | # mattflow_post.py is part of MattFlow 2 | # 3 | # MattFlow is free software; you may redistribute it and/or modify it 4 | # under the terms of the GNU General Public License as published by the 5 | # Free Software Foundation, either version 3 of the License, or (at your 6 | # option) any later version. You should have received a copy of the GNU 7 | # General Public License along with this program. If not, see 8 | # . 9 | # 10 | # (C) 2019 Athanasios Mattas 11 | # ====================================================================== 12 | """Handles the post-processing of the simulation.""" 13 | 14 | from datetime import datetime 15 | import os 16 | 17 | import numpy as np 18 | import matplotlib.animation as animation 19 | import matplotlib.pyplot as plt 20 | 21 | from mattflow import config as conf, logger, utils, __version__ 22 | from mattflow.utils import time_this 23 | 24 | 25 | def _plot_basin(sub): 26 | """Plots the basin that contains the fluid. 27 | 28 | Args: 29 | sub (axes.SubplotBase) : Axes3D subplot object 30 | """ 31 | if conf.SHOW_BASIN is True: 32 | # Make the basin a bit wider, because water appears to be out of the 33 | # basin because of the perspective mode. 34 | X_bas, Y_bas = np.meshgrid(conf.CX[conf.Ng - 1: conf.Nx + conf.Ng + 1], 35 | conf.CY[conf.Ng - 1: conf.Ny + conf.Ng + 1]) 36 | # BASIN 37 | BASIN = np.zeros((conf.Ny + 2, conf.Nx + 2)) 38 | # left-right walls 39 | BASIN[:, 0] = 2.5 40 | BASIN[:, conf.Nx + 1] = 2.5 41 | # top-bottom walls 42 | BASIN[0, :] = 2.5 43 | BASIN[conf.Ny + 1, :] = 2.5 44 | sub.plot_surface(X_bas, Y_bas, BASIN, rstride=2, cstride=2, linewidth=0, 45 | color=(0.4, 0.4, 0.5, 0.1)) 46 | elif conf.SHOW_BASIN is False: 47 | pass 48 | else: 49 | logger.log("Configure SHOW_BASIN. Options: True, False") 50 | 51 | 52 | def _data_from_dat(it): 53 | """Pulls solution data from a .dat file.""" 54 | zeros_left = (4 - len(str(it))) * '0' 55 | file_name = 'solution' + zeros_left + str(it) + '.dat' 56 | 57 | dat_path = os.path.join(os.getcwd(), "data_files", file_name) 58 | with open(dat_path, 'r') as fr: 59 | Nx = int(fr.readline().split(":")[1]) 60 | Ny = int(fr.readline().split(":")[1]) 61 | # Ng = int(fr.readline().split(":")[1]) 62 | time = float(fr.readline().split(":")[1]) 63 | 64 | # hu and hv are not written in the dat file, to reduce the overhead. 65 | # x, y, h, hu, hv = np.loadtxt('./data_files/' + file_name, skiprows = 4, 66 | # unpack = True) 67 | x, y, h = np.loadtxt('./data_files/' + file_name, skiprows=4, unpack=True) 68 | # Unpack the row-major vectors into matrices. 69 | X = x.reshape(Ny, Nx) 70 | Y = y.reshape(Ny, Nx) 71 | Z = h.reshape(Ny, Nx) 72 | return X, Y, Z, Nx, Ny 73 | 74 | 75 | def plot_from_dat(time, it): 76 | """Creates and saves a frame as .png, reading data from a .dat file. 77 | 78 | Args: 79 | time (float) : current time 80 | it (int) : current itereration 81 | """ 82 | # Create ./session directory for saving the results. 83 | utils.child_dir("session") 84 | 85 | # Extract data from dat. 86 | X, Y, Z, Nx, Ny = _data_from_dat(it) 87 | 88 | # plot { 89 | # 90 | fig = plt.figure(figsize=(9.6, 6.4), dpi=112) # 1080x720 91 | sub = fig.add_subplot(111, projection="3d") 92 | plt.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=0, hspace=0) 93 | 94 | if conf.PLOTTING_STYLE == 'water': 95 | if conf.ROTATION: 96 | # Azimuthal rotate every 8 frames and vetical every 20 frames 97 | azimuth = 45 + it / 8 98 | elevation = 55 - it / 20 99 | sub.view_init(elevation, azimuth) 100 | else: 101 | sub.view_init(45, 55) 102 | sub.plot_surface(X, Y, Z, 103 | rstride=1, cstride=1, linewidth=0, 104 | color=(0.251, 0.643, 0.875, 0.95), 105 | shade=True, antialiased=False) 106 | elif conf.PLOTTING_STYLE == 'contour': 107 | sub.view_init(45, 55) 108 | sub.contour3D(X, Y, Z, 140, cmap='plasma', vmin=0.6, vmax=1.4) 109 | elif conf.PLOTTING_STYLE == 'wireframe': 110 | if conf.ROTATION: 111 | # Azimuthal rotate every 3 frames and vetical every 4 frames 112 | azimuth = 45 + it / 3 113 | elevation = 55 - it / 4 114 | sub.view_init(elevation, azimuth) 115 | else: 116 | sub.view_init(45, 55) 117 | sub.plot_wireframe(X, Y, Z, rstride=2, cstride=2, linewidth=1,) 118 | else: 119 | styles = ['water', 'contour', 'wireframe'] 120 | logger.log(f"Configure PLOTTING_STYLE | options: {styles}") 121 | 122 | fig.gca().set_zlim([-0.5, 4]) 123 | plt.title(f"time: {time: >{6}.3f}\titer: {it: >{4}d}", y=0.8, fontsize=18) 124 | sub.title.set_position([0.51, 0.80]) 125 | plt.rcParams.update({'font.size': 20}) 126 | plt.axis('off') 127 | 128 | # Render the basin that contains the fluid. 129 | _plot_basin(sub) 130 | 131 | # save 132 | # fig.tight_layout() 133 | fig_file = os.path.join("session", f"iter_{it:0>{4}}.png") 134 | plt.savefig(fig_file) 135 | plt.close() 136 | # 137 | # } 138 | 139 | 140 | def _save_ani(ani): 141 | """Saves the animation in .mp4 and .gif formats. 142 | 143 | Args: 144 | ani (obj) : animation.FuncAnimation() object 145 | """ 146 | dpi = conf.DPI 147 | fps = conf.FPS 148 | # file name 149 | date_n_time = str(datetime.now())[:19] 150 | # Replace ':' with '-' for compatibility with windows file formating. 151 | date_n_time = date_n_time.replace(':', '-').replace(' ', '_') 152 | file_name = conf.MODE + '_animation_' + date_n_time 153 | 154 | # Configure the writer 155 | plt.rcParams['animation.ffmpeg_path'] = conf.PATH_TO_FFMPEG 156 | FFwriter = animation.FFMpegWriter( 157 | fps=fps, bitrate=-1, 158 | extra_args=['-r', str(fps), '-pix_fmt', 'yuv420p', '-vcodec', 159 | 'libx264', '-qscale:v', '1'] 160 | ) 161 | 162 | # Save 163 | try: 164 | ani.save(os.path.join(conf.SAVE_DIR, f"{file_name}.{conf.VID_FORMAT}"), 165 | writer=FFwriter, 166 | dpi=dpi) 167 | 168 | # log only if a log file is already initialzed 169 | if isinstance(logger.find_open_log(), str): 170 | logger.log(f'Animation saved as: {file_name}.' 171 | f'{conf.VID_FORMAT} | fps: {fps}') 172 | 173 | # convert to a lighter gif 174 | cmd = (f'ffmpeg -i {file_name}.{conf.VID_FORMAT} -vf ' 175 | f'"fps={fps},scale=240:-1:flags=lanczos,split' 176 | '[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -hide_banner' 177 | f' -loglevel panic -loop 0 {file_name}.gif') 178 | os.system(cmd) 179 | if isinstance(logger.find_open_log(), str): 180 | logger.log(f'Animation saved as: {file_name}.gif | fps: {fps}') 181 | except FileNotFoundError: 182 | logger.log('Configure PATH_TO_FFMPEG') 183 | 184 | 185 | def _update_plot(frame_number, X, Y, Z, plot, fig, sub, t_hist, ani_title): 186 | """Plots a single frame. 187 | 188 | It is used from FuncAnimation to iteratively create an animation. 189 | 190 | Args: 191 | frame_number (int) : current frame 192 | X, Y, Z (2D arrays) : meshgrid and values 193 | plot (list) : list holding current plot 194 | fig (figure) : activated plt.figure 195 | sub (subplot) : Axes3D subplot object 196 | t_hist (list) : holds the iter-wise times 197 | ani_title (str) : to be formated with the frame_number 198 | """ 199 | if conf.PLOTTING_STYLE == 'water': 200 | plot[0].remove() 201 | if conf.ROTATION: 202 | # Azimuthal rotate every 2 frames and vetical every 4 frames 203 | azimuth = 45 + frame_number / 2 204 | elevation = 55 - frame_number / 4 205 | sub.view_init(elevation, azimuth) 206 | plot[0] = sub.plot_surface(X, Y, Z[frame_number], 207 | rstride=1, cstride=1, linewidth=0, 208 | color=(0.251, 0.643, 0.875, 0.95), 209 | shade=True, antialiased=False) 210 | elif conf.PLOTTING_STYLE == 'contour': 211 | # Bliting with contour is not supported (because the corresponding 212 | # attributes are not artists), so the subplot has to be re-built. 213 | # That's why fig is passed (matplotlib==3.3.1). 214 | sub.clear() 215 | sub.view_init(45, 55) 216 | plt.subplots_adjust(left=0, bottom=0, right=1, top=1, 217 | wspace=0, hspace=0) 218 | fig.gca().set_zlim([-0.5, 4]) 219 | plt.axis('off') 220 | plot[0] = sub.contour3D(X, Y, Z[frame_number], 120, cmap='ocean', 221 | vmin=0.5, vmax=1.5) 222 | elif conf.PLOTTING_STYLE == 'wireframe': 223 | plot[0].remove() 224 | if conf.ROTATION: 225 | # Azimuthal rotate every 2 frames and vetical every 4 frames 226 | azimuth = 45 + frame_number / 2 227 | elevation = 55 - frame_number / 4 228 | sub.view_init(elevation, azimuth) 229 | plot[0] = sub.plot_wireframe(X, Y, Z[frame_number], 230 | rstride=2, cstride=2, linewidth=1) 231 | 232 | # Reverse engineer the iteration. 233 | it = ( 234 | frame_number 235 | + ((frame_number // conf.FRAMES_PER_PERIOD) 236 | * (conf.FRAME_SAVE_FREQ - conf.FRAMES_PER_PERIOD)) 237 | ) 238 | 239 | # Frame title 240 | if t_hist is None: 241 | ani_title = f"iter: {it:>{4}d}" 242 | else: 243 | ani_title = f"time: {t_hist[frame_number]:>{6}.3f} iter: {it:>{4}d}" 244 | plt.title(ani_title, y=0.8, fontsize=18) 245 | sub.title.set_position([0.51, 0.80]) 246 | 247 | 248 | @time_this 249 | def animate(h_hist, t_hist=None): 250 | """Generates and saves an animation of the simulation. 251 | 252 | Args: 253 | h_hist (array) : array of iter-wise heights solutions 254 | t_hist (array) : holds the iter-wise times 255 | 256 | Returns: 257 | ani (animation.FuncAnimation) : It is returned in case of ipython 258 | """ 259 | # resolution = figsize * dpi 260 | # -------------------------- 261 | # example: 262 | # figsize = (9.6, 5.4), dpi=200 263 | # resolution: 1920x1080 (1920/200=9.6) 264 | fps = conf.FPS 265 | dpi = conf.DPI 266 | figsize = (conf.FIG_HEIGHT * 1.618, conf.FIG_HEIGHT) 267 | 268 | # total frames 269 | frames = len(h_hist) 270 | 271 | # X, Y, Z 272 | X, Y = np.meshgrid(conf.CX[conf.Ng: -conf.Ng], conf.CY[conf.Ng: -conf.Ng]) 273 | Z = h_hist 274 | 275 | # Plot configuration 276 | fig = plt.figure(figsize=figsize, dpi=dpi) 277 | sub = fig.add_subplot(111, projection="3d") 278 | if conf.ROTATION: 279 | sub.view_init(45, 55) 280 | else: 281 | sub.view_init(30, 20) 282 | plt.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=0, hspace=0) 283 | fig.gca().set_zlim([-0.5, 4]) 284 | plt.axis('off') 285 | if t_hist is None: 286 | ani_title = f"iter: {0:>{5}d}" 287 | else: 288 | ani_title = f"time: {t_hist[0]:>{6}.3f} iter: {0:>{5}d}" 289 | plt.title(ani_title, y=0.8, fontsize=18) 290 | sub.title.set_position([0.51, 0.80]) 291 | plt.rcParams.update({'font.size': 20}) 292 | # Program name and version text 293 | fig.text(0.85, 0.06, s=f"MattFlow v{__version__}", fontsize=16, c='navy') 294 | 295 | # Plot initialization 296 | if conf.PLOTTING_STYLE == 'water': 297 | plot = [sub.plot_surface(X, Y, Z[0], 298 | rstride=1, cstride=1, linewidth=0, 299 | color=(0.251, 0.643, 0.875, 0.9), 300 | shade=True, antialiased=False)] 301 | elif conf.PLOTTING_STYLE == 'contour': 302 | plot = [sub.contour3D(X, Y, Z[0], 150, cmap='ocean', 303 | vmin=0.5, vmax=1.5)] 304 | elif conf.PLOTTING_STYLE == 'wireframe': 305 | plot = [sub.plot_wireframe(X, Y, Z[0], rstride=2, cstride=2, 306 | linewidth=1)] 307 | else: 308 | logger.log("Configure PLOTTING_STYLE | options: 'water', 'contour',", 309 | "'wireframe'") 310 | # Render the basin that contains the fluid. 311 | _plot_basin(sub) 312 | 313 | # Generate the animation. 314 | ani = animation.FuncAnimation( 315 | fig, _update_plot, frames, 316 | fargs=(X, Y, Z, plot, fig, sub, t_hist, ani_title), 317 | interval=1000 / fps, 318 | repeat=True 319 | ) 320 | 321 | # Save the animation. 322 | if conf.SAVE_ANIMATION: 323 | _save_ani(ani) 324 | 325 | # Play the animation. 326 | if conf.SHOW_ANIMATION is True: 327 | logger.log('Playing animation...') 328 | try: 329 | # In case of jupyter notebook, don't run plt.show(), to prevent 330 | # displaying a static figure. Instead, return the Funcanimation 331 | # object. 332 | get_ipython() # type: ignore 333 | return ani 334 | except NameError: 335 | plt.show() 336 | elif conf.SHOW_ANIMATION is False: 337 | pass 338 | else: 339 | logger.log("Configure SHOW_ANIMATION | Options: True, False") 340 | -------------------------------------------------------------------------------- /mattflow/mattflow_test.py: -------------------------------------------------------------------------------- 1 | # mattflow_test.py is part of MattFlow 2 | # 3 | # MattFlow is free software; you may redistribute it and/or modify it 4 | # under the terms of the GNU General Public License as published by the 5 | # Free Software Foundation, either version 3 of the License, or (at your 6 | # option) any later version. You should have received a copy of the GNU 7 | # General Public License along with this program. If not, see 8 | # . 9 | # 10 | # (C) 2019 Athanasios Mattas 11 | # ====================================================================== 12 | """Houses all the tests""" 13 | 14 | import time 15 | from unittest import mock 16 | 17 | import numpy as np 18 | from numpy.testing import assert_array_almost_equal 19 | import pytest 20 | 21 | from mattflow import (bcmanager, 22 | config as conf, 23 | initializer, 24 | mattflow_solver, 25 | utils) 26 | 27 | np.set_printoptions(suppress=True, formatter={"float": "{: 0.6f}".format}) 28 | 29 | 30 | @pytest.mark.parametrize("mode, factor", 31 | [("drop", 1.), ("rain", 1 / 6)]) 32 | @mock.patch("mattflow.initializer._variance", return_value=0.1) 33 | @mock.patch("mattflow.initializer.uniform", return_value=0) 34 | @mock.patch("mattflow.initializer.randint", return_value=10) 35 | class TestInitializer: 36 | """initializer.py tests""" 37 | 38 | def setup_method(self): 39 | conf.Nx = conf.Ny = 9 40 | conf.Ng = 1 41 | self.N = conf.Nx + 2 * conf.Ng 42 | self.old_mode = conf.MODE 43 | self.old_nx = conf.Nx 44 | self.old_ny = conf.Ny 45 | self.old_ng = conf.Ng 46 | self.h_history = np.ones((self.N, self.N)) 47 | 48 | conf.CX = np.linspace(-1, 1, self.N) 49 | conf.CY = np.linspace(-1, 1, self.N) 50 | 51 | conf.MAX_ITERS = 500 52 | conf.FRAME_SAVE_FREQ = 3 53 | conf.FRAMES_PER_PERIOD = 1 54 | 55 | # 11 x 11, variance = 0.1, center = (0, 0), {: 0.8f} 56 | self.gaussian = np.array( 57 | [[0.00005728, 0.00034649, 0.00140510, 0.00381946, 0.00695951, 0.00850037, 58 | 0.00695951, 0.00381946, 0.00140510, 0.00034649, 0.00005728], 59 | [0.00034649, 0.00209616, 0.00850037, 0.02310639, 0.04210259, 0.05142422, 60 | 0.04210259, 0.02310639, 0.00850037, 0.00209616, 0.00034649], 61 | [0.00140510, 0.00850037, 0.03447069, 0.09370104, 0.17073443, 0.20853550, 62 | 0.17073443, 0.09370104, 0.03447069, 0.00850037, 0.00140510], 63 | [0.00381946, 0.02310639, 0.09370104, 0.25470584, 0.46410429, 0.56685826, 64 | 0.46410429, 0.25470584, 0.09370104, 0.02310639, 0.00381946], 65 | [0.00695951, 0.04210259, 0.17073443, 0.46410429, 0.84565315, 1.03288309, 66 | 0.84565315, 0.46410429, 0.17073443, 0.04210259, 0.00695951], 67 | [0.00850037, 0.05142422, 0.20853550, 0.56685826, 1.03288309, 1.26156626, 68 | 1.03288309, 0.56685826, 0.20853550, 0.05142422, 0.00850037], 69 | [0.00695951, 0.04210259, 0.17073443, 0.46410429, 0.84565315, 1.03288309, 70 | 0.84565315, 0.46410429, 0.17073443, 0.04210259, 0.00695951], 71 | [0.00381946, 0.02310639, 0.09370104, 0.25470584, 0.46410429, 0.56685826, 72 | 0.46410429, 0.25470584, 0.09370104, 0.02310639, 0.00381946], 73 | [0.00140510, 0.00850037, 0.03447069, 0.09370104, 0.17073443, 0.20853550, 74 | 0.17073443, 0.09370104, 0.03447069, 0.00850037, 0.00140510], 75 | [0.00034649, 0.00209616, 0.00850037, 0.02310639, 0.04210259, 0.05142422, 76 | 0.04210259, 0.02310639, 0.00850037, 0.00209616, 0.00034649], 77 | [0.00005728, 0.00034649, 0.00140510, 0.00381946, 0.00695951, 0.00850037, 78 | 0.00695951, 0.00381946, 0.00140510, 0.00034649, 0.00005728]], 79 | dtype=conf.DTYPE 80 | ) 81 | 82 | def teardown_method(self): 83 | conf.MODE = self.old_mode 84 | conf.Nx = self.old_nx 85 | conf.Ny = self.old_ny 86 | conf.Ng = self.old_ng 87 | del self.h_history 88 | del self.gaussian 89 | 90 | def test_drop(self, 91 | mock_randint, mock_uniform, mock_variance, 92 | mode, factor): 93 | conf.MODE = mode 94 | 95 | drop_heights_expected = factor * self.gaussian 96 | drop_correction_expected = \ 97 | drop_heights_expected.sum() / drop_heights_expected.size / 2 98 | drop_expected = \ 99 | self.h_history + drop_heights_expected - drop_correction_expected 100 | drop_ = initializer.drop(self.h_history) 101 | assert_array_almost_equal(drop_, drop_expected, decimal=6) 102 | 103 | def test_initialize(self, 104 | mock_randint, mock_uniform, mock_variance, 105 | mode, factor): 106 | conf.MODE = mode 107 | 108 | # U 109 | drop_heights_expected = factor * self.gaussian 110 | drop_correction_expected = \ 111 | drop_heights_expected.sum() / drop_heights_expected.size / 2 112 | U_expected = np.zeros((3, self.N, self.N), dtype=conf.DTYPE) 113 | U_expected[0, :, :] = \ 114 | conf.SURFACE_LEVEL + drop_heights_expected - drop_correction_expected 115 | 116 | # h_hist 117 | num_states = 167 118 | h_hist_expected = np.zeros((num_states, conf.Nx, conf.Ny), 119 | dtype=conf.DTYPE) 120 | h_hist_expected[0] = U_expected[0, conf.Ng: - conf.Ng, conf.Ng: -conf.Ng] 121 | 122 | # t_hist 123 | t_hist_expected = np.zeros(len(h_hist_expected), dtype=conf.DTYPE) 124 | U_, h_hist, t_hist, _ = initializer.initialize() 125 | assert_array_almost_equal(U_, U_expected, decimal=6) 126 | assert_array_almost_equal(h_hist, h_hist_expected, decimal=6) 127 | assert_array_almost_equal(t_hist, t_hist_expected, decimal=6) 128 | 129 | 130 | class TestUtils(): 131 | """utils.py tests""" 132 | 133 | def setup_method(self): 134 | conf.MAX_ITERS = 550 135 | utils.preprocessing(mode="drops", max_len=0.1, N=9) 136 | 137 | def teardown_method(self): 138 | pass 139 | 140 | def test_cell_centers(self): 141 | cx_expected = cy_expected = np.array( 142 | [-0.1111111, -0.088889, -0.0666667, -0.0444444, 143 | -0.0222222, 2.3841858e-07, 0.0222222, 0.04444447, 144 | 0.06666670, 0.08888893, .1111112], 145 | dtype=conf.DTYPE 146 | ) 147 | 148 | cx, cy = utils.cell_centers() 149 | assert_array_almost_equal(cx, cx_expected) 150 | assert_array_almost_equal(cy, cy_expected) 151 | 152 | def test_time_this(self, capsys): 153 | 154 | @utils.time_this 155 | def sleep_for(secs: float): 156 | time.sleep(secs) 157 | 158 | sleep_for(0.1) 159 | captured = capsys.readouterr() 160 | expected_out = "Sleep_for duration------------0:00:00.10\n" 161 | assert captured.out == expected_out 162 | 163 | @pytest.mark.parametrize( 164 | "drop_iters_mode, drop_iters_expected", 165 | [("custom", [0, 120, 270, 410, 540, 750]), 166 | ("random", np.arange(0, 650, 100)), 167 | ("fixed", np.arange(0, 650, conf.FIXED_ITERS_BETWEEN_DROPS))] 168 | ) 169 | @mock.patch("mattflow.utils.random.randint", return_value=100) 170 | def test_drop_iters_list(self, mock_randint, drop_iters_mode, drop_iters_expected): 171 | conf.ITERS_BETWEEN_DROPS_MODE = drop_iters_mode 172 | drop_iters = utils.drop_iters_list() 173 | assert_array_almost_equal(drop_iters, drop_iters_expected) 174 | 175 | 176 | class TestBcmanager(): 177 | """bcmanager.py tests""" 178 | 179 | def setup_method(self): 180 | utils.preprocessing(mode="drops", max_len=0.1, N=5) 181 | self.U_ = np.array( 182 | [[[2.2657156, 2.2657156, 2.253398, 2.2343802, 183 | 2.21266, 2.1988487, 2.1988487], 184 | [2.2657156, 2.1992939, 2.1868532, 2.1697948, 185 | 2.1533852, 2.144444, 2.1988487], 186 | [2.2266014, 2.1764033, 2.1639748, 2.1462896, 187 | 2.128803, 2.1188493, 2.1579635], 188 | [2.135454, 2.121841, 2.1105056, 2.0922544, 189 | 2.0725465, 2.059794, 2.0652397], 190 | [1.9963144, 2.03645, 2.0284894, 2.011012, 191 | 1.9889272, 1.9717735, 1.9287541], 192 | [1.8737274, 1.9598783, 1.9562473, 1.9401051, 193 | 1.9164627, 1.895112, 1.8112943], 194 | [1.8737274, 1.8737274, 1.8757049, 1.8629198, 195 | 1.8371153, 1.8112943, 1.8112943]], 196 | 197 | [[-0.0386809, 0.0386809, 0.0595752, 0.10824296, 198 | 0.11708391, 0.03453719, -0.03453719], 199 | [-0.0386809, 0.04609778, 0.07631601, 0.12097389, 200 | 0.12148945, 0.03538336, -0.03453719], 201 | [-0.0217734, 0.03147381, 0.05890764, 0.10676077, 202 | 0.11549389, 0.04021141, -0.03914568], 203 | [0.00885441, 0.0029346, 0.02228841, 0.07638465, 204 | 0.10218683, 0.04805681, -0.04528352], 205 | [0.04344957, -0.03351239, -0.02361635, 0.03831967, 206 | 0.08548081, 0.05802898, -0.05164351], 207 | [0.06724989, -0.06239687, -0.05857066, 0.00910301, 208 | 0.07213843, 0.06605287, -0.05571622], 209 | [0.06724989, -0.06724989, -0.07572492, -0.0140384, 210 | 0.05140896, 0.05571622, -0.05571622]], 211 | 212 | [[-0.61991954, -0.61991954, -0.6143542, -0.60563844, 213 | -0.5964889, -0.59121823, -0.59121823], 214 | [0.61991954, 0.5917232, 0.5858375, 0.5782364, 215 | 0.57132876, 0.56843126, 0.59121823], 216 | [1.6210319, 1.5722902, 1.557693, 1.5367452, 217 | 1.5204405, 1.5126007, 1.5478977], 218 | [2.1372514, 2.137276, 2.1166012, 2.083918, 219 | 2.058113, 2.0434089, 2.0306606], 220 | [1.8609889, 1.9370515, 1.917395, 1.8849705, 221 | 1.8578848, 1.8398389, 1.760214], 222 | [0.7792303, 0.8336257, 0.8219294, 0.8069412, 223 | 0.7953553, 0.78840643, 0.73449546], 224 | [-0.7792303, -0.77923036, -0.77177703, -0.75726956, 225 | -0.7452003, -0.73449546, -0.73449546]]], 226 | dtype=conf.DTYPE 227 | ) 228 | 229 | def teardown_method(self): 230 | del self.U_ 231 | 232 | def test_update_ghost_cells(self): 233 | U_expected = np.array( 234 | [[[2.1992939, 2.1992939, 2.1868532, 2.1697948, 235 | 2.1533852, 2.1444440, 2.144444], 236 | [2.1992939, 2.1992939, 2.1868532, 2.1697948, 237 | 2.1533852, 2.1444440, 2.144444], 238 | [2.1764033, 2.1764033, 2.1639748, 2.1462896, 239 | 2.1288030, 2.1188493, 2.1188493], 240 | [2.1218410, 2.1218410, 2.1105056, 2.0922544, 241 | 2.0725465, 2.0597940, 2.059794], 242 | [2.0364500, 2.0364500, 2.0284894, 2.011012, 243 | 1.9889272, 1.9717735, 1.9717735], 244 | [1.9598783, 1.9598783, 1.9562473, 1.9401051, 245 | 1.9164627, 1.8951120, 1.895112], 246 | [1.9598783, 1.9598783, 1.9562473, 1.9401051, 247 | 1.9164627, 1.8951120, 1.895112]], 248 | 249 | [[-0.04609778, 0.04609778, 0.07631601, 0.12097389, 250 | 0.12148945, 0.03538336, -0.03538336], 251 | [-0.04609778, 0.04609778, 0.07631601, 0.12097389, 252 | 0.12148945, 0.03538336, -0.03538336], 253 | [-0.03147381, 0.03147381, 0.05890764, 0.10676077, 254 | 0.11549389, 0.04021141, -0.04021141], 255 | [-0.0029346, 0.00293460, 0.02228841, 0.07638465, 256 | 0.10218683, 0.04805681, -0.04805681], 257 | [0.03351239, -0.03351239, -0.02361635, 0.03831967, 258 | 0.08548081, 0.05802898, -0.05802898], 259 | [0.06239687, -0.06239687, -0.05857066, 0.00910301, 260 | 0.07213843, 0.06605287, -0.06605287], 261 | [0.06239687, -0.06239687, -0.05857066, 0.00910301, 262 | 0.07213843, 0.06605287, -0.06605287]], 263 | 264 | [[-0.5917232, -0.5917232, -0.5858375, -0.5782364, 265 | -0.57132876, -0.56843126, -0.56843126], 266 | [0.59172320, 0.59172320, 0.58583750, 0.5782364, 267 | 0.57132876, 0.56843126, 0.56843126], 268 | [1.5722902, 1.5722902, 1.557693, 1.5367452, 269 | 1.5204405, 1.5126007, 1.5126007], 270 | [2.1372760, 2.137276, 2.1166012, 2.083918, 271 | 2.0581130, 2.0434089, 2.0434089], 272 | [1.9370515, 1.9370515, 1.917395, 1.8849705, 273 | 1.8578848, 1.8398389, 1.8398389], 274 | [0.8336257, 0.8336257, 0.8219294, 0.8069412, 275 | 0.7953553, 0.78840643, 0.78840643], 276 | [-0.8336257, -0.8336257, -0.8219294, -0.8069412, 277 | -0.7953553, -0.78840643, -0.78840643]]], 278 | dtype=conf.DTYPE 279 | ) 280 | U_ = bcmanager.update_ghost_cells(self.U_) 281 | assert_array_almost_equal(U_, U_expected) 282 | 283 | 284 | class TestMattflowSolver(): 285 | """mattflow_solver.py tests""" 286 | 287 | def setup_method(self): 288 | conf.MAX_ITERS = 5 289 | utils.preprocessing(mode="drops", max_len=0.1, N=5) 290 | self.U_ = np.array( 291 | [[[2.2657156, 2.2657156, 2.2533980, 2.2343802, 292 | 2.2126600, 2.1988487, 2.1988487], 293 | [2.2657156, 2.1992939, 2.1868532, 2.1697948, 294 | 2.1533852, 2.1444440, 2.1988487], 295 | [2.2266014, 2.1764033, 2.1639748, 2.1462896, 296 | 2.1288030, 2.1188493, 2.1579635], 297 | [2.1354540, 2.1218410, 2.1105056, 2.0922544, 298 | 2.0725465, 2.0597940, 2.0652397], 299 | [1.9963144, 2.0364500, 2.0284894, 2.0110120, 300 | 1.9889272, 1.9717735, 1.9287541], 301 | [1.8737274, 1.9598783, 1.9562473, 1.9401051, 302 | 1.9164627, 1.8951120, 1.8112943], 303 | [1.8737274, 1.8737274, 1.8757049, 1.8629198, 304 | 1.8371153, 1.8112943, 1.8112943]], 305 | 306 | [[-0.0386809, 0.03868090, 0.05957520, 0.10824296, 307 | 0.11708391, 0.03453719, -0.03453719], 308 | [-0.0386809, 0.04609778, 0.07631601, 0.12097389, 309 | 0.12148945, 0.03538336, -0.03453719], 310 | [-0.0217734, 0.03147381, 0.05890764, 0.10676077, 311 | 0.11549389, 0.04021141, -0.03914568], 312 | [0.00885441, 0.00293460, 0.02228841, 0.07638465, 313 | 0.10218683, 0.04805681, -0.04528352], 314 | [0.04344957, -0.03351239, -0.02361635, 0.03831967, 315 | 0.08548081, 0.05802898, -0.05164351], 316 | [0.06724989, -0.06239687, -0.05857066, 0.00910301, 317 | 0.07213843, 0.06605287, -0.05571622], 318 | [0.06724989, -0.06724989, -0.07572492, -0.01403840, 319 | 0.05140896, 0.05571622, -0.05571622]], 320 | 321 | [[-0.61991954, -0.61991954, -0.61435420, -0.60563844, 322 | -0.5964889, -0.59121823, -0.59121823], 323 | [0.61991954, 0.59172320, 0.58583750, 0.5782364, 324 | 0.57132876, 0.56843126, 0.59121823], 325 | [1.62103190, 1.57229020, 1.55769300, 1.5367452, 326 | 1.52044050, 1.51260070, 1.54789770], 327 | [2.13725140, 2.13727600, 2.11660120, 2.083918, 328 | 2.05811300, 2.04340890, 2.03066060], 329 | [1.86098890, 1.93705150, 1.91739500, 1.8849705, 330 | 1.85788480, 1.83983890, 1.76021400], 331 | [0.77923036, 0.83362570, 0.82192940, 0.8069412, 332 | 0.79535530, 0.78840643, 0.73449546], 333 | [-0.77923036, -0.77923036, -0.77177703, -0.75726956, 334 | -0.74520030, -0.73449546, -0.73449546]]], 335 | dtype=conf.DTYPE 336 | ) 337 | 338 | def teardown_method(self): 339 | del self.U_ 340 | 341 | def test_dt(self, epsilon=1e-4): 342 | dt_expected = 0.001477 343 | dt = mattflow_solver._dt(self.U_, epsilon=epsilon) 344 | dt = round(dt * 1e6) / 1e6 345 | assert dt == dt_expected 346 | 347 | @pytest.mark.parametrize("workers", [1, 3]) 348 | def test_solve(self, workers): 349 | conf.RANDOM_DROP_CENTERS = False 350 | conf.WORKERS = workers 351 | dt = 0.001783 352 | it = 100 353 | 354 | drops_count = 1 355 | drop_its_iterator = iter([50, 100, 150]) 356 | next_drop_it = 105 357 | U_, dc, dii, ndi = mattflow_solver._solve(self.U_, 358 | dt, 359 | it, 360 | drops_count, 361 | drop_its_iterator, 362 | next_drop_it) 363 | U_expected = np.array( 364 | [[[2.265716, 2.265716, 2.253398, 2.234380, 365 | 2.212660, 2.198849, 2.198849], 366 | [2.265716, 2.142474, 2.121917, 2.106767, 367 | 2.095638, 2.097623, 2.198849], 368 | [2.226601, 2.125890, 2.106579, 2.090932, 369 | 2.079214, 2.079356, 2.157964], 370 | [2.135454, 2.105225, 2.090809, 2.073869, 371 | 2.059310, 2.052314, 2.065240], 372 | [1.996314, 2.073545, 2.067764, 2.049927, 373 | 2.031563, 2.013683, 1.928754], 374 | [1.873727, 2.033693, 2.036091, 2.018517, 375 | 1.996811, 1.969538, 1.811294], 376 | [1.873727, 1.873727, 1.875705, 1.862920, 377 | 1.837115, 1.811294, 1.811294]], 378 | 379 | [[-0.038681, 0.038681, 0.059575, 0.108243, 380 | 0.117084, 0.034537, -0.034537], 381 | [-0.038681, 0.096728, 0.093085, 0.128394, 382 | 0.117706, -0.001042, -0.034537], 383 | [-0.021773, 0.077391, 0.080878, 0.119748, 384 | 0.115628, 0.012494, -0.039146], 385 | [0.008854, 0.027160, 0.048449, 0.096730, 386 | 0.110042, 0.045107, -0.045284], 387 | [0.043450, -0.043857, 0.003202, 0.065059, 388 | 0.102794, 0.089755, -0.051644], 389 | [0.067250, -0.100397, -0.037687, 0.034823, 390 | 0.093436, 0.120846, -0.055716], 391 | [0.067250, -0.067250, -0.075725, -0.014038, 392 | 0.051409, 0.055716, -0.055716]], 393 | 394 | [[-0.619920, -0.619920, -0.614354, -0.605638, 395 | -0.596489, -0.591218, -0.591218], 396 | [0.619920, 0.594617, 0.586972, 0.579118, 397 | 0.571344, 0.569064, 0.591218], 398 | [1.621032, 1.497677, 1.475009, 1.458078, 399 | 1.448320, 1.452861, 1.547898], 400 | [2.137251, 2.076686, 2.051381, 2.023597, 401 | 2.006567, 2.002613, 2.030661], 402 | [1.860989, 1.941344, 1.924393, 1.894164, 403 | 1.873734, 1.858264, 1.760214], 404 | [0.779230, 0.906403, 0.898084, 0.882192, 405 | 0.873324, 0.864135, 0.734495], 406 | [-0.779230, -0.779230, -0.771777, -0.757270, 407 | -0.745200, -0.734495, -0.734495]]], 408 | dtype=conf.DTYPE 409 | ) 410 | assert_array_almost_equal(U_, U_expected) 411 | assert drops_count == dc 412 | assert dii == drop_its_iterator 413 | assert ndi == next_drop_it 414 | 415 | @pytest.mark.parametrize("drop_iters_mode", ["fixed", "custom"]) 416 | @mock.patch("mattflow.initializer._variance", return_value=0.1) 417 | @mock.patch("mattflow.initializer.uniform", return_value=0) 418 | @mock.patch("mattflow.initializer.randint", return_value=10) 419 | def test_simulate(self, mock_randint, mock_uniform, mock_variance, drop_iters_mode): 420 | """Can also be regarded as integration test.""" 421 | conf.RANDOM_DROP_CENTERS = False 422 | conf.ITERS_BETWEEN_DROPS_MODE = drop_iters_mode 423 | h_hist, t_hist, _ = mattflow_solver.simulate() 424 | h_hist_expected = np.array( 425 | [[[1.608689, 1.610595, 1.593548, 1.558355, 1.506661], 426 | [1.649861, 1.651833, 1.634196, 1.597786, 1.544305], 427 | [1.672231, 1.674239, 1.656282, 1.619211, 1.564758], 428 | [1.674742, 1.676754, 1.658760, 1.621615, 1.567053], 429 | [1.657273, 1.659257, 1.641514, 1.604885, 1.551082]], 430 | [[1.624658, 1.619051, 1.601293, 1.573089, 1.545764], 431 | [1.642868, 1.636539, 1.618507, 1.590551, 1.564204], 432 | [1.658777, 1.652079, 1.633820, 1.605770, 1.579657], 433 | [1.664113, 1.657498, 1.639163, 1.610902, 1.584465], 434 | [1.661089, 1.654912, 1.636628, 1.607942, 1.580575]]], 435 | dtype=conf.DTYPE 436 | ) 437 | t_hist_expected = np.array([0.000000, 0.055501]) 438 | assert_array_almost_equal(h_hist, h_hist_expected) 439 | assert_array_almost_equal(t_hist, t_hist_expected) 440 | -------------------------------------------------------------------------------- /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 | . -------------------------------------------------------------------------------- /bin/mattflow_profile.txt: -------------------------------------------------------------------------------- 1 | Solution duration-------------0:00:17.46 2 | Animating duration------------0:02:35.57 3 | Total duration----------------0:02:53.03 4 | Wrote profile results to __main__.py.lprof 5 | Timer unit: 1e-06 s 6 | 7 | Total time: 1.25051 s 8 | File: mattflow/bcmanager.py 9 | Function: update_ghost_cells at line 33 10 | 11 | Line # Hits Time Per Hit % Time Line Contents 12 | ============================================================== 13 | 33 @profile 14 | 34 def update_ghost_cells(U): 15 | 35 """Implements the boundary conditions. 16 | 36 17 | 37 reflective boundary conditions: 18 | 38 19 | 39 + vertical walls (x=X_MIN, x=X_max) 20 | 40 - h : dh/dx = 0 (Neumann) h_0 = h_-1 (1 ghost cell) 21 | 41 h_1 = h_-2 (2 ghost cells) 22 | 42 23 | 43 - u : u = 0 (Dirichlet) u_0 = -u_-1 (1 ghost cell) 24 | 44 u_1 = -u_-2 (2 ghost cells) 25 | 45 26 | 46 - v : dv/dx = 0 (Neumann) v_0 = v_-1 (1 ghost cell) 27 | 47 v_1 = v_-2 (2 ghost cells) 28 | 48 29 | 49 + horizontal walls (y=Y_MIN, y=Y_max) 30 | 50 - h : dh/dy = 0 (Neumann) h_0 = h_-1 (1 ghost cell) 31 | 51 h_1 = h_-2 (2 ghost cells) 32 | 52 33 | 53 - u : du/dy = 0 (Neumann) u_0 = u_-1 (1 ghost cell) 34 | 54 u_1 = u_-2 (2 ghost cells) 35 | 55 36 | 56 - v : v = 0 (Dirichlet) v_0 = -v_-1 (1 ghost cell) 37 | 57 v_1 = -v_-2 (2 ghost cells) 38 | 58 39 | 59 Args: 40 | 60 U (3D array) : the state variables, populating a x,y grid 41 | 61 42 | 62 Returns: 43 | 63 U 44 | 64 """ 45 | 65 9999 6260.0 0.6 0.5 if conf.BOUNDARY_CONDITIONS == 'reflective': 46 | 66 # left wall (0 <= x < Ng) 47 | 67 9999 181153.0 18.1 14.5 U[0, :, :conf.Ng] = np.flip(U[0, :, conf.Ng: 2 * conf.Ng], 1) 48 | 68 9999 140393.0 14.0 11.2 U[1, :, :conf.Ng] = - np.flip(U[1, :, conf.Ng: 2 * conf.Ng], 1) 49 | 69 9999 103415.0 10.3 8.3 U[2, :, :conf.Ng] = np.flip(U[2, :, conf.Ng: 2 * conf.Ng], 1) 50 | 70 51 | 71 # right wall (Nx + Ng <= x < Nx + 2Ng) 52 | 72 U[0, :, conf.Nx + conf.Ng: conf.Nx + 2 * conf.Ng] \ 53 | 73 9999 95443.0 9.5 7.6 = np.flip(U[0, :, conf.Nx: conf.Nx + conf.Ng], 1) 54 | 74 U[1, :, conf.Nx + conf.Ng: conf.Nx + 2 * conf.Ng] \ 55 | 75 9999 107004.0 10.7 8.6 = - np.flip(U[1, :, conf.Nx: conf.Nx + conf.Ng], 1) 56 | 76 U[2, :, conf.Nx + conf.Ng: conf.Nx + 2 * conf.Ng] \ 57 | 77 9999 94755.0 9.5 7.6 = np.flip(U[2, :, conf.Nx: conf.Nx + conf.Ng], 1) 58 | 78 59 | 79 # top wall (0 <= y < Ng) 60 | 80 9999 89410.0 8.9 7.1 U[0, :conf.Ng, :] = np.flip(U[0, conf.Ng: 2 * conf.Ng, :], 0) 61 | 81 9999 81884.0 8.2 6.5 U[1, :conf.Ng, :] = np.flip(U[1, conf.Ng: 2 * conf.Ng, :], 0) 62 | 82 9999 90867.0 9.1 7.3 U[2, :conf.Ng, :] = - np.flip(U[2, conf.Ng: 2 * conf.Ng, :], 0) 63 | 83 64 | 84 # bottom wall (Ny + Ng <= y < Ny + 2Ng) 65 | 85 U[0, conf.Ny + conf.Ng: conf.Ny + 2 * conf.Ng, :] \ 66 | 86 9999 85706.0 8.6 6.9 = np.flip(U[0, conf.Ny: conf.Ny + conf.Ng, :], 0) 67 | 87 U[1, conf.Ny + conf.Ng: conf.Ny + 2 * conf.Ng, :] \ 68 | 88 9999 82271.0 8.2 6.6 = np.flip(U[1, conf.Ny: conf.Ny + conf.Ng, :], 0) 69 | 89 U[2, conf.Ny + conf.Ng: conf.Ny + 2 * conf.Ng, :] \ 70 | 90 9999 88259.0 8.8 7.1 = - np.flip(U[2, conf.Ny: conf.Ny + conf.Ng, :], 0) 71 | 91 9999 3694.0 0.4 0.3 return U 72 | 73 | Total time: 3.31274 s 74 | File: mattflow/flux.py 75 | Function: _horizontal_flux at line 129 76 | 77 | Line # Hits Time Per Hit % Time Line Contents 78 | ============================================================== 79 | 129 @profile 80 | 130 def _horizontal_flux(U, Nx, Ng, dy, maxHorizontalSpeed, parallel=True): 81 | 131 """Lax-Friedrichs scheme (flux is calculated on each vertical interface) 82 | 132 83 | 133 flux = 0.5 * (F_left + F_right) - 0.5 * maxSpeed * (U_right - U_left) 84 | 134 """ 85 | 135 19998 8153.0 0.4 0.2 if parallel: 86 | 136 h_flux = ( 87 | 137 0.5 * (_F(U[:, Ng: -Ng, 0: -1]) + _F(U[:, Ng: -Ng, 1:])) 88 | 138 - 0.5 * maxHorizontalSpeed * (U[:, Ng: -Ng, 1:] 89 | 139 - U[:, Ng: -Ng, 0: -1]) 90 | 140 ) 91 | 141 else: 92 | 142 h_flux = ( 93 | 143 19998 890998.0 44.6 26.9 0.5 * (_F(U[:, Ng: -Ng, Ng - 1: -Ng]) 94 | 144 19998 1018923.0 51.0 30.8 + _F(U[:, Ng: -Ng, Ng: Nx + Ng + 1])) 95 | 145 19998 107749.0 5.4 3.3 - 0.5 * maxHorizontalSpeed * (U[:, Ng: -Ng, Ng: Nx + Ng + 1] 96 | 146 19998 1094995.0 54.8 33.1 - U[:, Ng: -Ng, Ng - 1: -Ng]) 97 | 147 ) 98 | 148 19998 184196.0 9.2 5.6 h_flux *= dy 99 | 149 19998 7730.0 0.4 0.2 return h_flux 100 | 101 | Total time: 3.04383 s 102 | File: mattflow/flux.py 103 | Function: _vertical_flux at line 152 104 | 105 | Line # Hits Time Per Hit % Time Line Contents 106 | ============================================================== 107 | 152 @profile 108 | 153 def _vertical_flux(U, Ny, Ng, dx, maxVerticalSpeed, parallel=True): 109 | 154 """Lax-Friedrichs scheme (flux is calculated on each horizontal interface) 110 | 155 111 | 156 flux = 0.5 * (F_top + F_bottom) - 0.5 * maxSpeed * (U_bottom - U_top) 112 | 157 """ 113 | 158 19998 9580.0 0.5 0.3 if parallel: 114 | 159 x_limit = 1 115 | 160 else: 116 | 161 19998 6695.0 0.3 0.2 x_limit = Ng 117 | 162 118 | 163 v_flux = ( 119 | 164 19998 839447.0 42.0 27.6 0.5 * dx * (_G(U[:, Ng - 1: -Ng, x_limit: -x_limit]) 120 | 165 19998 1000464.0 50.0 32.9 + _G(U[:, Ng: Ny + Ng + 1, x_limit: -x_limit])) 121 | 166 - 0.5 * dx * maxVerticalSpeed 122 | 167 19998 109233.0 5.5 3.6 * (U[:, Ng: Ny + Ng + 1, x_limit: -x_limit] 123 | 168 19998 1069564.0 53.5 35.1 - U[:, Ng - 1: -Ng, x_limit: -x_limit]) 124 | 169 ) 125 | 170 19998 8846.0 0.4 0.3 return v_flux 126 | 127 | Total time: 13.4497 s 128 | File: mattflow/flux.py 129 | Function: _flux_batch at line 173 130 | 131 | Line # Hits Time Per Hit % Time Line Contents 132 | ============================================================== 133 | 173 @profile 134 | 174 def _flux_batch(U, window=None, slicing_obj=None, 135 | 175 flux_out=None, idx=None, parallel=True): 136 | 176 """Evaluates the total flux that enters or leaves a cell, using the \ 137 | 177 Lax-Friedrichs scheme. 138 | 178 139 | 179 It runs on each joblib worker on a slice of the domain. 140 | 180 (The horizontal axis, x, is sliced.) 141 | 181 142 | 182 Args: 143 | 183 U (3D array) : the state variables 3D matrix 144 | 184 window (int) : the effective width of a slice 145 | 185 (default None, in case of single-processing) 146 | 186 slicing_obj (slice) : slice of the domain (1 + window + 1) 147 | 187 (default None, in case of single-processing) 148 | 188 flux_out (3D array) : container of the results of each worker 149 | 189 (default None, in case of single-processing) 150 | 190 idx (int) : the index in flux_out of the current results 151 | 191 (default None, in case of single-processing) 152 | 192 parallel (bool) : multi or single processing 153 | 193 154 | 194 Returns: 155 | 195 total_flux (3D array) 156 | 196 """ 157 | 197 19998 14516.0 0.7 0.1 Nx = conf.Nx 158 | 198 19998 10938.0 0.5 0.1 Ny = conf.Ny 159 | 199 19998 9753.0 0.5 0.1 Ng = conf.Ng 160 | 200 19998 9682.0 0.5 0.1 dx = conf.dx 161 | 201 19998 9633.0 0.5 0.1 dy = conf.dy 162 | 202 163 | 203 19998 9520.0 0.5 0.1 if parallel: 164 | 204 x_limit = 1 165 | 205 U_batch = U[:, :, slicing_obj] 166 | 206 total_flux = np.zeros(((3, Ny + 2 * Ng, window + 2)), dtype=conf.DTYPE) 167 | 207 else: 168 | 208 19998 9369.0 0.5 0.1 x_limit = Ng 169 | 209 19998 8821.0 0.4 0.1 U_batch = U 170 | 210 19998 101090.0 5.1 0.8 total_flux = np.zeros(((3, Ny + 2 * Ng, Nx + 2 * Ng)), dtype=conf.DTYPE) 171 | 211 172 | 212 # Vertical interfaces - Horizontal flux { 173 | 213 # 174 | 214 # Max horizontal speed between left and right cells for every interface 175 | 215 19998 11296.0 0.6 0.1 maxHorizontalSpeed = _max_horizontal_speed(U_batch, Nx, Ng, 176 | 216 19998 2492140.0 124.6 18.5 parallel=parallel) 177 | 217 178 | 218 # Lax-Friedrichs scheme 179 | 219 # flux = 0.5 * (F_left + F_right) - 0.5 * maxSpeed * (U_right - U_left) 180 | 220 # flux is calculated on each interface. 181 | 221 19998 12713.0 0.6 0.1 horizontalFlux = _horizontal_flux(U_batch, Nx, Ng, dy, maxHorizontalSpeed, 182 | 222 19998 3434421.0 171.7 25.5 parallel=parallel) 183 | 223 184 | 224 # horizontalFlux is subtracted from the left and added to the right cells. 185 | 225 19998 11697.0 0.6 0.1 if parallel: 186 | 226 total_flux[:, Ng: -Ng, 0: U_batch.shape[2] - 1] -= horizontalFlux 187 | 227 total_flux[:, Ng: -Ng, 1: U_batch.shape[2]] += horizontalFlux 188 | 228 else: 189 | 229 19998 582528.0 29.1 4.3 total_flux[:, Ng: -Ng, Ng - 1: -Ng] -= horizontalFlux 190 | 230 19998 551940.0 27.6 4.1 total_flux[:, Ng: -Ng, Ng: Nx + Ng + 1] += horizontalFlux 191 | 231 # } 192 | 232 193 | 233 # Horizontal interfaces - Vertical flux { 194 | 234 # 195 | 235 # Max vertical speed between top and bottom cells for every interface. 196 | 236 # (for the vertical calculations the extra horizontal cells are not needed) 197 | 237 19998 1877885.0 93.9 14.0 maxVerticalSpeed = _max_vertical_speed(U_batch, Ny, Ng, parallel=parallel) 198 | 238 199 | 239 # Lax-Friedrichs scheme 200 | 240 # flux = 0.5 * (F_top + F_bottom) - 0.5 * maxSpeed * (U_bottom - U_top) 201 | 241 19998 13627.0 0.7 0.1 verticalFlux = _vertical_flux(U_batch, Ny, Ng, dx, maxVerticalSpeed, 202 | 242 19998 3168367.0 158.4 23.6 parallel=parallel) 203 | 243 204 | 244 # verticalFlux is subtracted from the top and added to the bottom cells. 205 | 245 19998 11166.0 0.6 0.1 if parallel: 206 | 246 total_flux[:, Ng - 1: -Ng, 1: U_batch.shape[2] - 1] -= verticalFlux 207 | 247 total_flux[:, Ng: Ny + Ng + 1, 1: U_batch.shape[2] - 1] += verticalFlux 208 | 248 else: 209 | 249 19998 545833.0 27.3 4.1 total_flux[:, Ng - 1: -Ng, Ng: -Ng] -= verticalFlux 210 | 250 19998 519543.0 26.0 3.9 total_flux[:, Ng: Ny + Ng + 1, Ng: -Ng] += verticalFlux 211 | 251 # } 212 | 252 213 | 253 # No need to keep ghost cells --> removes 2*(Nx + Ny) operations stepwise 214 | 254 # Also, 1st and last nodes of each column are removed (they were only 215 | 255 # needed from the numerical scheme, to calculate the other nodes). 216 | 256 19998 14719.0 0.7 0.1 if flux_out is not None: 217 | 257 flux_out[idx] = total_flux[:, Ng: -Ng, x_limit: -x_limit] 218 | 258 else: 219 | 259 19998 18524.0 0.9 0.1 return total_flux[:, Ng: -Ng, x_limit: -x_limit] 220 | 221 | Total time: 0.015862 s 222 | File: mattflow/initializer.py 223 | Function: _gaussian at line 65 224 | 225 | Line # Hits Time Per Hit % Time Line Contents 226 | ============================================================== 227 | 65 @profile 228 | 66 def _gaussian(variance, drops_count): 229 | 67 '''Populates the mesh with a bivariate gaussian distribution of a certain 230 | 68 variance. 231 | 69 232 | 70 formula: amplitude * np.exp(-exponent) 233 | 71 234 | 72 Args: 235 | 73 variance (float) : target variance of the distribution 236 | 74 drops_count(int) : drop counter 237 | 75 238 | 76 Returs: 239 | 77 gaussian_distribution (2D array) 240 | 78 ''' 241 | 79 # random pick of drop center coordinates 242 | 80 # (mean or expectation of the gaussian distribution) 243 | 81 110 66.0 0.6 0.4 if conf.RANDOM_DROP_CENTERS: 244 | 82 110 220.0 2.0 1.4 drop_cx = uniform(conf.MIN_X, conf.MAX_X) 245 | 83 110 109.0 1.0 0.7 drop_cy = uniform(conf.MIN_Y, conf.MAX_Y) 246 | 84 else: 247 | 85 drop_cx = conf.DROPS_CX[drops_count % 10] 248 | 86 drop_cy = conf.DROPS_CY[drops_count % 10] 249 | 87 250 | 88 # grid of the cell centers 251 | 89 110 9659.0 87.8 60.9 CX, CY = np.meshgrid(conf.CX, conf.CY) 252 | 90 253 | 91 110 578.0 5.3 3.6 amplitude = 1 / np.sqrt(2 * np.pi * variance) 254 | 92 exponent = \ 255 | 93 110 2156.0 19.6 13.6 ((CX - drop_cx)**2 + (CY - drop_cy)**2) / (2 * variance) 256 | 94 257 | 95 110 3013.0 27.4 19.0 gaussian_distribution = amplitude * np.exp(-exponent) 258 | 96 110 61.0 0.6 0.4 return gaussian_distribution 259 | 260 | Total time: 0.023531 s 261 | File: mattflow/initializer.py 262 | Function: drop at line 121 263 | 264 | Line # Hits Time Per Hit % Time Line Contents 265 | ============================================================== 266 | 121 @profile 267 | 122 def drop(h_hist, drops_count=None): 268 | 123 """Generates a drop. 269 | 124 270 | 125 Drop is modeled as a bivariate gaussian distribution. 271 | 126 272 | 127 Args: 273 | 128 h_hist (array) : the 0th state variable, U[0, :, :] 274 | 129 drops_count(int) : drop counter 275 | 130 276 | 131 Returns: 277 | 132 h_hist(2D array) : drop is added to the input h_hist 278 | 133 """ 279 | 134 110 1832.0 16.7 7.8 variance = _variance() 280 | 135 110 18001.0 163.6 76.5 drop_heights = _drop_heights_multiplier() * _gaussian(variance, drops_count) 281 | 136 110 2828.0 25.7 12.0 drop_correction = _drop_heights_correction(drop_heights) 282 | 137 110 844.0 7.7 3.6 h_hist += drop_heights - drop_correction 283 | 138 110 26.0 0.2 0.1 return h_hist 284 | 285 | Total time: 0.000576 s 286 | File: mattflow/initializer.py 287 | Function: initialize at line 193 288 | 289 | Line # Hits Time Per Hit % Time Line Contents 290 | ============================================================== 291 | 193 @profile 292 | 194 def initialize(): 293 | 195 """Wrapper that initializes all necessary data structures. 294 | 196 295 | 197 Returns 296 | 198 U (3D array) : the state-variables-3D-matrix (populating a x,y grid) 297 | 199 - shape: (3, Nx + 2 * Ng, Ny + 2 * Ng) 298 | 200 - U[0] : state varables [h, hu, hv] 299 | 201 - U[1] : y dimention (rows) 300 | 202 - U[2] : x dimention (columns) 301 | 203 h_hist (array) : holds the step-wise height solutions for the 302 | 204 post-processing animation 303 | 205 t_hist (array) : holds the step-wise times for the post- 304 | 206 processing animation 305 | 207 U_ds (memmap) : holds the state-variables 3D matrix data for all 306 | 208 the timesteps 307 | 209 (conf.MAX_ITERS, 3, Nx + 2 * Ng, Ny + 2 * Ng) 308 | 210 """ 309 | 211 1 5.0 5.0 0.9 logger.log('Initialization...') 310 | 212 311 | 213 1 528.0 528.0 91.7 U = _init_U() 312 | 214 1 35.0 35.0 6.1 h_hist = _init_h_hist(U) 313 | 215 1 6.0 6.0 1.0 t_hist = t_hist = np.zeros(len(h_hist), dtype=conf.DTYPE) 314 | 216 1 0.0 0.0 0.0 if conf.SAVE_DS_FOR_ML: 315 | 217 U_ds = _init_U_ds(U) 316 | 218 else: 317 | 219 1 1.0 1.0 0.2 U_ds = None 318 | 220 1 1.0 1.0 0.2 return U, h_hist, t_hist, U_ds 319 | 320 | Total time: 155.395 s 321 | File: mattflow/mattflow_post.py 322 | Function: _save_ani at line 142 323 | 324 | Line # Hits Time Per Hit % Time Line Contents 325 | ============================================================== 326 | 142 @profile 327 | 143 def _save_ani(ani, fps, dpi): 328 | 144 """Saves the animation in .mp4 and .gif formats. 329 | 145 330 | 146 Args: 331 | 147 ani (obj) : animation.FuncAnimation() object 332 | 148 fps (int) : frames per second 333 | 149 dpi (int) : dots per inch 334 | 150 """ 335 | 151 1 1.0 1.0 0.0 if conf.SAVE_ANIMATION is True: 336 | 152 # file name 337 | 153 1 11.0 11.0 0.0 date_n_time = str(datetime.now())[:19] 338 | 154 # Replace ':' with '-' for compatibility with windows file formating. 339 | 155 1 3.0 3.0 0.0 date_n_time = date_n_time.replace(':', '-').replace(' ', '_') 340 | 156 1 1.0 1.0 0.0 file_name = conf.MODE + '_animation_' + date_n_time 341 | 157 342 | 158 # Configure the writer 343 | 159 1 8.0 8.0 0.0 plt.rcParams['animation.ffmpeg_path'] = conf.PATH_TO_FFMPEG 344 | 160 1 1.0 1.0 0.0 FFwriter = animation.FFMpegWriter( 345 | 161 1 0.0 0.0 0.0 fps=fps, bitrate=-1, 346 | 162 1 1.0 1.0 0.0 extra_args=['-r', str(fps), '-pix_fmt', 'yuv420p', '-vcodec', 347 | 163 1 13.0 13.0 0.0 'libx264', '-qscale:v', '1'] 348 | 164 ) 349 | 165 350 | 166 # Save 351 | 167 1 1.0 1.0 0.0 try: 352 | 168 1 2.0 2.0 0.0 ani.save(file_name + '.' + conf.VID_FORMAT, 353 | 169 1 146906648.0 146906648.0 94.5 writer=FFwriter, dpi=dpi) 354 | 170 355 | 171 # log only if a log file is already initialzed 356 | 172 1 54.0 54.0 0.0 if isinstance(logger.find_open_log(), str): 357 | 173 logger.log('Animation saved as: ' + file_name + '.' 358 | 174 + conf.VID_FORMAT + ' | fps: ' + str(fps)) 359 | 175 360 | 176 # convert to a lighter gif 361 | 177 cmd = 'ffmpeg -i ' + file_name + '.' + conf.VID_FORMAT + ' -vf ' \ 362 | 178 '"fps=' + str(fps) + ',scale=240:-1:flags=lanczos,split' \ 363 | 179 '[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -hide_banner' \ 364 | 180 1 4.0 4.0 0.0 ' -loglevel panic -loop 0 ' + file_name + '.gif' 365 | 181 1 8487940.0 8487940.0 5.5 os.system(cmd) 366 | 182 1 64.0 64.0 0.0 if isinstance(logger.find_open_log(), str): 367 | 183 logger.log('Animation saved as: ' + file_name + '.gif' 368 | 184 + ' | fps: ' + str(fps)) 369 | 185 except FileNotFoundError: 370 | 186 logger.log('Configure PATH_TO_FFMPEG') 371 | 187 elif conf.SAVE_ANIMATION is False: 372 | 188 pass 373 | 189 else: 374 | 190 logger.log("Configure SAVE_ANIMATION | Options: True, False") 375 | 376 | Total time: 18.4625 s 377 | File: mattflow/mattflow_post.py 378 | Function: _update_plot at line 193 379 | 380 | Line # Hits Time Per Hit % Time Line Contents 381 | ============================================================== 382 | 193 @profile 383 | 194 def _update_plot(frame_number, X, Y, Z, plot, fig, sub, t_hist, ani_title): 384 | 195 """Plots a single frame. 385 | 196 386 | 197 It is used from FuncAnimation to iteratively create an animation. 387 | 198 388 | 199 Args: 389 | 200 frame_number (int) : current frame 390 | 201 X, Y, Z (2D arrays) : meshgrid and values 391 | 202 plot (list) : list holding current plot 392 | 203 fig (figure) : activated plt.figure 393 | 204 sub (subplot) : Axes3D subplot object 394 | 205 t_hist (list) : holds the iter-wise times 395 | 206 ani_title (str) : to be formated with the frame_number 396 | 207 """ 397 | 208 3335 10337.0 3.1 0.1 if conf.PLOTTING_STYLE == 'water': 398 | 209 plot[0].remove() 399 | 210 if conf.ROTATION: 400 | 211 # Rotate the domain horizontally every 2 frames and vetically 401 | 212 # every 4 frames. 402 | 213 horizontal_rotate = 45 + frame_number / 2 403 | 214 vertical_rotate = 55 - frame_number / 4 404 | 215 sub.view_init(vertical_rotate, horizontal_rotate) 405 | 216 plot[0] = sub.plot_surface(X, Y, Z[frame_number], 406 | 217 rstride=1, cstride=1, linewidth=0, 407 | 218 color=(0.251, 0.643, 0.875, 0.95), 408 | 219 shade=True, antialiased=False) 409 | 220 3335 5202.0 1.6 0.0 elif conf.PLOTTING_STYLE == 'contour': 410 | 221 # Bliting with contour is not supported (because the corresponding 411 | 222 # attributes are not artists), so the subplot has to be re-built. 412 | 223 # That's why fig is passed (matplotlib==3.3.1). 413 | 224 sub.clear() 414 | 225 sub.view_init(45, 55) 415 | 226 plt.subplots_adjust(left=0, bottom=0, right=1, top=1, 416 | 227 wspace=0, hspace=0) 417 | 228 fig.gca().set_zlim([-0.5, 4]) 418 | 229 plt.axis('off') 419 | 230 plot[0] = sub.contour3D(X, Y, Z[frame_number], 120, cmap='ocean', 420 | 231 vmin=0.5, vmax=1.5) 421 | 232 3335 4565.0 1.4 0.0 elif conf.PLOTTING_STYLE == 'wireframe': 422 | 233 3335 79194.0 23.7 0.4 plot[0].remove() 423 | 234 3335 5123.0 1.5 0.0 if conf.ROTATION: 424 | 235 # Rotate the domain horizontally every 3 frames and vetically 425 | 236 # every 2 frames. 426 | 237 3335 5425.0 1.6 0.0 horizontal_rotate = 45 + frame_number / 2 427 | 238 3335 4888.0 1.5 0.0 vertical_rotate = 55 - frame_number / 4 428 | 239 3335 12896.0 3.9 0.1 sub.view_init(vertical_rotate, horizontal_rotate) 429 | 240 3335 9791.0 2.9 0.1 plot[0] = sub.plot_wireframe(X, Y, Z[frame_number], 430 | 241 3335 17145791.0 5141.2 92.9 rstride=2, cstride=2, linewidth=1) 431 | 242 432 | 243 # Reverse engineer the iteration. 433 | 244 it = ( 434 | 245 3335 6962.0 2.1 0.0 frame_number 435 | 246 3335 7669.0 2.3 0.0 + ((frame_number // conf.FRAMES_PER_PERIOD) 436 | 247 3335 6245.0 1.9 0.0 * (conf.FRAME_SAVE_FREQ - conf.FRAMES_PER_PERIOD)) 437 | 248 ) 438 | 249 439 | 250 # Frame title 440 | 251 3335 4176.0 1.3 0.0 if t_hist is not None: 441 | 252 ani_title = \ 442 | 253 3335 36062.0 10.8 0.2 f"time: {t_hist[frame_number]: >{6}.3f} iter: {it: >{4}d}" 443 | 254 3335 1063271.0 318.8 5.8 plt.title(ani_title, y=0.8, fontsize=18) 444 | 255 3335 54892.0 16.5 0.3 sub.title.set_position([0.51, 0.80]) 445 | 446 | Total time: 155.574 s 447 | File: mattflow/mattflow_post.py 448 | Function: animate at line 258 449 | 450 | Line # Hits Time Per Hit % Time Line Contents 451 | ============================================================== 452 | 258 @time_this 453 | 259 @profile 454 | 260 def animate(h_hist, t_hist=None): 455 | 261 """Generates and saves an animation of the simulation. 456 | 262 457 | 263 Args: 458 | 264 h_hist (array) : array of iter-wise heights solutions 459 | 265 t_hist (array) : holds the iter-wise times 460 | 266 461 | 267 Returns: 462 | 268 ani (animation.FuncAnimation) : It is returned in case of ipython 463 | 269 """ 464 | 270 # resolution = figsize * dpi 465 | 271 # -------------------------- 466 | 272 # example: 467 | 273 # figsize = (9.6, 5.4), dpi=200 468 | 274 # resolution: 1920x1080 (1920/200=9.6) 469 | 275 1 3.0 3.0 0.0 fps = conf.FPS 470 | 276 1 1.0 1.0 0.0 dpi = conf.DPI 471 | 277 1 2.0 2.0 0.0 figsize = conf.FIGSIZE 472 | 278 473 | 279 # total frames 474 | 280 1 1.0 1.0 0.0 frames = len(h_hist) 475 | 281 476 | 282 # X, Y, Z 477 | 283 1 93.0 93.0 0.0 X, Y = np.meshgrid(conf.CX[conf.Ng: -conf.Ng], conf.CY[conf.Ng: -conf.Ng]) 478 | 284 1 2.0 2.0 0.0 Z = h_hist 479 | 285 480 | 286 # Plot configuration 481 | 287 1 91527.0 91527.0 0.1 fig = plt.figure(figsize=figsize, dpi=dpi) 482 | 288 1 82963.0 82963.0 0.1 sub = fig.add_subplot(111, projection="3d") 483 | 289 1 6.0 6.0 0.0 sub.view_init(45, 55) 484 | 290 1 479.0 479.0 0.0 plt.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=0, hspace=0) 485 | 291 1 130.0 130.0 0.0 fig.gca().set_zlim([-0.5, 4]) 486 | 292 1 42.0 42.0 0.0 plt.axis('off') 487 | 293 1 2.0 2.0 0.0 if t_hist is None: 488 | 294 ani_title = f"mesh: {conf.Nx}x{conf.Ny} solver: {conf.SOLVER_TYPE}" 489 | 295 else: 490 | 296 1 11.0 11.0 0.0 ani_title = f"time: {t_hist[0]: >{6}.3f} iter: {0: >{5}d}" 491 | 297 1 305.0 305.0 0.0 plt.title(ani_title, y=0.8, fontsize=18) 492 | 298 1 16.0 16.0 0.0 sub.title.set_position([0.51, 0.80]) 493 | 299 1 24.0 24.0 0.0 plt.rcParams.update({'font.size': 20}) 494 | 300 # Program name and version text 495 | 301 1 174.0 174.0 0.0 fig.text(0.85, 0.06, s=f"MattFlow v{__version__}", fontsize=16, c='navy') 496 | 302 497 | 303 # Plot initialization 498 | 304 1 2.0 2.0 0.0 if conf.PLOTTING_STYLE == 'water': 499 | 305 plot = [sub.plot_surface(X, Y, Z[0], 500 | 306 rstride=1, cstride=1, linewidth=0, 501 | 307 color=(0.251, 0.643, 0.875, 0.9), 502 | 308 shade=True, antialiased=False)] 503 | 309 1 2.0 2.0 0.0 elif conf.PLOTTING_STYLE == 'contour': 504 | 310 plot = [sub.contour3D(X, Y, Z[0], 150, cmap='ocean', 505 | 311 vmin=0.5, vmax=1.5)] 506 | 312 1 2.0 2.0 0.0 elif conf.PLOTTING_STYLE == 'wireframe': 507 | 313 1 3.0 3.0 0.0 plot = [sub.plot_wireframe(X, Y, Z[0], rstride=2, cstride=2, 508 | 314 1 2876.0 2876.0 0.0 linewidth=1)] 509 | 315 else: 510 | 316 logger.log("Configure PLOTTING_STYLE | options: 'water', 'contour',", 511 | 317 "'wireframe'") 512 | 318 # Render the basin that contains the fluid. 513 | 319 1 5.0 5.0 0.0 _plot_basin(sub) 514 | 320 515 | 321 # Generate the animation. 516 | 322 1 2.0 2.0 0.0 ani = animation.FuncAnimation( 517 | 323 1 1.0 1.0 0.0 fig, _update_plot, frames, 518 | 324 1 2.0 2.0 0.0 fargs=(X, Y, Z, plot, fig, sub, t_hist, ani_title), 519 | 325 1 2.0 2.0 0.0 interval=1000 / fps, 520 | 326 1 635.0 635.0 0.0 repeat=True 521 | 327 ) 522 | 328 523 | 329 # Save the animation. 524 | 330 1 155394819.0 155394819.0 99.9 _save_ani(ani, fps, dpi) 525 | 331 526 | 332 # Play the animation. 527 | 333 1 3.0 3.0 0.0 if conf.SHOW_ANIMATION is True: 528 | 334 logger.log('Playing animation...') 529 | 335 try: 530 | 336 # In case of jupyter notebook, don't run plt.show(), to prevent 531 | 337 # displaying a static figure. Instead, return the Funcanimation 532 | 338 # object. 533 | 339 get_ipython() 534 | 340 return ani 535 | 341 except NameError: 536 | 342 plt.show() 537 | 343 1 2.0 2.0 0.0 elif conf.SHOW_ANIMATION is False: 538 | 344 1 1.0 1.0 0.0 pass 539 | 345 else: 540 | 346 logger.log("Configure SHOW_ANIMATION | Options: True, False") 541 | 542 | Total time: 14.8754 s 543 | File: mattflow/mattflow_solver.py 544 | Function: _solve at line 31 545 | 546 | Line # Hits Time Per Hit % Time Line Contents 547 | ============================================================== 548 | 31 @profile 549 | 32 def _solve(U, 550 | 33 delta_t, 551 | 34 it, 552 | 35 drops_count, 553 | 36 drop_its_iterator, 554 | 37 next_drop_it): 555 | 38 """Evaluates the state variables (h, hu, hv) at a new time-step. 556 | 39 557 | 40 It can be used in a for/while loop, iterating through each time-step. 558 | 41 559 | 42 Args: 560 | 43 U (3D array) : the state variables, populating a x,y grid 561 | 44 delta_t (float) : time discretization step 562 | 45 it (int) : current iteration 563 | 46 drops_count (int) : number of drops been generated 564 | 47 drop_its_iterator (iterator) 565 | 48 : iterator of the drop_its list (the list with 566 | 49 the iters at which a new drop falls) 567 | 50 next_drop_it (int) : the next iteration at which a new drop will fall 568 | 51 569 | 52 Returns: 570 | 53 U, drops_count, drop_its_iterator, next_drop_it 571 | 54 """ 572 | 55 # Retrieve the mesh 573 | 56 9999 8850.0 0.9 0.1 Ng = conf.Ng 574 | 57 9999 6511.0 0.7 0.0 cx = conf.CX 575 | 58 9999 6156.0 0.6 0.0 cy = conf.CY 576 | 59 9999 7469.0 0.7 0.1 cellArea = conf.dx * conf.dy 577 | 60 578 | 61 # Simulation mode 579 | 62 # --------------- 580 | 63 # 'single drop': handled at the initialization 581 | 64 9999 7207.0 0.7 0.0 if conf.MODE == 'single drop': 582 | 65 pass 583 | 66 584 | 67 # 'drops': specified number of drops are generated at specified frequency 585 | 68 9999 6770.0 0.7 0.0 elif conf.MODE == 'drops': 586 | 69 9999 6275.0 0.6 0.0 if conf.ITERS_BETWEEN_DROPS_MODE == "fixed": 587 | 70 drop_condition = (it % conf.FIXED_ITERS_BETWEEN_DROPS == 0 588 | 71 and drops_count < conf.MAX_N_DROPS) 589 | 72 else: # conf.ITERS_TO_NEXT_DROP_MODE in ["custom", "random"] 590 | 73 9999 6841.0 0.7 0.0 drop_condition = (it == next_drop_it 591 | 74 109 81.0 0.7 0.0 and drops_count < conf.MAX_N_DROPS) 592 | 75 593 | 76 9999 6585.0 0.7 0.0 if drop_condition: 594 | 77 109 23951.0 219.7 0.2 U[0, :, :] = initializer.drop(U[0, :, :], drops_count + 1) 595 | 78 109 89.0 0.8 0.0 drops_count += 1 596 | 79 109 116.0 1.1 0.0 if (conf.ITERS_BETWEEN_DROPS_MODE in ["custom", "random"] 597 | 80 109 87.0 0.8 0.0 and drops_count < conf.MAX_N_DROPS): 598 | 81 108 125.0 1.2 0.0 next_drop_it = next(drop_its_iterator) 599 | 82 600 | 83 # 'rain': random number of drops are generated at random frequency 601 | 84 elif conf.MODE == 'rain': 602 | 85 if it % random.randrange(1, 15) == 0: 603 | 86 simultaneous_drops = range(random.randrange(1, 2)) 604 | 87 for _ in simultaneous_drops: 605 | 88 U[0, :, :] = initializer.drop(U[0, :, :]) 606 | 89 else: 607 | 90 modes = ['single drop', 'drops', 'rain'] 608 | 91 logger.log(f"Configure MODE | options: {modes}") 609 | 92 610 | 93 # Numerical scheme 611 | 94 # flux.flux() returns the total flux entering and leaving each cell. 612 | 95 9999 6290.0 0.6 0.0 if conf.SOLVER_TYPE == 'Lax-Friedrichs Riemann': 613 | 96 U[:, Ng: -Ng, Ng: -Ng] += delta_t / cellArea * flux.flux(U) 614 | 97 9999 6756.0 0.7 0.0 elif conf.SOLVER_TYPE == '2-stage Runge-Kutta': 615 | 98 # 1st stage 616 | 99 9999 5726.0 0.6 0.0 U_pred = U 617 | 100 9999 8343193.0 834.4 56.1 U_pred[:, Ng: -Ng, Ng: -Ng] += delta_t / cellArea * flux.flux(U) 618 | 101 619 | 102 # 2nd stage 620 | 103 U[:, Ng: -Ng, Ng: -Ng] = \ 621 | 104 9999 7903.0 0.8 0.1 0.5 * (U[:, Ng: -Ng, Ng: -Ng] 622 | 105 + U_pred[:, Ng: -Ng, Ng: -Ng] 623 | 106 9999 6410074.0 641.1 43.1 + delta_t / cellArea * flux.flux(U_pred) 624 | 107 ) 625 | 108 else: 626 | 109 solver_types = ['Lax-Friedrichs Riemann', '2-stage Runge-Kutta'] 627 | 110 logger.log(f"Configure SOLVER_TYPE | Options: {solver_types}") 628 | 111 9999 8393.0 0.8 0.1 return U, drops_count, drop_its_iterator, next_drop_it 629 | 112 630 | 113 ''' 631 | 114 # Experimenting on the finite differences form of the MacCormack solver. 632 | 115 # TODO somewhere delta_t/dx becomes the greatest eigenvalue of the jacobian 633 | 116 elif conf.SOLVER_TYPE == 'MacCormack experimental': 634 | 117 # 1st step: prediction (FTFS) 635 | 118 U_pred = U 636 | 119 U_pred[:, Ng: -Ng, Ng: -Ng] = U[:, Ng: -Ng, Ng: -Ng] \ 637 | 120 - delta_t / dx * (flux.F(U[:, Ng: -Ng, Ng + 1: Nx + Ng + 1]) \ 638 | 121 - flux.F(U[:, Ng: -Ng, Ng: -Ng])) \ 639 | 122 - delta_t / dy * (flux.G(U[:, Ng + 1: Ny + Ng + 1, Ng: -Ng]) \ 640 | 123 - flux.G(U[:, Ng: -Ng, Ng: -Ng])) 641 | 124 642 | 125 U_pred = bcmanager.updateGhostCells(U_pred) 643 | 126 delta_t = _dt(U_pred, dx, dy) 644 | 127 645 | 128 # 2nd step: correction (BTBS) 646 | 129 U[:, Ng: -Ng, Ng: -Ng] \ 647 | 130 = 0.5 * (U[:, Ng: -Ng, Ng: -Ng] + U_pred[:, Ng: -Ng, Ng: -Ng]) \ 648 | 131 - 0.5 * delta_t / dx * (flux.F(U_pred[:, Ng: -Ng, Ng: -Ng]) \ 649 | 132 - flux.F(U_pred[:, Ng: -Ng, Ng - 1: Nx + Ng - 1])) \ 650 | 133 - 0.5 * delta_t / dy * (flux.G(U_pred[:, Ng: -Ng, Ng: -Ng]) \ 651 | 134 - flux.G(U_pred[:, Ng - 1: Ny + Ng - 1, Ng: -Ng])) 652 | 135 ''' 653 | 654 | Total time: 17.3242 s 655 | File: mattflow/mattflow_solver.py 656 | Function: simulate at line 196 657 | 658 | Line # Hits Time Per Hit % Time Line Contents 659 | ============================================================== 660 | 196 @time_this 661 | 197 @profile 662 | 198 def simulate(): 663 | 199 1 1.0 1.0 0.0 time = 0 664 | 200 665 | 201 1 586.0 586.0 0.0 U, h_hist, t_hist, U_ds = initializer.initialize() 666 | 202 1 1.0 1.0 0.0 drops_count = 1 667 | 203 # idx of the frame saved in h_hist 668 | 204 1 0.0 0.0 0.0 saving_frame_idx = 0 669 | 205 # Counts up to conf.FRAMES_PER_PERIOD (1st frame saved at initialization). 670 | 206 1 1.0 1.0 0.0 consecutive_frames_counter = 1 671 | 207 672 | 208 1 2.0 2.0 0.0 if conf.ITERS_BETWEEN_DROPS_MODE in ["custom", "random"]: 673 | 209 # List with the simulation iterations at which a drop is going to fall 674 | 210 1 687.0 687.0 0.0 drop_its = utils.drop_iters_list() 675 | 211 # Drop the 0th drop 676 | 212 1 2.0 2.0 0.0 drop_its_iterator = iter(drop_its[1:]) 677 | 213 # The iteration at which the next drop will fall 678 | 214 1 2.0 2.0 0.0 next_drop_it = next(drop_its_iterator) 679 | 215 else: 680 | 216 drop_its_iterator = None 681 | 217 next_drop_it = None 682 | 218 683 | 219 10000 7650.0 0.8 0.0 for it in range(1, conf.MAX_ITERS): 684 | 220 685 | 221 # Time discretization step (CFL condition) 686 | 222 9999 768725.0 76.9 4.4 delta_t = _dt(U) 687 | 223 688 | 224 # Update current time 689 | 225 9999 11673.0 1.2 0.1 time += delta_t 690 | 226 9999 15495.0 1.5 0.1 if time > conf.STOPPING_TIME: 691 | 227 break 692 | 228 693 | 229 # Apply boundary conditions (reflective) 694 | 230 9999 1329108.0 132.9 7.7 U = bcmanager.update_ghost_cells(U) 695 | 231 696 | 232 # Numerical iterative scheme 697 | 233 9999 6848.0 0.7 0.0 U, drops_count, drop_its_iterator, next_drop_it = _solve( 698 | 234 9999 5942.0 0.6 0.0 U=U, 699 | 235 9999 5375.0 0.5 0.0 delta_t=delta_t, 700 | 236 9999 5224.0 0.5 0.0 it=it, 701 | 237 9999 5359.0 0.5 0.0 drops_count=drops_count, 702 | 238 9999 5463.0 0.5 0.0 drop_its_iterator=drop_its_iterator, 703 | 239 9999 15035493.0 1503.7 86.8 next_drop_it=next_drop_it 704 | 240 ) 705 | 241 706 | 242 9999 9347.0 0.9 0.1 if conf.WRITE_DAT: 707 | 243 dat_writer.write_dat( 708 | 244 U[0, conf.Ng: conf.Ny + conf.Ng, conf.Ng: conf.Nx + conf.Ng], 709 | 245 time, it 710 | 246 ) 711 | 247 mattflow_post.plot_from_dat(time, it) 712 | 248 9999 7051.0 0.7 0.0 elif not conf.WRITE_DAT: 713 | 249 # Append current frame to the list, to be animated at 714 | 250 # post-processing. 715 | 251 9999 10007.0 1.0 0.1 if it % conf.FRAME_SAVE_FREQ == 0: 716 | 252 # Zero the counter, when a perfect division occurs. 717 | 253 3333 2092.0 0.6 0.0 consecutive_frames_counter = 0 718 | 254 9999 7272.0 0.7 0.0 if consecutive_frames_counter < conf.FRAMES_PER_PERIOD: 719 | 255 3333 2606.0 0.8 0.0 saving_frame_idx += 1 720 | 256 h_hist[saving_frame_idx] = \ 721 | 257 3333 44856.0 13.5 0.3 U[0, conf.Ng: -conf.Ng, conf.Ng: -conf.Ng] 722 | 258 # time * 10 is insertd, because space is scaled about x10. 723 | 259 3333 7607.0 2.3 0.0 t_hist[saving_frame_idx] = time * 10 724 | 260 3333 2431.0 0.7 0.0 consecutive_frames_counter += 1 725 | 261 9999 7333.0 0.7 0.0 if conf.SAVE_DS_FOR_ML: 726 | 262 U_ds[it] = U[:, conf.Ng: -conf.Ng, conf.Ng: -conf.Ng] 727 | 263 else: 728 | 264 logger.log("Configure WRITE_DAT | Options: True, False") 729 | 265 730 | 266 9999 19974.0 2.0 0.1 logger.log_timestep(it, time) 731 | 267 732 | 268 # Clean-up the memmap 733 | 269 1 2.0 2.0 0.0 if conf.DUMP_MEMMAP and conf.WORKERS > 1: 734 | 270 utils.delete_memmap() 735 | 271 736 | 272 1 1.0 1.0 0.0 return h_hist, t_hist, U_ds 737 | 738 | Total time: 173.035 s 739 | File: mattflow/__main__.py 740 | Function: main at line 21 741 | 742 | Line # Hits Time Per Hit % Time Line Contents 743 | ============================================================== 744 | 21 @time_this 745 | 22 @profile 746 | 23 def main(): 747 | 24 # Uncomment this to delete previous log, dat and png files (for debugging). 748 | 25 # utils.delete_prev_runs_data() 749 | 26 750 | 27 # Pre-processing (mesh construction) 751 | 28 1 2.0 2.0 0.0 utils.preprocessing(Nx=90, Ny=90, Ng=1, 752 | 29 1 1.0 1.0 0.0 max_x=0.7, min_x=-0.7, 753 | 30 1 49.0 49.0 0.0 max_y=0.7, min_y=-0.7) 754 | 31 755 | 32 # Solution 756 | 33 1 17460524.0 17460524.0 10.1 h_hist, t_hist, U_ds = mattflow_solver.simulate() 757 | 34 758 | 35 # Post-processing 759 | 36 1 155574418.0 155574418.0 89.9 mattflow_post.animate(h_hist, t_hist) 760 | 761 | --------------------------------------------------------------------------------