├── .github └── workflows │ ├── extract_nd.yml │ └── extract_nd_main.py ├── .gitmodules ├── LICENSE ├── Makefile ├── README.md ├── docs └── source │ ├── conf.py │ ├── conf_params │ └── mathjax_params.py │ ├── equation.rst │ ├── index.rst │ ├── snapshot2d.png │ └── snapshot3d.png ├── exec.sh ├── include ├── array.h ├── array_macros │ ├── domain │ │ ├── dxc.h │ │ ├── dxf.h │ │ ├── xc.h │ │ └── xf.h │ ├── fluid │ │ ├── p.h │ │ ├── psi.h │ │ ├── srct.h │ │ ├── srcux.h │ │ ├── srcuy.h │ │ ├── srcuz.h │ │ ├── t.h │ │ ├── ux.h │ │ ├── uy.h │ │ └── uz.h │ ├── ib │ │ ├── ibfx.h │ │ ├── ibfy.h │ │ └── ibfz.h │ └── statistics │ │ ├── t1.h │ │ ├── t2.h │ │ ├── ux1.h │ │ ├── ux2.h │ │ ├── uxt.h │ │ ├── uy1.h │ │ ├── uy2.h │ │ ├── uz1.h │ │ └── uz2.h ├── config.h ├── decide_dt.h ├── domain.h ├── fileio.h ├── fluid.h ├── fluid_solver.h ├── halo.h ├── ib.h ├── ib_solver.h ├── integrate.h ├── linear_system.h ├── logging.h ├── memory.h ├── param.h ├── runge_kutta.h ├── save.h ├── statistics.h ├── tdm.h └── timer.h ├── initial_condition ├── Makefile ├── README.rst ├── exec.sh └── main.py ├── src ├── README.rst ├── array.c ├── config.c ├── decide_dt.c ├── domain.c ├── fileio.c ├── fluid │ ├── README.rst │ ├── boundary │ │ ├── p.c │ │ ├── psi.c │ │ ├── t.c │ │ ├── ux.c │ │ └── uy.c │ ├── compute_potential.c │ ├── correct_velocity │ │ ├── internal.h │ │ ├── main.c │ │ ├── ux.c │ │ └── uy.c │ ├── init.c │ ├── integrate │ │ ├── compute_rhs.c │ │ ├── couple_external_force.c │ │ ├── internal.h │ │ ├── predict_field.c │ │ ├── t.c │ │ ├── ux.c │ │ └── uy.c │ ├── save.c │ └── update_pressure.c ├── halo.c ├── ib │ ├── collision.c │ ├── exchange.c │ ├── increment.c │ ├── inertia.c │ ├── init.c │ ├── internal.c │ ├── internal.h │ ├── reset.c │ ├── save.c │ └── update.c ├── integrate.c ├── linear_system.c ├── logging │ ├── README.rst │ ├── divergence.c │ ├── energy.c │ ├── internal.h │ ├── main.c │ ├── momentum.c │ └── nusselt │ │ ├── README.rst │ │ ├── heat_flux.c │ │ ├── internal.h │ │ ├── kinetic_energy_dissipation.c │ │ ├── kinetic_energy_injection.c │ │ ├── main.c │ │ ├── reference.c │ │ └── thermal_energy_dissipation.c ├── main.c ├── memory.c ├── param │ ├── boundary-condition.c │ ├── buoyancy.c │ └── implicit.c ├── runge_kutta.c ├── save.c ├── statistics.c ├── tdm.c └── timer.c ├── tools ├── README.rst └── define_arrays.py └── visualise └── 2d.py /.github/workflows/extract_nd.yml: -------------------------------------------------------------------------------- 1 | name: ExtractND 2 | 3 | on: 4 | 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | 11 | push-branches: 12 | name: Create branch 2D and 3D which only contains the dimension 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | dimension: [2, 3] 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@main 20 | with: 21 | repository: "NaokiHori/SimpleIBMSolver" 22 | ref: "main" 23 | submodules: "recursive" 24 | - name: Install dependencies 25 | run: | 26 | sudo apt-get -y update && \ 27 | sudo apt-get -y install make libopenmpi-dev libfftw3-dev 28 | - name: Remove another dimension 29 | run: | 30 | set -x 31 | set -e 32 | python .github/workflows/extract_nd_main.py ${{ matrix.dimension }} 33 | - name: Modify Makefile 34 | run: | 35 | set -x 36 | set -e 37 | sed -i "s/DNDIMS=2/DNDIMS=${{ matrix.dimension }}/g" Makefile 38 | - name: Compile 39 | run: | 40 | make all 41 | - name: Commit and push change 42 | run: | 43 | set -x 44 | set -e 45 | git switch -c ${{ matrix.dimension }}d 46 | git config --local user.email "36466440+NaokiHori@users.noreply.github.com" 47 | git config --local user.name "NaokiHori" 48 | # let IC generator of the given dimension as main.py 49 | cd initial_condition 50 | find . -type f -name "*d.py" | grep -v ${{ matrix.dimension }}d.py | xargs git rm 51 | git mv ${{ matrix.dimension }}d.py main.py 52 | cd .. 53 | # add, commit, and push 54 | git add Makefile 55 | git add src 56 | git add include 57 | git commit -m "Extract ${{ matrix.dimension }}d sources" -a || true 58 | git push -f origin ${{ matrix.dimension }}d 59 | 60 | -------------------------------------------------------------------------------- /.github/workflows/extract_nd_main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import glob 4 | import enum 5 | 6 | 7 | def get_filenames(root): 8 | results = glob.glob(f"{root}/**", recursive=True) 9 | retvals = list() 10 | for result in results: 11 | if result.endswith(".c") or result.endswith(".h"): 12 | retvals.append(result) 13 | return retvals 14 | 15 | 16 | class NdimsType(enum.Enum): 17 | IN_2D = enum.auto() 18 | IN_3D = enum.auto() 19 | OTHER = enum.auto() 20 | 21 | 22 | def extract_given_dim(ndims, lines): 23 | state = NdimsType.OTHER 24 | if_level = 0 25 | if_level_ndims = 0 26 | newlines = list() 27 | for line in lines: 28 | is_on_ndims_macro = False 29 | if "#if" in line: 30 | # found "if", increase nest counter 31 | if_level += 1 32 | if " NDIMS" in line: 33 | if "NDIMS==2" in line.replace(" ", ""): 34 | is_on_ndims_macro = True 35 | # now in 2D condition 36 | state = NdimsType.IN_2D 37 | if_level_ndims = if_level 38 | if "NDIMS==3" in line.replace(" ", ""): 39 | is_on_ndims_macro = True 40 | # now in 3D condition 41 | state = NdimsType.IN_3D 42 | if_level_ndims = if_level 43 | elif "#else" in line: 44 | # check this "else" is for ndims 45 | if if_level == if_level_ndims: 46 | is_on_ndims_macro = True 47 | # if it is, swap state (3d if now 2d, vice versa) 48 | if state == NdimsType.IN_2D: 49 | state = NdimsType.IN_3D 50 | elif state == NdimsType.IN_3D: 51 | state = NdimsType.IN_2D 52 | else: 53 | print("else found but if not found beforehand") 54 | sys.exit() 55 | elif "#endif" in line: 56 | if if_level == if_level_ndims: 57 | is_on_ndims_macro = True 58 | state = NdimsType.OTHER 59 | # found "endif", reduce nest counter 60 | if_level -= 1 61 | if not is_on_ndims_macro: 62 | # we do not include macro about ndims 63 | if ndims == 2 and state != NdimsType.IN_3D: 64 | newlines.append(line) 65 | if ndims == 3 and state != NdimsType.IN_2D: 66 | newlines.append(line) 67 | return newlines 68 | 69 | 70 | def modify_comments(lines): 71 | """ 72 | there are weird comments which are used by Sphinx, which look like 73 | // | 74 | I use this function to modify this kind of stuffs as 75 | // 76 | """ 77 | delim = " | " 78 | newlines = list() 79 | for line in lines: 80 | if "//" in line and delim in line: 81 | line = line.split(delim)[0] + "\n" 82 | newlines.append(line) 83 | return newlines 84 | 85 | 86 | def adjust_blank_lines(lines): 87 | """ 88 | this function merges two (and more) successive blank lines 89 | into one blank 90 | """ 91 | nitems = len(lines) 92 | flags = [True for _ in range(nitems)] 93 | for n in range(1, nitems): 94 | # check two neighbouring lines 95 | l0 = lines[n - 1] 96 | l1 = lines[n] 97 | if "\n" == l0 and "\n" == l1: 98 | flags[n] = False 99 | newlines = list() 100 | for line, flag in zip(lines, flags): 101 | if flag: 102 | newlines.append(line) 103 | return newlines 104 | 105 | 106 | def main(): 107 | argv = sys.argv 108 | # sanitise input 109 | assert 2 == len(argv) 110 | ndims = int(argv[1]) 111 | # input source files 112 | fnames = list() 113 | fnames += get_filenames("src") 114 | fnames += get_filenames("include") 115 | for fname in fnames: 116 | with open(fname, "r") as f: 117 | lines = f.readlines() 118 | lines = extract_given_dim(ndims, lines) 119 | lines = modify_comments(lines) 120 | lines = adjust_blank_lines(lines) 121 | if 0 == len(lines): 122 | # nothing remains, delete file 123 | os.system(f"rm {fname}") 124 | continue 125 | # dump 126 | lines = "".join(lines) 127 | with open(fname, "w") as f: 128 | f.write(lines) 129 | 130 | 131 | if __name__ == "__main__": 132 | main() 133 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "SimpleDecomp"] 2 | path = SimpleDecomp 3 | url = https://github.com/NaokiHori/SimpleDecomp 4 | branch = submodule 5 | [submodule "SimpleNpyIO"] 6 | path = SimpleNpyIO 7 | url = https://github.com/NaokiHori/SimpleNpyIO 8 | branch = submodule 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 NaokiHori 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC := mpicc 2 | CFLAG := -std=c99 -Wall -Wextra -O3 -DNDIMS=2 3 | INC := -Iinclude -ISimpleDecomp/include -ISimpleNpyIO/include 4 | LIB := -lfftw3 -lm 5 | SRCDIR := src SimpleDecomp/src SimpleNpyIO/src 6 | OBJDIR := obj 7 | SRCS := $(shell find $(SRCDIR) -type f -name *.c) 8 | OBJS := $(patsubst %.c,obj/%.o,$(SRCS)) 9 | DEPS := $(patsubst %.c,obj/%.d,$(SRCS)) 10 | OUTDIR := output 11 | TARGET := a.out 12 | 13 | help: 14 | @echo "all : create \"$(TARGET)\"" 15 | @echo "clean : remove \"$(TARGET)\" and object files under \"$(OBJDIR)\"" 16 | @echo "output : create \"$(OUTDIR)\" to store output" 17 | @echo "datadel : clean-up \"$(OUTDIR)\"" 18 | @echo "help : show this message" 19 | 20 | all: $(TARGET) 21 | 22 | $(TARGET): $(OBJS) 23 | $(CC) $(CFLAG) -o $@ $^ $(LIB) 24 | 25 | $(OBJDIR)/%.o: %.c 26 | @if [ ! -e $(dir $@) ]; then \ 27 | mkdir -p $(dir $@); \ 28 | fi 29 | $(CC) $(CFLAG) -MMD $(INC) -c $< -o $@ 30 | 31 | clean: 32 | $(RM) -r $(OBJDIR) $(TARGET) 33 | 34 | output: 35 | @if [ ! -e $(OUTDIR)/log ]; then \ 36 | mkdir -p $(OUTDIR)/log; \ 37 | fi 38 | @if [ ! -e $(OUTDIR)/save ]; then \ 39 | mkdir -p $(OUTDIR)/save; \ 40 | fi 41 | @if [ ! -e $(OUTDIR)/stat ]; then \ 42 | mkdir -p $(OUTDIR)/stat; \ 43 | fi 44 | 45 | datadel: 46 | $(RM) -r $(OUTDIR)/log/* 47 | $(RM) -r $(OUTDIR)/save/* 48 | $(RM) -r $(OUTDIR)/stat/* 49 | 50 | -include $(DEPS) 51 | 52 | .PHONY : all clean output datadel help 53 | 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple IBM Solver 2 | 3 | [![License](https://img.shields.io/github/license/NaokiHori/SimpleIBMSolver)](https://opensource.org/licenses/MIT) 4 | [![Last Commit](https://img.shields.io/github/last-commit/NaokiHori/SimpleIBMSolver/main)](https://github.com/NaokiHori/SimpleIBMSolver/commits/main) 5 | 6 | [![Simulation Snapshot](https://github.com/NaokiHori/SimpleIBMSolver/blob/main/docs/source/snapshot2d.png)](https://youtu.be/nMAyrIYET10) 7 | 8 | ## Overview 9 | 10 | This library numerically simulates the motion of rigid particles in two- and three-dimensional Cartesian domains using the finite-difference and immersed boundary methods. 11 | 12 | ## Dependency 13 | 14 | This solver is built on top of [`SimpleNSSolver`](https://github.com/NaokiHori/SimpleNSSolver). 15 | Please check its repository for dependency details. 16 | 17 | ## Quick Start 18 | 19 | 1. **Set up your workspace** 20 | 21 | ```console 22 | mkdir -p /path/to/your/directory 23 | cd /path/to/your/directory 24 | ``` 25 | 26 | 2. **Clone the repository** 27 | 28 | ```console 29 | git clone --recurse-submodules https://github.com/NaokiHori/SimpleIBMSolver 30 | cd SimpleIBMSolver 31 | ``` 32 | 33 | 3. **Set the initial condition** 34 | 35 | Python 3 is used to conveniently initialize the flow fields. 36 | Alternatively, `NPY` files can be provided in a different manner under `initial_condition/output/`. 37 | 38 | ```console 39 | cd initial_condition 40 | make output 41 | bash exec.sh 42 | cd .. 43 | ``` 44 | 45 | 4. **Build the solver** 46 | 47 | ```console 48 | make output 49 | make all 50 | ``` 51 | 52 | 5. **Run the simulation** 53 | 54 | ```console 55 | bash exec.sh 56 | ``` 57 | 58 | ## Note 59 | 60 | The immersed boundary method and the collision model are based on [this publication](https://www.sciencedirect.com/science/article/pii/S0045793021003716) with some modifications. 61 | 62 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | sys.path.append(os.path.abspath("./conf_params")) 5 | 6 | project = "Simple IBM Solver" 7 | author = "Naoki Hori" 8 | copyright = f"2019, {author}" 9 | 10 | extensions = [ 11 | "sphinx.ext.mathjax", 12 | ] 13 | 14 | from mathjax_params import mathjax_path 15 | from mathjax_params import mathjax3_config 16 | 17 | -------------------------------------------------------------------------------- /docs/source/conf_params/mathjax_params.py: -------------------------------------------------------------------------------- 1 | mathjax_path = "https://cdn.jsdelivr.net/npm/mathjax@2/MathJax.js?config=TeX-AMS-MML_HTMLorMML" 2 | mathjax3_config = { 3 | "TeX": { 4 | "Macros": { 5 | "pder": ["{\\frac{\\partial #1}{\\partial #2}}", 2], 6 | "tder": ["{\\frac{d #1}{d #2}}", 2], 7 | } 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /docs/source/equation.rst: -------------------------------------------------------------------------------- 1 | ################## 2 | Governing equation 3 | ################## 4 | 5 | I consider a domain which is filled by an incompressible liquid, which is governed by the following equations 6 | 7 | .. math:: 8 | 9 | \pder{u_i}{x_i} 10 | = 11 | 0, 12 | 13 | .. math:: 14 | 15 | \rho^f 16 | \left( 17 | \pder{u_i}{t} 18 | + 19 | u_j \pder{u_i}{x_j} 20 | \right) 21 | = 22 | \pder{\sigma_{ij}}{x_j} 23 | + 24 | \rho^f 25 | g_i 26 | + 27 | \rho^f 28 | a_i^{IBM}. 29 | 30 | Here :math:`a_i^{IBM}` is a vector field, whose concrete formula is not necessary here. 31 | The only one important thing is that its role is to **constraint the behaviour of the surrounding fluid as if there were an object inside the domain**. 32 | This is to be determined later, such that a desired boundary condition is fulfilled on the surface of the object. 33 | 34 | On the other hand, I consider another domain, in which an object sits and surrounded by liquid. 35 | The governing equation describing the behaviour of this one is the Newton's law: 36 | 37 | .. math:: 38 | 39 | \rho^p V^p \tder{U_i^p}{t} 40 | = 41 | \sum_{\forall} \left( \text{force} \right)_i. 42 | 43 | I consider two contributions to the right-hand-side forcing term: the hydrodynamic force acting on the particle: 44 | 45 | .. math:: 46 | 47 | \int_{\partial V^p} \sigma_{ij} n_j dS, 48 | 49 | and the gravitational force: 50 | 51 | .. math:: 52 | 53 | \int_{\partial V^p} \rho^p g_i dV. 54 | 55 | Now I consider to couple the fluid behaviour and the particulate motion. 56 | To do so, I integrate the momentum balance of the fluid inside the same volume containing this object: 57 | 58 | .. math:: 59 | 60 | \int_{V^p} 61 | \left\{ 62 | \rho^f 63 | \left( 64 | \pder{u_i}{t} 65 | + 66 | u_j \pder{u_i}{x_j} 67 | \right) 68 | - 69 | \pder{\sigma_{ij}}{x_j} 70 | - 71 | \rho^f 72 | g_i 73 | - 74 | \rho^f 75 | a_i^{IBM} 76 | \right\} 77 | dV 78 | = 79 | 0, 80 | 81 | giving 82 | 83 | .. math:: 84 | 85 | \int_{\partial V^p} \sigma_{ij} n_j dS 86 | = 87 | \int_{V^p} 88 | \left\{ 89 | \rho^f 90 | \left( 91 | \pder{u_i}{t} 92 | + 93 | u_j \pder{u_i}{x_j} 94 | \right) 95 | - 96 | \rho^f 97 | g_i 98 | - 99 | \rho^f 100 | a_i^{IBM} 101 | \right\} 102 | dV, 103 | 104 | where the Gauss theorem is used. 105 | Thus I have 106 | 107 | .. math:: 108 | 109 | \rho^p V^p \tder{U_i^p}{t} 110 | = 111 | \int_{V^p} 112 | \left\{ 113 | \rho^f 114 | \left( 115 | \pder{u_i}{t} 116 | + 117 | u_j \pder{u_i}{x_j} 118 | \right) 119 | - 120 | \rho^f 121 | g_i 122 | - 123 | \rho^f 124 | a_i^{IBM} 125 | \right\} 126 | dV 127 | + 128 | \int_{\partial V} \rho^p g_i dV^p, 129 | 130 | which is simplified as 131 | 132 | .. math:: 133 | 134 | \rho^p V^p \tder{U_i^p}{t} 135 | = 136 | \int_{V^p} 137 | \left\{ 138 | \rho^f 139 | \left( 140 | \pder{u_i}{t} 141 | + 142 | u_j \pder{u_i}{x_j} 143 | \right) 144 | - 145 | \rho^f 146 | a_i^{IBM} 147 | \right\} 148 | dV 149 | + 150 | \left( 151 | \rho^p 152 | - 153 | \rho^f 154 | \right) 155 | g_i V^p, 156 | 157 | In summary, a set of the governing equations describing the dynamics of the whole system results in 158 | 159 | .. math:: 160 | 161 | \pder{u_i}{x_i} 162 | = 163 | 0, 164 | 165 | .. math:: 166 | 167 | \pder{u_i}{t} 168 | + 169 | u_j \pder{u_i}{x_j} 170 | = 171 | \pder{\sigma_{ij}}{x_j} 172 | + 173 | g_i 174 | + 175 | a_i^{IBM}, 176 | 177 | and 178 | 179 | .. math:: 180 | 181 | \rho^p V^p \tder{U_i^p}{t} 182 | & = 183 | \int_{V^p} 184 | \left( 185 | \pder{u_i}{t} 186 | + 187 | u_j \pder{u_i}{x_j} 188 | \right) 189 | dV \\ 190 | & - 191 | \int_{V^p} 192 | a_i^{IBM} 193 | dV \\ 194 | & + 195 | \left( 196 | \rho^p 197 | - 198 | 1 199 | \right) 200 | g_i V^p, 201 | 202 | where I fix :math:`\rho^f \equiv 1` for notational simplicity, and as a result :math:`\rho^p` is now used to tell the density ratio. 203 | 204 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | ################# 2 | Simple IBM Solver 3 | ################# 4 | 5 | .. note:: 6 | 7 | Under construction. 8 | 9 | .. toctree:: 10 | :hidden: 11 | 12 | equation 13 | 14 | -------------------------------------------------------------------------------- /docs/source/snapshot2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NaokiHori/SimpleIBMSolver/e42c0a36730604664c2f2c6b2bb9b1196924266b/docs/source/snapshot2d.png -------------------------------------------------------------------------------- /docs/source/snapshot3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NaokiHori/SimpleIBMSolver/e42c0a36730604664c2f2c6b2bb9b1196924266b/docs/source/snapshot3d.png -------------------------------------------------------------------------------- /exec.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## temporal information 4 | # maximum duration (in free-fall time) 5 | export timemax=2.0e+1 6 | # maximum duration (in wall time [s]) 7 | export wtimemax=6.0e+2 8 | # logging rate (in free-fall time) 9 | export log_rate=1.0e-1 10 | # save rate (in free-fall time) 11 | export save_rate=5.0e-1 12 | # save after (in free-fall time) 13 | export save_after=0.0e+0 14 | # statistics collection rate (in free-fall time) 15 | export stat_rate=1.0e-1 16 | # statistics collection after (in free-fall time) 17 | export stat_after=1.0e+2 18 | 19 | ## safety factors to decide time step size 20 | ## for advective and diffusive terms 21 | export coef_dt_adv=0.95 22 | export coef_dt_dif=0.95 23 | 24 | ## physical parameters 25 | export Ra=1.0e+8 26 | export Pr=1.0e+0 27 | 28 | # give name of the directory in which the initial conditions 29 | # (incl. domain size etc.) are stored as an argument 30 | dirname_ic=initial_condition/output 31 | # dirname_ic=$(find output/save -type d | sort | tail -n 1) 32 | 33 | mpirun -n 2 --oversubscribe ./a.out ${dirname_ic} 34 | -------------------------------------------------------------------------------- /include/array.h: -------------------------------------------------------------------------------- 1 | #if !defined(ARRAY_H) 2 | #define ARRAY_H 3 | 4 | // struct and methods for multi-dimensional arrays 5 | // distributed among multiple processes 6 | 7 | #include 8 | #include "domain.h" 9 | 10 | typedef struct { 11 | // size of each element 12 | size_t size; 13 | // number of additional cells w.r.t. no-halo array 14 | // i.e. 0th elements: [1 : mysizes[0]] 15 | // 1st elements: [1 : mysizes[1]] 16 | // 2nd elements: [1 : mysizes[2]] 17 | // ... 18 | int (* nadds)[2]; 19 | // total size of local array (i.e. product of mysizes) 20 | size_t datasize; 21 | // pointer to the raw local array 22 | void * data; 23 | } array_t; 24 | 25 | typedef struct { 26 | // allocate array and store its size information 27 | int (* const prepare)( 28 | const domain_t * domain, 29 | const int nadds[NDIMS][2], 30 | const size_t size, 31 | array_t * array 32 | ); 33 | // clean-up local memory to store the array 34 | int (* const destroy)( 35 | array_t * array 36 | ); 37 | // load array from NPY file 38 | int (* const load)( 39 | const domain_t * domain, 40 | const char dirname[], 41 | const char dsetname[], 42 | const char dtype[], 43 | array_t * array 44 | ); 45 | // save array to NPY file 46 | int (* const dump)( 47 | const domain_t * domain, 48 | const char dirname[], 49 | const char dsetname[], 50 | const char dtype[], 51 | const array_t * array 52 | ); 53 | } array_method_t; 54 | 55 | extern const array_method_t array; 56 | 57 | #endif // ARRAY_H 58 | -------------------------------------------------------------------------------- /include/array_macros/domain/dxc.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_DOMAIN_DXC_H) 2 | #define INCLUDE_ARRAY_MACROS_DOMAIN_DXC_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | // [1 : isize+1] 7 | #define DXC(I) (dxc[(I-1)]) 8 | #define DXC_NADDS (int [2]){0, 1} 9 | 10 | #endif // INCLUDE_ARRAY_MACROS_DOMAIN_DXC_H 11 | -------------------------------------------------------------------------------- /include/array_macros/domain/dxf.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_DOMAIN_DXF_H) 2 | #define INCLUDE_ARRAY_MACROS_DOMAIN_DXF_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | // [1 : isize+0] 7 | #define DXF(I) (dxf[(I-1)]) 8 | #define DXF_NADDS (int [2]){0, 0} 9 | 10 | #endif // INCLUDE_ARRAY_MACROS_DOMAIN_DXF_H 11 | -------------------------------------------------------------------------------- /include/array_macros/domain/xc.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_DOMAIN_XC_H) 2 | #define INCLUDE_ARRAY_MACROS_DOMAIN_XC_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | // [0 : isize+1] 7 | #define XC(I) (xc[(I )]) 8 | #define XC_NADDS (int [2]){1, 1} 9 | 10 | #endif // INCLUDE_ARRAY_MACROS_DOMAIN_XC_H 11 | -------------------------------------------------------------------------------- /include/array_macros/domain/xf.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_DOMAIN_XF_H) 2 | #define INCLUDE_ARRAY_MACROS_DOMAIN_XF_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | // [1 : isize+1] 7 | #define XF(I) (xf[(I-1)]) 8 | #define XF_NADDS (int [2]){0, 1} 9 | 10 | #endif // INCLUDE_ARRAY_MACROS_DOMAIN_XF_H 11 | -------------------------------------------------------------------------------- /include/array_macros/fluid/p.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_FLUID_P_H) 2 | #define INCLUDE_ARRAY_MACROS_FLUID_P_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | // [0 : isize+1], [0 : jsize+1] 7 | #define P(I, J) (p[(I ) + (isize+2) * (J )]) 8 | #define P_NADDS (int [NDIMS][2]){ {1, 1}, {1, 1}, } 9 | 10 | #endif // INCLUDE_ARRAY_MACROS_FLUID_P_H 11 | -------------------------------------------------------------------------------- /include/array_macros/fluid/psi.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_FLUID_PSI_H) 2 | #define INCLUDE_ARRAY_MACROS_FLUID_PSI_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | // [0 : isize+1], [0 : jsize+1] 7 | #define PSI(I, J) (psi[(I ) + (isize+2) * (J )]) 8 | #define PSI_NADDS (int [NDIMS][2]){ {1, 1}, {1, 1}, } 9 | 10 | #endif // INCLUDE_ARRAY_MACROS_FLUID_PSI_H 11 | -------------------------------------------------------------------------------- /include/array_macros/fluid/srct.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_FLUID_SRCT_H) 2 | #define INCLUDE_ARRAY_MACROS_FLUID_SRCT_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | // [1 : isize+0], [1 : jsize+0] 7 | #define SRCT(I, J) (srct[(I-1) + (isize+0) * (J-1)]) 8 | #define SRCT_NADDS (int [NDIMS][2]){ {0, 0}, {0, 0}, } 9 | 10 | #endif // INCLUDE_ARRAY_MACROS_FLUID_SRCT_H 11 | -------------------------------------------------------------------------------- /include/array_macros/fluid/srcux.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_FLUID_SRCUX_H) 2 | #define INCLUDE_ARRAY_MACROS_FLUID_SRCUX_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | // [2 : isize+0], [1 : jsize+0] 7 | #define SRCUX(I, J) (srcux[(I-2) + (isize-1) * (J-1)]) 8 | #define SRCUX_NADDS (int [NDIMS][2]){ {-1, 0}, {0, 0}, } 9 | 10 | #endif // INCLUDE_ARRAY_MACROS_FLUID_SRCUX_H 11 | -------------------------------------------------------------------------------- /include/array_macros/fluid/srcuy.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_FLUID_SRCUY_H) 2 | #define INCLUDE_ARRAY_MACROS_FLUID_SRCUY_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | // [1 : isize+0], [1 : jsize+0] 7 | #define SRCUY(I, J) (srcuy[(I-1) + (isize+0) * (J-1)]) 8 | #define SRCUY_NADDS (int [NDIMS][2]){ {0, 0}, {0, 0}, } 9 | 10 | #endif // INCLUDE_ARRAY_MACROS_FLUID_SRCUY_H 11 | -------------------------------------------------------------------------------- /include/array_macros/fluid/srcuz.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_FLUID_SRCUZ_H) 2 | #define INCLUDE_ARRAY_MACROS_FLUID_SRCUZ_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | #endif // INCLUDE_ARRAY_MACROS_FLUID_SRCUZ_H 7 | -------------------------------------------------------------------------------- /include/array_macros/fluid/t.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_FLUID_T_H) 2 | #define INCLUDE_ARRAY_MACROS_FLUID_T_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | // [0 : isize+1], [0 : jsize+1] 7 | #define T(I, J) (t[(I ) + (isize+2) * (J )]) 8 | #define T_NADDS (int [NDIMS][2]){ {1, 1}, {1, 1}, } 9 | 10 | #endif // INCLUDE_ARRAY_MACROS_FLUID_T_H 11 | -------------------------------------------------------------------------------- /include/array_macros/fluid/ux.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_FLUID_UX_H) 2 | #define INCLUDE_ARRAY_MACROS_FLUID_UX_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | // [1 : isize+1], [0 : jsize+1] 7 | #define UX(I, J) (ux[(I-1) + (isize+1) * (J )]) 8 | #define UX_NADDS (int [NDIMS][2]){ {0, 1}, {1, 1}, } 9 | 10 | #endif // INCLUDE_ARRAY_MACROS_FLUID_UX_H 11 | -------------------------------------------------------------------------------- /include/array_macros/fluid/uy.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_FLUID_UY_H) 2 | #define INCLUDE_ARRAY_MACROS_FLUID_UY_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | // [0 : isize+1], [0 : jsize+1] 7 | #define UY(I, J) (uy[(I ) + (isize+2) * (J )]) 8 | #define UY_NADDS (int [NDIMS][2]){ {1, 1}, {1, 1}, } 9 | 10 | #endif // INCLUDE_ARRAY_MACROS_FLUID_UY_H 11 | -------------------------------------------------------------------------------- /include/array_macros/fluid/uz.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_FLUID_UZ_H) 2 | #define INCLUDE_ARRAY_MACROS_FLUID_UZ_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | #endif // INCLUDE_ARRAY_MACROS_FLUID_UZ_H 7 | -------------------------------------------------------------------------------- /include/array_macros/ib/ibfx.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_IB_IBFX_H) 2 | #define INCLUDE_ARRAY_MACROS_IB_IBFX_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | // [1 : isize+0], [0 : jsize+1] 7 | #define IBFX(I, J) (ibfx[(I-1) + (isize+0) * (J )]) 8 | #define IBFX_NADDS (int [NDIMS][2]){ {0, 0}, {1, 1}, } 9 | 10 | #endif // INCLUDE_ARRAY_MACROS_IB_IBFX_H 11 | -------------------------------------------------------------------------------- /include/array_macros/ib/ibfy.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_IB_IBFY_H) 2 | #define INCLUDE_ARRAY_MACROS_IB_IBFY_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | // [1 : isize+0], [0 : jsize+1] 7 | #define IBFY(I, J) (ibfy[(I-1) + (isize+0) * (J )]) 8 | #define IBFY_NADDS (int [NDIMS][2]){ {0, 0}, {1, 1}, } 9 | 10 | #endif // INCLUDE_ARRAY_MACROS_IB_IBFY_H 11 | -------------------------------------------------------------------------------- /include/array_macros/ib/ibfz.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_IB_IBFZ_H) 2 | #define INCLUDE_ARRAY_MACROS_IB_IBFZ_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | #endif // INCLUDE_ARRAY_MACROS_IB_IBFZ_H 7 | -------------------------------------------------------------------------------- /include/array_macros/statistics/t1.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_STATISTICS_T1_H) 2 | #define INCLUDE_ARRAY_MACROS_STATISTICS_T1_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | // [0 : isize+1], [1 : jsize+0] 7 | #define T1(I, J) (t1[(I ) + (isize+2) * (J-1)]) 8 | #define T1_NADDS (int [NDIMS][2]){ {1, 1}, {0, 0}, } 9 | 10 | #endif // INCLUDE_ARRAY_MACROS_STATISTICS_T1_H 11 | -------------------------------------------------------------------------------- /include/array_macros/statistics/t2.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_STATISTICS_T2_H) 2 | #define INCLUDE_ARRAY_MACROS_STATISTICS_T2_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | // [0 : isize+1], [1 : jsize+0] 7 | #define T2(I, J) (t2[(I ) + (isize+2) * (J-1)]) 8 | #define T2_NADDS (int [NDIMS][2]){ {1, 1}, {0, 0}, } 9 | 10 | #endif // INCLUDE_ARRAY_MACROS_STATISTICS_T2_H 11 | -------------------------------------------------------------------------------- /include/array_macros/statistics/ux1.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_STATISTICS_UX1_H) 2 | #define INCLUDE_ARRAY_MACROS_STATISTICS_UX1_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | // [1 : isize+1], [1 : jsize+0] 7 | #define UX1(I, J) (ux1[(I-1) + (isize+1) * (J-1)]) 8 | #define UX1_NADDS (int [NDIMS][2]){ {0, 1}, {0, 0}, } 9 | 10 | #endif // INCLUDE_ARRAY_MACROS_STATISTICS_UX1_H 11 | -------------------------------------------------------------------------------- /include/array_macros/statistics/ux2.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_STATISTICS_UX2_H) 2 | #define INCLUDE_ARRAY_MACROS_STATISTICS_UX2_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | // [1 : isize+1], [1 : jsize+0] 7 | #define UX2(I, J) (ux2[(I-1) + (isize+1) * (J-1)]) 8 | #define UX2_NADDS (int [NDIMS][2]){ {0, 1}, {0, 0}, } 9 | 10 | #endif // INCLUDE_ARRAY_MACROS_STATISTICS_UX2_H 11 | -------------------------------------------------------------------------------- /include/array_macros/statistics/uxt.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_STATISTICS_UXT_H) 2 | #define INCLUDE_ARRAY_MACROS_STATISTICS_UXT_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | // [1 : isize+1], [1 : jsize+0] 7 | #define UXT(I, J) (uxt[(I-1) + (isize+1) * (J-1)]) 8 | #define UXT_NADDS (int [NDIMS][2]){ {0, 1}, {0, 0}, } 9 | 10 | #endif // INCLUDE_ARRAY_MACROS_STATISTICS_UXT_H 11 | -------------------------------------------------------------------------------- /include/array_macros/statistics/uy1.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_STATISTICS_UY1_H) 2 | #define INCLUDE_ARRAY_MACROS_STATISTICS_UY1_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | // [0 : isize+1], [1 : jsize+0] 7 | #define UY1(I, J) (uy1[(I ) + (isize+2) * (J-1)]) 8 | #define UY1_NADDS (int [NDIMS][2]){ {1, 1}, {0, 0}, } 9 | 10 | #endif // INCLUDE_ARRAY_MACROS_STATISTICS_UY1_H 11 | -------------------------------------------------------------------------------- /include/array_macros/statistics/uy2.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_STATISTICS_UY2_H) 2 | #define INCLUDE_ARRAY_MACROS_STATISTICS_UY2_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | // [0 : isize+1], [1 : jsize+0] 7 | #define UY2(I, J) (uy2[(I ) + (isize+2) * (J-1)]) 8 | #define UY2_NADDS (int [NDIMS][2]){ {1, 1}, {0, 0}, } 9 | 10 | #endif // INCLUDE_ARRAY_MACROS_STATISTICS_UY2_H 11 | -------------------------------------------------------------------------------- /include/array_macros/statistics/uz1.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_STATISTICS_UZ1_H) 2 | #define INCLUDE_ARRAY_MACROS_STATISTICS_UZ1_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | #endif // INCLUDE_ARRAY_MACROS_STATISTICS_UZ1_H 7 | -------------------------------------------------------------------------------- /include/array_macros/statistics/uz2.h: -------------------------------------------------------------------------------- 1 | #if !defined(INCLUDE_ARRAY_MACROS_STATISTICS_UZ2_H) 2 | #define INCLUDE_ARRAY_MACROS_STATISTICS_UZ2_H 3 | 4 | // This file is generated by tools/define_arrays.py 5 | 6 | #endif // INCLUDE_ARRAY_MACROS_STATISTICS_UZ2_H 7 | -------------------------------------------------------------------------------- /include/config.h: -------------------------------------------------------------------------------- 1 | #if !defined(CONFIG_H) 2 | #define CONFIG_H 3 | 4 | typedef struct { 5 | // getters for a double-precision value 6 | int (* const get_double)( 7 | const char dsetname[], 8 | double * value 9 | ); 10 | } config_t; 11 | 12 | extern const config_t config; 13 | 14 | #endif // CONFIG_H 15 | -------------------------------------------------------------------------------- /include/decide_dt.h: -------------------------------------------------------------------------------- 1 | #if !defined(DECIDE_DT_H) 2 | #define DECIDE_DT_H 3 | 4 | // decide next time step size 5 | extern int decide_dt( 6 | const domain_t * domain, 7 | const fluid_t * fluid, 8 | double * dt 9 | ); 10 | 11 | #endif // DECIDE_DT_H 12 | -------------------------------------------------------------------------------- /include/domain.h: -------------------------------------------------------------------------------- 1 | #if !defined(DOMAIN_H) 2 | #define DOMAIN_H 3 | 4 | #include "sdecomp.h" 5 | 6 | // definition of a structure domain_t 7 | /** 8 | * @struct domain_t 9 | * @brief struct storing parameters relevant to spatial domain 10 | * @var info : MPI domain decomposition 11 | * @var glsizes : global number of grid points in each direction 12 | * @var mysizes : local (my) number of grid points in each direction 13 | * @var offsets : offsets to my starting index in each direction 14 | * @var lengths : domain size in each direction 15 | * @var xf, xc : cell-face and cell-center locations in x direction 16 | * @var dxf, dxc : face-to-face and center-to-center distances in x direction 17 | * @var dx, dy, dz : face-to-face grid sizes 18 | */ 19 | typedef struct { 20 | sdecomp_info_t * info; 21 | size_t glsizes[NDIMS]; 22 | size_t mysizes[NDIMS]; 23 | size_t offsets[NDIMS]; 24 | double lengths[NDIMS]; 25 | double * restrict xf, * restrict xc; 26 | double * restrict dxf, * restrict dxc; 27 | double dx; 28 | double dy; 29 | } domain_t; 30 | 31 | // constructor 32 | extern int domain_init( 33 | const char dirname_ic[], 34 | domain_t * domain 35 | ); 36 | 37 | // save members which are necessary to restart 38 | extern int domain_save( 39 | const char dirname[], 40 | const domain_t * domain 41 | ); 42 | 43 | // check grid size uniformity 44 | extern int domain_check_x_grid_is_uniform( 45 | const domain_t * domain, 46 | bool * x_grid_is_uniform 47 | ); 48 | 49 | #endif // DOMAIN_H 50 | -------------------------------------------------------------------------------- /include/fileio.h: -------------------------------------------------------------------------------- 1 | #if !defined(FILEIO_H) 2 | #define FILEIO_H 3 | 4 | #include // FILE, size_t 5 | #include // MPI_Datatype 6 | 7 | typedef struct { 8 | // NPY datatypes, which are embedded in NPY files ("dtype" argument) 9 | // they are declared here and defined in src/fileio.c 10 | // NOTE: size (x-byte) may be wrong, depending on the architecture 11 | // 8-byte little-endian unsigned integer 12 | const char * npy_size_t; 13 | // 8-byte little-endian floating point 14 | const char * npy_double; 15 | // initialiser 16 | int (* const init)( 17 | void 18 | ); 19 | // general-purpose file opener 20 | FILE * (* const fopen)( 21 | const char * path, 22 | const char * mode 23 | ); 24 | // general-purpose file closer 25 | int (* const fclose)( 26 | FILE * stream 27 | ); 28 | // prepare directory to be stored 29 | int (* const mkdir)( 30 | const char dirname[] 31 | ); 32 | // NPY serial read (called by one process) 33 | int (* const r_serial)( 34 | const char dirname[], 35 | const char dsetname[], 36 | const size_t ndims, 37 | const size_t * shape, 38 | const char dtype[], 39 | const size_t size, 40 | void * data 41 | ); 42 | // NPY serial write (called by one process) 43 | int (* const w_serial)( 44 | const char dirname[], 45 | const char dsetname[], 46 | const size_t ndims, 47 | const size_t * shape, 48 | const char dtype[], 49 | const size_t size, 50 | const void * data 51 | ); 52 | // NPY parallel read of N-dimensional array (called by all processes) 53 | int (* const r_nd_parallel)( 54 | const MPI_Comm comm, 55 | const char dirname[], 56 | const char dsetname[], 57 | const size_t ndims, 58 | const int * array_of_sizes, 59 | const int * array_of_subsizes, 60 | const int * array_of_starts, 61 | const char dtype[], 62 | const size_t size, 63 | void * data 64 | ); 65 | // NPY parallel write of N-dimensional array (called by all processes) 66 | int (* const w_nd_parallel)( 67 | const MPI_Comm comm, 68 | const char dirname[], 69 | const char dsetname[], 70 | const size_t ndims, 71 | const int * array_of_sizes, 72 | const int * array_of_subsizes, 73 | const int * array_of_starts, 74 | const char dtype[], 75 | const size_t size, 76 | const void * data 77 | ); 78 | } fileio_t; 79 | 80 | extern const fileio_t fileio; 81 | 82 | #endif // FILEIO_H 83 | -------------------------------------------------------------------------------- /include/fluid.h: -------------------------------------------------------------------------------- 1 | #if !defined(FLUID_H) 2 | #define FLUID_H 3 | 4 | #include "array.h" 5 | #include "domain.h" 6 | 7 | // definition of a structure fluid_t_ 8 | /** 9 | * @struct fluid_t 10 | * @brief struct storing fluid-related variables 11 | * @var ux, uy, uz : velocity in each direction 12 | * @var p, psi : pressure, scalar potential 13 | * @var t : temperature 14 | * @var srcux : Runge-Kutta source terms for ux 15 | * @var srcuy : Runge-Kutta source terms for uy 16 | * @var srcuz : Runge-Kutta source terms for uz 17 | * @var Ra, Pr : non-dimensional parameters 18 | * @var m_dif, t_dif : momentum / temperature diffusivities 19 | */ 20 | typedef struct { 21 | array_t ux; 22 | array_t uy; 23 | array_t p; 24 | array_t psi; 25 | array_t t; 26 | array_t srcux[3]; 27 | array_t srcuy[3]; 28 | array_t srct[3]; 29 | double Ra, Pr; 30 | double m_dif, t_dif; 31 | } fluid_t; 32 | 33 | #endif // FLUID_H 34 | -------------------------------------------------------------------------------- /include/fluid_solver.h: -------------------------------------------------------------------------------- 1 | #if !defined(FLUID_SOLVER_H) 2 | #define FLUID_SOLVER_H 3 | 4 | #include "array.h" 5 | #include "domain.h" 6 | #include "fluid.h" 7 | #include "ib.h" 8 | 9 | // initialiser of fluid_t 10 | extern int fluid_init( 11 | const char dirname_ic[], 12 | const domain_t * domain, 13 | fluid_t * fluid 14 | ); 15 | 16 | // save flow field 17 | extern int fluid_save( 18 | const char dirname[], 19 | const domain_t * domain, 20 | const fluid_t * fluid 21 | ); 22 | 23 | // predict the new velocity field and update the temperature field 24 | extern int fluid_compute_rhs( 25 | const domain_t * domain, 26 | fluid_t * fluid 27 | ); 28 | 29 | // couple external forces 30 | extern int fluid_couple_external_force( 31 | const domain_t * domain, 32 | fluid_t * fluid 33 | ); 34 | 35 | // update fields using the previously-computed RK source terms 36 | extern int fluid_predict_field( 37 | const domain_t * domain, 38 | const size_t rkstep, 39 | const double dt, 40 | fluid_t * fluid 41 | ); 42 | 43 | // compute scalar potential by solving Poisson equation 44 | extern int fluid_compute_potential( 45 | const domain_t * domain, 46 | const size_t rkstep, 47 | const double dt, 48 | fluid_t * fluid 49 | ); 50 | 51 | // correct velocity field using scalar potential 52 | extern int fluid_correct_velocity( 53 | const domain_t * domain, 54 | const size_t rkstep, 55 | const double dt, 56 | fluid_t * fluid 57 | ); 58 | 59 | // update pressure 60 | extern int fluid_update_pressure( 61 | const domain_t * domain, 62 | const size_t rkstep, 63 | const double dt, 64 | fluid_t * fluid 65 | ); 66 | 67 | // exchange halos and impose boundary conditions 68 | 69 | extern int fluid_update_boundaries_ux( 70 | const domain_t * domain, 71 | array_t * ux 72 | ); 73 | 74 | extern int fluid_update_boundaries_uy( 75 | const domain_t * domain, 76 | array_t * uy 77 | ); 78 | 79 | extern int fluid_update_boundaries_p( 80 | const domain_t * domain, 81 | array_t * p 82 | ); 83 | 84 | extern int fluid_update_boundaries_psi( 85 | const domain_t * domain, 86 | array_t * psi 87 | ); 88 | 89 | extern int fluid_update_boundaries_t( 90 | const domain_t * domain, 91 | array_t * t 92 | ); 93 | 94 | #endif // FLUID_SOLVER_H 95 | -------------------------------------------------------------------------------- /include/halo.h: -------------------------------------------------------------------------------- 1 | #if !defined(HALO_H) 2 | #define HALO_H 3 | 4 | int halo_communicate_in_y( 5 | const domain_t * domain, 6 | MPI_Datatype * dtype, 7 | array_t * array 8 | ); 9 | 10 | #endif // HALO_H 11 | -------------------------------------------------------------------------------- /include/ib.h: -------------------------------------------------------------------------------- 1 | #if !defined(IB_H) 2 | #define IB_H 3 | 4 | #include "array.h" 5 | #include "domain.h" 6 | 7 | typedef struct { 8 | // radius and density 9 | double r, d; 10 | // mass and moment of inertia 11 | double m, mi; 12 | // position and displacement 13 | double x, dx; 14 | double y, dy; 15 | // translational velocity, surface force, internal inertia, collision force 16 | double ux, dux, fux, iux[2], cfx[2]; 17 | double uy, duy, fuy, iuy[2], cfy[2]; 18 | // angular velocity, torque, internal angular inertia 19 | double vz, dvz, tvz, ivz[2]; 20 | } particle_t; 21 | 22 | typedef struct { 23 | // circular objects 24 | size_t nitems; 25 | particle_t * particles; 26 | // response to the momentum field 27 | array_t ibfx; 28 | array_t ibfy; 29 | array_t ibfz; 30 | } ib_t; 31 | 32 | #endif // IB_H 33 | -------------------------------------------------------------------------------- /include/ib_solver.h: -------------------------------------------------------------------------------- 1 | #if !defined(IB_SOLVER_H) 2 | #define IB_SOLVER_H 3 | 4 | #include "domain.h" 5 | #include "fluid.h" 6 | #include "ib.h" 7 | 8 | extern int ib_init( 9 | const char dirname_ic[], 10 | const domain_t * domain, 11 | ib_t * ib 12 | ); 13 | 14 | extern int ib_reset_variables( 15 | ib_t * ib 16 | ); 17 | 18 | extern int ib_exchange_momentum( 19 | const domain_t * domain, 20 | const size_t rkstep, 21 | const double dt, 22 | fluid_t * fluid, 23 | ib_t * ib 24 | ); 25 | 26 | extern int ib_compute_inertia( 27 | const domain_t * domain, 28 | const size_t index, 29 | const fluid_t * fluid, 30 | ib_t * ib 31 | ); 32 | 33 | extern int ib_compute_collision_force( 34 | const domain_t * domain, 35 | const size_t index, 36 | ib_t * ib 37 | ); 38 | 39 | extern int ib_increment_particles( 40 | const size_t rkstep, 41 | double dt, 42 | ib_t * ib 43 | ); 44 | 45 | extern int ib_update_particles( 46 | const domain_t * domain, 47 | ib_t * ib 48 | ); 49 | 50 | extern int ib_save( 51 | const char dirname[], 52 | const domain_t * domain, 53 | const ib_t * ib 54 | ); 55 | 56 | #endif // IB_SOLVER_H 57 | -------------------------------------------------------------------------------- /include/integrate.h: -------------------------------------------------------------------------------- 1 | #if !defined(INTEGRATE_H) 2 | #define INTEGRATE_H 3 | 4 | // main integrator 5 | extern int integrate( 6 | const domain_t * domain, 7 | fluid_t * fluid, 8 | ib_t * ib, 9 | double * dt 10 | ); 11 | 12 | #endif // INTEGRATE_H 13 | -------------------------------------------------------------------------------- /include/linear_system.h: -------------------------------------------------------------------------------- 1 | #if !defined(LINEAR_SYSTEM_H) 2 | #define LINEAR_SYSTEM_H 3 | 4 | #include 5 | #include "sdecomp.h" 6 | #include "domain.h" 7 | #include "tdm.h" 8 | 9 | /** 10 | * @struct linear_system_t 11 | * @brief structure storing buffers and plans to solve tri-diagonal linear systems in each dimension A x = b 12 | * @var is_initialised : flag to check the variable is initialised 13 | * @var implicit : flags whether directions are treated implicitly, 14 | * namely linear systems are to be solved 15 | * @var x1pncl : buffers to store x1-pencil 16 | * @var y1pncl : buffers to store y1-pencil 17 | * @var z2pncl : buffers to store z2-pencil 18 | * @var x1pncl_mysizes : size of (local) x1pencil 19 | * @var y1pncl_mysizes : size of (local) y1pencil 20 | * @var z2pncl_mysizes : size of (local) z2pencil 21 | * @var tdm_[x-z] : thomas algorithm solvers in all directions 22 | * @var transposer_xx_to_xx : plans to transpose between two pencils 23 | */ 24 | typedef struct { 25 | bool is_initialised; 26 | bool implicit[NDIMS]; 27 | double * restrict x1pncl; 28 | double * restrict y1pncl; 29 | size_t x1pncl_mysizes[NDIMS]; 30 | size_t y1pncl_mysizes[NDIMS]; 31 | tdm_info_t * tdm_x; 32 | tdm_info_t * tdm_y; 33 | sdecomp_transpose_plan_t * transposer_x1_to_y1; 34 | sdecomp_transpose_plan_t * transposer_y1_to_x1; 35 | } linear_system_t; 36 | 37 | extern int linear_system_init( 38 | const sdecomp_info_t * info, 39 | const bool implicit[NDIMS], 40 | const size_t glsizes[NDIMS], 41 | linear_system_t * linear_system 42 | ); 43 | 44 | extern int linear_system_finalise( 45 | linear_system_t * linear_system 46 | ); 47 | 48 | #endif // LINEAR_SYSTEM_H 49 | -------------------------------------------------------------------------------- /include/logging.h: -------------------------------------------------------------------------------- 1 | #if !defined(LOGGING_H) 2 | #define LOGGING_H 3 | 4 | #include "domain.h" 5 | #include "fluid.h" 6 | 7 | typedef struct { 8 | // constructor 9 | int (* const init)( 10 | const domain_t * domain, 11 | const double time 12 | ); 13 | // check quantities and dump to log files 14 | void (* const check_and_output)( 15 | const domain_t * domain, 16 | const size_t step, 17 | const double time, 18 | const double dt, 19 | const double wtime, 20 | const fluid_t * fluid 21 | ); 22 | // getter, next timing to call "check_and_output" 23 | double (* const get_next_time)( 24 | void 25 | ); 26 | } logging_t; 27 | 28 | extern const logging_t logging; 29 | 30 | #endif // LOGGING_H 31 | -------------------------------------------------------------------------------- /include/memory.h: -------------------------------------------------------------------------------- 1 | #if !defined(MEMORY_H) 2 | #define MEMORY_H 3 | 4 | #include // size_t 5 | 6 | // general-purpose memory allocator 7 | extern void * memory_calloc( 8 | const size_t count, 9 | const size_t size 10 | ); 11 | 12 | // corresponding memory deallocator 13 | extern void memory_free( 14 | void * ptr 15 | ); 16 | 17 | #endif // MEMORY_H 18 | -------------------------------------------------------------------------------- /include/param.h: -------------------------------------------------------------------------------- 1 | #if !defined(PARAM_H) 2 | #define PARAM_H 3 | 4 | // fixed parameters, which are usually fixed 5 | // but still user can easily control, are declared 6 | // they are defined under src/param/xxx.c 7 | 8 | #include 9 | 10 | /* buoyancy.c */ 11 | // flag to specify whether the buoyancy force is added 12 | // to the RHS of the momentum equation or not 13 | // if not, the temperature behaves as a passive scalar 14 | extern const bool param_add_buoyancy; 15 | 16 | /* implicit.c */ 17 | // flags to specify the diffusive treatment of the momentum equations 18 | extern const bool param_m_implicit_x; 19 | extern const bool param_m_implicit_y; 20 | // flags to specify the diffusive treatment of the temperature equation 21 | extern const bool param_t_implicit_x; 22 | extern const bool param_t_implicit_y; 23 | 24 | /* boundary-condition.c */ 25 | // NOTE: changing values may break the Nusselt balance 26 | // NOTE: impermeable walls and Neumann BC for the pressure are unchangeable 27 | // negative-x-wall velocity in y direction 28 | extern const double param_uy_xm; 29 | // positive-x-wall velocity in y direction 30 | extern const double param_uy_xp; 31 | // negative-x-wall temperature 32 | extern const double param_t_xm; 33 | // positive-x-wall temperature 34 | extern const double param_t_xp; 35 | 36 | #endif // PARAM_H 37 | -------------------------------------------------------------------------------- /include/runge_kutta.h: -------------------------------------------------------------------------------- 1 | #if !defined(RUNGE_KUTTA_H) 2 | #define RUNGE_KUTTA_H 3 | 4 | #include 5 | 6 | // Runge-Kutta configurations 7 | // indices 8 | extern const uint_fast8_t rk_a; // 0 9 | extern const uint_fast8_t rk_b; // 1 10 | extern const uint_fast8_t rk_g; // 2 11 | // NOTE: alpha, beta, gamma and thus three here 12 | typedef double rkcoef_t[3]; 13 | // NOTE: only three-step Wray is allowed 14 | #define RKSTEPMAX 3 15 | extern const rkcoef_t rkcoefs[RKSTEPMAX]; 16 | 17 | #endif // RUNGE_KUTTA_H 18 | -------------------------------------------------------------------------------- /include/save.h: -------------------------------------------------------------------------------- 1 | #if !defined(SAVE_H) 2 | #define SAVE_H 3 | 4 | #include "domain.h" 5 | #include "fluid.h" 6 | 7 | typedef struct save_t_ { 8 | // constructor 9 | int (* const init)( 10 | const domain_t * domain, 11 | const double time 12 | ); 13 | // make space to save flow fields, save some scalars 14 | int (* const prepare)( 15 | const domain_t * domain, 16 | const int step, 17 | char ** dirname 18 | ); 19 | // getter, next timing to call "output" 20 | double (* const get_next_time)( 21 | void 22 | ); 23 | } save_t; 24 | 25 | extern const save_t save; 26 | 27 | #endif // SAVE_H 28 | -------------------------------------------------------------------------------- /include/statistics.h: -------------------------------------------------------------------------------- 1 | #if !defined(STATISTICS_H) 2 | #define STATISTICS_H 3 | 4 | #include "domain.h" 5 | #include "fluid.h" 6 | 7 | typedef struct { 8 | // constructor 9 | int (* const init)( 10 | const domain_t * domain, 11 | const double time 12 | ); 13 | // collecting statistics 14 | int (* const collect)( 15 | const domain_t * domain, 16 | const fluid_t * fluid 17 | ); 18 | // save statistics to files 19 | int (* const output)( 20 | const domain_t * domain, 21 | const size_t step 22 | ); 23 | // getter, next timing to call "collect" 24 | double (* const get_next_time)( 25 | void 26 | ); 27 | } statistics_t; 28 | 29 | extern const statistics_t statistics; 30 | 31 | #endif // STATISTICS_H 32 | -------------------------------------------------------------------------------- /include/tdm.h: -------------------------------------------------------------------------------- 1 | #if !defined(TDM_H) 2 | #define TDM_H 3 | 4 | #include 5 | 6 | typedef struct tdm_info_t_ tdm_info_t; 7 | 8 | typedef struct { 9 | int (* const construct)( 10 | const int size, 11 | const int nrhs, 12 | const bool is_periodic, 13 | const bool is_complex, 14 | tdm_info_t ** info 15 | ); 16 | int (* const get_l)( 17 | const tdm_info_t * info, 18 | double * restrict * l 19 | ); 20 | int (* const get_c)( 21 | const tdm_info_t * info, 22 | double * restrict * c 23 | ); 24 | int (* const get_u)( 25 | const tdm_info_t * info, 26 | double * restrict * u 27 | ); 28 | int (* const get_size)( 29 | const tdm_info_t * info, 30 | int * size 31 | ); 32 | int (* const get_nrhs)( 33 | const tdm_info_t * info, 34 | int * nrhs 35 | ); 36 | int (* const solve)( 37 | const tdm_info_t * info, 38 | void * restrict data 39 | ); 40 | int (* const destruct)( 41 | tdm_info_t * info 42 | ); 43 | } tdm_t; 44 | 45 | extern const tdm_t tdm; 46 | 47 | #endif // TDM_H 48 | -------------------------------------------------------------------------------- /include/timer.h: -------------------------------------------------------------------------------- 1 | #if !defined(TIMER_H) 2 | #define TIMER_H 3 | 4 | // get current time 5 | extern double timer( 6 | void 7 | ); 8 | 9 | #endif // TIMER_H 10 | -------------------------------------------------------------------------------- /initial_condition/Makefile: -------------------------------------------------------------------------------- 1 | help: 2 | @echo "output : make directory to store NPY files" 3 | @echo "datadel : remove NPY files" 4 | @echo "help : show this message" 5 | 6 | output: 7 | @if [ ! -e output ]; then \ 8 | mkdir output; \ 9 | fi 10 | 11 | datadel: 12 | $(RM) output/*.npy 13 | 14 | .PHONY : help output datadel 15 | 16 | -------------------------------------------------------------------------------- /initial_condition/README.rst: -------------------------------------------------------------------------------- 1 | ################# 2 | initial_condition 3 | ################# 4 | 5 | ******** 6 | Overview 7 | ******** 8 | 9 | This directory contains a Python script to initialise the domain and the flow field, which will be simulated by the main solver. 10 | Note that this initialiser is not parallelised for simplicity. 11 | 12 | ************* 13 | Configuration 14 | ************* 15 | 16 | See ``main.py``. 17 | 18 | ***** 19 | Usage 20 | ***** 21 | 22 | .. code-block:: console 23 | 24 | make output 25 | bash exec.sh 26 | 27 | giving several ``NPY`` files under the specified directory (``output`` by default). 28 | 29 | These files will be loaded by the main simulator. 30 | 31 | -------------------------------------------------------------------------------- /initial_condition/exec.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## domain 4 | # domain lengths 5 | export lx=1.0e+0 6 | export ly=2.0e+0 7 | # number of cell centers 8 | export glisize=128 9 | export gljsize=256 10 | 11 | ## where to write resulting NPY files 12 | dirname="output" 13 | 14 | python3 main.py ${dirname} 15 | -------------------------------------------------------------------------------- /initial_condition/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import numpy as np 4 | 5 | 6 | def init_time(dest): 7 | # iterator and time 8 | step = np.array(0, dtype=np.uint64) 9 | time = np.array(0, dtype=np.float64) 10 | np.save(f"{dest}/step.npy", step) 11 | np.save(f"{dest}/time.npy", time) 12 | return 13 | 14 | 15 | def init_domain(lengths, glsizes, dest): 16 | np.save(f"{dest}/glsizes.npy", np.array(glsizes, dtype=np.uint64)) 17 | np.save(f"{dest}/lengths.npy", np.array(lengths, dtype=np.float64)) 18 | return 19 | 20 | 21 | def init_fluid(lengths, glsizes, dest): 22 | shape0 = (glsizes[1], glsizes[0] + 1) 23 | shape1 = (glsizes[1], glsizes[0] + 2) 24 | ux = np.zeros(shape0, dtype=np.float64) 25 | uy = np.zeros(shape1, dtype=np.float64) 26 | p = np.zeros(shape1, dtype=np.float64) 27 | t = -0.5 + np.random.random_sample(shape1) 28 | np.save(f"{dest}/ux.npy", ux) 29 | np.save(f"{dest}/uy.npy", uy) 30 | np.save(f"{dest}/p.npy", p) 31 | np.save(f"{dest}/t.npy", t) 32 | return 33 | 34 | 35 | def init_particles(lengths, glsizes, dest): 36 | nitems = 32 37 | rs = [1. / 8. - 1. / 16. * np.random.random_sample() for _ in range(nitems)] 38 | ds = [1. for _ in range(nitems)] 39 | xs = [0.125, 0.375, 0.625, 0.875] 40 | ys = [0.125, 0.375, 0.625, 0.875, 1.125, 1.375, 1.625, 1.875] 41 | xs, ys = np.meshgrid(xs, ys) 42 | xs = np.ravel(xs) 43 | ys = np.ravel(ys) 44 | uxs = [0. for _ in range(nitems)] 45 | uys = [0. for _ in range(nitems)] 46 | vzs = [0. for _ in range(nitems)] 47 | np.save(f"{dest}/p_nitems.npy", np.uint64(nitems)) 48 | np.save(f"{dest}/p_rs.npy", np.float64(rs)) 49 | np.save(f"{dest}/p_ds.npy", np.float64(ds)) 50 | np.save(f"{dest}/p_xs.npy", np.float64(xs)) 51 | np.save(f"{dest}/p_ys.npy", np.float64(ys)) 52 | np.save(f"{dest}/p_uxs.npy", np.float64(uxs)) 53 | np.save(f"{dest}/p_uys.npy", np.float64(uys)) 54 | np.save(f"{dest}/p_vzs.npy", np.float64(vzs)) 55 | return 56 | 57 | 58 | def main(): 59 | argv = sys.argv 60 | assert 2 == len(argv) 61 | lengths = [ 62 | float(os.environ["lx"]), 63 | float(os.environ["ly"]), 64 | ] 65 | glsizes = [ 66 | int(os.environ["glisize"]), 67 | int(os.environ["gljsize"]), 68 | ] 69 | dest = argv[1] 70 | init_time(dest) 71 | init_domain(lengths, glsizes, dest) 72 | init_fluid(lengths, glsizes, dest) 73 | init_particles(lengths, glsizes, dest) 74 | return 75 | 76 | 77 | main() 78 | -------------------------------------------------------------------------------- /src/README.rst: -------------------------------------------------------------------------------- 1 | #### 2 | src/ 3 | #### 4 | 5 | All functions are implemented here. 6 | For the directories, see the corresponding README file. 7 | 8 | * array.c 9 | 10 | Function to initialise, destruct, load, and save multi-dimensional arrays including halo cells are implemented. 11 | 12 | * config.c 13 | 14 | Environment variable loader which is called when the solver is launched to acquire the runtime-parameters by the user is implemented. 15 | 16 | * domain.c 17 | 18 | Information about the coordinate systems, the grid configuration, and the domain parallelisation is included. 19 | 20 | * linear_system.c 21 | 22 | Functions to initialise and destruct the tri-diagonal linear system solver are included. 23 | 24 | * main.c 25 | 26 | Main function is here. 27 | 28 | * memory.c 29 | 30 | Utility functions and global parameters are defined. 31 | 32 | * runge_kutta.c 33 | 34 | Runge-Kutta coefficients are defined. 35 | 36 | * save.c 37 | 38 | Functions to save flow field and parameters for restart are included. 39 | 40 | * statistics.c 41 | 42 | Routines to collect statistical data are implemented. 43 | 44 | * tdm.c 45 | 46 | Kernel functions to solve tri-diagonal matrices are implemented. 47 | 48 | * timer.c 49 | 50 | A function to obtain the current wall time is implemented. 51 | 52 | -------------------------------------------------------------------------------- /src/array.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "sdecomp.h" 5 | #include "memory.h" 6 | #include "domain.h" 7 | #include "array.h" 8 | #include "fileio.h" 9 | 10 | static int prepare( 11 | const domain_t * domain, 12 | const int nadds[NDIMS][2], 13 | size_t size, 14 | array_t * array 15 | ){ 16 | // get total number of cells 17 | const size_t * mysizes = domain->mysizes; 18 | size_t nitems = 1; 19 | for(size_t dim = 0; dim < NDIMS; dim++){ 20 | nitems *= mysizes[dim] + nadds[dim][0] + nadds[dim][1]; 21 | } 22 | // for now local array size should be smaller than INT_MAX, 23 | // since (4-byte) integer counters are used to sweep arrays 24 | // to allow negative array indices 25 | int retval = 0; 26 | if(nitems > INT_MAX){ 27 | printf("local array size (%zu) exceeds INT_MAX (%d)\n", nitems, INT_MAX); 28 | retval = 1; 29 | } 30 | MPI_Comm comm_cart = MPI_COMM_NULL; 31 | sdecomp.get_comm_cart(domain->info, &comm_cart); 32 | MPI_Allreduce(MPI_IN_PLACE, &retval, 1, MPI_INT, MPI_MAX, comm_cart); 33 | if(0 != retval){ 34 | return retval; 35 | } 36 | // assign members 37 | array->size = size; 38 | array->nadds = memory_calloc(NDIMS, 2 * sizeof(int)); 39 | for(size_t dim = 0; dim < NDIMS; dim++){ 40 | array->nadds[dim][0] = nadds[dim][0]; 41 | array->nadds[dim][1] = nadds[dim][1]; 42 | } 43 | array->datasize = nitems * size; 44 | array->data = memory_calloc(nitems, size); 45 | return 0; 46 | } 47 | 48 | static int destroy( 49 | array_t * array 50 | ){ 51 | memory_free(array->nadds); 52 | memory_free(array->data); 53 | return 0; 54 | } 55 | 56 | // array->data, including additional (boundary & halo) cells 57 | // [1 - nadds[0][0] : mysizes[0] + nadds[0][1]] 58 | // x 59 | // [1 - nadds[1][0] : mysizes[1] + nadds[1][1]] 60 | // x 61 | // [1 - nadds[2][0] : mysizes[2] + nadds[2][1]] 62 | // buf, holding only data to be written / loaded 63 | // [1 - nadds[0][0] : mysizes[0] + nadds[0][1]] 64 | // x 65 | // [1 : mysizes[1] ] 66 | // x 67 | // [1 : mysizes[2] ] 68 | 69 | static int get_index( 70 | const int mysizes[NDIMS], 71 | const int nadds[NDIMS][2], 72 | const int indices[NDIMS] 73 | ){ 74 | const int index = 75 | + indices[0] 76 | + (indices[1] + nadds[1][0]) * (mysizes[0] + nadds[0][0] + nadds[0][1]); 77 | return index; 78 | } 79 | 80 | static int load( 81 | const domain_t * domain, 82 | const char dirname[], 83 | const char dsetname[], 84 | const char dtype[], 85 | array_t * array 86 | ){ 87 | const size_t * glsizes = domain->glsizes; 88 | const size_t * mysizes = domain->mysizes; 89 | const size_t * offsets = domain->offsets; 90 | const int nadds[NDIMS][2] = { 91 | {array->nadds[0][0], array->nadds[0][1]}, 92 | {array->nadds[1][0], array->nadds[1][1]}, 93 | }; 94 | const size_t size = array->size; 95 | char * data = array->data; 96 | // prepare a buffer 97 | char * buf = memory_calloc( 98 | (mysizes[0] + nadds[0][0] + nadds[0][1]) * mysizes[1], 99 | size 100 | ); 101 | // read 102 | MPI_Comm comm_cart = MPI_COMM_NULL; 103 | sdecomp.get_comm_cart(domain->info, &comm_cart); 104 | const int retval = fileio.r_nd_parallel( 105 | comm_cart, 106 | dirname, 107 | dsetname, 108 | NDIMS, 109 | (int [NDIMS]){ 110 | glsizes[1], 111 | glsizes[0] + nadds[0][0] + nadds[0][1], 112 | }, 113 | (int [NDIMS]){ 114 | mysizes[1], 115 | mysizes[0] + nadds[0][0] + nadds[0][1], 116 | }, 117 | (int [NDIMS]){ 118 | offsets[1], 119 | offsets[0], 120 | }, 121 | dtype, 122 | size, 123 | buf 124 | ); 125 | if(0 != retval){ 126 | memory_free(buf); 127 | return 1; 128 | } 129 | // copy 130 | const int imax = mysizes[0] + nadds[0][0] + nadds[0][1]; 131 | const int jmax = mysizes[1]; 132 | for(int cnt = 0, j = 0; j < jmax; j++){ 133 | for(int i = 0; i < imax; i++, cnt++){ 134 | const int index = get_index( 135 | (int [NDIMS]){mysizes[0], mysizes[1]}, 136 | nadds, 137 | (int [NDIMS]){i, j} 138 | ); 139 | memcpy(data + size * index, buf + size * cnt, size); 140 | } 141 | } 142 | memory_free(buf); 143 | return 0; 144 | } 145 | 146 | static int dump( 147 | const domain_t * domain, 148 | const char dirname[], 149 | const char dsetname[], 150 | const char dtype[], 151 | const array_t * array 152 | ){ 153 | const size_t * glsizes = domain->glsizes; 154 | const size_t * mysizes = domain->mysizes; 155 | const size_t * offsets = domain->offsets; 156 | const int nadds[NDIMS][2] = { 157 | {array->nadds[0][0], array->nadds[0][1]}, 158 | {array->nadds[1][0], array->nadds[1][1]}, 159 | }; 160 | const size_t size = array->size; 161 | const char * data = array->data; 162 | // prepare a buffer 163 | char * buf = memory_calloc( 164 | (mysizes[0] + nadds[0][0] + nadds[0][1]) * mysizes[1], 165 | size 166 | ); 167 | // copy 168 | const int imax = mysizes[0] + nadds[0][0] + nadds[0][1]; 169 | const int jmax = mysizes[1]; 170 | for(int cnt = 0, j = 0; j < jmax; j++){ 171 | for(int i = 0; i < imax; i++, cnt++){ 172 | const int index = get_index( 173 | (int [NDIMS]){mysizes[0], mysizes[1]}, 174 | nadds, 175 | (int [NDIMS]){i, j} 176 | ); 177 | memcpy(buf + size * cnt, data + size * index, size); 178 | } 179 | } 180 | // write 181 | MPI_Comm comm_cart = MPI_COMM_NULL; 182 | sdecomp.get_comm_cart(domain->info, &comm_cart); 183 | fileio.w_nd_parallel( 184 | comm_cart, 185 | dirname, 186 | dsetname, 187 | NDIMS, 188 | (int [NDIMS]){ 189 | glsizes[1], 190 | glsizes[0] + nadds[0][0] + nadds[0][1], 191 | }, 192 | (int [NDIMS]){ 193 | mysizes[1], 194 | mysizes[0] + nadds[0][0] + nadds[0][1], 195 | }, 196 | (int [NDIMS]){ 197 | offsets[1], 198 | offsets[0], 199 | }, 200 | dtype, 201 | size, 202 | buf 203 | ); 204 | memory_free(buf); 205 | return 0; 206 | } 207 | 208 | const array_method_t array = { 209 | .prepare = prepare, 210 | .destroy = destroy, 211 | .load = load, 212 | .dump = dump, 213 | }; 214 | 215 | -------------------------------------------------------------------------------- /src/config.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "config.h" 7 | 8 | /** 9 | * @brief load environment variable and interpret it as an double-precision value 10 | * @param[in] dsetname : name of the environment variable 11 | * @param[out] value : resulting value 12 | * @return : error code 13 | */ 14 | static int get_double( 15 | const char dsetname[], 16 | double * value 17 | ){ 18 | // error code 19 | int retval = 0; 20 | // try to load, may fail if the variable is not defined 21 | char * string = getenv(dsetname); 22 | if(NULL == string){ 23 | retval = 1; 24 | } 25 | MPI_Allreduce(MPI_IN_PLACE, &retval, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD); 26 | if(0 != retval){ 27 | printf("%s not found\n", dsetname); 28 | return 1; 29 | } 30 | // try to convert a string to a double-precision value 31 | errno = 0; 32 | *value = strtod(string, NULL); 33 | if(0 != errno){ 34 | retval = 1; 35 | } 36 | MPI_Allreduce(MPI_IN_PLACE, &retval, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD); 37 | if(0 != retval){ 38 | printf("%s: invalid value as double\n", dsetname); 39 | return 1; 40 | } 41 | return 0; 42 | } 43 | 44 | const config_t config = { 45 | .get_double = get_double, 46 | }; 47 | 48 | -------------------------------------------------------------------------------- /src/decide_dt.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "config.h" 6 | #include "param.h" 7 | #include "array.h" 8 | #include "sdecomp.h" 9 | #include "domain.h" 10 | #include "fluid.h" 11 | #include "ib.h" 12 | #include "array_macros/fluid/ux.h" 13 | #include "array_macros/fluid/uy.h" 14 | 15 | // overriden later using environment variables 16 | static bool coefs_are_initialised = false; 17 | static double coef_dt_adv = 0.; 18 | static double coef_dt_dif = 0.; 19 | 20 | /** 21 | * @brief decide time step size restricted by the advective terms 22 | * @param[in] domain : information about domain decomposition and size 23 | * @param[in] fluid : velocity 24 | * @param[out] dt : time step size 25 | * @return : error code 26 | */ 27 | static int decide_dt_adv( 28 | const domain_t * domain, 29 | const fluid_t * fluid, 30 | double * restrict dt 31 | ){ 32 | MPI_Comm comm_cart = MPI_COMM_NULL; 33 | sdecomp.get_comm_cart(domain->info, &comm_cart); 34 | const int isize = domain->mysizes[0]; 35 | const int jsize = domain->mysizes[1]; 36 | const double dx = domain->dx; 37 | const double dy = domain->dy; 38 | const double * restrict ux = fluid->ux.data; 39 | const double * restrict uy = fluid->uy.data; 40 | // sufficiently small number to avoid zero division 41 | const double small = 1.e-8; 42 | // init with max possible dt 43 | *dt = 1.; 44 | // compute grid-size over velocity in x 45 | for(int j = 1; j <= jsize; j++){ 46 | for(int i = 2; i <= isize; i++){ 47 | double vel = fabs(UX(i, j)) + small; 48 | *dt = fmin(*dt, dx / vel); 49 | } 50 | } 51 | // compute grid-size over velocity in y 52 | for(int j = 1; j <= jsize; j++){ 53 | for(int i = 1; i <= isize; i++){ 54 | double vel = fabs(UY(i, j)) + small; 55 | *dt = fmin(*dt, dy / vel); 56 | } 57 | } 58 | // compute grid-size over velocity in z 59 | // unify result, multiply safety factor 60 | MPI_Allreduce(MPI_IN_PLACE, dt, 1, MPI_DOUBLE, MPI_MIN, comm_cart); 61 | *dt *= coef_dt_adv; 62 | return 0; 63 | } 64 | 65 | /** 66 | * @brief decide time step size restricted by the diffusive terms 67 | * @param[in] domain : grid size 68 | * @param[in] diffusivity : fluid / temperature diffusivity 69 | * @param[out] dt : time step size 70 | * @return : error code 71 | */ 72 | static int decide_dt_dif( 73 | const domain_t * domain, 74 | const double diffusivity, 75 | double * dt 76 | ){ 77 | const double dx = domain->dx; 78 | const double dy = domain->dy; 79 | double grid_sizes[NDIMS] = {0.}; 80 | grid_sizes[0] = dx; 81 | grid_sizes[1] = dy; 82 | // compute diffusive constraints 83 | for(int dim = 0; dim < NDIMS; dim++){ 84 | dt[dim] = coef_dt_dif / diffusivity * 0.5 / NDIMS * pow(grid_sizes[dim], 2.); 85 | } 86 | return 0; 87 | } 88 | 89 | static int decide_dt_ib( 90 | const domain_t * domain, 91 | const ib_t * ib, 92 | double * dt 93 | ){ 94 | MPI_Comm comm_cart = MPI_COMM_NULL; 95 | sdecomp.get_comm_cart(domain->info, &comm_cart); 96 | const double dx = domain->dx; 97 | const double dy = domain->dy; 98 | const size_t nitems = ib->nitems; 99 | const particle_t * particles = ib->particles; 100 | // sufficiently small number to avoid zero division 101 | const double small = 1.e-8; 102 | // init with max possible dt 103 | *dt = 1.; 104 | // compute grid-size over velocity in x 105 | for(size_t n = 0; n < nitems; n++){ 106 | const particle_t * p = particles + n; 107 | double ux = fabs(p->ux) + p->r * fabs(p->vz) + small; 108 | double uy = fabs(p->uy) + p->r * fabs(p->vz) + small; 109 | *dt = fmin(*dt, dx / ux); 110 | *dt = fmin(*dt, dy / uy); 111 | } 112 | return 0; 113 | } 114 | 115 | /** 116 | * @brief decide time step size which can integrate the equations stably 117 | * @param[in] domain : information about domain decomposition and size 118 | * @param[in] fluid : velocity and diffusivities 119 | * @param[in] ib : particle velocities 120 | * @param[out] : time step size 121 | * @return : (success) 0 122 | * : (failure) non-zero value 123 | */ 124 | int decide_dt( 125 | const domain_t * domain, 126 | const fluid_t * fluid, 127 | const ib_t * ib, 128 | double * dt 129 | ){ 130 | if(!coefs_are_initialised){ 131 | if(0 != config.get_double("coef_dt_adv", &coef_dt_adv)) return 1; 132 | if(0 != config.get_double("coef_dt_dif", &coef_dt_dif)) return 1; 133 | coefs_are_initialised = true; 134 | const int root = 0; 135 | int myrank = root; 136 | sdecomp.get_comm_rank(domain->info, &myrank); 137 | if(root == myrank){ 138 | printf("coefs: (adv) % .3e, (dif) % .3e\n", coef_dt_adv, coef_dt_dif); 139 | } 140 | } 141 | // compute advective and diffusive constraints 142 | double dt_adv[1] = {0.}; 143 | double dt_dif_m[NDIMS] = {0.}; 144 | double dt_dif_t[NDIMS] = {0.}; 145 | double dt_ib[1] = {0.}; 146 | decide_dt_adv(domain, fluid, dt_adv ); 147 | decide_dt_dif(domain, fluid->m_dif, dt_dif_m); 148 | decide_dt_dif(domain, fluid->t_dif, dt_dif_t); 149 | decide_dt_ib (domain, ib, dt_ib); 150 | // choose smallest value as dt 151 | // advection 152 | *dt = dt_adv[0]; 153 | // diffusion, momentum 154 | if(!param_m_implicit_x){ 155 | *dt = fmin(*dt, dt_dif_m[0]); 156 | } 157 | if(!param_m_implicit_y){ 158 | *dt = fmin(*dt, dt_dif_m[1]); 159 | } 160 | // diffusion, temperature 161 | if(!param_t_implicit_x){ 162 | *dt = fmin(*dt, dt_dif_t[0]); 163 | } 164 | if(!param_t_implicit_y){ 165 | *dt = fmin(*dt, dt_dif_t[1]); 166 | } 167 | // ib-related 168 | *dt = fmin(*dt, dt_ib[0]); 169 | return 0; 170 | } 171 | 172 | -------------------------------------------------------------------------------- /src/domain.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "sdecomp.h" 7 | #include "memory.h" 8 | #include "domain.h" 9 | #include "fileio.h" 10 | #include "array_macros/domain/xf.h" 11 | #include "array_macros/domain/xc.h" 12 | #include "array_macros/domain/dxf.h" 13 | #include "array_macros/domain/dxc.h" 14 | 15 | /** 16 | * @brief load members in domain_t 17 | * @param[in] dirname : name of directory from which data is loaded 18 | * @param[out] domain : global domain sizes and resolutions 19 | * @return : error code 20 | */ 21 | static int domain_load( 22 | const char dirname[], 23 | domain_t * domain 24 | ){ 25 | size_t * glsizes = domain->glsizes; 26 | double * restrict lengths = domain->lengths; 27 | double * restrict * restrict xf = &domain->xf; 28 | double * restrict * restrict xc = &domain->xc; 29 | if(0 != fileio.r_serial(dirname, "glsizes", 1, (size_t [1]){NDIMS}, fileio.npy_size_t, sizeof(size_t), glsizes)){ 30 | return 1; 31 | } 32 | if(0 != fileio.r_serial(dirname, "lengths", 1, (size_t [1]){NDIMS}, fileio.npy_double, sizeof(double), lengths)){ 33 | return 1; 34 | } 35 | *xf = memory_calloc(glsizes[0] + 1, sizeof(double)); 36 | for(size_t n = 0; n < glsizes[0] + 1; n++){ 37 | const double dx = lengths[0] / glsizes[0]; 38 | (*xf)[n] = 1. * n * dx; 39 | } 40 | *xc = memory_calloc(glsizes[0] + 2, sizeof(double)); 41 | (*xc)[0] = 0.; 42 | (*xc)[glsizes[0] + 1] = lengths[0]; 43 | for(size_t n = 1; n < glsizes[0] + 1; n++){ 44 | const double dx = lengths[0] / glsizes[0]; 45 | (*xc)[n] = 1. * (n - 0.5) * dx; 46 | } 47 | return 0; 48 | } 49 | 50 | /** 51 | * @brief save members in domain_t 52 | * @param[in] dirname : name of directory to which data is saved 53 | * @param[in] domain : global domain sizes and resolutions 54 | * @return : error code 55 | */ 56 | int domain_save( 57 | const char dirname[], 58 | const domain_t * domain 59 | ){ 60 | const int root = 0; 61 | int myrank = root; 62 | sdecomp.get_comm_rank(domain->info, &myrank); 63 | // since this is a serial operation, 64 | // other processes are not involved 65 | if(root != myrank){ 66 | return 0; 67 | } 68 | const size_t * glsizes = domain->glsizes; 69 | const double * lengths = domain->lengths; 70 | fileio.w_serial(dirname, "glsizes", 1, (size_t [1]){NDIMS}, fileio.npy_size_t, sizeof(size_t), glsizes); 71 | fileio.w_serial(dirname, "lengths", 1, (size_t [1]){NDIMS}, fileio.npy_double, sizeof(double), lengths); 72 | return 0; 73 | } 74 | 75 | /** 76 | * @brief define face-to-face distances in x direction 77 | * @param[in] isize : number of cell-centers in x direction (boundary excluded) 78 | * @param[in] xf : cell-face positions in x direction 79 | * @return : face-to-face distances in x direction 80 | */ 81 | static double * allocate_and_init_dxf( 82 | const int isize, 83 | const double * xf 84 | ){ 85 | // dxf: distance from cell face to cell face 86 | // NOTE: since xf has "isize + 1" items, 87 | // dxf, which tells the distance of the two neighbouring cell faces, 88 | // has "isize" elements, whose index starts from 1 89 | const size_t nitems = isize; 90 | double * dxf = memory_calloc(nitems, sizeof(double)); 91 | for(size_t i = 1; i <= nitems; i++){ 92 | DXF(i ) = XF(i+1) - XF(i ); 93 | } 94 | return dxf; 95 | } 96 | 97 | /** 98 | * @brief define center-to-center distances in x direction 99 | * @param[in] isize : number of cell-centers in x direction (boundary excluded) 100 | * @param[in] xc : cell-center positions in x direction 101 | * @return : center-to-center distances in x direction 102 | */ 103 | static double * allocate_and_init_dxc( 104 | const int isize, 105 | const double * xc 106 | ){ 107 | // dxc: distance from cell center to cell center (generally) 108 | // NOTE: since xc has "isize + 2" items, 109 | // dxc, which tells the distance of the two neighbouring cell centers, 110 | // has "isize + 1" elements, whose index starts from 1 111 | const size_t nitems = isize + 1; 112 | double * dxc = memory_calloc(nitems, sizeof(double)); 113 | for(size_t i = 1; i <= nitems; i++){ 114 | DXC(i ) = XC(i ) - XC(i-1); 115 | } 116 | return dxc; 117 | } 118 | 119 | static void report( 120 | const domain_t * domain 121 | ){ 122 | const int root = 0; 123 | int myrank = root; 124 | sdecomp.get_comm_rank(domain->info, &myrank); 125 | if(root == myrank){ 126 | printf("DOMAIN\n"); 127 | for(sdecomp_dir_t dim = 0; dim < NDIMS; dim++){ 128 | printf("\tglsizes[%u]: %zu\n", dim, domain->glsizes[dim]); 129 | } 130 | for(sdecomp_dir_t dim = 0; dim < NDIMS; dim++){ 131 | printf("\tlengths[%u]: % .7e\n", dim, domain->lengths[dim]); 132 | } 133 | fflush(stdout); 134 | } 135 | } 136 | 137 | /** 138 | * @brief constructor of the structure 139 | * @param[in] dirname_ic : name of directory in which initial conditions are stored 140 | * @param[out] domain : structure being allocated and initalised 141 | * @return : (success) 0 142 | * (failure) non-zero value 143 | */ 144 | int domain_init( 145 | const char dirname_ic[], 146 | domain_t * domain 147 | ){ 148 | sdecomp_info_t ** info = &domain->info; 149 | size_t * restrict glsizes = domain->glsizes; 150 | size_t * restrict mysizes = domain->mysizes; 151 | size_t * restrict offsets = domain->offsets; 152 | double * restrict lengths = domain->lengths; 153 | double * restrict * xf = &domain->xf; 154 | double * restrict * xc = &domain->xc; 155 | double * restrict * dxf = &domain->dxf; 156 | double * restrict * dxc = &domain->dxc; 157 | double * restrict dx = &domain->dx; 158 | double * restrict dy = &domain->dy; 159 | // load spatial information 160 | if(0 != domain_load(dirname_ic, domain)){ 161 | return 1; 162 | } 163 | // compute grid sizes 164 | // allocate and initialise x coordinates 165 | *dxf = allocate_and_init_dxf(glsizes[0], *xf); 166 | *dxc = allocate_and_init_dxc(glsizes[0], *xc); 167 | // grid sizes 168 | *dx = lengths[0] / glsizes[0]; 169 | *dy = lengths[1] / glsizes[1]; 170 | // initialise sdecomp to distribute the domain 171 | if(0 != sdecomp.construct( 172 | MPI_COMM_WORLD, 173 | NDIMS, 174 | (size_t [NDIMS]){0, 0}, 175 | (bool [NDIMS]){false, true}, 176 | info 177 | )) return 1; 178 | // local array sizes and offsets 179 | for(size_t dim = 0; dim < NDIMS; dim++){ 180 | sdecomp.get_pencil_mysize(*info, SDECOMP_X1PENCIL, dim, glsizes[dim], mysizes + dim); 181 | sdecomp.get_pencil_offset(*info, SDECOMP_X1PENCIL, dim, glsizes[dim], offsets + dim); 182 | } 183 | report(domain); 184 | return 0; 185 | } 186 | 187 | -------------------------------------------------------------------------------- /src/fluid/README.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | fluid/ 3 | ###### 4 | 5 | Fluid solver. 6 | 7 | * arrays 8 | 9 | Macros to access flow field. 10 | 11 | * boundary_conditions 12 | 13 | Impose boundary conditions and exchange halo cells. 14 | 15 | * compute_potential 16 | 17 | Poisson solver. 18 | 19 | * compute_rhs 20 | 21 | Evaluate right-hand-side of the momentum equation. 22 | 23 | * correct_velocity 24 | 25 | Project velocity from non-solenoidal to divergence-free field. 26 | 27 | * decide_dt 28 | 29 | Decide time step size in the next step. 30 | 31 | * init.c 32 | 33 | Allocator and flow-field loader. 34 | 35 | * internal.h 36 | 37 | Private functions only used in this directory is declared. 38 | 39 | * update_pressure.c 40 | 41 | Function to update pressure field. 42 | 43 | * update_field 44 | 45 | Update velocity and temperature field using what is computed by ``compute_rhs``. 46 | 47 | -------------------------------------------------------------------------------- /src/fluid/boundary/p.c: -------------------------------------------------------------------------------- 1 | #include "array.h" 2 | #include "domain.h" 3 | #include "halo.h" 4 | #include "fluid_solver.h" 5 | #include "array_macros/fluid/p.h" 6 | 7 | static int assign_boundary_conditions_in_x( 8 | const domain_t * domain, 9 | double * p 10 | ){ 11 | const int isize = domain->mysizes[0]; 12 | const int jsize = domain->mysizes[1]; 13 | // set boundary values 14 | for(int j = 1; j <= jsize; j++){ 15 | P( 0, j) = P( 1, j); // Neumann 16 | P(isize+1, j) = P(isize, j); // Neumann 17 | } 18 | return 0; 19 | } 20 | 21 | /** 22 | * @brief update boundary values of the pressure 23 | * @param[in] domain : information about domain decomposition and size 24 | * @param[in,out] p : pressure 25 | * @return : error code 26 | */ 27 | int fluid_update_boundaries_p( 28 | const domain_t * domain, 29 | array_t * p 30 | ){ 31 | static MPI_Datatype dtypes[NDIMS - 1] = { 32 | MPI_DOUBLE, 33 | }; 34 | if(0 != halo_communicate_in_y(domain, dtypes + 0, p)){ 35 | return 1; 36 | } 37 | assign_boundary_conditions_in_x(domain, p->data); 38 | return 0; 39 | } 40 | 41 | -------------------------------------------------------------------------------- /src/fluid/boundary/psi.c: -------------------------------------------------------------------------------- 1 | #include "array.h" 2 | #include "domain.h" 3 | #include "halo.h" 4 | #include "fluid_solver.h" 5 | #include "array_macros/fluid/psi.h" 6 | 7 | static int assign_boundary_conditions_in_x( 8 | const domain_t * domain, 9 | double * psi 10 | ){ 11 | const int isize = domain->mysizes[0]; 12 | const int jsize = domain->mysizes[1]; 13 | // set boundary values 14 | for(int j = 1; j <= jsize; j++){ 15 | PSI( 0, j) = PSI( 1, j); // Neumann 16 | PSI(isize+1, j) = PSI(isize, j); // Neumann 17 | } 18 | return 0; 19 | } 20 | 21 | /** 22 | * @brief update boundary values of the scalar potential 23 | * @param[in] domain : information about domain decomposition and size 24 | * @param[in,out] p : pressure 25 | * @return : error code 26 | */ 27 | int fluid_update_boundaries_psi( 28 | const domain_t * domain, 29 | array_t * psi 30 | ){ 31 | static MPI_Datatype dtypes[NDIMS - 1] = { 32 | MPI_DOUBLE, 33 | }; 34 | if(0 != halo_communicate_in_y(domain, dtypes + 0, psi)){ 35 | return 1; 36 | } 37 | assign_boundary_conditions_in_x(domain, psi->data); 38 | return 0; 39 | } 40 | 41 | -------------------------------------------------------------------------------- /src/fluid/boundary/t.c: -------------------------------------------------------------------------------- 1 | #include "param.h" 2 | #include "array.h" 3 | #include "domain.h" 4 | #include "halo.h" 5 | #include "fluid_solver.h" 6 | #include "array_macros/fluid/t.h" 7 | 8 | static int assign_boundary_conditions_in_x( 9 | const domain_t * domain, 10 | double * t 11 | ){ 12 | const int isize = domain->mysizes[0]; 13 | const int jsize = domain->mysizes[1]; 14 | // set boundary values 15 | for(int j = 1; j <= jsize; j++){ 16 | T( 0, j) = param_t_xm; 17 | T(isize+1, j) = param_t_xp; 18 | } 19 | return 0; 20 | } 21 | 22 | /** 23 | * @brief update boundary values of temperature 24 | * @param[in] domain : information about domain decomposition and size 25 | * @param[in,out] t : temperature 26 | * @return : error code 27 | */ 28 | int fluid_update_boundaries_t( 29 | const domain_t * domain, 30 | array_t * t 31 | ){ 32 | static MPI_Datatype dtypes[NDIMS - 1] = { 33 | MPI_DOUBLE, 34 | }; 35 | if(0 != halo_communicate_in_y(domain, dtypes + 0, t)){ 36 | return 1; 37 | } 38 | assign_boundary_conditions_in_x(domain, t->data); 39 | return 0; 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/fluid/boundary/ux.c: -------------------------------------------------------------------------------- 1 | #include "array.h" 2 | #include "domain.h" 3 | #include "halo.h" 4 | #include "fluid_solver.h" 5 | #include "array_macros/fluid/ux.h" 6 | 7 | static int assign_boundary_conditions_in_x( 8 | const domain_t * domain, 9 | double * ux 10 | ){ 11 | const int isize = domain->mysizes[0]; 12 | const int jsize = domain->mysizes[1]; 13 | // set boundary values 14 | for(int j = 1; j <= jsize; j++){ 15 | UX( 1, j) = 0.; // impermeable 16 | UX(isize+1, j) = 0.; // impermeable 17 | } 18 | return 0; 19 | } 20 | 21 | /** 22 | * @brief update boundary values of x velocity 23 | * @param[in] domain : information about domain decomposition and size 24 | * @param[in,out] ux : x velocity 25 | * @return : error code 26 | */ 27 | int fluid_update_boundaries_ux( 28 | const domain_t * domain, 29 | array_t * ux 30 | ){ 31 | static MPI_Datatype dtypes[NDIMS - 1] = { 32 | MPI_DOUBLE, 33 | }; 34 | if(0 != halo_communicate_in_y(domain, dtypes + 0, ux)){ 35 | return 1; 36 | } 37 | assign_boundary_conditions_in_x(domain, ux->data); 38 | return 0; 39 | } 40 | 41 | -------------------------------------------------------------------------------- /src/fluid/boundary/uy.c: -------------------------------------------------------------------------------- 1 | #include "param.h" 2 | #include "array.h" 3 | #include "domain.h" 4 | #include "halo.h" 5 | #include "fluid_solver.h" 6 | #include "array_macros/fluid/uy.h" 7 | 8 | static int assign_boundary_conditions_in_x( 9 | const domain_t * domain, 10 | double * uy 11 | ){ 12 | const int isize = domain->mysizes[0]; 13 | const int jsize = domain->mysizes[1]; 14 | // set boundary values 15 | for(int j = 1; j <= jsize; j++){ 16 | UY( 0, j) = param_uy_xm; // no-slip 17 | UY(isize+1, j) = param_uy_xp; // no-slip 18 | } 19 | return 0; 20 | } 21 | 22 | /** 23 | * @brief update boundary values of y velocity 24 | * @param[in] domain : information about domain decomposition and size 25 | * @param[in,out] uy : y velocity 26 | * @return : error code 27 | */ 28 | int fluid_update_boundaries_uy( 29 | const domain_t * domain, 30 | array_t * uy 31 | ){ 32 | static MPI_Datatype dtypes[NDIMS - 1] = { 33 | MPI_DOUBLE, 34 | }; 35 | if(0 != halo_communicate_in_y(domain, dtypes + 0, uy)){ 36 | return 1; 37 | } 38 | assign_boundary_conditions_in_x(domain, uy->data); 39 | return 0; 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/fluid/correct_velocity/internal.h: -------------------------------------------------------------------------------- 1 | #if !defined(FLUID_CORRECT_VELOCITY_INTERNAL_H) 2 | #define FLUID_CORRECT_VELOCITY_INTERNAL_H 3 | 4 | extern int fluid_correct_velocity_ux( 5 | const domain_t * domain, 6 | const double prefactor, 7 | fluid_t * fluid 8 | ); 9 | 10 | extern int fluid_correct_velocity_uy( 11 | const domain_t * domain, 12 | const double prefactor, 13 | fluid_t * fluid 14 | ); 15 | 16 | #endif // FLUID_CORRECT_VELOCITY_INTERNAL_H 17 | -------------------------------------------------------------------------------- /src/fluid/correct_velocity/main.c: -------------------------------------------------------------------------------- 1 | #include "runge_kutta.h" 2 | #include "domain.h" 3 | #include "fluid.h" 4 | #include "fluid_solver.h" 5 | #include "internal.h" 6 | 7 | /** 8 | * @brief correct non-solenoidal velocity using scalar potential psi 9 | * @param[in] domain : information about domain decomposition and size 10 | * @param[in] rkstep : Runge-Kutta step 11 | * @param[in] dt : time step size 12 | * @param[in,out] fluid : scalar potential (in), velocity (out) 13 | * @return : error code 14 | */ 15 | int fluid_correct_velocity( 16 | const domain_t * domain, 17 | const size_t rkstep, 18 | const double dt, 19 | fluid_t * fluid 20 | ){ 21 | // compute prefactor gamma dt 22 | const double gamma = rkcoefs[rkstep][rk_g]; 23 | const double prefactor = gamma * dt; 24 | if(0 != fluid_correct_velocity_ux(domain, prefactor, fluid)) return 1; 25 | if(0 != fluid_correct_velocity_uy(domain, prefactor, fluid)) return 1; 26 | return 0; 27 | } 28 | 29 | -------------------------------------------------------------------------------- /src/fluid/correct_velocity/ux.c: -------------------------------------------------------------------------------- 1 | #include "domain.h" 2 | #include "fluid.h" 3 | #include "fluid_solver.h" 4 | #include "internal.h" 5 | #include "array_macros/domain/dxc.h" 6 | #include "array_macros/fluid/ux.h" 7 | #include "array_macros/fluid/psi.h" 8 | 9 | /** 10 | * @brief correct ux using scalar potential psi 11 | * @param[in] domain : information about domain decomposition and size 12 | * @param[in] prefactor : pre-factor in front of grad psi 13 | * @param[in,out] fluid : scalar potential psi (in), ux (out) 14 | * @return : error code 15 | */ 16 | int fluid_correct_velocity_ux( 17 | const domain_t * domain, 18 | const double prefactor, 19 | fluid_t * fluid 20 | ){ 21 | const int isize = domain->mysizes[0]; 22 | const int jsize = domain->mysizes[1]; 23 | const double * restrict dxc = domain->dxc; 24 | const double * restrict psi = fluid->psi.data; 25 | double * restrict ux = fluid->ux.data; 26 | for(int j = 1; j <= jsize; j++){ 27 | for(int i = 2; i <= isize; i++){ 28 | // correct x velocity 29 | const double dx = DXC(i ); 30 | const double psi_xm = PSI(i-1, j ); 31 | const double psi_xp = PSI(i , j ); 32 | UX(i, j) -= prefactor / dx * ( 33 | + psi_xp 34 | - psi_xm 35 | ); 36 | } 37 | } 38 | // update boundary and halo cells 39 | if(0 != fluid_update_boundaries_ux(domain, &fluid->ux)){ 40 | return 1; 41 | } 42 | return 0; 43 | } 44 | 45 | -------------------------------------------------------------------------------- /src/fluid/correct_velocity/uy.c: -------------------------------------------------------------------------------- 1 | #include "domain.h" 2 | #include "fluid.h" 3 | #include "fluid_solver.h" 4 | #include "internal.h" 5 | #include "array_macros/fluid/uy.h" 6 | #include "array_macros/fluid/psi.h" 7 | 8 | /** 9 | * @brief correct uy using scalar potential psi 10 | * @param[in] domain : information about domain decomposition and size 11 | * @param[in] prefactor : pre-factor in front of grad psi 12 | * @param[in,out] fluid : scalar potential psi (in), uy (out) 13 | * @return : error code 14 | */ 15 | int fluid_correct_velocity_uy( 16 | const domain_t * domain, 17 | const double prefactor, 18 | fluid_t * fluid 19 | ){ 20 | const int isize = domain->mysizes[0]; 21 | const int jsize = domain->mysizes[1]; 22 | const double dy = domain->dy; 23 | const double * restrict psi = fluid->psi.data; 24 | double * restrict uy = fluid->uy.data; 25 | for(int j = 1; j <= jsize; j++){ 26 | for(int i = 1; i <= isize; i++){ 27 | // correct y velocity 28 | const double psi_ym = PSI(i , j-1); 29 | const double psi_yp = PSI(i , j ); 30 | UY(i, j) -= prefactor / dy * ( 31 | + psi_yp 32 | - psi_ym 33 | ); 34 | } 35 | } 36 | // update boundary and halo cells 37 | if(0 != fluid_update_boundaries_uy(domain, &fluid->uy)){ 38 | return 1; 39 | } 40 | return 0; 41 | } 42 | 43 | -------------------------------------------------------------------------------- /src/fluid/init.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "param.h" 3 | #include "memory.h" 4 | #include "config.h" 5 | #include "domain.h" 6 | #include "fluid.h" 7 | #include "fluid_solver.h" 8 | #include "fileio.h" 9 | #include "array_macros/fluid/ux.h" 10 | #include "array_macros/fluid/uy.h" 11 | #include "array_macros/fluid/p.h" 12 | #include "array_macros/fluid/t.h" 13 | #include "array_macros/fluid/psi.h" 14 | #include "array_macros/fluid/srcux.h" 15 | #include "array_macros/fluid/srcuy.h" 16 | #include "array_macros/fluid/srct.h" 17 | 18 | /** 19 | * @brief allocate members 20 | * @param[in] domain : information about domain decomposition and size 21 | * @param[out] fluid : structure storing flow fields and auxiliary buffers 22 | * @return : error code 23 | */ 24 | static int allocate( 25 | const domain_t * domain, 26 | fluid_t * fluid 27 | ){ 28 | // velocity 29 | if(0 != array.prepare(domain, UX_NADDS, sizeof(double), &fluid->ux )) return 1; 30 | if(0 != array.prepare(domain, UY_NADDS, sizeof(double), &fluid->uy )) return 1; 31 | // pressure and scalar potential 32 | if(0 != array.prepare(domain, P_NADDS, sizeof(double), &fluid->p )) return 1; 33 | if(0 != array.prepare(domain, PSI_NADDS, sizeof(double), &fluid->psi)) return 1; 34 | // temperature 35 | if(0 != array.prepare(domain, T_NADDS, sizeof(double), &fluid->t)) return 1; 36 | // Runge-Kutta source terms 37 | for(size_t n = 0; n < 3; n++){ 38 | if(0 != array.prepare(domain, SRCUX_NADDS, sizeof(double), &fluid->srcux[n])) return 1; 39 | if(0 != array.prepare(domain, SRCUY_NADDS, sizeof(double), &fluid->srcuy[n])) return 1; 40 | if(0 != array.prepare(domain, SRCT_NADDS, sizeof(double), &fluid->srct [n])) return 1; 41 | } 42 | return 0; 43 | } 44 | 45 | static void report( 46 | const sdecomp_info_t * info, 47 | const fluid_t * fluid 48 | ){ 49 | const int root = 0; 50 | int myrank = root; 51 | sdecomp.get_comm_rank(info, &myrank); 52 | if(root == myrank){ 53 | printf("FLUID\n"); 54 | printf("\tRa: % .7e\n", fluid->Ra); 55 | printf("\tPr: % .7e\n", fluid->Pr); 56 | printf("\tMomentum diffusivity: % .7e\n", fluid->m_dif); 57 | printf("\tTemperature diffusivity: % .7e\n", fluid->t_dif); 58 | printf("\tdiffusive treatment in x: %s\n", param_m_implicit_x ? "implicit" : "explicit"); 59 | printf("\tdiffusive treatment in y: %s\n", param_m_implicit_y ? "implicit" : "explicit"); 60 | fflush(stdout); 61 | } 62 | } 63 | 64 | /** 65 | * @brief constructor of the structure 66 | * @param[in] dirname_ic : name of directory in which initial flow fields are stored 67 | * @param[in] domain : information about domain decomposition and size 68 | * @param[out] : structure being allocated and initalised 69 | * @return : (success) 0 70 | * (failure) non-zero value 71 | */ 72 | int fluid_init( 73 | const char dirname_ic[], 74 | const domain_t * domain, 75 | fluid_t * fluid 76 | ){ 77 | // allocate arrays 78 | if(0 != allocate(domain, fluid)) return 1; 79 | // load flow fields 80 | if(0 != array.load(domain, dirname_ic, "ux", fileio.npy_double, &fluid->ux)) return 1; 81 | if(0 != array.load(domain, dirname_ic, "uy", fileio.npy_double, &fluid->uy)) return 1; 82 | if(0 != array.load(domain, dirname_ic, "p", fileio.npy_double, &fluid-> p)) return 1; 83 | if(0 != array.load(domain, dirname_ic, "t", fileio.npy_double, &fluid-> t)) return 1; 84 | // impose boundary conditions and communicate halo cells 85 | if(0 != fluid_update_boundaries_ux(domain, &fluid->ux)) return 1; 86 | if(0 != fluid_update_boundaries_uy(domain, &fluid->uy)) return 1; 87 | if(0 != fluid_update_boundaries_p(domain, &fluid->p)) return 1; 88 | if(0 != fluid_update_boundaries_t(domain, &fluid->t)) return 1; 89 | // compute diffusivities 90 | if(0 != config.get_double("Pr", &fluid->Pr)) return 1; 91 | if(0 != config.get_double("Ra", &fluid->Ra)) return 1; 92 | fluid->m_dif = 1. * sqrt(fluid->Pr) / sqrt(fluid->Ra); 93 | fluid->t_dif = 1. / sqrt(fluid->Pr) / sqrt(fluid->Ra); 94 | report(domain->info, fluid); 95 | return 0; 96 | } 97 | 98 | -------------------------------------------------------------------------------- /src/fluid/integrate/compute_rhs.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "runge_kutta.h" 3 | #include "fluid.h" 4 | #include "fluid_solver.h" 5 | #include "internal.h" 6 | 7 | static int reset_srcs( 8 | array_t * restrict srca, 9 | array_t * restrict srcb, 10 | array_t * restrict srcg 11 | ){ 12 | // stash previous RK source term, 13 | // which is achieved by swapping 14 | // the pointers to "data" 15 | double * tmp = srca->data; 16 | srca->data = srcb->data; 17 | srcb->data = tmp; 18 | // zero-clear current RK source terms (exp/imp) 19 | // NOTE: when 0 == rkstep, rkcoef for "beta" is zero 20 | // and thus zero-clearing"beta" buffers is not needed 21 | memset(srca->data, 0, srca->datasize); 22 | memset(srcg->data, 0, srcg->datasize); 23 | return 0; 24 | } 25 | 26 | /** 27 | * @brief compute right-hand-side terms of the Runge-Kutta scheme 28 | * @param[in] domain : information related to MPI domain decomposition 29 | * @param[in,out] fluid : flow field (in), RK source terms (out) 30 | * @return : error code 31 | */ 32 | int fluid_compute_rhs( 33 | const domain_t * domain, 34 | fluid_t * fluid 35 | ){ 36 | // reset buffers 37 | // copy previous k-step source term and reset 38 | if(0 != reset_srcs(fluid->srcux + rk_a, fluid->srcux + rk_b, fluid->srcux + rk_g)){ 39 | return 1; 40 | } 41 | if(0 != reset_srcs(fluid->srcuy + rk_a, fluid->srcuy + rk_b, fluid->srcuy + rk_g)){ 42 | return 1; 43 | } 44 | if(0 != reset_srcs(fluid->srct + rk_a, fluid->srct + rk_b, fluid->srct + rk_g)){ 45 | return 1; 46 | } 47 | // update buffers 48 | if(0 != compute_rhs_ux(domain, fluid)){ 49 | return 1; 50 | } 51 | if(0 != compute_rhs_uy(domain, fluid)){ 52 | return 1; 53 | } 54 | if(0 != compute_rhs_t (domain, fluid)){ 55 | return 1; 56 | } 57 | return 0; 58 | } 59 | 60 | -------------------------------------------------------------------------------- /src/fluid/integrate/couple_external_force.c: -------------------------------------------------------------------------------- 1 | #include "param.h" 2 | #include "domain.h" 3 | #include "fluid.h" 4 | #include "fluid_solver.h" 5 | #include "internal.h" 6 | 7 | /** 8 | * @brief couple external forces 9 | * @param[in] domain : information related to MPI domain decomposition 10 | * @param[in,out] fluid : buoyancy force (in), x RK source term (in/out) 11 | * @return : error code 12 | */ 13 | int fluid_couple_external_force( 14 | const domain_t * domain, 15 | fluid_t * fluid 16 | ){ 17 | // add buoyancy in x when spcified 18 | if(param_add_buoyancy){ 19 | if(0 != buoyancy_ux(domain, fluid)){ 20 | return 1; 21 | } 22 | } 23 | return 0; 24 | } 25 | 26 | -------------------------------------------------------------------------------- /src/fluid/integrate/internal.h: -------------------------------------------------------------------------------- 1 | #if !defined(FLUID_INTEGRATE_INTERNAL) 2 | #define FLUID_INTEGRATE_INTERNAL 3 | 4 | extern int compute_rhs_ux( 5 | const domain_t * domain, 6 | fluid_t * fluid 7 | ); 8 | 9 | extern int compute_rhs_uy( 10 | const domain_t * domain, 11 | fluid_t * fluid 12 | ); 13 | 14 | extern int compute_rhs_t( 15 | const domain_t * domain, 16 | fluid_t * fluid 17 | ); 18 | 19 | extern int buoyancy_ux( 20 | const domain_t * domain, 21 | fluid_t * fluid 22 | ); 23 | 24 | extern int update_ux( 25 | const domain_t * domain, 26 | const size_t rkstep, 27 | const double dt, 28 | fluid_t * fluid 29 | ); 30 | 31 | extern int update_uy( 32 | const domain_t * domain, 33 | const size_t rkstep, 34 | const double dt, 35 | fluid_t * fluid 36 | ); 37 | 38 | extern int update_t( 39 | const domain_t * domain, 40 | const size_t rkstep, 41 | const double dt, 42 | fluid_t * fluid 43 | ); 44 | 45 | #endif // FLUID_INTEGRATE_INTERNAL 46 | -------------------------------------------------------------------------------- /src/fluid/integrate/predict_field.c: -------------------------------------------------------------------------------- 1 | #include "domain.h" 2 | #include "fluid.h" 3 | #include "fluid_solver.h" 4 | #include "internal.h" 5 | 6 | /** 7 | * @brief update fields using the previously-computed RK source terms 8 | * @param[in] domain : information related to MPI domain decomposition 9 | * @param[in] rkstep : Runge-Kutta step 10 | * @param[in] dt : time step size 11 | * @param[in,out] fluid : RK source terms (in), flow field (out) 12 | * @return : error code 13 | */ 14 | int fluid_predict_field( 15 | const domain_t * domain, 16 | const size_t rkstep, 17 | const double dt, 18 | fluid_t * fluid 19 | ){ 20 | if(0 != update_ux(domain, rkstep, dt, fluid)){ 21 | return 1; 22 | } 23 | if(0 != update_uy(domain, rkstep, dt, fluid)){ 24 | return 1; 25 | } 26 | if(0 != update_t (domain, rkstep, dt, fluid)){ 27 | return 1; 28 | } 29 | return 0; 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/fluid/integrate/t.c: -------------------------------------------------------------------------------- 1 | #include "param.h" 2 | #include "memory.h" 3 | #include "runge_kutta.h" 4 | #include "linear_system.h" 5 | #include "tdm.h" 6 | #include "domain.h" 7 | #include "fluid.h" 8 | #include "fluid_solver.h" 9 | #include "internal.h" 10 | #include "array_macros/domain/dxf.h" 11 | #include "array_macros/domain/dxc.h" 12 | #include "array_macros/fluid/ux.h" 13 | #include "array_macros/fluid/uy.h" 14 | #include "array_macros/fluid/t.h" 15 | 16 | // store approximation of laplacian 17 | typedef double laplacian_t[3]; 18 | 19 | typedef struct { 20 | bool is_initialised; 21 | laplacian_t * lapx; 22 | laplacian_t lapy; 23 | } laplacians_t; 24 | 25 | static laplacians_t laplacians = { 26 | .is_initialised = false, 27 | }; 28 | 29 | // [1 : isize] 30 | #define LAPX(I) lapx[(I)-1] 31 | 32 | static int init_lap( 33 | const domain_t * domain 34 | ){ 35 | // Laplacian w.r.t. temp in x 36 | { 37 | const size_t isize = domain->glsizes[0]; 38 | const double * dxf = domain->dxf; 39 | const double * dxc = domain->dxc; 40 | laplacians.lapx = memory_calloc(isize, sizeof(laplacian_t)); 41 | for(size_t i = 1; i <= isize; i++){ 42 | const double l = 1. / DXC(i ) / DXF(i ); 43 | const double u = 1. / DXC(i+1) / DXF(i ); 44 | const double c = - l - u; 45 | laplacians.LAPX(i)[0] = l; 46 | laplacians.LAPX(i)[1] = c; 47 | laplacians.LAPX(i)[2] = u; 48 | } 49 | } 50 | // Laplacian in y 51 | { 52 | const double dy = domain->dy; 53 | laplacians.lapy[0] = + 1. / dy / dy; 54 | laplacians.lapy[1] = - 2. / dy / dy; 55 | laplacians.lapy[2] = + 1. / dy / dy; 56 | } 57 | laplacians.is_initialised = true; 58 | return 0; 59 | } 60 | 61 | #define BEGIN \ 62 | for(int cnt = 0, j = 1; j <= jsize; j++){ \ 63 | for(int i = 1; i <= isize; i++, cnt++){ 64 | #define END \ 65 | } \ 66 | } 67 | 68 | static int advection_x( 69 | const domain_t * domain, 70 | const double * restrict t, 71 | const double * restrict ux, 72 | double * restrict src 73 | ){ 74 | const int isize = domain->mysizes[0]; 75 | const int jsize = domain->mysizes[1]; 76 | const double * restrict dxf = domain->dxf; 77 | BEGIN 78 | // T is transported by ux 79 | const double l = + 0.5 / DXF(i ) * UX(i , j ); 80 | const double u = - 0.5 / DXF(i ) * UX(i+1, j ); 81 | const double c = - l - u; 82 | src[cnt] += 83 | + l * T(i-1, j ) 84 | + c * T(i , j ) 85 | + u * T(i+1, j ); 86 | END 87 | return 0; 88 | } 89 | 90 | static int advection_y( 91 | const domain_t * domain, 92 | const double * restrict t, 93 | const double * restrict uy, 94 | double * restrict src 95 | ){ 96 | const int isize = domain->mysizes[0]; 97 | const int jsize = domain->mysizes[1]; 98 | const double dy = domain->dy; 99 | BEGIN 100 | // T is transported by uy 101 | const double l = + 0.5 / dy * UY(i , j ); 102 | const double u = - 0.5 / dy * UY(i , j+1); 103 | const double c = - l - u; 104 | src[cnt] += 105 | + l * T(i , j-1) 106 | + c * T(i , j ) 107 | + u * T(i , j+1); 108 | END 109 | return 0; 110 | } 111 | 112 | static int diffusion_x( 113 | const domain_t * domain, 114 | const double diffusivity, 115 | const double * restrict t, 116 | double * restrict src 117 | ){ 118 | const int isize = domain->mysizes[0]; 119 | const int jsize = domain->mysizes[1]; 120 | const laplacian_t * restrict lapx = laplacians.lapx; 121 | BEGIN 122 | // T is diffused in x 123 | src[cnt] += diffusivity * ( 124 | + LAPX(i)[0] * T(i-1, j ) 125 | + LAPX(i)[1] * T(i , j ) 126 | + LAPX(i)[2] * T(i+1, j ) 127 | ); 128 | END 129 | return 0; 130 | } 131 | 132 | static int diffusion_y( 133 | const domain_t * domain, 134 | const double diffusivity, 135 | const double * restrict t, 136 | double * restrict src 137 | ){ 138 | const int isize = domain->mysizes[0]; 139 | const int jsize = domain->mysizes[1]; 140 | const laplacian_t * restrict lapy = &laplacians.lapy; 141 | BEGIN 142 | // T is diffused in y 143 | src[cnt] += diffusivity * ( 144 | + (*lapy)[0] * T(i , j-1) 145 | + (*lapy)[1] * T(i , j ) 146 | + (*lapy)[2] * T(i , j+1) 147 | ); 148 | END 149 | return 0; 150 | } 151 | 152 | /** 153 | * @brief comute right-hand-side of Runge-Kutta scheme 154 | * @param[in] domain : information related to domain decomposition and size 155 | * @param[in,out] fluid : n-step flow field (in), RK source terms (out) 156 | * @return : error code 157 | */ 158 | int compute_rhs_t( 159 | const domain_t * domain, 160 | fluid_t * fluid 161 | ){ 162 | if(!laplacians.is_initialised){ 163 | if(0 != init_lap(domain)){ 164 | return 1; 165 | } 166 | } 167 | const double * restrict ux = fluid->ux.data; 168 | const double * restrict uy = fluid->uy.data; 169 | const double * restrict t = fluid-> t.data; 170 | double * restrict srca = fluid->srct[rk_a].data; 171 | double * restrict srcg = fluid->srct[rk_g].data; 172 | const double diffusivity = fluid->t_dif; 173 | // advective contributions, always explicit 174 | advection_x(domain, t, ux, srca); 175 | advection_y(domain, t, uy, srca); 176 | // diffusive contributions, can be explicit or implicit 177 | diffusion_x(domain, diffusivity, t, param_t_implicit_x ? srcg : srca); 178 | diffusion_y(domain, diffusivity, t, param_t_implicit_y ? srcg : srca); 179 | return 0; 180 | } 181 | 182 | static int solve_in_x( 183 | const double prefactor, 184 | linear_system_t * linear_system 185 | ){ 186 | tdm_info_t * tdm_info = linear_system->tdm_x; 187 | int size = 0; 188 | double * restrict tdm_l = NULL; 189 | double * restrict tdm_c = NULL; 190 | double * restrict tdm_u = NULL; 191 | tdm.get_size(tdm_info, &size); 192 | tdm.get_l(tdm_info, &tdm_l); 193 | tdm.get_c(tdm_info, &tdm_c); 194 | tdm.get_u(tdm_info, &tdm_u); 195 | const laplacian_t * restrict lapx = laplacians.lapx; 196 | for(int i = 0; i < size; i++){ 197 | tdm_l[i] = - prefactor * lapx[i][0]; 198 | tdm_c[i] = 1. - prefactor * lapx[i][1]; 199 | tdm_u[i] = - prefactor * lapx[i][2]; 200 | } 201 | tdm.solve(tdm_info, linear_system->x1pncl); 202 | return 0; 203 | } 204 | 205 | static int solve_in_y( 206 | const double prefactor, 207 | linear_system_t * linear_system 208 | ){ 209 | tdm_info_t * tdm_info = linear_system->tdm_y; 210 | int size = 0; 211 | double * restrict tdm_l = NULL; 212 | double * restrict tdm_c = NULL; 213 | double * restrict tdm_u = NULL; 214 | tdm.get_size(tdm_info, &size); 215 | tdm.get_l(tdm_info, &tdm_l); 216 | tdm.get_c(tdm_info, &tdm_c); 217 | tdm.get_u(tdm_info, &tdm_u); 218 | const laplacian_t * restrict lapy = &laplacians.lapy; 219 | for(int j = 0; j < size; j++){ 220 | tdm_l[j] = - prefactor * (*lapy)[0]; 221 | tdm_c[j] = 1. - prefactor * (*lapy)[1]; 222 | tdm_u[j] = - prefactor * (*lapy)[2]; 223 | } 224 | tdm.solve(tdm_info, linear_system->y1pncl); 225 | return 0; 226 | } 227 | 228 | /** 229 | * @brief update temperature field 230 | * @param[in] domain : information about domain decomposition and size 231 | * @param[in] rkstep : Runge-Kutta step 232 | * @param[in] dt : time step size 233 | * @param[in,out] fluid : Runge-Kutta source terms (in), temperature (out) 234 | * @return : error code 235 | */ 236 | int update_t( 237 | const domain_t * domain, 238 | const size_t rkstep, 239 | const double dt, 240 | fluid_t * fluid 241 | ){ 242 | static linear_system_t linear_system = { 243 | .is_initialised = false, 244 | }; 245 | if(!linear_system.is_initialised){ 246 | // if not initialised yet, prepare linear solver 247 | // for implicit diffusive term treatment 248 | const bool implicit[NDIMS] = { 249 | param_t_implicit_x, 250 | param_t_implicit_y, 251 | }; 252 | const size_t glsizes[NDIMS] = { 253 | domain->glsizes[0], 254 | domain->glsizes[1], 255 | }; 256 | if(0 != linear_system_init(domain->info, implicit, glsizes, &linear_system)){ 257 | return 1; 258 | } 259 | } 260 | // compute increments 261 | { 262 | const double coef_a = rkcoefs[rkstep][rk_a]; 263 | const double coef_b = rkcoefs[rkstep][rk_b]; 264 | const double coef_g = rkcoefs[rkstep][rk_g]; 265 | const double * restrict srcta = fluid->srct[rk_a].data; 266 | const double * restrict srctb = fluid->srct[rk_b].data; 267 | const double * restrict srctg = fluid->srct[rk_g].data; 268 | const int isize = domain->mysizes[0]; 269 | const int jsize = domain->mysizes[1]; 270 | double * restrict dtemp = linear_system.x1pncl; 271 | const size_t nitems = isize * jsize; 272 | for(size_t n = 0; n < nitems; n++){ 273 | dtemp[n] = 274 | + coef_a * dt * srcta[n] 275 | + coef_b * dt * srctb[n] 276 | + coef_g * dt * srctg[n]; 277 | } 278 | } 279 | // gamma dt diffusivity / 2 280 | const double prefactor = 281 | 0.5 * rkcoefs[rkstep][rk_g] * dt * fluid->t_dif; 282 | // solve linear systems in x 283 | if(param_t_implicit_x){ 284 | solve_in_x( 285 | prefactor, 286 | &linear_system 287 | ); 288 | } 289 | // solve linear systems in y 290 | if(param_t_implicit_y){ 291 | sdecomp.transpose.execute( 292 | linear_system.transposer_x1_to_y1, 293 | linear_system.x1pncl, 294 | linear_system.y1pncl 295 | ); 296 | solve_in_y( 297 | prefactor, 298 | &linear_system 299 | ); 300 | sdecomp.transpose.execute( 301 | linear_system.transposer_y1_to_x1, 302 | linear_system.y1pncl, 303 | linear_system.x1pncl 304 | ); 305 | } 306 | // the field is actually updated here 307 | { 308 | const int isize = domain->mysizes[0]; 309 | const int jsize = domain->mysizes[1]; 310 | const double * restrict dtemp = linear_system.x1pncl; 311 | double * restrict t = fluid->t.data; 312 | BEGIN 313 | T(i, j) += dtemp[cnt]; 314 | END 315 | if(0 != fluid_update_boundaries_t(domain, &fluid->t)){ 316 | return 1; 317 | } 318 | } 319 | return 0; 320 | } 321 | 322 | -------------------------------------------------------------------------------- /src/fluid/save.c: -------------------------------------------------------------------------------- 1 | #include "sdecomp.h" 2 | #include "array.h" 3 | #include "domain.h" 4 | #include "fluid.h" 5 | #include "fluid_solver.h" 6 | #include "fileio.h" 7 | 8 | int fluid_save( 9 | const char dirname[], 10 | const domain_t * domain, 11 | const fluid_t * fluid 12 | ){ 13 | // serial 14 | const int root = 0; 15 | int myrank = root; 16 | sdecomp.get_comm_rank(domain->info, &myrank); 17 | if(root == myrank){ 18 | fileio.w_serial(dirname, "m_dif", 0, NULL, fileio.npy_double, sizeof(double), &fluid->m_dif); 19 | fileio.w_serial(dirname, "t_dif", 0, NULL, fileio.npy_double, sizeof(double), &fluid->t_dif); 20 | } 21 | // collective 22 | array.dump(domain, dirname, "ux", fileio.npy_double, &fluid->ux); 23 | array.dump(domain, dirname, "uy", fileio.npy_double, &fluid->uy); 24 | array.dump(domain, dirname, "p", fileio.npy_double, &fluid-> p); 25 | array.dump(domain, dirname, "t", fileio.npy_double, &fluid-> t); 26 | return 0; 27 | } 28 | 29 | -------------------------------------------------------------------------------- /src/fluid/update_pressure.c: -------------------------------------------------------------------------------- 1 | #include "param.h" 2 | #include "runge_kutta.h" 3 | #include "memory.h" 4 | #include "domain.h" 5 | #include "fluid.h" 6 | #include "fluid_solver.h" 7 | #include "array_macros/domain/dxf.h" 8 | #include "array_macros/domain/dxc.h" 9 | #include "array_macros/fluid/p.h" 10 | #include "array_macros/fluid/psi.h" 11 | 12 | #define BEGIN \ 13 | for(int j = 1; j <= jsize; j++){ \ 14 | for(int i = 1; i <= isize; i++){ 15 | #define END \ 16 | } \ 17 | } 18 | 19 | static inline int add_explicit( 20 | const domain_t * domain, 21 | const fluid_t * fluid 22 | ){ 23 | const int isize = domain->mysizes[0]; 24 | const int jsize = domain->mysizes[1]; 25 | const double * restrict psi = fluid->psi.data; 26 | double * restrict p = fluid->p.data; 27 | BEGIN 28 | // explicit contribution 29 | P(i, j) += PSI(i, j); 30 | END 31 | return 0; 32 | } 33 | 34 | static inline int add_implicit_x( 35 | const domain_t * domain, 36 | const double prefactor, 37 | fluid_t * fluid 38 | ){ 39 | const int isize = domain->mysizes[0]; 40 | const int jsize = domain->mysizes[1]; 41 | const double * restrict dxf = domain->dxf; 42 | const double * restrict dxc = domain->dxc; 43 | const double * restrict psi = fluid->psi.data; 44 | double * restrict p = fluid->p.data; 45 | BEGIN 46 | // x implicit contribution 47 | const double dpsidx_xm = (- PSI(i-1, j ) + PSI(i , j )) / DXC(i ); 48 | const double dpsidx_xp = (- PSI(i , j ) + PSI(i+1, j )) / DXC(i+1); 49 | P(i, j) -= prefactor / DXF(i ) * ( 50 | - dpsidx_xm 51 | + dpsidx_xp 52 | ); 53 | END 54 | return 0; 55 | } 56 | 57 | static inline int add_implicit_y( 58 | const domain_t * domain, 59 | const double prefactor, 60 | fluid_t * fluid 61 | ){ 62 | const int isize = domain->mysizes[0]; 63 | const int jsize = domain->mysizes[1]; 64 | const double dy = domain->dy; 65 | const double * restrict psi = fluid->psi.data; 66 | double * restrict p = fluid->p.data; 67 | BEGIN 68 | // y implicit contribution 69 | const double dpsidy_ym = (- PSI(i , j-1) + PSI(i , j )) / dy; 70 | const double dpsidy_yp = (- PSI(i , j ) + PSI(i , j+1)) / dy; 71 | P(i, j) -= prefactor / dy * ( 72 | - dpsidy_ym 73 | + dpsidy_yp 74 | ); 75 | END 76 | return 0; 77 | } 78 | 79 | /** 80 | * @brief update pressure using scalar potential psi 81 | * @param[in] domain : information related to MPI domain decomposition 82 | * @param[in] rkstep : Runge-Kutta step 83 | * @param[in] dt : time step size 84 | * @param[in,out] fluid : scalar potential (in), pressure (out) 85 | * @return : error code 86 | */ 87 | int fluid_update_pressure( 88 | const domain_t * domain, 89 | const size_t rkstep, 90 | const double dt, 91 | fluid_t * fluid 92 | ){ 93 | // explicit contribution, always present 94 | add_explicit(domain, fluid); 95 | // gamma dt diffusivity / 2 96 | const double prefactor = 97 | 0.5 * rkcoefs[rkstep][rk_g] * dt * fluid->m_dif; 98 | // additional corrections if diffusive terms 99 | // in the direction is treated implicitly 100 | if(param_m_implicit_x){ 101 | add_implicit_x(domain, prefactor, fluid); 102 | } 103 | if(param_m_implicit_y){ 104 | add_implicit_y(domain, prefactor, fluid); 105 | } 106 | // impose boundary conditions and communicate halo cells 107 | if(0 != fluid_update_boundaries_p(domain, &fluid->p)){ 108 | return 1; 109 | } 110 | return 0; 111 | } 112 | 113 | -------------------------------------------------------------------------------- /src/halo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "array.h" 4 | #include "domain.h" 5 | #include "halo.h" 6 | 7 | // fixed parameters 8 | // since data type is defined, number of items is 1 9 | static const int nitems = 1; 10 | // same tag can be used since I use blocking communication 11 | static const int tag = 0; 12 | 13 | // assume the given data type has not been initialised yet 14 | static const MPI_Datatype dtype_default = MPI_DOUBLE; 15 | 16 | // communicate halo cells with the y-neighbour processes 17 | // NOTE: send boundary cells for simplicity 18 | int halo_communicate_in_y( 19 | const domain_t * domain, 20 | MPI_Datatype * dtype, 21 | array_t * array 22 | ){ 23 | // extract communicator 24 | const sdecomp_info_t * info = domain->info; 25 | MPI_Comm comm_cart = MPI_COMM_NULL; 26 | sdecomp.get_comm_cart(info, &comm_cart); 27 | // check negative / positive neighbour ranks 28 | int neighbours[2] = {MPI_PROC_NULL, MPI_PROC_NULL}; 29 | sdecomp.get_neighbours(info, SDECOMP_X1PENCIL, SDECOMP_YDIR, neighbours); 30 | // array size (with halo and boundary cells) 31 | const int isize_ = domain->mysizes[0] + array->nadds[0][0] + array->nadds[0][1]; 32 | const int jsize_ = domain->mysizes[1] + array->nadds[1][0] + array->nadds[1][1]; 33 | // number of halo cells 34 | // this function assumes same number of halo cells 35 | // in the negative / positive directions 36 | if(array->nadds[1][0] != array->nadds[1][1]){ 37 | printf("%s: number of halo cells in y (%d and %d) mismatch\n", 38 | __func__, array->nadds[1][0], array->nadds[1][1]); 39 | return 1; 40 | } 41 | const int nhalos_y = array->nadds[1][0]; 42 | // define datatype in y 43 | if(dtype_default == *dtype){ 44 | MPI_Type_contiguous( 45 | isize_ * nhalos_y, 46 | *dtype, 47 | dtype 48 | ); 49 | MPI_Type_commit(dtype); 50 | } 51 | // send to positive, receive from negative 52 | { 53 | const int sindices[NDIMS] = {0, jsize_ - 2 * nhalos_y}; 54 | const int rindices[NDIMS] = {0, 0 * nhalos_y}; 55 | const size_t soffset = sindices[0] + isize_ * sindices[1]; 56 | const size_t roffset = rindices[0] + isize_ * rindices[1]; 57 | MPI_Sendrecv( 58 | (char *)array->data + array->size * soffset, nitems, *dtype, neighbours[1], tag, 59 | (char *)array->data + array->size * roffset, nitems, *dtype, neighbours[0], tag, 60 | comm_cart, MPI_STATUS_IGNORE 61 | ); 62 | } 63 | // send to negative, receive from positive 64 | { 65 | const int sindices[NDIMS] = {0, 1 * nhalos_y}; 66 | const int rindices[NDIMS] = {0, jsize_ - 1 * nhalos_y}; 67 | const size_t soffset = sindices[0] + isize_ * sindices[1]; 68 | const size_t roffset = rindices[0] + isize_ * rindices[1]; 69 | MPI_Sendrecv( 70 | (char *)array->data + array->size * soffset, nitems, *dtype, neighbours[0], tag, 71 | (char *)array->data + array->size * roffset, nitems, *dtype, neighbours[1], tag, 72 | comm_cart, MPI_STATUS_IGNORE 73 | ); 74 | } 75 | return 0; 76 | } 77 | 78 | -------------------------------------------------------------------------------- /src/ib/collision.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "sdecomp.h" 6 | #include "memory.h" 7 | #include "domain.h" 8 | #include "ib.h" 9 | #include "ib_solver.h" 10 | 11 | static const double pi = 3.14159265358979324; 12 | 13 | // Collision pair table 14 | // 15 | // 16 | // ---+------------------------------ 17 | // 0 18 | // 1 19 | // 2 20 | // ... 21 | // ^ | 22 | // | 23 | // n0 24 | 25 | static size_t get_l( 26 | const size_t nitems, 27 | const size_t n0 28 | ){ 29 | // left -most index of the collision pair matrix for particle index "n0" 30 | return (2 * nitems - n0 - 1) * (n0 ) / 2 ; 31 | } 32 | 33 | static size_t get_r( 34 | const size_t nitems, 35 | const size_t n0 36 | ){ 37 | // right-most index of the collision pair matrix for particle index "n0" 38 | return (2 * nitems - n0 - 2) * (n0 + 1) / 2 - 1; 39 | } 40 | 41 | static int get_particle_indices( 42 | const size_t nitems, 43 | const size_t n, 44 | size_t * n0, 45 | size_t * n1 46 | ){ 47 | // convert index of collision pair "n" to the corresponding particle indices "n0" and "n1" 48 | for(*n0 = 0; ; (*n0)++){ 49 | if(get_l(nitems, *n0) <= n && n <= get_r(nitems, *n0)){ 50 | break; 51 | } 52 | } 53 | *n1 = n - get_l(nitems, *n0) + (*n0) + 1; 54 | return 0; 55 | } 56 | 57 | static int get_my_range( 58 | const domain_t * domain, 59 | const size_t nitems, 60 | size_t * n_min, 61 | size_t * n_max 62 | ){ 63 | int nprocs_ = 0; 64 | int myrank_ = 0; 65 | sdecomp.get_comm_size(domain->info, &nprocs_); 66 | sdecomp.get_comm_rank(domain->info, &myrank_); 67 | size_t nprocs = nprocs_; 68 | size_t myrank = myrank_; 69 | *n_min = 0; 70 | for(size_t n = 0; n < myrank; n++){ 71 | *n_min += (nitems + n) / nprocs; 72 | } 73 | *n_max = *n_min + (nitems + myrank) / nprocs; 74 | return 0; 75 | } 76 | 77 | static double harmonic_average( 78 | const double v0, 79 | const double v1 80 | ){ 81 | const double retval = 1. / v0 + 1. / v1; 82 | return 2. / retval; 83 | } 84 | 85 | static int compute_collision_force_p_p( 86 | const domain_t * domain, 87 | const size_t index, 88 | particle_t * p0, 89 | particle_t * p1 90 | ){ 91 | // correct periodicity 92 | double yoffset = 0.; 93 | { 94 | const double ly = domain->lengths[1]; 95 | const double p0y = p0->y + p0->dy; 96 | const double p1y = p1->y + p1->dy; 97 | double minval = DBL_MAX; 98 | for(int yper = -1; yper <= 1; yper++){ 99 | double val = fabs((p1y + ly * yper) - p0y); 100 | if(val < minval){ 101 | minval = val; 102 | yoffset = ly * yper; 103 | } 104 | } 105 | } 106 | // check collision and compute collision force when needed 107 | const double r0 = p0->r; 108 | const double m0 = p0->m; 109 | const double x0 = p0->x + p0->dx; 110 | const double y0 = p0->y + p0->dy; 111 | const double r1 = p1->r; 112 | const double m1 = p1->m; 113 | const double x1 = p1->x + p1->dx; 114 | const double y1 = p1->y + p1->dy + yoffset; 115 | // normal and overlap distance 116 | double nx = x1 - x0; 117 | double ny = y1 - y0; 118 | double norm = sqrt( 119 | + nx * nx 120 | + ny * ny 121 | ); 122 | // to avoid zero division just in case 123 | norm = fmax(norm, DBL_EPSILON); 124 | nx /= norm; 125 | ny /= norm; 126 | const double overlap_dist = r0 + r1 - norm; 127 | if(overlap_dist < 0.){ 128 | // do not collide 129 | return 0; 130 | } 131 | // do collide 132 | // compute force 133 | // k: pre-factor (spring stiffness) 134 | const double m = harmonic_average(m0, m1); 135 | const double r = harmonic_average(r0, r1); 136 | const double k = m * (pi * pi) / (r * r); 137 | const double cfx = k * overlap_dist * nx; 138 | const double cfy = k * overlap_dist * ny; 139 | p0->cfx[index] -= 1. / m0 * cfx; 140 | p1->cfx[index] += 1. / m1 * cfx; 141 | p0->cfy[index] -= 1. / m0 * cfy; 142 | p1->cfy[index] += 1. / m1 * cfy; 143 | return 0; 144 | } 145 | 146 | static int compute_collision_force_p_w( 147 | const double wallx, 148 | const size_t index, 149 | particle_t * p 150 | ){ 151 | const double x = p->x + p->dx; 152 | const double r = p->r; 153 | const double m = p->m; 154 | double nx = wallx - x; 155 | const double norm = fabs(nx); 156 | nx /= norm; 157 | const double overlap_dist = r - norm; 158 | if(overlap_dist < 0.){ 159 | return 0; 160 | } 161 | // compute force and torque 162 | // k: pre-factor (spring stiffness) 163 | const double k = m * (pi * pi) / (r * r); 164 | const double cfx = k * overlap_dist * nx; 165 | const double cfy = 0.; 166 | p->cfx[index] -= 1. / m * cfx; 167 | p->cfy[index] -= 1. / m * cfy; 168 | return 0; 169 | } 170 | 171 | static int synchronise_information( 172 | const domain_t * domain, 173 | const size_t index, 174 | ib_t * ib 175 | ){ 176 | MPI_Comm comm_cart = MPI_COMM_NULL; 177 | sdecomp.get_comm_cart(domain->info, &comm_cart); 178 | const size_t nitems = ib->nitems; 179 | particle_t * particles = ib->particles; 180 | // prepare message buffer 181 | const size_t nvars = 2; 182 | double * buf = memory_calloc(nvars * nitems, sizeof(double)); 183 | // pack 184 | for(size_t n = 0; n < nitems; n++){ 185 | particle_t * p = particles + n; 186 | buf[nvars * n + 0] = p->cfx[index]; 187 | buf[nvars * n + 1] = p->cfy[index]; 188 | } 189 | // sum up all 190 | MPI_Allreduce(MPI_IN_PLACE, buf, nvars * nitems, MPI_DOUBLE, MPI_SUM, comm_cart); 191 | // unpack 192 | for(size_t n = 0; n < nitems; n++){ 193 | particle_t * p = particles + n; 194 | p->cfx[index] = buf[nvars * n + 0]; 195 | p->cfy[index] = buf[nvars * n + 1]; 196 | } 197 | memory_free(buf); 198 | return 0; 199 | } 200 | 201 | int ib_compute_collision_force( 202 | const domain_t * domain, 203 | const size_t index, 204 | ib_t * ib 205 | ){ 206 | // NOTE: only the spring in the normal direction is considered for simplicity 207 | const double lx = domain->lengths[0]; 208 | const size_t nps = ib->nitems; 209 | particle_t * particles = ib->particles; 210 | // reset collision force at this CN step 211 | for(size_t n = 0; n < nps; n++){ 212 | particle_t * p = particles + n; 213 | p->cfx[index] = 0.; 214 | p->cfy[index] = 0.; 215 | } 216 | // particle-particle collisions 217 | { 218 | const size_t ncols = nps * (nps - 1) / 2; 219 | size_t n_min = 0; 220 | size_t n_max = 0; 221 | get_my_range(domain, ncols, &n_min, &n_max); 222 | for(size_t n = n_min; n < n_max; n++){ 223 | size_t n0 = 0; 224 | size_t n1 = 0; 225 | get_particle_indices(nps, n, &n0, &n1); 226 | compute_collision_force_p_p(domain, index, particles + n0, particles + n1); 227 | } 228 | } 229 | // particle-wall collisions 230 | { 231 | size_t n_min = 0; 232 | size_t n_max = 0; 233 | get_my_range(domain, nps, &n_min, &n_max); 234 | for(size_t n = n_min; n < n_max; n++){ 235 | compute_collision_force_p_w(0., index, particles + n); 236 | compute_collision_force_p_w(lx, index, particles + n); 237 | } 238 | } 239 | // synchronise computed forcings 240 | if(0 != synchronise_information(domain, index, ib)){ 241 | return 1; 242 | } 243 | return 0; 244 | } 245 | 246 | -------------------------------------------------------------------------------- /src/ib/exchange.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "runge_kutta.h" 4 | #include "memory.h" 5 | #include "array.h" 6 | #include "domain.h" 7 | #include "halo.h" 8 | #include "fluid.h" 9 | #include "fluid_solver.h" 10 | #include "ib.h" 11 | #include "ib_solver.h" 12 | #include "internal.h" 13 | #include "array_macros/fluid/ux.h" 14 | #include "array_macros/fluid/uy.h" 15 | #include "array_macros/ib/ibfx.h" 16 | #include "array_macros/ib/ibfy.h" 17 | 18 | static int update_boundaries( 19 | const domain_t * domain, 20 | array_t * arr 21 | ){ 22 | static MPI_Datatype dtypes[NDIMS - 1] = { 23 | MPI_DOUBLE, 24 | }; 25 | if(0 != halo_communicate_in_y(domain, dtypes + 0, arr)){ 26 | return 1; 27 | } 28 | return 0; 29 | } 30 | 31 | static int exchange_momentum( 32 | const domain_t * domain, 33 | const fluid_t * fluid, 34 | const double dt, 35 | ib_t * ib 36 | ){ 37 | // \int -fx dV 38 | // \int -fy dV 39 | // \int ( + ry fx - rx fy ) dV 40 | const int isize = domain->mysizes[0]; 41 | const int jsize = domain->mysizes[1]; 42 | const int ioffs = domain->offsets[0]; 43 | const int joffs = domain->offsets[1]; 44 | const double ly = domain->lengths[1]; 45 | const double dx = domain->dx; 46 | const double dy = domain->dy; 47 | const double deltas[NDIMS] = {dx, dy}; 48 | const double cellsize = dx * dy; 49 | const double xoffs = dx * ioffs; 50 | const double yoffs = dy * joffs; 51 | const double * ux = fluid->ux.data; 52 | const double * uy = fluid->uy.data; 53 | const size_t nitems = ib->nitems; 54 | particle_t * particles = ib->particles; 55 | double * ibfx = ib->ibfx.data; 56 | double * ibfy = ib->ibfy.data; 57 | for(size_t n = 0; n < nitems; n++){ 58 | particle_t * p = particles + n; 59 | // constant parameters 60 | const double pr = p->r; 61 | const double px = p->x; 62 | const double py = p->y; 63 | const double pux = p->ux; 64 | const double puy = p->uy; 65 | const double pvz = p->vz; 66 | // mass and moment of inertia 67 | const double pm = p->m; 68 | const double pmi = p->mi; 69 | // targets 70 | double * fux = &p->fux; 71 | double * fuy = &p->fuy; 72 | double * tvz = &p->tvz; 73 | *fux = 0.; 74 | *fuy = 0.; 75 | *tvz = 0.; 76 | for(int yper = -1; yper <= 1; yper++){ 77 | const double py_ = py + ly * yper; 78 | int imin = 0; 79 | int imax = 0; 80 | int jmin = 0; 81 | int jmax = 0; 82 | ib_decide_loop_size(1, isize, dx, pr, px - xoffs, &imin, &imax); 83 | ib_decide_loop_size(1, jsize, dy, pr, py_ - yoffs, &jmin, &jmax); 84 | for(int j = jmin; j <= jmax; j++){ 85 | const double y = yoffs + 0.5 * (2 * j - 1) * dy; 86 | for(int i = imin; i <= imax; i++){ 87 | const double x = xoffs + 0.5 * (2 * i - 1) * dx; 88 | const double w = ib_s_weight( 89 | deltas, 90 | pr, 91 | (double [NDIMS]){px, py_}, 92 | (double [NDIMS]){x, y} 93 | ); 94 | const double ux_p = pux - pvz * (y - py_); 95 | const double uy_p = puy + pvz * (x - px ); 96 | const double ux_f = + 0.5 * UX(i , j ) + 0.5 * UX(i+1, j ); 97 | const double uy_f = + 0.5 * UY(i , j ) + 0.5 * UY(i , j+1); 98 | const double fx = w * (ux_p - ux_f) / dt; 99 | const double fy = w * (uy_p - uy_f) / dt; 100 | IBFX(i, j) += fx * dt; 101 | IBFY(i, j) += fy * dt; 102 | *fux -= + fx * cellsize / pm; 103 | *fuy -= + fy * cellsize / pm; 104 | *tvz -= - (y - py_) * (fx * cellsize) / pmi; 105 | *tvz -= + (x - px ) * (fy * cellsize) / pmi; 106 | } 107 | } 108 | } 109 | update_boundaries(domain, &ib->ibfx); 110 | update_boundaries(domain, &ib->ibfy); 111 | } 112 | return 0; 113 | } 114 | 115 | static int update_predicted_velocity( 116 | const domain_t * domain, 117 | fluid_t * fluid, 118 | const ib_t * ib 119 | ){ 120 | const int isize = domain->mysizes[0]; 121 | const int jsize = domain->mysizes[1]; 122 | double * ux = fluid->ux.data; 123 | double * uy = fluid->uy.data; 124 | const double * ibfx = ib->ibfx.data; 125 | const double * ibfy = ib->ibfy.data; 126 | for(int j = 1; j <= jsize; j++){ 127 | for(int i = 2; i <= isize; i++){ 128 | UX(i, j) += 129 | + 0.5 * IBFX(i-1, j ) 130 | + 0.5 * IBFX(i , j ); 131 | } 132 | } 133 | if(0 != fluid_update_boundaries_ux(domain, &fluid->ux)){ 134 | return 1; 135 | } 136 | for(int j = 1; j <= jsize; j++){ 137 | for(int i = 1; i <= isize; i++){ 138 | UY(i, j) += 139 | + 0.5 * IBFY(i , j-1) 140 | + 0.5 * IBFY(i , j ); 141 | } 142 | } 143 | if(0 != fluid_update_boundaries_uy(domain, &fluid->uy)){ 144 | return 1; 145 | } 146 | return 0; 147 | } 148 | 149 | static int synchronise_information( 150 | const domain_t * domain, 151 | ib_t * ib 152 | ){ 153 | MPI_Comm comm_cart = MPI_COMM_NULL; 154 | sdecomp.get_comm_cart(domain->info, &comm_cart); 155 | const size_t nitems = ib->nitems; 156 | particle_t * particles = ib->particles; 157 | // prepare message buffer 158 | const size_t nvars = 3; 159 | double * buf = memory_calloc(nvars * nitems, sizeof(double)); 160 | // pack 161 | for(size_t n = 0; n < nitems; n++){ 162 | particle_t * p = particles + n; 163 | buf[nvars * n + 0] = p->fux; 164 | buf[nvars * n + 1] = p->fuy; 165 | buf[nvars * n + 2] = p->tvz; 166 | } 167 | // sum up all 168 | MPI_Allreduce(MPI_IN_PLACE, buf, nvars * nitems, MPI_DOUBLE, MPI_SUM, comm_cart); 169 | // unpack 170 | for(size_t n = 0; n < nitems; n++){ 171 | particle_t * p = particles + n; 172 | p->fux = buf[nvars * n + 0]; 173 | p->fuy = buf[nvars * n + 1]; 174 | p->tvz = buf[nvars * n + 2]; 175 | } 176 | memory_free(buf); 177 | return 0; 178 | } 179 | 180 | int ib_exchange_momentum( 181 | const domain_t * domain, 182 | const size_t rkstep, 183 | const double dt, 184 | fluid_t * fluid, 185 | ib_t * ib 186 | ){ 187 | if(0 != exchange_momentum(domain, fluid, rkcoefs[rkstep][rk_g] * dt, ib)){ 188 | return 1; 189 | } 190 | if(0 != update_predicted_velocity(domain, fluid, ib)){ 191 | return 1; 192 | } 193 | if(0 != synchronise_information(domain, ib)){ 194 | return 1; 195 | } 196 | return 0; 197 | } 198 | 199 | -------------------------------------------------------------------------------- /src/ib/increment.c: -------------------------------------------------------------------------------- 1 | #include "runge_kutta.h" 2 | #include "ib.h" 3 | #include "ib_solver.h" 4 | 5 | static int increment_particle_velocities( 6 | const double dt, 7 | ib_t * ib 8 | ){ 9 | const size_t nitems = ib->nitems; 10 | particle_t * particles = ib->particles; 11 | for(size_t n = 0; n < nitems; n++){ 12 | particle_t * p = particles + n; 13 | // compute new increments 14 | p->dux = 15 | + 0.5 * dt * (p->cfx[0] + p->cfx[1]) // collision 16 | + dt * p->fux // boundary force 17 | + p->iux[1] - p->iux[0] // internal inertia 18 | ; 19 | p->duy = 20 | + 0.5 * dt * (p->cfy[0] + p->cfy[1]) // collision 21 | + dt * p->fuy // boundary force 22 | + p->iuy[1] - p->iuy[0] // internal inertia 23 | ; 24 | p->dvz = 25 | + dt * p->tvz // boundary torque 26 | + p->ivz[1] - p->ivz[0] // internal inertia 27 | ; 28 | } 29 | return 0; 30 | } 31 | 32 | static int increment_particle_positions( 33 | const double dt, 34 | ib_t * ib 35 | ){ 36 | const size_t nitems = ib->nitems; 37 | particle_t * particles = ib->particles; 38 | for(size_t n = 0; n < nitems; n++){ 39 | particle_t * p = particles + n; 40 | const double ux0 = p->ux; 41 | const double uy0 = p->uy; 42 | const double ux1 = p->ux + p->dux; 43 | const double uy1 = p->uy + p->duy; 44 | p->dx = 0.5 * dt * (ux0 + ux1); 45 | p->dy = 0.5 * dt * (uy0 + uy1); 46 | } 47 | return 0; 48 | } 49 | 50 | int ib_increment_particles( 51 | const size_t rkstep, 52 | double dt, 53 | ib_t * ib 54 | ){ 55 | dt *= rkcoefs[rkstep][rk_g]; 56 | increment_particle_velocities(dt, ib); 57 | increment_particle_positions(dt, ib); 58 | return 0; 59 | } 60 | 61 | -------------------------------------------------------------------------------- /src/ib/inertia.c: -------------------------------------------------------------------------------- 1 | #include "memory.h" 2 | #include "domain.h" 3 | #include "fluid.h" 4 | #include "ib.h" 5 | #include "ib_solver.h" 6 | #include "internal.h" 7 | #include "array_macros/fluid/ux.h" 8 | #include "array_macros/fluid/uy.h" 9 | 10 | static int compute_inertia( 11 | const domain_t * domain, 12 | const size_t index, 13 | const fluid_t * fluid, 14 | ib_t * ib 15 | ){ 16 | // \int ux dV 17 | // \int uy dV 18 | // \int ( - ry ux + rx uy ) dV 19 | const int isize = domain->mysizes[0]; 20 | const int jsize = domain->mysizes[1]; 21 | const int ioffs = domain->offsets[0]; 22 | const int joffs = domain->offsets[1]; 23 | const double ly = domain->lengths[1]; 24 | const double dx = domain->dx; 25 | const double dy = domain->dy; 26 | const double deltas[NDIMS] = {dx, dy}; 27 | const double cellsize = dx * dy; 28 | const double xoffs = dx * ioffs; 29 | const double yoffs = dy * joffs; 30 | const double * ux = fluid->ux.data; 31 | const double * uy = fluid->uy.data; 32 | const size_t nitems = ib->nitems; 33 | particle_t * particles = ib->particles; 34 | for(size_t n = 0; n < nitems; n++){ 35 | particle_t * p = particles + n; 36 | // constant parameters 37 | const double pr = p->r; 38 | const double px = p->x + p->dx; 39 | const double py = p->y + p->dy; 40 | const double pm = p->m; 41 | const double pmi = p->mi; 42 | // targets 43 | double * iux = &p->iux[index]; 44 | double * iuy = &p->iuy[index]; 45 | double * ivz = &p->ivz[index]; 46 | *iux = 0.; 47 | *iuy = 0.; 48 | *ivz = 0.; 49 | for(int yper = -1; yper <= 1; yper++){ 50 | const double py_ = py + ly * yper; 51 | int imin = 0; 52 | int imax = 0; 53 | int jmin = 0; 54 | int jmax = 0; 55 | ib_decide_loop_size(1, isize, dx, pr, px - xoffs, &imin, &imax); 56 | ib_decide_loop_size(1, jsize, dy, pr, py_ - yoffs, &jmin, &jmax); 57 | for(int j = jmin; j <= jmax; j++){ 58 | const double y = yoffs + 0.5 * (2 * j - 1) * dy; 59 | for(int i = imin; i <= imax; i++){ 60 | const double x = xoffs + 0.5 * (2 * i - 1) * dx; 61 | const double w = ib_v_weight( 62 | deltas, 63 | pr, 64 | (double [NDIMS]){px, py_}, 65 | (double [NDIMS]){x, y} 66 | ); 67 | const double velx = + 0.5 * UX(i , j ) + 0.5 * UX(i+1, j ); 68 | const double vely = + 0.5 * UY(i , j ) + 0.5 * UY(i , j+1); 69 | const double valx = w * velx * cellsize; 70 | const double valy = w * vely * cellsize; 71 | *iux += valx / pm; 72 | *iuy += valy / pm; 73 | *ivz += - (y - py_) * valx / pmi; 74 | *ivz += + (x - px ) * valy / pmi; 75 | } 76 | } 77 | } 78 | } 79 | return 0; 80 | } 81 | 82 | static int synchronise_information( 83 | const domain_t * domain, 84 | const size_t index, 85 | ib_t * ib 86 | ){ 87 | MPI_Comm comm_cart = MPI_COMM_NULL; 88 | sdecomp.get_comm_cart(domain->info, &comm_cart); 89 | const size_t nitems = ib->nitems; 90 | particle_t * particles = ib->particles; 91 | // prepare message buffer 92 | const size_t nvars = 3; 93 | double * buf = memory_calloc(nvars * nitems, sizeof(double)); 94 | // pack 95 | for(size_t n = 0; n < nitems; n++){ 96 | particle_t * p = particles + n; 97 | buf[nvars * n + 0] = p->iux[index]; 98 | buf[nvars * n + 1] = p->iuy[index]; 99 | buf[nvars * n + 2] = p->ivz[index]; 100 | } 101 | // sum up all 102 | MPI_Allreduce(MPI_IN_PLACE, buf, nvars * nitems, MPI_DOUBLE, MPI_SUM, comm_cart); 103 | // unpack 104 | for(size_t n = 0; n < nitems; n++){ 105 | particle_t * p = particles + n; 106 | p->iux[index] = buf[nvars * n + 0]; 107 | p->iuy[index] = buf[nvars * n + 1]; 108 | p->ivz[index] = buf[nvars * n + 2]; 109 | } 110 | memory_free(buf); 111 | return 0; 112 | } 113 | 114 | int ib_compute_inertia( 115 | const domain_t * domain, 116 | const size_t index, 117 | const fluid_t * fluid, 118 | ib_t * ib 119 | ){ 120 | if(0 != compute_inertia(domain, index, fluid, ib)){ 121 | return 1; 122 | } 123 | if(0 != synchronise_information(domain, index, ib)){ 124 | return 1; 125 | } 126 | return 0; 127 | } 128 | 129 | -------------------------------------------------------------------------------- /src/ib/init.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "memory.h" 4 | #include "fileio.h" 5 | #include "ib.h" 6 | #include "ib_solver.h" 7 | #include "array_macros/ib/ibfx.h" 8 | #include "array_macros/ib/ibfy.h" 9 | 10 | static double compute_m( 11 | const double d, 12 | const double r 13 | ){ 14 | const double pi = 3.14159265358979324; 15 | const double vol = pi * pow(r, 2.); 16 | return d * vol; 17 | } 18 | 19 | static double compute_mi( 20 | const double d, 21 | const double r 22 | ){ 23 | const double m = compute_m(d, r); 24 | return 0.5 * m * pow(r, 2.); 25 | } 26 | 27 | static void report( 28 | const sdecomp_info_t * info, 29 | const ib_t * ib 30 | ){ 31 | const int root = 0; 32 | int myrank = root; 33 | sdecomp.get_comm_rank(info, &myrank); 34 | if(root == myrank){ 35 | printf("IB\n"); 36 | printf("\tnitems: %zu\n", ib->nitems); 37 | fflush(stdout); 38 | } 39 | } 40 | 41 | int ib_init( 42 | const char dirname_ic[], 43 | const domain_t * domain, 44 | ib_t * ib 45 | ){ 46 | // load number of particles 47 | if(0 != fileio.r_serial(dirname_ic, "p_nitems", 0, NULL, fileio.npy_size_t, sizeof(size_t), &ib->nitems)){ 48 | return 1; 49 | } 50 | // allocate buffers 51 | const size_t nitems = ib->nitems; 52 | ib->particles = memory_calloc(nitems, sizeof(particle_t)); 53 | if(0 != array.prepare(domain, IBFX_NADDS, sizeof(double), &ib->ibfx)) return 1; 54 | if(0 != array.prepare(domain, IBFY_NADDS, sizeof(double), &ib->ibfy)) return 1; 55 | // convert SoA to AoS 56 | double * rs = memory_calloc(nitems, sizeof(double)); 57 | double * ds = memory_calloc(nitems, sizeof(double)); 58 | double * xs = memory_calloc(nitems, sizeof(double)); 59 | double * ys = memory_calloc(nitems, sizeof(double)); 60 | double * uxs = memory_calloc(nitems, sizeof(double)); 61 | double * uys = memory_calloc(nitems, sizeof(double)); 62 | double * vzs = memory_calloc(nitems, sizeof(double)); 63 | if(0 != fileio.r_serial(dirname_ic, "p_rs", 1, (size_t [1]){nitems}, fileio.npy_double, sizeof(double), rs)){ 64 | return 1; 65 | } 66 | if(0 != fileio.r_serial(dirname_ic, "p_ds", 1, (size_t [1]){nitems}, fileio.npy_double, sizeof(double), ds)){ 67 | return 1; 68 | } 69 | if(0 != fileio.r_serial(dirname_ic, "p_xs", 1, (size_t [1]){nitems}, fileio.npy_double, sizeof(double), xs)){ 70 | return 1; 71 | } 72 | if(0 != fileio.r_serial(dirname_ic, "p_ys", 1, (size_t [1]){nitems}, fileio.npy_double, sizeof(double), ys)){ 73 | return 1; 74 | } 75 | if(0 != fileio.r_serial(dirname_ic, "p_uxs", 1, (size_t [1]){nitems}, fileio.npy_double, sizeof(double), uxs)){ 76 | return 1; 77 | } 78 | if(0 != fileio.r_serial(dirname_ic, "p_uys", 1, (size_t [1]){nitems}, fileio.npy_double, sizeof(double), uys)){ 79 | return 1; 80 | } 81 | if(0 != fileio.r_serial(dirname_ic, "p_vzs", 1, (size_t [1]){nitems}, fileio.npy_double, sizeof(double), vzs)){ 82 | return 1; 83 | } 84 | particle_t * ps = ib->particles; 85 | for(size_t n = 0; n < nitems; n++){ 86 | ps[n].r = rs[n]; 87 | ps[n].d = ds[n]; 88 | ps[n].m = compute_m (ps[n].d, ps[n].r); 89 | ps[n].mi = compute_mi(ps[n].d, ps[n].r); 90 | ps[n].x = xs[n]; 91 | ps[n].y = ys[n]; 92 | ps[n].ux = uxs[n]; 93 | ps[n].uy = uys[n]; 94 | ps[n].vz = vzs[n]; 95 | } 96 | memory_free(rs); 97 | memory_free(ds); 98 | memory_free(xs); 99 | memory_free(ys); 100 | memory_free(uxs); 101 | memory_free(uys); 102 | memory_free(vzs); 103 | report(domain->info, ib); 104 | return 0; 105 | } 106 | 107 | -------------------------------------------------------------------------------- /src/ib/internal.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "ib.h" 3 | #include "internal.h" 4 | 5 | // sharpness of diffusive surface 6 | static const double beta = 2.; 7 | 8 | // number of grid points outside circles 9 | static const int nextra = 3; 10 | 11 | static double min( 12 | const double vals[NDIMS] 13 | ){ 14 | double val = 1.e+16; 15 | for(size_t dim = 0; dim < NDIMS; dim++){ 16 | val = fmin(val, vals[dim]); 17 | } 18 | return val; 19 | } 20 | 21 | static double compute_signed_dist( 22 | const double delta, 23 | const double radius, 24 | const double center[NDIMS], 25 | const double point[NDIMS] 26 | ){ 27 | const double deltas[NDIMS] = { 28 | point[0] - center[0], 29 | point[1] - center[1], 30 | }; 31 | double dist = sqrt( 32 | + deltas[0] * deltas[0] 33 | + deltas[1] * deltas[1] 34 | ); 35 | // convert it such that inside: >0, outside: <0 36 | dist = radius - dist; 37 | return dist / delta; 38 | } 39 | 40 | double ib_s_weight( 41 | const double deltas[NDIMS], 42 | const double pr, 43 | const double center[NDIMS], 44 | const double point[NDIMS] 45 | ){ 46 | const double dist = compute_signed_dist(min(deltas), pr, center, point); 47 | const double tanh_ = tanh(beta * dist); 48 | return 0.5 * beta * (1. - tanh_ * tanh_); 49 | } 50 | 51 | double ib_v_weight( 52 | const double deltas[NDIMS], 53 | const double pr, 54 | const double center[NDIMS], 55 | const double point[NDIMS] 56 | ){ 57 | const double dist = compute_signed_dist(min(deltas), pr, center, point); 58 | const double tanh_ = tanh(beta * dist); 59 | return 0.5 * (1. + tanh_); 60 | } 61 | 62 | int ib_decide_loop_size( 63 | const int lbound, 64 | const int ubound, 65 | const double delta, 66 | const double radius, 67 | const double center, 68 | int * min, 69 | int * max 70 | ){ 71 | *min = (int)( floor((center - radius) / delta) - nextra ); 72 | *max = (int)( ceil ((center + radius) / delta) + nextra ); 73 | *min = *min < lbound ? lbound : *min; 74 | *max = *max > ubound ? ubound : *max; 75 | return 0; 76 | } 77 | 78 | -------------------------------------------------------------------------------- /src/ib/internal.h: -------------------------------------------------------------------------------- 1 | #if !defined(IB_INTERNAL_H) 2 | #define IB_INTERNAL_H 3 | 4 | #include "domain.h" 5 | 6 | extern int ib_decide_loop_size( 7 | const int lbound, 8 | const int ubound, 9 | const double delta, 10 | const double radius, 11 | const double center, 12 | int * min, 13 | int * max 14 | ); 15 | 16 | extern double ib_s_weight( 17 | const double deltas[NDIMS], 18 | const double pr, 19 | const double center[NDIMS], 20 | const double point[NDIMS] 21 | ); 22 | 23 | extern double ib_v_weight( 24 | const double deltas[NDIMS], 25 | const double pr, 26 | const double center[NDIMS], 27 | const double point[NDIMS] 28 | ); 29 | 30 | #endif // IB_INTERNAL_H 31 | -------------------------------------------------------------------------------- /src/ib/reset.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "ib.h" 3 | #include "ib_solver.h" 4 | 5 | static int reset_particles( 6 | ib_t * ib 7 | ){ 8 | const size_t nitems = ib->nitems; 9 | particle_t * particles = ib->particles; 10 | for(size_t n = 0; n < nitems; n++){ 11 | particle_t * p = particles + n; 12 | p->dx = 0.; 13 | p->dy = 0.; 14 | p->dux = 0.; 15 | p->duy = 0.; 16 | p->dvz = 0.; 17 | } 18 | return 0; 19 | } 20 | 21 | static int reset_arrays( 22 | ib_t * ib 23 | ){ 24 | array_t ibfx = ib->ibfx; 25 | array_t ibfy = ib->ibfy; 26 | memset(ibfx.data, 0, ibfx.datasize); 27 | memset(ibfy.data, 0, ibfy.datasize); 28 | return 0; 29 | } 30 | 31 | int ib_reset_variables( 32 | ib_t * ib 33 | ){ 34 | if(0 != reset_particles(ib)){ 35 | return 1; 36 | } 37 | if(0 != reset_arrays(ib)){ 38 | return 1; 39 | } 40 | return 0; 41 | } 42 | 43 | -------------------------------------------------------------------------------- /src/ib/save.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "memory.h" 3 | #include "array.h" 4 | #include "fileio.h" 5 | #include "domain.h" 6 | #include "ib.h" 7 | #include "ib_solver.h" 8 | 9 | static const bool dump_arrays = false; 10 | 11 | int ib_save( 12 | const char dirname[], 13 | const domain_t * domain, 14 | const ib_t * ib 15 | ){ 16 | if(dump_arrays){ 17 | array.dump(domain, dirname, "ibfx", fileio.npy_double, &ib->ibfx); 18 | array.dump(domain, dirname, "ibfy", fileio.npy_double, &ib->ibfy); 19 | } 20 | const int root = 0; 21 | int myrank = root; 22 | sdecomp.get_comm_rank(domain->info, &myrank); 23 | if(root != myrank){ 24 | return 0; 25 | } 26 | const size_t nitems = ib->nitems; 27 | // convert AoS to SoA 28 | double * rs = memory_calloc(nitems, sizeof(double)); 29 | double * ds = memory_calloc(nitems, sizeof(double)); 30 | double * xs = memory_calloc(nitems, sizeof(double)); 31 | double * ys = memory_calloc(nitems, sizeof(double)); 32 | double * uxs = memory_calloc(nitems, sizeof(double)); 33 | double * uys = memory_calloc(nitems, sizeof(double)); 34 | double * vzs = memory_calloc(nitems, sizeof(double)); 35 | particle_t * ps = ib->particles; 36 | for(size_t n = 0; n < nitems; n++){ 37 | rs[n] = ps[n].r; 38 | ds[n] = ps[n].d; 39 | xs[n] = ps[n].x; 40 | ys[n] = ps[n].y; 41 | uxs[n] = ps[n].ux; 42 | uys[n] = ps[n].uy; 43 | vzs[n] = ps[n].vz; 44 | } 45 | if(0 != fileio.w_serial(dirname, "p_nitems", 0, NULL, fileio.npy_size_t, sizeof(size_t), &nitems)){ 46 | return 1; 47 | } 48 | if(0 != fileio.w_serial(dirname, "p_rs", 1, (size_t [1]){nitems}, fileio.npy_double, sizeof(double), rs)){ 49 | return 1; 50 | } 51 | if(0 != fileio.w_serial(dirname, "p_ds", 1, (size_t [1]){nitems}, fileio.npy_double, sizeof(double), ds)){ 52 | return 1; 53 | } 54 | if(0 != fileio.w_serial(dirname, "p_xs", 1, (size_t [1]){nitems}, fileio.npy_double, sizeof(double), xs)){ 55 | return 1; 56 | } 57 | if(0 != fileio.w_serial(dirname, "p_ys", 1, (size_t [1]){nitems}, fileio.npy_double, sizeof(double), ys)){ 58 | return 1; 59 | } 60 | if(0 != fileio.w_serial(dirname, "p_uxs", 1, (size_t [1]){nitems}, fileio.npy_double, sizeof(double), uxs)){ 61 | return 1; 62 | } 63 | if(0 != fileio.w_serial(dirname, "p_uys", 1, (size_t [1]){nitems}, fileio.npy_double, sizeof(double), uys)){ 64 | return 1; 65 | } 66 | if(0 != fileio.w_serial(dirname, "p_vzs", 1, (size_t [1]){nitems}, fileio.npy_double, sizeof(double), vzs)){ 67 | return 1; 68 | } 69 | memory_free(rs); 70 | memory_free(ds); 71 | memory_free(xs); 72 | memory_free(ys); 73 | memory_free(uxs); 74 | memory_free(uys); 75 | memory_free(vzs); 76 | return 0; 77 | } 78 | 79 | -------------------------------------------------------------------------------- /src/ib/update.c: -------------------------------------------------------------------------------- 1 | #include "domain.h" 2 | #include "ib.h" 3 | #include "ib_solver.h" 4 | 5 | static int update_particle_velocities( 6 | ib_t * ib 7 | ){ 8 | const size_t nitems = ib->nitems; 9 | particle_t * particles = ib->particles; 10 | for(size_t n = 0; n < nitems; n++){ 11 | particle_t * p = particles + n; 12 | p->ux += p->dux; 13 | p->uy += p->duy; 14 | p->vz += p->dvz; 15 | } 16 | return 0; 17 | } 18 | 19 | static int update_particle_positions( 20 | const domain_t * domain, 21 | ib_t * ib 22 | ){ 23 | const double ly = domain->lengths[1]; 24 | const size_t nitems = ib->nitems; 25 | particle_t * particles = ib->particles; 26 | for(size_t n = 0; n < nitems; n++){ 27 | particle_t * p = particles + n; 28 | p->x += p->dx; 29 | p->y += p->dy; 30 | // correct periodicity 31 | if(p->y < 0.) p->y += ly; 32 | if(p->y > ly) p->y -= ly; 33 | } 34 | return 0; 35 | } 36 | 37 | int ib_update_particles( 38 | const domain_t * domain, 39 | ib_t * ib 40 | ){ 41 | update_particle_velocities(ib); 42 | update_particle_positions(domain, ib); 43 | return 0; 44 | } 45 | 46 | -------------------------------------------------------------------------------- /src/integrate.c: -------------------------------------------------------------------------------- 1 | #include "runge_kutta.h" 2 | #include "domain.h" 3 | #include "fluid.h" 4 | #include "fluid_solver.h" 5 | #include "ib.h" 6 | #include "ib_solver.h" 7 | #include "integrate.h" 8 | #include "decide_dt.h" 9 | 10 | // integrate the equations for one time step 11 | int integrate( 12 | const domain_t * domain, 13 | fluid_t * fluid, 14 | ib_t * ib, 15 | double * dt 16 | ){ 17 | // decide time step size 18 | if(0 != decide_dt(domain, fluid, dt)){ 19 | return 1; 20 | } 21 | // Runge-Kutta iterations 22 | // max iteration, should be three 23 | for(size_t rkstep = 0; rkstep < RKSTEPMAX; rkstep++){ 24 | // reset ib-related right-hand-side variables 25 | if(0 != ib_reset_variables(ib)){ 26 | return 1; 27 | } 28 | // predict flow field 29 | // compute right-hand-side terms of RK scheme 30 | if(0 != fluid_compute_rhs(domain, fluid)){ 31 | return 1; 32 | } 33 | // compute advective terms inside particle at k step 34 | if(0 != ib_compute_inertia(domain, 0, fluid, ib)){ 35 | return 1; 36 | } 37 | // compute collision force based on k-step position 38 | if(0 != ib_compute_collision_force(domain, 0, ib)){ 39 | return 1; 40 | } 41 | // couple external factors: buoyancy force 42 | if(0 != fluid_couple_external_force(domain, fluid)){ 43 | return 1; 44 | } 45 | // update flow field 46 | // NOTE: while the temperature is fully updated here, 47 | // the velocity field is still non-solenoidal 48 | if(0 != fluid_predict_field(domain, rkstep, *dt, fluid)){ 49 | return 1; 50 | } 51 | // compute hydrodynamic force between fluid and objects 52 | if(0 != ib_exchange_momentum(domain, rkstep, *dt, fluid, ib)){ 53 | return 1; 54 | } 55 | // compute scalar potential 56 | // now the temperature field has been updated, 57 | // while the velocity field is not divergence free 58 | // and thus the following correction step is needed 59 | if(0 != fluid_compute_potential(domain, rkstep, *dt, fluid)){ 60 | return 1; 61 | } 62 | // correct velocity field to satisfy mass conservation 63 | if(0 != fluid_correct_velocity(domain, rkstep, *dt, fluid)){ 64 | return 1; 65 | } 66 | // update pressure 67 | if(0 != fluid_update_pressure(domain, rkstep, *dt, fluid)){ 68 | return 1; 69 | } 70 | // update particles iteratively 71 | for(size_t substep = 0; substep < 4; substep++){ 72 | // compute advective terms inside particle at k+1 step 73 | if(0 != ib_compute_inertia(domain, 1, fluid, ib)){ 74 | return 1; 75 | } 76 | // compute collision force based on k-step position 77 | if(0 != ib_compute_collision_force(domain, 1, ib)){ 78 | return 1; 79 | } 80 | // compute particle increments 81 | if(0 != ib_increment_particles(rkstep, *dt, ib)){ 82 | return 1; 83 | } 84 | } 85 | // update particle velocity and position 86 | if(0 != ib_update_particles(domain, ib)){ 87 | return 1; 88 | } 89 | } 90 | return 0; 91 | } 92 | 93 | -------------------------------------------------------------------------------- /src/linear_system.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "memory.h" 5 | #include "sdecomp.h" 6 | #include "domain.h" 7 | #include "linear_system.h" 8 | #include "tdm.h" 9 | 10 | static size_t get_nitems( 11 | const size_t sizes[NDIMS] 12 | ){ 13 | // helper function just to multiply sizes and return 14 | size_t nitems = 1; 15 | for(int dim = 0; dim < NDIMS; dim++){ 16 | nitems *= sizes[dim]; 17 | } 18 | return nitems; 19 | } 20 | 21 | /** 22 | * @brief initialise linear solver to update field implicitly 23 | * @param[in] info : information about domain decomposition 24 | * @param[in] implicit : treatment of the diffusive terms in each direction 25 | * @param[in] glsizes : GLOBAL size of array 26 | * @return : structure storing buffers and plans to solve linear systems in each direction 27 | */ 28 | int linear_system_init( 29 | const sdecomp_info_t * info, 30 | const bool implicit[NDIMS], 31 | const size_t glsizes[NDIMS], 32 | linear_system_t * linear_system 33 | ){ 34 | if(linear_system->is_initialised){ 35 | printf("this linear_system object is already initialised\n"); 36 | return 1; 37 | } 38 | memcpy(linear_system->implicit, implicit, sizeof(bool) * NDIMS); 39 | // pencils (and their sizes) to store input and output of linear systems 40 | double * restrict * x1pncl = &linear_system->x1pncl; 41 | double * restrict * y1pncl = &linear_system->y1pncl; 42 | // check size first 43 | size_t * x1pncl_mysizes = linear_system->x1pncl_mysizes; 44 | size_t * y1pncl_mysizes = linear_system->y1pncl_mysizes; 45 | for(int dim = 0; dim < NDIMS; dim++){ 46 | if(0 != sdecomp.get_pencil_mysize(info, SDECOMP_X1PENCIL, dim, glsizes[dim], x1pncl_mysizes + dim)) return 1; 47 | if(0 != sdecomp.get_pencil_mysize(info, SDECOMP_Y1PENCIL, dim, glsizes[dim], y1pncl_mysizes + dim)) return 1; 48 | } 49 | // allocate pencils if needed 50 | // NOTE: x1pncl is not needed for fully-explicit case, 51 | // but I always allocate it here for simplicity (to store delta values) 52 | *x1pncl = memory_calloc(get_nitems(x1pncl_mysizes), sizeof(double)); 53 | *y1pncl = implicit[1] ? memory_calloc(get_nitems(y1pncl_mysizes), sizeof(double)) : NULL; 54 | // initialise parallel matrix transpose if needed 55 | // between x1 and y1 56 | if(implicit[1]){ 57 | sdecomp_transpose_plan_t ** plan_f = &linear_system->transposer_x1_to_y1; 58 | sdecomp_transpose_plan_t ** plan_b = &linear_system->transposer_y1_to_x1; 59 | if(0 != sdecomp.transpose.construct(info, SDECOMP_X1PENCIL, SDECOMP_Y1PENCIL, glsizes, sizeof(double), plan_f)) return 1; 60 | if(0 != sdecomp.transpose.construct(info, SDECOMP_Y1PENCIL, SDECOMP_X1PENCIL, glsizes, sizeof(double), plan_b)) return 1; 61 | } 62 | // initialise tri-diagonal matrix solvers 63 | // Thomas algorithm in x direction 64 | if(implicit[0]){ 65 | if(0 != tdm.construct( 66 | /* size of system */ (int)(x1pncl_mysizes[0]), 67 | /* number of rhs */ (int)(x1pncl_mysizes[1]), 68 | /* is periodic */ false, 69 | /* is complex */ false, 70 | /* output */ &linear_system->tdm_x 71 | )) return 1; 72 | } 73 | // Thomas algorithm in y direction 74 | if(implicit[1]){ 75 | if(0 != tdm.construct( 76 | /* size of system */ (int)(y1pncl_mysizes[1]), 77 | /* number of rhs */ (int)(y1pncl_mysizes[0]), 78 | /* is periodic */ true, 79 | /* is complex */ false, 80 | /* output */ &linear_system->tdm_y 81 | )) return 1; 82 | } 83 | linear_system->is_initialised = true; 84 | return 0; 85 | } 86 | 87 | int linear_system_finalise( 88 | linear_system_t * linear_system 89 | ){ 90 | if(NULL == linear_system || !linear_system->is_initialised){ 91 | return 1; 92 | } 93 | const bool * implicit = linear_system->implicit; 94 | memory_free(linear_system->x1pncl); 95 | if(implicit[0]){ 96 | tdm.destruct(linear_system->tdm_x); 97 | } 98 | if(implicit[1]){ 99 | memory_free(linear_system->y1pncl); 100 | sdecomp.transpose.destruct(linear_system->transposer_x1_to_y1); 101 | sdecomp.transpose.destruct(linear_system->transposer_y1_to_x1); 102 | tdm.destruct(linear_system->tdm_y); 103 | } 104 | return 0; 105 | } 106 | 107 | -------------------------------------------------------------------------------- /src/logging/README.rst: -------------------------------------------------------------------------------- 1 | ######## 2 | logging/ 3 | ######## 4 | 5 | Functions to monitor the running simulation. 6 | 7 | * divergence.c 8 | 9 | Compute and output maximum local divergence of the flow field. 10 | 11 | * energy.c 12 | 13 | Compute and output the total squared velocity (in each direction) and the total squared temperature of the flow field. 14 | 15 | * internal.h 16 | 17 | Private functions which are only used by this directory is declared. 18 | 19 | * main.c 20 | 21 | Call other logging functions. 22 | 23 | * momentum.c 24 | 25 | Compute and output the net momentum in each direction. 26 | 27 | -------------------------------------------------------------------------------- /src/logging/divergence.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "domain.h" 4 | #include "fluid.h" 5 | #include "fileio.h" 6 | #include "array_macros/domain/dxf.h" 7 | #include "array_macros/fluid/ux.h" 8 | #include "array_macros/fluid/uy.h" 9 | #include "internal.h" 10 | 11 | /** 12 | * @brief check divergence and write the maximum value 13 | * @param[in] fname : file name to which the log is written 14 | * @param[in] domain : domain information 15 | * @param[in] time : current simulation time 16 | * @param[in] fluid : velocity 17 | * @return : error code 18 | */ 19 | int logging_check_divergence( 20 | const char fname[], 21 | const domain_t * domain, 22 | const double time, 23 | const fluid_t * fluid 24 | ){ 25 | const int root = 0; 26 | int myrank = root; 27 | MPI_Comm comm_cart = MPI_COMM_NULL; 28 | sdecomp.get_comm_rank(domain->info, &myrank); 29 | sdecomp.get_comm_cart(domain->info, &comm_cart); 30 | const int isize = domain->mysizes[0]; 31 | const int jsize = domain->mysizes[1]; 32 | const double * restrict dxf = domain->dxf; 33 | const double dy = domain->dy; 34 | const double * restrict ux = fluid->ux.data; 35 | const double * restrict uy = fluid->uy.data; 36 | double divmax = 0.; 37 | for(int j = 1; j <= jsize; j++){ 38 | for(int i = 1; i <= isize; i++){ 39 | // compute local divergence 40 | const double dx = DXF(i ); 41 | const double ux_xm = UX(i , j ); 42 | const double ux_xp = UX(i+1, j ); 43 | const double uy_ym = UY(i , j ); 44 | const double uy_yp = UY(i , j+1); 45 | const double div = 46 | +(ux_xp - ux_xm) / dx 47 | +(uy_yp - uy_ym) / dy; 48 | // check maximum 49 | divmax = fmax(divmax, fabs(div)); 50 | } 51 | } 52 | // collect information among all processes 53 | const void * sendbuf = root == myrank ? MPI_IN_PLACE : &divmax; 54 | void * recvbuf = &divmax; 55 | MPI_Reduce(sendbuf, recvbuf, 1, MPI_DOUBLE, MPI_MAX, root, comm_cart); 56 | // result is written to a file from the main process 57 | if(root == myrank){ 58 | FILE * fp = fileio.fopen(fname, "a"); 59 | if(NULL == fp){ 60 | return 0; 61 | } 62 | fprintf(fp, "%8.2f % .1e\n", time, divmax); 63 | fileio.fclose(fp); 64 | } 65 | return 0; 66 | } 67 | 68 | -------------------------------------------------------------------------------- /src/logging/energy.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "domain.h" 4 | #include "fluid.h" 5 | #include "fileio.h" 6 | #include "array_macros/domain/dxf.h" 7 | #include "array_macros/domain/dxc.h" 8 | #include "array_macros/fluid/ux.h" 9 | #include "array_macros/fluid/uy.h" 10 | #include "array_macros/fluid/t.h" 11 | #include "internal.h" 12 | 13 | /** 14 | * @brief compute total kinetic and thermal energies 15 | * @param[in] fname : file name to which the log is written 16 | * @param[in] domain : information related to MPI domain decomposition 17 | * @param[in] time : current simulation time 18 | * @param[in] fluid : velocity and temperature 19 | * @return : error code 20 | */ 21 | int logging_check_energy( 22 | const char fname[], 23 | const domain_t * domain, 24 | const double time, 25 | const fluid_t * fluid 26 | ){ 27 | const int root = 0; 28 | int myrank = root; 29 | MPI_Comm comm_cart = MPI_COMM_NULL; 30 | sdecomp.get_comm_rank(domain->info, &myrank); 31 | sdecomp.get_comm_cart(domain->info, &comm_cart); 32 | const int isize = domain->mysizes[0]; 33 | const int jsize = domain->mysizes[1]; 34 | const double * restrict dxf = domain->dxf; 35 | const double * restrict dxc = domain->dxc; 36 | const double dy = domain->dy; 37 | const double * restrict ux = fluid->ux.data; 38 | const double * restrict uy = fluid->uy.data; 39 | const double * restrict t = fluid->t.data; 40 | // velocity in each dimension and plus thermal energy 41 | double quantities[NDIMS + 1] = {0.}; 42 | // compute quadratic quantity in x direction 43 | for(int j = 1; j <= jsize; j++){ 44 | for(int i = 2; i <= isize; i++){ 45 | const double dx = DXC(i ); 46 | const double cellsize = dx * dy; 47 | quantities[0] += 0.5 * pow(UX(i, j), 2.) * cellsize; 48 | } 49 | } 50 | // compute quadratic quantity in y direction 51 | for(int j = 1; j <= jsize; j++){ 52 | for(int i = 1; i <= isize; i++){ 53 | const double dx = DXF(i ); 54 | const double cellsize = dx * dy; 55 | quantities[1] += 0.5 * pow(UY(i, j), 2.) * cellsize; 56 | } 57 | } 58 | // compute thermal energy 59 | for(int j = 1; j <= jsize; j++){ 60 | for(int i = 1; i <= isize; i++){ 61 | const double dx = DXF(i ); 62 | const double cellsize = dx * dy; 63 | quantities[NDIMS] += 0.5 * pow(T(i, j), 2.) * cellsize; 64 | } 65 | } 66 | const void * sendbuf = root == myrank ? MPI_IN_PLACE : quantities; 67 | void * recvbuf = quantities; 68 | MPI_Reduce(sendbuf, recvbuf, NDIMS + 1, MPI_DOUBLE, MPI_SUM, root, comm_cart); 69 | if(root == myrank){ 70 | FILE * fp = fileio.fopen(fname, "a"); 71 | if(NULL == fp){ 72 | return 0; 73 | } 74 | fprintf(fp, "%8.2f ", time); 75 | for(int n = 0; n < NDIMS + 1; n++){ 76 | fprintf(fp, "% 18.15e%c", quantities[n], NDIMS == n ? '\n' : ' '); 77 | } 78 | fileio.fclose(fp); 79 | } 80 | return 0; 81 | } 82 | 83 | -------------------------------------------------------------------------------- /src/logging/internal.h: -------------------------------------------------------------------------------- 1 | #if !defined(LOGGING_INTERNAL_H) 2 | #define LOGGING_INTERNAL_H 3 | 4 | extern int logging_check_divergence( 5 | const char fname[], 6 | const domain_t * domain, 7 | const double time, 8 | const fluid_t * fluid 9 | ); 10 | 11 | extern int logging_check_momentum( 12 | const char fname[], 13 | const domain_t * domain, 14 | const double time, 15 | const fluid_t * fluid 16 | ); 17 | 18 | extern int logging_check_energy( 19 | const char fname[], 20 | const domain_t * domain, 21 | const double time, 22 | const fluid_t * fluid 23 | ); 24 | 25 | extern int logging_check_nusselt( 26 | const char fname[], 27 | const domain_t * domain, 28 | const double time, 29 | const fluid_t * fluid 30 | ); 31 | 32 | #endif // LOGGING_INTERNAL_H 33 | -------------------------------------------------------------------------------- /src/logging/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "memory.h" 5 | #include "config.h" 6 | #include "domain.h" 7 | #include "fluid.h" 8 | #include "fileio.h" 9 | #include "logging.h" 10 | #include "internal.h" 11 | 12 | static double g_rate = 0.; 13 | static double g_next = 0.; 14 | 15 | /** 16 | * @brief constructor - schedule logging 17 | * @param[in] domain : MPI communicator 18 | * @param[in] time : current time (hereafter in free-fall time units) 19 | */ 20 | static int init( 21 | const domain_t * domain, 22 | const double time 23 | ){ 24 | if(0 != config.get_double("log_rate", &g_rate)){ 25 | return 1; 26 | } 27 | g_next = g_rate * ceil( 28 | fmax(DBL_EPSILON, time) / g_rate 29 | ); 30 | const int root = 0; 31 | int myrank = root; 32 | sdecomp.get_comm_rank(domain->info, &myrank); 33 | if(root == myrank){ 34 | printf("LOGGING\n"); 35 | printf("\tnext: % .3e\n", g_next); 36 | printf("\trate: % .3e\n", g_rate); 37 | fflush(stdout); 38 | } 39 | return 0; 40 | } 41 | 42 | /** 43 | * @brief show current step, time, time step size, diffusive treatments 44 | * @param[in] domain : information related to MPI domain decomposition 45 | * @param[in] fname : file name to which the log is written 46 | * @param[in] time : current simulation time 47 | * @param[in] step : current time step 48 | * @param[in] dt : time step size 49 | * @param[in] wtime : current wall time 50 | */ 51 | static void show_progress( 52 | const char fname[], 53 | const domain_t * domain, 54 | const double time, 55 | const size_t step, 56 | const double dt, 57 | const double wtime 58 | ){ 59 | const int root = 0; 60 | int myrank = root; 61 | sdecomp.get_comm_rank(domain->info, &myrank); 62 | if(root == myrank){ 63 | FILE * fp = fileio.fopen(fname, "a"); 64 | if(NULL != fp){ 65 | // show progress to standard output and file 66 | // output to stdout and file 67 | #define MPRINT(...) { \ 68 | fprintf(fp, __VA_ARGS__); \ 69 | fprintf(stdout, __VA_ARGS__); \ 70 | } 71 | MPRINT("step %zu, time %.1f, dt %.2e, elapsed %.1f [sec]\n", step, time, dt, wtime); 72 | #undef MPRINT 73 | fileio.fclose(fp); 74 | } 75 | } 76 | } 77 | 78 | /** 79 | * @brief output log files to be monitored during simulation 80 | * @param[in] domain : information related to MPI domain decomposition 81 | * @param[in] step : current time step 82 | * @param[in] time : current simulation time 83 | * @param[in] dt : time step size 84 | * @param[in] wtime : current wall time 85 | * @param[in] fluid : velocity and temperature 86 | */ 87 | static void check_and_output( 88 | const domain_t * domain, 89 | const size_t step, 90 | const double time, 91 | const double dt, 92 | const double wtime, 93 | const fluid_t * fluid 94 | ){ 95 | show_progress ("output/log/progress.dat", domain, time, step, dt, wtime); 96 | logging_check_divergence("output/log/divergence.dat", domain, time, fluid); 97 | logging_check_momentum ("output/log/momentum.dat", domain, time, fluid); 98 | logging_check_energy ("output/log/energy.dat", domain, time, fluid); 99 | logging_check_nusselt ("output/log/nusselt.dat", domain, time, fluid); 100 | g_next += g_rate; 101 | } 102 | 103 | /** 104 | * @brief getter of a member: g_next 105 | * @return : g_next 106 | */ 107 | static double get_next_time( 108 | void 109 | ){ 110 | return g_next; 111 | } 112 | 113 | const logging_t logging = { 114 | .init = init, 115 | .check_and_output = check_and_output, 116 | .get_next_time = get_next_time, 117 | }; 118 | 119 | -------------------------------------------------------------------------------- /src/logging/momentum.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "domain.h" 3 | #include "fluid.h" 4 | #include "fileio.h" 5 | #include "array_macros/domain/dxf.h" 6 | #include "array_macros/domain/dxc.h" 7 | #include "array_macros/fluid/ux.h" 8 | #include "array_macros/fluid/uy.h" 9 | #include "internal.h" 10 | 11 | /** 12 | * @brief compute total momenta 13 | * @param[in] fname : file name to which the log is written 14 | * @param[in] domain : information about domain decomposition and size 15 | * @param[in] time : current simulation time 16 | * @param[in] fluid : velocity 17 | * @return : error code 18 | */ 19 | int logging_check_momentum( 20 | const char fname[], 21 | const domain_t * domain, 22 | const double time, 23 | const fluid_t * fluid 24 | ){ 25 | const int root = 0; 26 | int myrank = root; 27 | MPI_Comm comm_cart = MPI_COMM_NULL; 28 | sdecomp.get_comm_rank(domain->info, &myrank); 29 | sdecomp.get_comm_cart(domain->info, &comm_cart); 30 | const int isize = domain->mysizes[0]; 31 | const int jsize = domain->mysizes[1]; 32 | const double * restrict dxf = domain->dxf; 33 | const double * restrict dxc = domain->dxc; 34 | const double dy = domain->dy; 35 | const double * restrict ux = fluid->ux.data; 36 | const double * restrict uy = fluid->uy.data; 37 | double moms[NDIMS] = {0.}; 38 | // compute total x-momentum 39 | for(int j = 1; j <= jsize; j++){ 40 | for(int i = 2; i <= isize; i++){ 41 | const double dx = DXC(i ); 42 | const double cellsize = dx * dy; 43 | moms[0] += UX(i, j) * cellsize; 44 | } 45 | } 46 | // compute total y-momentum 47 | for(int j = 1; j <= jsize; j++){ 48 | for(int i = 1; i <= isize; i++){ 49 | const double dx = DXF(i ); 50 | const double cellsize = dx * dy; 51 | moms[1] += UY(i, j) * cellsize; 52 | } 53 | } 54 | const void * sendbuf = root == myrank ? MPI_IN_PLACE : moms; 55 | void * recvbuf = moms; 56 | MPI_Reduce(sendbuf, recvbuf, NDIMS, MPI_DOUBLE, MPI_SUM, root, comm_cart); 57 | if(root == myrank){ 58 | FILE * fp = fileio.fopen(fname, "a"); 59 | if(NULL == fp){ 60 | return 0; 61 | } 62 | fprintf(fp, "%8.2f ", time); 63 | for(int n = 0; n < NDIMS; n++){ 64 | fprintf(fp, "% 18.15e%c", moms[n], NDIMS - 1 == n ? '\n' : ' '); 65 | } 66 | fileio.fclose(fp); 67 | } 68 | return 0; 69 | } 70 | 71 | -------------------------------------------------------------------------------- /src/logging/nusselt/README.rst: -------------------------------------------------------------------------------- 1 | ######## 2 | nusselt/ 3 | ######## 4 | 5 | Functions to monitor the instantaneous Nusselt number computed differently. 6 | 7 | -------------------------------------------------------------------------------- /src/logging/nusselt/heat_flux.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "domain.h" 3 | #include "fluid.h" 4 | #include "array_macros/domain/dxc.h" 5 | #include "array_macros/fluid/t.h" 6 | #include "internal.h" 7 | 8 | /** 9 | * @brief compute Nusselt number based on the heat flux on the walls 10 | * @param[in] domain : information related to MPI domain decomposition 11 | * @param[in] fluid : temperature and diffusivity 12 | * @return : Nusselt number 13 | */ 14 | double logging_internal_compute_nu_heat_flux( 15 | const domain_t * domain, 16 | const fluid_t * fluid 17 | ){ 18 | MPI_Comm comm_cart = MPI_COMM_NULL; 19 | sdecomp.get_comm_cart(domain->info, &comm_cart); 20 | const int isize = domain->mysizes[0]; 21 | const int jsize = domain->mysizes[1]; 22 | const double * restrict dxc = domain->dxc; 23 | const double dy = domain->dy; 24 | const double * restrict t = fluid->t.data; 25 | const double diffusivity = fluid->t_dif; 26 | // heat flux on the walls 27 | // reference heat flux 28 | const double ref = logging_internal_compute_reference_heat_flux(domain, fluid); 29 | // integral on the two walls 30 | double retval = 0.; 31 | for(int j = 1; j <= jsize; j++){ 32 | const double ds = dy; 33 | const double dx_xm = DXC( 1); 34 | const double dx_xp = DXC(isize+1); 35 | const double dt_xm = + T( 1, j) - T( 0, j); 36 | const double dt_xp = + T(isize+1, j) - T(isize, j); 37 | const double dtdx_xm = dt_xm / dx_xm; 38 | const double dtdx_xp = dt_xp / dx_xp; 39 | // average two walls 40 | retval -= 0.5 * diffusivity * (dtdx_xm + dtdx_xp) * ds; 41 | } 42 | const int root = 0; 43 | int myrank = root; 44 | sdecomp.get_comm_rank(domain->info, &myrank); 45 | const void * sendbuf = root == myrank ? MPI_IN_PLACE : &retval; 46 | void * recvbuf = &retval; 47 | MPI_Reduce(sendbuf, recvbuf, 1, MPI_DOUBLE, MPI_SUM, root, comm_cart); 48 | return retval / ref; 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/logging/nusselt/internal.h: -------------------------------------------------------------------------------- 1 | #if !defined(LOGGING_NUSSELT_INTERNAL_H) 2 | #define LOGGING_NUSSELT_INTERNAL_H 3 | 4 | extern double logging_internal_compute_reference_heat_flux( 5 | const domain_t * domain, 6 | const fluid_t * fluid 7 | ); 8 | 9 | extern double logging_internal_compute_nu_heat_flux( 10 | const domain_t * domain, 11 | const fluid_t * fluid 12 | ); 13 | 14 | extern double logging_internal_compute_nu_kinetic_energy_injection( 15 | const domain_t * domain, 16 | const fluid_t * fluid 17 | ); 18 | 19 | extern double logging_internal_compute_nu_kinetic_energy_dissipation( 20 | const domain_t * domain, 21 | const fluid_t * fluid 22 | ); 23 | 24 | extern double logging_internal_compute_nu_thermal_energy_dissipation( 25 | const domain_t * domain, 26 | const fluid_t * fluid 27 | ); 28 | 29 | #endif // LOGGING_NUSSELT_INTERNAL_H 30 | -------------------------------------------------------------------------------- /src/logging/nusselt/kinetic_energy_dissipation.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "domain.h" 4 | #include "fluid.h" 5 | #include "array_macros/domain/dxf.h" 6 | #include "array_macros/domain/dxc.h" 7 | #include "array_macros/fluid/ux.h" 8 | #include "array_macros/fluid/uy.h" 9 | #include "internal.h" 10 | 11 | static double get_ux_x_contribution( 12 | const domain_t * domain, 13 | const fluid_t * fluid 14 | ){ 15 | const int isize = domain->mysizes[0]; 16 | const int jsize = domain->mysizes[1]; 17 | const double * restrict dxf = domain->dxf; 18 | const double * restrict dxc = domain->dxc; 19 | const double dy = domain->dy; 20 | const double * restrict ux = fluid->ux.data; 21 | const double diffusivity = fluid->m_dif; 22 | double dissipation = 0.; 23 | for(int j = 1; j <= jsize; j++){ 24 | for(int i = 1; i <= isize + 1; i++){ 25 | // ux-x contribution 26 | const double cellsize = DXC(i ) * dy; 27 | // negative direction 28 | if(1 != i){ 29 | const double duxdx = 1. / DXF(i-1) * (UX(i , j ) - UX(i-1, j )); 30 | const double w = DXF(i-1) / DXC(i ); 31 | dissipation += diffusivity * 0.5 * w * pow(duxdx, 2.) * cellsize; 32 | } 33 | // positive direction 34 | if(isize + 1 != i){ 35 | const double duxdx = 1. / DXF(i ) * (UX(i+1, j ) - UX(i , j )); 36 | const double w = DXF(i ) / DXC(i ); 37 | dissipation += diffusivity * 0.5 * w * pow(duxdx, 2.) * cellsize; 38 | } 39 | } 40 | } 41 | return dissipation; 42 | } 43 | 44 | static double get_ux_y_contribution( 45 | const domain_t * domain, 46 | const fluid_t * fluid 47 | ){ 48 | const int isize = domain->mysizes[0]; 49 | const int jsize = domain->mysizes[1]; 50 | const double * restrict dxc = domain->dxc; 51 | const double dy = domain->dy; 52 | const double * restrict ux = fluid->ux.data; 53 | const double diffusivity = fluid->m_dif; 54 | double dissipation = 0.; 55 | for(int j = 1; j <= jsize; j++){ 56 | for(int i = 1; i <= isize + 1; i++){ 57 | // ux-y contribution 58 | const double cellsize = DXC(i ) * dy; 59 | // negative direction 60 | { 61 | const double duxdy = 1. / dy * (UX(i , j ) - UX(i , j-1)); 62 | dissipation += diffusivity * 0.5 * pow(duxdy, 2.) * cellsize; 63 | } 64 | // positive direction 65 | { 66 | const double duxdy = 1. / dy * (UX(i , j+1) - UX(i , j )); 67 | dissipation += diffusivity * 0.5 * pow(duxdy, 2.) * cellsize; 68 | } 69 | } 70 | } 71 | return dissipation; 72 | } 73 | 74 | static double get_uy_x_contribution( 75 | const domain_t * domain, 76 | const fluid_t * fluid 77 | ){ 78 | const int isize = domain->mysizes[0]; 79 | const int jsize = domain->mysizes[1]; 80 | const double * restrict dxf = domain->dxf; 81 | const double * restrict dxc = domain->dxc; 82 | const double dy = domain->dy; 83 | const double * restrict uy = fluid->uy.data; 84 | const double diffusivity = fluid->m_dif; 85 | double dissipation = 0.; 86 | for(int j = 1; j <= jsize; j++){ 87 | for(int i = 1; i <= isize; i++){ 88 | // uy-x contribution 89 | const double cellsize = DXF(i ) * dy; 90 | // negative direction 91 | { 92 | const double duydx = 1. / DXC(i ) * (UY(i , j ) - UY(i-1, j )); 93 | const double w0 = DXC(i ) / DXF(i ); 94 | const double w1 = 1 == i ? 2. : 1.; 95 | dissipation += diffusivity * 0.5 * w0 * w1 * pow(duydx, 2.) * cellsize; 96 | } 97 | // positive direction 98 | { 99 | const double duydx = 1. / DXC(i+1) * (UY(i+1, j ) - UY(i , j )); 100 | const double w0 = DXC(i+1) / DXF(i ); 101 | const double w1 = isize == i ? 2. : 1.; 102 | dissipation += diffusivity * 0.5 * w0 * w1 * pow(duydx, 2.) * cellsize; 103 | } 104 | } 105 | } 106 | return dissipation; 107 | } 108 | 109 | static double get_uy_y_contribution( 110 | const domain_t * domain, 111 | const fluid_t * fluid 112 | ){ 113 | const int isize = domain->mysizes[0]; 114 | const int jsize = domain->mysizes[1]; 115 | const double * restrict dxf = domain->dxf; 116 | const double dy = domain->dy; 117 | const double * restrict uy = fluid->uy.data; 118 | const double diffusivity = fluid->m_dif; 119 | double dissipation = 0.; 120 | for(int j = 1; j <= jsize; j++){ 121 | for(int i = 1; i <= isize; i++){ 122 | // uy-y contribution 123 | const double cellsize = DXF(i ) * dy; 124 | // negative direction 125 | { 126 | const double duydy = 1. / dy * (UY(i , j ) - UY(i , j-1)); 127 | dissipation += diffusivity * 0.5 * pow(duydy, 2.) * cellsize; 128 | } 129 | // positive direction 130 | { 131 | const double duydy = 1. / dy * (UY(i , j+1) - UY(i , j )); 132 | dissipation += diffusivity * 0.5 * pow(duydy, 2.) * cellsize; 133 | } 134 | } 135 | } 136 | return dissipation; 137 | } 138 | 139 | /** 140 | * @brief compute Nusselt number based on kinetic dissipation 141 | * @param[in] domain : information related to MPI domain decomposition 142 | * @param[in] fluid : flow field 143 | * @return : Nusselt number 144 | */ 145 | double logging_internal_compute_nu_kinetic_energy_dissipation( 146 | const domain_t * domain, 147 | const fluid_t * fluid 148 | ){ 149 | MPI_Comm comm_cart = MPI_COMM_NULL; 150 | sdecomp.get_comm_cart(domain->info, &comm_cart); 151 | // compute kinetic energy dissipation 152 | // reference heat flux 153 | const double ref = logging_internal_compute_reference_heat_flux(domain, fluid); 154 | double retval = 0.; 155 | // ux contribution 156 | retval += get_ux_x_contribution(domain, fluid); 157 | retval += get_ux_y_contribution(domain, fluid); 158 | // uy contribution 159 | retval += get_uy_x_contribution(domain, fluid); 160 | retval += get_uy_y_contribution(domain, fluid); 161 | const int root = 0; 162 | int myrank = root; 163 | sdecomp.get_comm_rank(domain->info, &myrank); 164 | const void * sendbuf = root == myrank ? MPI_IN_PLACE : &retval; 165 | void * recvbuf = &retval; 166 | MPI_Reduce(sendbuf, recvbuf, 1, MPI_DOUBLE, MPI_SUM, root, comm_cart); 167 | retval /= ref; 168 | return retval + 1.; 169 | } 170 | 171 | -------------------------------------------------------------------------------- /src/logging/nusselt/kinetic_energy_injection.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "domain.h" 3 | #include "fluid.h" 4 | #include "array_macros/domain/dxc.h" 5 | #include "array_macros/fluid/ux.h" 6 | #include "array_macros/fluid/t.h" 7 | #include "internal.h" 8 | 9 | /** 10 | * @brief compute Nusselt number based on the total energy injection 11 | * @param[in] domain : information related to MPI domain decomposition 12 | * @param[in] fluid : wall-normal velocity (ux) and temperature 13 | * @return : Nusselt number 14 | */ 15 | double logging_internal_compute_nu_kinetic_energy_injection( 16 | const domain_t * domain, 17 | const fluid_t * fluid 18 | ){ 19 | MPI_Comm comm_cart = MPI_COMM_NULL; 20 | sdecomp.get_comm_cart(domain->info, &comm_cart); 21 | const int isize = domain->mysizes[0]; 22 | const int jsize = domain->mysizes[1]; 23 | const double * restrict dxc = domain->dxc; 24 | const double dy = domain->dy; 25 | const double * restrict ux = fluid->ux.data; 26 | const double * restrict t = fluid->t .data; 27 | // energy injection 28 | // reference heat flux 29 | const double ref = logging_internal_compute_reference_heat_flux(domain, fluid); 30 | // integral in the whole domain 31 | double retval = 0.; 32 | for(int j = 1; j <= jsize; j++){ 33 | for(int i = 2; i <= isize; i++){ 34 | const double dx = DXC(i ); 35 | const double vel = UX(i, j); 36 | const double t_ = + 0.5 * T(i-1, j) + 0.5 * T(i, j); 37 | const double integrand = vel * t_; 38 | const double cellsize = dx * dy; 39 | retval += integrand * cellsize; 40 | } 41 | } 42 | const int root = 0; 43 | int myrank = root; 44 | sdecomp.get_comm_rank(domain->info, &myrank); 45 | const void * sendbuf = root == myrank ? MPI_IN_PLACE : &retval; 46 | void * recvbuf = &retval; 47 | MPI_Reduce(sendbuf, recvbuf, 1, MPI_DOUBLE, MPI_SUM, root, comm_cart); 48 | // normalise by the reference value 49 | retval /= ref; 50 | // add laminar contribution 51 | retval += 1.; 52 | return retval; 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/logging/nusselt/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "domain.h" 4 | #include "fluid.h" 5 | #include "fileio.h" 6 | #include "internal.h" 7 | 8 | /** 9 | * @brief compute Nusselt number based on various definitions 10 | * @param[in] fname : file name to which the log is written 11 | * @param[in] domain : information related to MPI domain decomposition 12 | * @param[in] time : current simulation time 13 | * @param[in] fluid : velocity, temperature, and their diffusivities 14 | * @return : error code 15 | */ 16 | int logging_check_nusselt( 17 | const char fname[], 18 | const domain_t * domain, 19 | const double time, 20 | const fluid_t * fluid 21 | ){ 22 | const int root = 0; 23 | int myrank = root; 24 | MPI_Comm comm_cart = MPI_COMM_NULL; 25 | sdecomp.get_comm_rank(domain->info, &myrank); 26 | sdecomp.get_comm_cart(domain->info, &comm_cart); 27 | // compute Nusselt in several ways 28 | const double results[] = { 29 | // compute heat flux on the walls 30 | logging_internal_compute_nu_heat_flux(domain, fluid), 31 | // compute kinetic energy injection 32 | logging_internal_compute_nu_kinetic_energy_injection(domain, fluid), 33 | // compute kinetic energy dissipation 34 | logging_internal_compute_nu_kinetic_energy_dissipation(domain, fluid), 35 | // comoute thermal energy dissipation 36 | logging_internal_compute_nu_thermal_energy_dissipation(domain, fluid), 37 | }; 38 | if(root == myrank){ 39 | FILE * fp = fileio.fopen(fname, "a"); 40 | if(NULL == fp){ 41 | return 0; 42 | } 43 | fprintf(fp, "%8.2f ", time); 44 | const int ntypes = sizeof(results) / sizeof(results[0]); 45 | for(int n = 0; n < ntypes; n++){ 46 | fprintf(fp, "% 18.15e%c", results[n], ntypes - 1 == n ? '\n' : ' '); 47 | } 48 | fileio.fclose(fp); 49 | } 50 | return 0; 51 | } 52 | 53 | -------------------------------------------------------------------------------- /src/logging/nusselt/reference.c: -------------------------------------------------------------------------------- 1 | #include "domain.h" 2 | #include "fluid.h" 3 | 4 | double logging_internal_compute_reference_heat_flux( 5 | const domain_t * domain, 6 | const fluid_t * fluid 7 | ){ 8 | // compute laminar heat flux 9 | const double ly = domain->lengths[1]; 10 | const double diffusivity = fluid->t_dif; 11 | return diffusivity * ly; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/logging/nusselt/thermal_energy_dissipation.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "domain.h" 4 | #include "fluid.h" 5 | #include "array_macros/domain/dxf.h" 6 | #include "array_macros/domain/dxc.h" 7 | #include "array_macros/fluid/t.h" 8 | #include "internal.h" 9 | 10 | static double get_x_contribution( 11 | const domain_t * domain, 12 | const fluid_t * fluid 13 | ){ 14 | const int isize = domain->mysizes[0]; 15 | const int jsize = domain->mysizes[1]; 16 | const double * restrict dxf = domain->dxf; 17 | const double * restrict dxc = domain->dxc; 18 | const double dy = domain->dy; 19 | const double * restrict t = fluid->t.data; 20 | const double diffusivity = fluid->t_dif; 21 | double dissipation = 0.; 22 | for(int j = 1; j <= jsize; j++){ 23 | for(int i = 1; i <= isize; i++){ 24 | // x contribution 25 | const double cellsize = DXF(i ) * dy; 26 | // negative direction 27 | { 28 | const double dtdx = 1. / DXC(i ) * (T(i , j ) - T(i-1, j )); 29 | const double w0 = DXC(i ) / DXF(i ); 30 | const double w1 = 1 == i ? 2. : 1.; 31 | dissipation += diffusivity * 0.5 * w0 * w1 * pow(dtdx, 2.) * cellsize; 32 | } 33 | // positive direction 34 | { 35 | const double dtdx = 1. / DXC(i+1) * (T(i+1, j ) - T(i , j )); 36 | const double w0 = DXC(i+1) / DXF(i ); 37 | const double w1 = isize == i ? 2. : 1.; 38 | dissipation += diffusivity * 0.5 * w0 * w1 * pow(dtdx, 2.) * cellsize; 39 | } 40 | } 41 | } 42 | return dissipation; 43 | } 44 | 45 | static double get_y_contribution( 46 | const domain_t * domain, 47 | const fluid_t * fluid 48 | ){ 49 | const int isize = domain->mysizes[0]; 50 | const int jsize = domain->mysizes[1]; 51 | const double * restrict dxf = domain->dxf; 52 | const double dy = domain->dy; 53 | const double * restrict t = fluid->t.data; 54 | const double diffusivity = fluid->t_dif; 55 | double dissipation = 0.; 56 | for(int j = 1; j <= jsize; j++){ 57 | for(int i = 1; i <= isize; i++){ 58 | // y contribution 59 | const double cellsize = DXF(i ) * dy; 60 | // negative direction 61 | { 62 | const double dtdy = 1. / dy * (T(i , j ) - T(i , j-1)); 63 | dissipation += diffusivity * 0.5 * pow(dtdy, 2.) * cellsize; 64 | } 65 | // positive direction 66 | { 67 | const double dtdy = 1. / dy * (T(i , j+1) - T(i , j )); 68 | dissipation += diffusivity * 0.5 * pow(dtdy, 2.) * cellsize; 69 | } 70 | } 71 | } 72 | return dissipation; 73 | } 74 | 75 | /** 76 | * @brief compute Nusselt number based on thermal dissipation 77 | * @param[in] domain : information related to MPI domain decomposition 78 | * @param[in] fluid : temperature and its diffusivity 79 | * @return : Nusselt number 80 | */ 81 | double logging_internal_compute_nu_thermal_energy_dissipation( 82 | const domain_t * domain, 83 | const fluid_t * fluid 84 | ){ 85 | const int root = 0; 86 | int myrank = root; 87 | MPI_Comm comm_cart = MPI_COMM_NULL; 88 | sdecomp.get_comm_rank(domain->info, &myrank); 89 | sdecomp.get_comm_cart(domain->info, &comm_cart); 90 | // compute thermal energy dissipation 91 | // reference heat flux 92 | const double ref = logging_internal_compute_reference_heat_flux(domain, fluid); 93 | double retval = 0.; 94 | retval += get_x_contribution(domain, fluid); 95 | retval += get_y_contribution(domain, fluid); 96 | const void * sendbuf = root == myrank ? MPI_IN_PLACE : &retval; 97 | void * recvbuf = &retval; 98 | MPI_Reduce(sendbuf, recvbuf, 1, MPI_DOUBLE, MPI_SUM, root, comm_cart); 99 | retval /= ref; 100 | return retval; 101 | } 102 | 103 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "memory.h" 4 | #include "timer.h" 5 | #include "domain.h" 6 | #include "fluid.h" 7 | #include "fluid_solver.h" 8 | #include "ib.h" 9 | #include "ib_solver.h" 10 | #include "integrate.h" 11 | #include "statistics.h" 12 | #include "save.h" 13 | #include "logging.h" 14 | #include "config.h" 15 | #include "fileio.h" 16 | 17 | static int save_entrypoint( 18 | const domain_t * domain, 19 | const size_t step, 20 | const double time, 21 | const fluid_t * fluid, 22 | const ib_t * ib 23 | ){ 24 | char * dirname = NULL; 25 | save.prepare(domain, step, &dirname); 26 | fileio.w_serial(dirname, "step", 0, NULL, fileio.npy_size_t, sizeof(size_t), &step); 27 | fileio.w_serial(dirname, "time", 0, NULL, fileio.npy_double, sizeof(double), &time); 28 | domain_save(dirname, domain); 29 | fluid_save(dirname, domain, fluid); 30 | ib_save(dirname, domain, ib); 31 | return 0; 32 | } 33 | 34 | /** 35 | * @brief main function 36 | * @param[in] argc : number of arguments (expect 2) 37 | * @param[in] argv : name of the directory 38 | * where a set of initial condition is contained 39 | * @return : error code 40 | */ 41 | int main( 42 | int argc, 43 | char * argv[] 44 | ){ 45 | // launch MPI, start timer 46 | MPI_Init(NULL, NULL); 47 | const int root = 0; 48 | int myrank = root; 49 | MPI_Comm_rank(MPI_COMM_WORLD, &myrank); 50 | const double tic = timer(); 51 | // find name of directory where IC is stored 52 | if(2 != argc){ 53 | if(root == myrank){ 54 | printf("directory name should be given as input\n"); 55 | } 56 | goto abort; 57 | } 58 | const char * dirname_ic = argv[1]; 59 | // initialise fileio object 60 | if(0 != fileio.init()){ 61 | goto abort; 62 | } 63 | // initialise time step and time units 64 | size_t step = 0; 65 | if(0 != fileio.r_serial(dirname_ic, "step", 0, NULL, fileio.npy_size_t, sizeof(size_t), &step)){ 66 | goto abort; 67 | } 68 | double time = 0.; 69 | if(0 != fileio.r_serial(dirname_ic, "time", 0, NULL, fileio.npy_double, sizeof(double), &time)){ 70 | goto abort; 71 | } 72 | // initialise structures 73 | domain_t domain = {0}; 74 | if(0 != domain_init(dirname_ic, &domain)){ 75 | goto abort; 76 | } 77 | fluid_t fluid = {0}; 78 | if(0 != fluid_init(dirname_ic, &domain, &fluid)){ 79 | goto abort; 80 | } 81 | ib_t ib = {0}; 82 | if(0 != ib_init(dirname_ic, &domain, &ib)){ 83 | goto abort; 84 | } 85 | // initialise auxiliary objects 86 | if(0 != logging.init(&domain, time)){ 87 | goto abort; 88 | } 89 | if(0 != save.init(&domain, time)){ 90 | goto abort; 91 | } 92 | if(0 != statistics.init(&domain, time)){ 93 | goto abort; 94 | } 95 | // check termination conditions 96 | double timemax = 0.; 97 | if(0 != config.get_double("timemax", &timemax)){ 98 | goto abort; 99 | } 100 | double wtimemax = 0.; 101 | if(0 != config.get_double("wtimemax", &wtimemax)){ 102 | goto abort; 103 | } 104 | // report 105 | if(root == myrank){ 106 | printf("step: %zu, time: % .7e\n", step, time); 107 | printf("timemax: % .7e, wtimemax: % .7e\n", timemax, wtimemax); 108 | } 109 | // main loop 110 | for(;;){ 111 | // proceed for one step 112 | double dt = 0.; 113 | if(0 != integrate(&domain, &fluid, &ib, &dt)){ 114 | goto abort; 115 | } 116 | // update step and simulation / wall time 117 | step += 1; 118 | time += dt; 119 | const double toc = timer(); 120 | // terminate if one of the following conditions is met 121 | // the simulation is finished 122 | if(timemax < time){ 123 | break; 124 | } 125 | // wall time limit is reached 126 | if(wtimemax < toc - tic){ 127 | break; 128 | } 129 | // compute and output log regularly 130 | if(logging.get_next_time() < time){ 131 | logging.check_and_output(&domain, step, time, dt, toc - tic, &fluid); 132 | } 133 | // save flow fields regularly 134 | if(save.get_next_time() < time){ 135 | save_entrypoint(&domain, step, time, &fluid, &ib); 136 | } 137 | // collect statistics regularly 138 | if(statistics.get_next_time() < time){ 139 | statistics.collect(&domain, &fluid); 140 | } 141 | } 142 | // finalisation 143 | // save final flow fields 144 | save_entrypoint(&domain, step, time, &fluid, &ib); 145 | // save collected statistics 146 | statistics.output(&domain, step); 147 | // finalise MPI 148 | abort: 149 | MPI_Finalize(); 150 | return 0; 151 | } 152 | 153 | -------------------------------------------------------------------------------- /src/memory.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "memory.h" 5 | 6 | /** 7 | * @brief general-purpose memory allocator 8 | * @param[in] count : number of elements to be allocated 9 | * @param[in] size : size of each element 10 | * @return : pointer to the allocated buffer 11 | */ 12 | void * memory_calloc( 13 | const size_t count, 14 | const size_t size 15 | ){ 16 | // try to allocate 17 | void * ptr = calloc(count, size); 18 | if(NULL == ptr){ 19 | // failed to allocate 20 | FILE * stream = stderr; 21 | fprintf(stream, "memory allocation error: calloc(%zu, %zu)\n", count, size); 22 | fflush(stream); 23 | // since memory errors are fatal, I abort the program 24 | MPI_Barrier(MPI_COMM_WORLD); 25 | MPI_Abort(MPI_COMM_WORLD, 0); 26 | } 27 | return ptr; 28 | } 29 | 30 | /** 31 | * @brief corresponding memory deallocator 32 | * @param[in] ptr : pointer to the allocated buffer 33 | */ 34 | void memory_free( 35 | void * ptr 36 | ){ 37 | // for now just wrap free 38 | free(ptr); 39 | } 40 | 41 | -------------------------------------------------------------------------------- /src/param/boundary-condition.c: -------------------------------------------------------------------------------- 1 | #include "param.h" 2 | 3 | const double param_uy_xm = 0.; 4 | const double param_uy_xp = 0.; 5 | const double param_t_xm = + 0.5; 6 | const double param_t_xp = - 0.5; 7 | 8 | -------------------------------------------------------------------------------- /src/param/buoyancy.c: -------------------------------------------------------------------------------- 1 | #include "param.h" 2 | 3 | const bool param_add_buoyancy = true; 4 | 5 | -------------------------------------------------------------------------------- /src/param/implicit.c: -------------------------------------------------------------------------------- 1 | #include "param.h" 2 | 3 | const bool param_m_implicit_x = false; 4 | const bool param_m_implicit_y = false; 5 | 6 | const bool param_t_implicit_x = false; 7 | const bool param_t_implicit_y = false; 8 | 9 | -------------------------------------------------------------------------------- /src/runge_kutta.c: -------------------------------------------------------------------------------- 1 | #include "runge_kutta.h" 2 | 3 | const uint_fast8_t rk_a = 0; 4 | const uint_fast8_t rk_b = 1; 5 | const uint_fast8_t rk_g = 2; 6 | 7 | // coefficients of three-stage Runge-Kutta scheme 8 | static const double a0 = +32. / 60., b0 = 0. / 60.; 9 | static const double a1 = +25. / 60., b1 = -17. / 60.; 10 | static const double a2 = +45. / 60., b2 = -25. / 60.; 11 | const rkcoef_t rkcoefs[RKSTEPMAX] = { 12 | {a0, b0, a0 + b0}, 13 | {a1, b1, a1 + b1}, 14 | {a2, b2, a2 + b2}, 15 | }; 16 | 17 | -------------------------------------------------------------------------------- /src/save.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "sdecomp.h" 6 | #include "param.h" 7 | #include "memory.h" 8 | #include "domain.h" 9 | #include "save.h" 10 | #include "fileio.h" 11 | #include "config.h" 12 | 13 | // parameters to specify directory name 14 | static const char g_dirname_prefix[] = {"output/save/step"}; 15 | static const int g_dirname_ndigits = 10; 16 | 17 | // name of directory 18 | static char * g_dirname = NULL; 19 | static size_t g_dirname_nchars = 0; 20 | 21 | // scheduler 22 | static double g_rate = 0.; 23 | static double g_next = 0.; 24 | 25 | /** 26 | * @brief constructor - schedule saving flow fields 27 | * @param[in] domain : MPI communicator 28 | * @param[in] time : current time (hereafter in free-fall time units) 29 | */ 30 | static int init( 31 | const domain_t * domain, 32 | const double time 33 | ){ 34 | // fetch timings 35 | if(0 != config.get_double("save_rate", &g_rate)){ 36 | return 1; 37 | } 38 | double after = 0.; 39 | if(0 != config.get_double("save_after", &after)){ 40 | return 1; 41 | } 42 | // schedule next event 43 | g_next = g_rate * ceil( 44 | fmax(DBL_EPSILON, fmax(time, after)) / g_rate 45 | ); 46 | // allocate directory name 47 | g_dirname_nchars = 48 | + strlen(g_dirname_prefix) 49 | + g_dirname_ndigits; 50 | g_dirname = memory_calloc(g_dirname_nchars + 2, sizeof(char)); 51 | // report 52 | const int root = 0; 53 | int myrank = root; 54 | sdecomp.get_comm_rank(domain->info, &myrank); 55 | if(root == myrank){ 56 | FILE * stream = stdout; 57 | fprintf(stream, "SAVE\n"); 58 | fprintf(stream, "\tdest: %s\n", g_dirname_prefix); 59 | fprintf(stream, "\tnext: % .3e\n", g_next); 60 | fprintf(stream, "\trate: % .3e\n", g_rate); 61 | fflush(stream); 62 | } 63 | return 0; 64 | } 65 | 66 | /** 67 | * @brief prepare place to output flow fields 68 | * @param[in] domain : information related to MPI domain decomposition 69 | * @param[in] step : time step 70 | * @param[out] dirname : name of created directory 71 | */ 72 | static int prepare( 73 | const domain_t * domain, 74 | const int step, 75 | char ** dirname 76 | ){ 77 | // set directory name 78 | snprintf( 79 | g_dirname, 80 | g_dirname_nchars + 1, 81 | "%s%0*d", 82 | g_dirname_prefix, 83 | g_dirname_ndigits, 84 | step 85 | ); 86 | *dirname = g_dirname; 87 | // get communicator to identify the main process 88 | const int root = 0; 89 | int myrank = root; 90 | sdecomp.get_comm_rank(domain->info, &myrank); 91 | // create directory 92 | if(root == myrank){ 93 | // although it may fail, anyway continue, which is designed to be safe 94 | fileio.mkdir(*dirname); 95 | } 96 | // wait for the main process to complete making directory 97 | MPI_Barrier(MPI_COMM_WORLD); 98 | // schedule next saving event 99 | g_next += g_rate; 100 | return 0; 101 | } 102 | 103 | /** 104 | * @brief getter of a member: g_next 105 | * @return : g_next 106 | */ 107 | static double get_next_time( 108 | void 109 | ){ 110 | return g_next; 111 | } 112 | 113 | const save_t save = { 114 | .init = init, 115 | .prepare = prepare, 116 | .get_next_time = get_next_time, 117 | }; 118 | 119 | -------------------------------------------------------------------------------- /src/tdm.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "memory.h" 6 | #include "tdm.h" 7 | 8 | /** 9 | * @brief kernel function to solve a linear system 10 | * @param[in] n : matrix size 11 | * @param[in] l : lower diagonal part 12 | * @param[in] c : center diagonal part 13 | * @param[in] u : upper diagonal part 14 | * @param[inout] q : right-hand-side & answers 15 | * @return : error code 16 | */ 17 | #define GTSV(type) \ 18 | static int gtsv_##type( \ 19 | const int n, \ 20 | const double * restrict l, \ 21 | const double * restrict c, \ 22 | const double * restrict u, \ 23 | double * restrict v, \ 24 | type * restrict q \ 25 | ){ \ 26 | /* divide the first row by center-diagonal term | 2 */ \ 27 | v[0] = u[0] / c[0]; \ 28 | q[0] = q[0] / c[0]; \ 29 | /* forward substitution | 7 */ \ 30 | for(int i = 1; i < n - 1; i++){ \ 31 | /* assume positive-definite system */ \ 32 | /* to skip zero-division checks */ \ 33 | double val = 1. / (c[i] - l[i] * v[i-1]); \ 34 | v[i] = val * (u[i] ); \ 35 | q[i] = val * (q[i] - l[i] * q[i-1]); \ 36 | } \ 37 | /* last row, do the same thing but consider singularity | 7 */ \ 38 | double val = c[n-1] - l[n-1] * v[n-2]; \ 39 | if(fabs(val) > DBL_EPSILON){ \ 40 | q[n-1] = 1. / val * (q[n-1] - l[n-1] * q[n-2]); \ 41 | }else{ \ 42 | /* singular, zero mean */ \ 43 | q[n-1] = 0.; \ 44 | } \ 45 | /* backward substitution | 3 */ \ 46 | for(int i = n - 2; i >= 0; i--){ \ 47 | q[i] -= v[i] * q[i+1]; \ 48 | } \ 49 | return 0; \ 50 | } 51 | 52 | /** 53 | * @brief solve linear system 54 | * @param[in] n : size of tri-diagonal matrix 55 | * @param[in] m : how many right-hand-sides do you want to solve? 56 | * @param[in] is_periodic : periodic boundary condition is imposed 57 | * (Sherman-Morrison formula is used) 58 | * or not (normal Thomas algorithm is used) 59 | * @param[in] l : pointer to lower- diagonal components 60 | * @param[in] c : pointer to center-diagonal components 61 | * @param[in] u : pointer to upper- diagonal components 62 | * @param[inout] q : right-hand-sides (size: "n", repeat for "m" times) & answers 63 | * N.B. memory is contiguous in "n" direction, sparse in "m" direction 64 | * @return : error code 65 | */ 66 | #define TDM_SOLVE(type) \ 67 | static int tdm_solve_##type( \ 68 | const int n, \ 69 | const int nrhs, \ 70 | const bool is_periodic, \ 71 | const double * restrict l, \ 72 | const double * restrict c, \ 73 | const double * restrict u, \ 74 | double * restrict v, \ 75 | type * restrict q, \ 76 | double * restrict q1 \ 77 | ){ \ 78 | if(is_periodic){ \ 79 | /* solve additional system coming from periodicity | 7 */ \ 80 | for(int i = 0; i < n-1; i++){ \ 81 | q1[i] \ 82 | = i == 0 ? -l[i] \ 83 | : i == n-2 ? -u[i] \ 84 | : 0.; \ 85 | } \ 86 | gtsv_double(n-1, l, c, u, v, q1); \ 87 | for(int j = 0; j < nrhs; j++){ \ 88 | /* solve normal system | 2 */ \ 89 | type *q0 = q + j * n; \ 90 | gtsv_##type(n-1, l, c, u, v, q0); \ 91 | /* find x_{n-1} | 3 */ \ 92 | type num = q0[n-1] - u[n-1] * q0[0] - l[n-1] * q0[n-2]; \ 93 | double den = c [n-1] + u[n-1] * q1[0] + l[n-1] * q1[n-2]; \ 94 | q0[n-1] = fabs(den) < DBL_EPSILON ? 0. : num / den; \ 95 | /* solve original system | 3 */ \ 96 | for(int i = 0; i < n-1; i++){ \ 97 | q0[i] = q0[i] + q0[n-1] * q1[i]; \ 98 | } \ 99 | } \ 100 | }else{ \ 101 | for(int j = 0; j < nrhs; j++){ \ 102 | gtsv_##type(n, l, c, u, v, q + j * n); \ 103 | } \ 104 | } \ 105 | return 0; \ 106 | } 107 | 108 | // expand macros to define solvers 109 | GTSV(double) 110 | GTSV(fftw_complex) 111 | TDM_SOLVE(double) 112 | TDM_SOLVE(fftw_complex) 113 | 114 | // definition of tdm_info_t_ 115 | /** 116 | * @struct tdm_info_t_ 117 | * @brief struct to keep information about tri-diagonal system and internal buffers 118 | * @var size : size of the system 119 | * @var nrhs : number of right-hand-side terms 120 | * @var is_periodic : periodic boundary condition is imposed or not 121 | * @var is_complex : data type of the right-hand-side terms is fftw_complex or not (double) 122 | * @var l, c, u : lower, center and upper-diagonal parts of the system 123 | * @var v : internal buffer (updated "u" is stored) 124 | * @var q1 : internal buffer (additional right-hand-side term to be solved in addition to "q" is stored) 125 | */ 126 | struct tdm_info_t_ { 127 | int size; 128 | int nrhs; 129 | bool is_periodic; 130 | bool is_complex; 131 | double * restrict l; 132 | double * restrict c; 133 | double * restrict u; 134 | double * restrict v; 135 | double * restrict q1; 136 | }; 137 | 138 | /** 139 | * @brief initialise tdm_info_t 140 | * @param[in] size : size of the system 141 | * @param[in] nrhs : number of right-hand-side terms 142 | * @param[in] is_periodic : periodic boundary condition is imposed or not 143 | * @param[in] is_complex : data type of the right-hand-side terms is fftw_complex or not (double) 144 | * @param[out] info : pointer to the resulting structure 145 | * @return : error code 146 | */ 147 | static int construct( 148 | const int size, 149 | const int nrhs, 150 | const bool is_periodic, 151 | const bool is_complex, 152 | tdm_info_t ** info 153 | ){ 154 | // sanitise input 155 | if(size <= 0){ 156 | printf("ERROR(%s): size should be positive: %d\n", __func__, size); 157 | *info = NULL; 158 | return 1; 159 | } 160 | if(nrhs <= 0){ 161 | printf("ERROR(%s): nrhs should be positive: %d\n", __func__, nrhs); 162 | *info = NULL; 163 | return 1; 164 | } 165 | *info = memory_calloc(1, sizeof(tdm_info_t)); 166 | // sizes 167 | (*info)->size = size; 168 | (*info)->nrhs = nrhs; 169 | // flags 170 | (*info)->is_periodic = is_periodic; 171 | (*info)->is_complex = is_complex; 172 | // buffers 173 | (*info)->l = memory_calloc(size, sizeof(double)); 174 | (*info)->c = memory_calloc(size, sizeof(double)); 175 | (*info)->u = memory_calloc(size, sizeof(double)); 176 | (*info)->v = memory_calloc(size, sizeof(double)); 177 | if(is_periodic && /* to avoid zero-size allocation */ 1 < size){ 178 | (*info)->q1 = memory_calloc(size - 1, sizeof(double)); 179 | }else{ 180 | (*info)->q1 = NULL; 181 | } 182 | return 0; 183 | } 184 | 185 | /** 186 | * @brief return pointer to the lower-diagonal matrix 187 | * @param[in] info : initialised by constructor 188 | * @param[out] l : pointer to the lower-diagonal matrix 189 | * @return : error code 190 | */ 191 | static int get_l( 192 | const tdm_info_t * info, 193 | double * restrict * l 194 | ){ 195 | if(NULL == info){ 196 | printf("ERROR(%s): info is NULL\n", __func__); 197 | return 1; 198 | } 199 | *l = info->l; 200 | return 0; 201 | } 202 | 203 | /** 204 | * @brief return pointer to the center-diagonal matrix 205 | * @param[in] info : initialised by constructor 206 | * @param[out] c : pointer to the center-diagonal matrix 207 | * @return : error code 208 | */ 209 | static int get_c( 210 | const tdm_info_t * info, 211 | double * restrict * c 212 | ){ 213 | if(NULL == info){ 214 | printf("ERROR(%s): info is NULL\n", __func__); 215 | return 1; 216 | } 217 | *c = info->c; 218 | return 0; 219 | } 220 | 221 | /** 222 | * @brief return pointer to the upper-diagonal matrix 223 | * @param[in] info : initialised by constructor 224 | * @param[out] u : pointer to the upper-diagonal matrix 225 | * @return : error code 226 | */ 227 | static int get_u( 228 | const tdm_info_t * info, 229 | double * restrict * u 230 | ){ 231 | if(NULL == info){ 232 | printf("ERROR(%s): info is NULL\n", __func__); 233 | return 1; 234 | } 235 | *u = info->u; 236 | return 0; 237 | } 238 | 239 | static int get_size( 240 | const tdm_info_t * info, 241 | int * size 242 | ){ 243 | if(NULL == info){ 244 | printf("ERROR(%s): info is NULL\n", __func__); 245 | return 1; 246 | } 247 | *size = info->size; 248 | return 0; 249 | } 250 | 251 | static int get_nrhs( 252 | const tdm_info_t * info, 253 | int * nrhs 254 | ){ 255 | if(NULL == info){ 256 | printf("ERROR(%s): info is NULL\n", __func__); 257 | return 1; 258 | } 259 | *nrhs = info->nrhs; 260 | return 0; 261 | } 262 | 263 | /** 264 | * @brief solve tri-diagonal systems for the given input 265 | * @param[in] info : initialised by constructor 266 | * @param[in,out] data : pointer to the right-hand-side terms, also used as a place to store the result 267 | * @return : error code 268 | */ 269 | static int solve( 270 | const tdm_info_t * info, 271 | void * restrict data 272 | ){ 273 | if(NULL == info){ 274 | printf("ERROR(%s): info is NULL\n", __func__); 275 | return 1; 276 | } 277 | const int size = info->size; 278 | const int nrhs = info->nrhs; 279 | const bool is_periodic = info->is_periodic; 280 | const bool is_complex = info->is_complex; 281 | const double * restrict l = info->l; 282 | const double * restrict c = info->c; 283 | const double * restrict u = info->u; 284 | double * restrict v = info->v; 285 | double * restrict q1 = info->q1; 286 | if(is_complex){ 287 | tdm_solve_fftw_complex(size, nrhs, is_periodic, l, c, u, v, data, q1); 288 | }else{ 289 | tdm_solve_double(size, nrhs, is_periodic, l, c, u, v, data, q1); 290 | } 291 | return 0; 292 | } 293 | 294 | /** 295 | * @brief deallocate internal buffers 296 | * @param[in] info : initialised by constructor 297 | * @return : error code 298 | */ 299 | static int destruct( 300 | tdm_info_t * info 301 | ){ 302 | if(NULL == info){ 303 | printf("ERROR(%s): info is NULL\n", __func__); 304 | return 1; 305 | } 306 | memory_free(info->l); 307 | memory_free(info->c); 308 | memory_free(info->u); 309 | memory_free(info->v); 310 | memory_free(info->q1); 311 | memory_free(info); 312 | return 0; 313 | } 314 | 315 | const tdm_t tdm = { 316 | .construct = construct, 317 | .get_l = get_l, 318 | .get_c = get_c, 319 | .get_u = get_u, 320 | .get_size = get_size, 321 | .get_nrhs = get_nrhs, 322 | .solve = solve, 323 | .destruct = destruct, 324 | }; 325 | 326 | -------------------------------------------------------------------------------- /src/timer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "timer.h" 3 | 4 | /** 5 | * @brief get current time 6 | * @return : current time 7 | */ 8 | double timer( 9 | void 10 | ){ 11 | const int root = 0; 12 | // although this is called by all processes, 13 | double time = MPI_Wtime(); 14 | // use the result of the main process 15 | MPI_Bcast(&time, 1, MPI_DOUBLE, root, MPI_COMM_WORLD); 16 | return time; 17 | } 18 | 19 | -------------------------------------------------------------------------------- /tools/README.rst: -------------------------------------------------------------------------------- 1 | ###### 2 | tools/ 3 | ###### 4 | 5 | **** 6 | Note 7 | **** 8 | 9 | Normally you do not have to pay attention to the files here. 10 | 11 | ***** 12 | Files 13 | ***** 14 | 15 | #. ``define_arrays.py`` 16 | 17 | This script defines macros to enable ``UX(i, j, k)`` notations in C. 18 | Since macros have already been included in the package, you do not have to regenerate them. 19 | 20 | -------------------------------------------------------------------------------- /tools/define_arrays.py: -------------------------------------------------------------------------------- 1 | # define macros to access arrays easily 2 | # for 1D, x-face, x-center, and their distances are defined 3 | # for ND, arrays to hold velocity, pressure, temperature, etc. are defined 4 | 5 | import os 6 | import sys 7 | import re 8 | 9 | 10 | def get_lbound(lbound): 11 | # -1 -> "2" 12 | # 0 -> "1" 13 | # +1 -> "0" 14 | lbound = - lbound + 1 15 | return f"{lbound}" 16 | 17 | 18 | def get_ubound(ubound, prefix): 19 | # -1 -> "isize-1" 20 | # 0 -> "isize+0" 21 | # +1 -> "isize+1" 22 | return f"{prefix}{ubound:+d}" 23 | 24 | 25 | def get_nitems(lbound, ubound): 26 | # get size of a vector whose lower- / upper-bounds are given 27 | # e.g. 28 | # nitems = isize+1 if lbound = 1 and ubound = isize+1 29 | # nitems = jsize+2 if lbound = 0 and ubound = jsize+1 30 | # extract numbers 31 | s_ = re.sub("[a-zA-Z]", "", lbound.strip()) 32 | e_ = re.sub("[a-zA-Z]", "", ubound.strip()) 33 | s_ = int(s_) 34 | if e_ == "": 35 | e_ = 0 36 | else: 37 | e_ = int(e_) 38 | nitems = f"{e_ - s_ + 1:+d}" 39 | return nitems 40 | 41 | 42 | def get_index(index, lbound): 43 | # get index of a vector whose lower-bound is given 44 | # e.g. 45 | # return I-1 if lbound is 1 to map I = 1 to the head of vector 46 | # return I+1 if lbound is -1 to map I = -1 to the head of vector 47 | if lbound == "0": 48 | return f"{index} " 49 | else: 50 | val = -int(lbound) 51 | return f"{index}{val:+d}" 52 | 53 | 54 | def get_index_1d(lbound_0): 55 | iindex = get_index("I", lbound_0) 56 | string = ( 57 | f"({iindex})" 58 | ) 59 | return string 60 | 61 | 62 | def get_index_2d(nitems_0, lbound_0, lbound_1): 63 | iindex = get_index("I", lbound_0) 64 | jindex = get_index("J", lbound_1) 65 | string = ( 66 | f"({iindex})" 67 | f" + " 68 | f"(isize{nitems_0})" 69 | f" * " 70 | f"({jindex})" 71 | ) 72 | return string 73 | 74 | 75 | def get_index_3d(nitems_0, nitems_1, lbound_0, lbound_1, lbound_2): 76 | iindex = get_index("I", lbound_0) 77 | jindex = get_index("J", lbound_1) 78 | kindex = get_index("K", lbound_2) 79 | string = ( 80 | f"({iindex})" 81 | f" + " 82 | f"(isize{nitems_0})" 83 | f" * " 84 | f"(" 85 | f"({jindex})" 86 | f" + " 87 | f"(jsize{nitems_1})" 88 | f" * " 89 | f"({kindex})" 90 | f")" 91 | ) 92 | return string 93 | 94 | 95 | def output(dname, vname, text): 96 | fname = f"{dname}/{vname}.h" 97 | # attach include-guard 98 | include_guard = fname.replace("/", "_").replace(".", "_").upper() 99 | header = ( 100 | f"#if !defined({include_guard})\n" 101 | f"#define {include_guard}\n" 102 | f"\n" 103 | f"// This file is generated by {sys.argv[0]}\n" 104 | f"\n" 105 | ) 106 | footer = ( 107 | f"#endif // {include_guard}\n" 108 | ) 109 | with open(fname, "w") as f: 110 | f.write("".join([header, text, footer])) 111 | 112 | 113 | def gen_1d(dname, vname, bounds): 114 | # prepare macros for one-dimensional vector 115 | lbound_0 = get_lbound(bounds[0]) 116 | ubound_0 = get_ubound(bounds[1], "isize") 117 | index_1d = get_index_1d(lbound_0) 118 | char_0 = "I" 119 | text = ( 120 | f"// [{lbound_0} : {ubound_0}]\n" 121 | f"#define {vname.upper()}({char_0})" 122 | f" ({vname}[{index_1d}])\n" 123 | f"#define {vname.upper()}_NADDS" 124 | f" (int [2]){{{bounds[0]}, {bounds[1]}}}\n" 125 | f"\n" 126 | ) 127 | output(dname, vname, text) 128 | 129 | 130 | def gen_nd(dname, vname, bounds): 131 | # prepare macros for N-dimensional array 132 | lbound_0 = get_lbound(bounds[0][0]) 133 | ubound_0 = get_ubound(bounds[0][1], "isize") 134 | lbound_1 = get_lbound(bounds[1][0]) 135 | ubound_1 = get_ubound(bounds[1][1], "jsize") 136 | lbound_2 = get_lbound(bounds[2][0]) 137 | ubound_2 = get_ubound(bounds[2][1], "ksize") 138 | char_0 = "I" 139 | char_1 = "J" 140 | char_2 = "K" 141 | nitems_0 = get_nitems(lbound_0, ubound_0) 142 | nitems_1 = get_nitems(lbound_1, ubound_1) 143 | index_2d = get_index_2d( 144 | nitems_0, 145 | lbound_0, 146 | lbound_1 147 | ) 148 | index_3d = get_index_3d( 149 | nitems_0, 150 | nitems_1, 151 | lbound_0, 152 | lbound_1, 153 | lbound_2 154 | ) 155 | text = str() 156 | # 2D array (not for z-related things) 157 | if "z" not in vname: 158 | text += ( 159 | f"#if NDIMS == 2\n" 160 | f"// [{lbound_0} : {ubound_0}]," 161 | f" [{lbound_1} : {ubound_1}]\n" 162 | f"#define {vname.upper()}({char_0}, {char_1})" 163 | f" ({vname}[{index_2d}])\n" 164 | f"#define {vname.upper()}_NADDS" 165 | f" (int [NDIMS][2]){{" 166 | f" {{{bounds[0][0]}, {bounds[0][1]}}}," 167 | f" {{{bounds[1][0]}, {bounds[1][1]}}}," 168 | f" }}\n" 169 | f"#endif\n" 170 | f"\n" 171 | ) 172 | # 3D array 173 | text += ( 174 | f"#if NDIMS == 3\n" 175 | f"// [{lbound_0} : {ubound_0}]," 176 | f" [{lbound_1} : {ubound_1}]," 177 | f" [{lbound_2} : {ubound_2}]\n" 178 | f"#define {vname.upper()}({char_0}, {char_1}, {char_2})" 179 | f" ({vname}[{index_3d}])\n" 180 | f"#define {vname.upper()}_NADDS" 181 | f" (int [NDIMS][2]){{" 182 | f" {{{bounds[0][0]}, {bounds[0][1]}}}," 183 | f" {{{bounds[1][0]}, {bounds[1][1]}}}," 184 | f" {{{bounds[2][0]}, {bounds[2][1]}}}," 185 | f" }}\n" 186 | f"#endif\n" 187 | f"\n" 188 | ) 189 | output(dname, vname, text) 190 | 191 | 192 | def domain(root): 193 | dname = f"{root}/domain" 194 | os.system(f"rm {dname}/*.h") 195 | gen_1d(dname, "xf", (+0, +1)) 196 | gen_1d(dname, "xc", (+1, +1)) 197 | gen_1d(dname, "dxf", (+0, +0)) 198 | gen_1d(dname, "dxc", (+0, +1)) 199 | 200 | 201 | def fluid(root): 202 | dname = f"{root}/fluid" 203 | os.system(f"rm {dname}/*.h") 204 | gen_nd(dname, "ux", ((+0, +1), (+1, +1), (+1, +1))) 205 | gen_nd(dname, "uy", ((+1, +1), (+1, +1), (+1, +1))) 206 | gen_nd(dname, "uz", ((+1, +1), (+1, +1), (+1, +1))) 207 | gen_nd(dname, "p", ((+1, +1), (+1, +1), (+1, +1))) 208 | gen_nd(dname, "t", ((+1, +1), (+1, +1), (+1, +1))) 209 | gen_nd(dname, "psi", ((+1, +1), (+1, +1), (+1, +1))) 210 | gen_nd(dname, "srcux", ((-1, +0), (+0, +0), (+0, +0))) 211 | gen_nd(dname, "srcuy", ((+0, +0), (+0, +0), (+0, +0))) 212 | gen_nd(dname, "srcuz", ((+0, +0), (+0, +0), (+0, +0))) 213 | gen_nd(dname, "srct", ((+0, +0), (+0, +0), (+0, +0))) 214 | 215 | 216 | def ib(root): 217 | dname = f"{root}/ib" 218 | os.system(f"rm {dname}/*.h") 219 | gen_nd(dname, "ibfx", ((+0, +0), (+1, +1), (+1, +1))) 220 | gen_nd(dname, "ibfy", ((+0, +0), (+1, +1), (+1, +1))) 221 | gen_nd(dname, "ibfz", ((+0, +0), (+1, +1), (+1, +1))) 222 | 223 | 224 | def statistics(root): 225 | dname = f"{root}/statistics" 226 | os.system(f"rm {dname}/*.h") 227 | gen_nd(dname, "ux1", ((+0, +1), (+0, +0), (+0, +0))) 228 | gen_nd(dname, "ux2", ((+0, +1), (+0, +0), (+0, +0))) 229 | gen_nd(dname, "uy1", ((+1, +1), (+0, +0), (+0, +0))) 230 | gen_nd(dname, "uy2", ((+1, +1), (+0, +0), (+0, +0))) 231 | gen_nd(dname, "uz1", ((+1, +1), (+0, +0), (+0, +0))) 232 | gen_nd(dname, "uz2", ((+1, +1), (+0, +0), (+0, +0))) 233 | gen_nd(dname, "t1", ((+1, +1), (+0, +0), (+0, +0))) 234 | gen_nd(dname, "t2", ((+1, +1), (+0, +0), (+0, +0))) 235 | gen_nd(dname, "uxt", ((+0, +1), (+0, +0), (+0, +0))) 236 | 237 | 238 | if __name__ == "__main__": 239 | root = "include/array_macros" 240 | # coordinates in the wall-normal direction 241 | domain(root) 242 | # velocity, pressure, etc. 243 | fluid(root) 244 | # force by objects 245 | ib(root) 246 | # arrays to store temporally-averaged statistics 247 | statistics(root) 248 | -------------------------------------------------------------------------------- /visualise/2d.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import numpy as np 4 | from tqdm import tqdm 5 | from matplotlib import pyplot 6 | from matplotlib import patches 7 | 8 | 9 | def load_data(dname): 10 | glsizes = np.load(f"{dname}/glsizes.npy") 11 | lengths = np.load(f"{dname}/lengths.npy") 12 | ly, lx = lengths 13 | ny = glsizes[0] 14 | nx = glsizes[1] 15 | dx = lx / nx 16 | dy = ly / ny 17 | xc = np.linspace(0.5 * dx, lx - 0.5 * dx, nx) 18 | yc = np.linspace(0.5 * dy, ly - 0.5 * dy, ny) 19 | temp = np.load("{}/t.npy".format(dname))[:, 1:-1].T 20 | prs = np.load(f"{dname}/p_rs.npy") 21 | pys = np.load(f"{dname}/p_xs.npy") 22 | pxs = np.load(f"{dname}/p_ys.npy") 23 | return lx, ly, xc, yc, temp, prs, pxs, pys 24 | 25 | 26 | def plot(ax, lx, ly, xs, ys, temp, prs, pxs, pys): 27 | ax.clear() 28 | ax.contourf(xs, ys, temp, vmin=-0.5, vmax=+0.5, levels=51, cmap="bwr") 29 | fc = "#AAAAAA" 30 | ec = "#000000" 31 | for pr, px, py in zip(prs, pxs, pys): 32 | c = patches.Circle(xy=(px, py), radius=pr, fc=fc, ec=ec) 33 | ax.add_patch(c) 34 | c = patches.Circle(xy=(px - lx, py), radius=pr, fc=fc, ec=ec) 35 | ax.add_patch(c) 36 | c = patches.Circle(xy=(px + lx, py), radius=pr, fc=fc, ec=ec) 37 | ax.add_patch(c) 38 | kwrds = { 39 | "title": "", 40 | "aspect": "equal", 41 | "xlim": [0.0, lx], 42 | "ylim": [0.0, ly], 43 | "xlabel": "", 44 | "ylabel": "", 45 | "xticks": [], 46 | "yticks": [], 47 | } 48 | ax.set(**kwrds) 49 | 50 | 51 | def main(ax, root): 52 | lx, ly, xs, ys, temp, prs, pxs, pys = load_data(root) 53 | plot(ax, lx, ly, xs, ys, temp, prs, pxs, pys) 54 | 55 | 56 | if __name__ == "__main__": 57 | argv = sys.argv 58 | assert 2 == len(argv) 59 | root = argv[1] 60 | fig = pyplot.figure(figsize=(8, 4), frameon=False) 61 | ax = fig.add_axes(rect=[0.0, 0.0, 1.0, 1.0]) 62 | if "step" in root: 63 | main(ax, root) 64 | pyplot.show(block=True) 65 | else: 66 | roots = [f"{root}/{dname}" for dname in os.listdir(root) if "step" in dname] 67 | roots = sorted(roots) 68 | for cnt, root in enumerate(tqdm(roots)): 69 | main(ax, root) 70 | pyplot.show(block=False) 71 | pyplot.pause(1.e-1) 72 | # pyplot.savefig(f"images/image{cnt:03d}.jpg", dpi=160) 73 | pyplot.close() 74 | --------------------------------------------------------------------------------