├── .gitignore ├── How_use_netCDF_Reader.ipynb ├── README.md ├── defaults.json ├── fourier.py ├── initial.py ├── internal_forced.py ├── namelist.py ├── nc_tools.py ├── netCDF_Reader.py ├── plot_wake_directly.py ├── plotting.py ├── requirements.txt ├── rossby.py ├── screenshots └── wake.png ├── script_read_netcdf.py ├── shipwake.py ├── shipwake_arbitrary.py ├── starter.py ├── wave2d.py └── wavepackets.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | history.nc 3 | 4 | -------------------------------------------------------------------------------- /How_use_netCDF_Reader.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Read a wave2d netCDF output" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "### First, import the script" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 1, 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "import netCDF_Reader as nrd" 24 | ] 25 | }, 26 | { 27 | "cell_type": "markdown", 28 | "metadata": {}, 29 | "source": [ 30 | "### Now, build your Wave object with the filename" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": 2, 36 | "metadata": {}, 37 | "outputs": [], 38 | "source": [ 39 | "filename = 'history.nc'\n", 40 | "wave = nrd.import_Wave(filename)" 41 | ] 42 | }, 43 | { 44 | "cell_type": "markdown", 45 | "metadata": {}, 46 | "source": [ 47 | "#### What is a Wave object ?" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": 3, 53 | "metadata": {}, 54 | "outputs": [], 55 | "source": [ 56 | "wave?" 57 | ] 58 | }, 59 | { 60 | "cell_type": "markdown", 61 | "metadata": {}, 62 | "source": [ 63 | "#### Now, we want to know the names of variables that we can plot" 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": 4, 69 | "metadata": {}, 70 | "outputs": [ 71 | { 72 | "name": "stdout", 73 | "output_type": "stream", 74 | "text": [ 75 | "Data variables:\n", 76 | " p (time, y, x) float64 ...\n" 77 | ] 78 | } 79 | ], 80 | "source": [ 81 | "wave.variable_list()" 82 | ] 83 | }, 84 | { 85 | "cell_type": "markdown", 86 | "metadata": {}, 87 | "source": [ 88 | "We can choose our variable" 89 | ] 90 | }, 91 | { 92 | "cell_type": "code", 93 | "execution_count": 5, 94 | "metadata": {}, 95 | "outputs": [], 96 | "source": [ 97 | "variable = 'p'" 98 | ] 99 | }, 100 | { 101 | "cell_type": "markdown", 102 | "metadata": {}, 103 | "source": [ 104 | "### Now, we want to build the animation" 105 | ] 106 | }, 107 | { 108 | "cell_type": "markdown", 109 | "metadata": {}, 110 | "source": [ 111 | "How works the method animate?" 112 | ] 113 | }, 114 | { 115 | "cell_type": "code", 116 | "execution_count": 6, 117 | "metadata": {}, 118 | "outputs": [], 119 | "source": [ 120 | "wave.animate?" 121 | ] 122 | }, 123 | { 124 | "cell_type": "markdown", 125 | "metadata": {}, 126 | "source": [ 127 | "So, we just have to use the method with the variable that we choose" 128 | ] 129 | }, 130 | { 131 | "cell_type": "code", 132 | "execution_count": 7, 133 | "metadata": {}, 134 | "outputs": [ 135 | { 136 | "name": "stderr", 137 | "output_type": "stream", 138 | "text": [ 139 | "MovieWriter PillowWriter unavailable; trying to use instead.\n" 140 | ] 141 | }, 142 | { 143 | "name": "stdout", 144 | "output_type": "stream", 145 | "text": [ 146 | "...........Done!\n" 147 | ] 148 | }, 149 | { 150 | "data": { 151 | "image/png": "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\n", 152 | "text/plain": [ 153 | "
" 154 | ] 155 | }, 156 | "metadata": { 157 | "needs_background": "light" 158 | }, 159 | "output_type": "display_data" 160 | } 161 | ], 162 | "source": [ 163 | "wave.animate(variable, save=True)" 164 | ] 165 | } 166 | ], 167 | "metadata": { 168 | "kernelspec": { 169 | "display_name": "Python 3", 170 | "language": "python", 171 | "name": "python3" 172 | }, 173 | "language_info": { 174 | "codemirror_mode": { 175 | "name": "ipython", 176 | "version": 3 177 | }, 178 | "file_extension": ".py", 179 | "mimetype": "text/x-python", 180 | "name": "python", 181 | "nbconvert_exporter": "python", 182 | "pygments_lexer": "ipython3", 183 | "version": "3.7.6" 184 | } 185 | }, 186 | "nbformat": 4, 187 | "nbformat_minor": 2 188 | } 189 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wave2d 2 | 3 | Wave2d is a very simple Python code to investigate a few properties of 4 | linear waves encountered in fluids: surface waves, internal gravity 5 | waves and Rossby waves. The code illustrates their dispersive effects 6 | through the evolution of the wave field. The code offers three 7 | different ways to generate waves: a localized initial 8 | perturbation, a moving object, generating a wake, and an oscillating 9 | wavemaker. The code also illustrates two aspects of the wave energy: 10 | injection or conservation, depending on the generation mechanism, and 11 | propagation in space. This last aspect is so central that it could be 12 | almost the definition of a wave: a wave is a process able to transport 13 | energy with negligible mass transport (quote from R. Feynman). 14 | 15 | ![ScreenShot](/screenshots/wake.png) 16 | -------------------------------------------------------------------------------- /defaults.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": { 3 | "Lx": { 4 | "type": "float", 5 | "doc": "domain length in x", 6 | "default": 1.0 7 | }, 8 | "Ly": { 9 | "type": "float", 10 | "doc": "domain length in y", 11 | "default": 1.0 12 | }, 13 | "nx": { 14 | "type": "int", 15 | "doc": "number of grid points in x", 16 | "default": 128 17 | }, 18 | "ny": { 19 | "type": "int", 20 | "doc": "number of grid points in y", 21 | "default": 128 22 | } 23 | }, 24 | "time": { 25 | "tend": { 26 | "type": "float", 27 | "doc": "integration time", 28 | "default": 2.0 29 | }, 30 | "tplot": { 31 | "type": "float", 32 | "doc": "time intervale between two consecutive frames. The bottleneck of the code is ... the plotting, not the computation!", 33 | "default": 0.1 34 | }, 35 | "dt": { 36 | "type": "float", 37 | "doc": "time step used in the computation. Should be smaller than tplot. Can be anything for initial value problem. Should be small enough for the wake and oscillator cases because of the source terms in the complex amplitude equation", 38 | "default": 0.01 39 | } 40 | }, 41 | "physics": { 42 | "typewave": { 43 | "type": "str", 44 | "doc": "dispersion relation", 45 | "avail": ["gw", "gwshort", "gwlong", "internal", "rossby", "inertiagravity", "capillary"], 46 | "default": "gwshort" 47 | }, 48 | "g": { 49 | "type": "float", 50 | "doc": "acceleration of gravity", 51 | "default": 9.81 52 | }, 53 | "H": { 54 | "type": "float", 55 | "doc": "water depth", 56 | "default": 10.0 57 | }, 58 | "beta": { 59 | "type": "float", 60 | "doc": "beta coefficient for Rossby waves", 61 | "default": 1.0 62 | }, 63 | "Rd": { 64 | "type": "float", 65 | "doc": "Rossby deformation radius", 66 | "default": 1.0 67 | }, 68 | "f0": { 69 | "type": "float", 70 | "doc": "Coriolis parameter", 71 | "default": 1.0 72 | }, 73 | "ageos": { 74 | "type": "bool", 75 | "doc": "determines whether (u,v) is the geostrophic or the ageostrophic velocity (Rossby wave case)", 76 | "default": true 77 | }, 78 | "BVF": { 79 | "type": "float", 80 | "doc": "Brunt-Vaisala frequency", 81 | "default": 1e-1 82 | }, 83 | "gammarho": { 84 | "type": "float", 85 | "doc": "gamma/rho with gamma=surface tension and rho=water density", 86 | "default": 1.0 87 | } 88 | }, 89 | "plotting": { 90 | "macuser": { 91 | "type": "bool", 92 | "doc": "set it to True if you are using a mac. Make the animation working.", 93 | "default": false 94 | }, 95 | "cax": { 96 | "type": "float", 97 | "doc": "colorbar interval: two values list, e.g. [-1., 1.]", 98 | "default": [-0.1, 0.1] 99 | }, 100 | "varplot": { 101 | "type": "str", 102 | "doc": "variable to plot during the animation", 103 | "default": "p", 104 | "avail": ["p", "u", "v", "up", "vp"] 105 | }, 106 | "plotvector": { 107 | "type": "str", 108 | "doc": "vector field to superimpose during the animation", 109 | "default": "None", 110 | "avail": ["None", "velocity", "energyflux"] 111 | }, 112 | "vectorscale": { 113 | "type": "float", 114 | "doc": "scale coefficient to make arrows longer (scale>1) or shorter (scale<1)", 115 | "default": 1.0 116 | }, 117 | "figwidth": { 118 | "type": "int", 119 | "doc": "figure width (in pixels)", 120 | "default": 1080 121 | } 122 | }, 123 | "IO": { 124 | "netcdf": { 125 | "type": "bool", 126 | "doc": "if True, save the results into a netcdf file", 127 | "default": true 128 | }, 129 | "filename": { 130 | "type": "str", 131 | "doc": "file name of the netCDF", 132 | "default": "history.nc" 133 | } 134 | }, 135 | "forcing": { 136 | "generation": { 137 | "type": "str", 138 | "doc": "forcing that generates the wave pattern", 139 | "avail": ["wake", "initial", "oscillator"], 140 | "default": "wake" 141 | }, 142 | "sigma": { 143 | "type": "float", 144 | "doc": "wavepacket/object width", 145 | "default": 0.08 146 | }, 147 | "U": { 148 | "type": "float", 149 | "doc": "object speed for the wake problem", 150 | "default": 1.0 151 | }, 152 | "alphaU": { 153 | "type": "float", 154 | "doc": "heading (in degrees) of the moving object (wake problem)", 155 | "default": 0.0 156 | }, 157 | "omega0": { 158 | "type": "float", 159 | "doc": "pulsation of the oscillator (oscillator problem)", 160 | "default": 10.0 161 | }, 162 | "waveform": { 163 | "type": "string", 164 | "doc": "wave packet form", 165 | "default": "gaussian", 166 | "avail": ["gaussian", "square", "triangle", "packet"] 167 | }, 168 | "aspect_ratio": { 169 | "type": "float", 170 | "doc": "aspect ratio of the wavepacket (>1: elongated in x, <1, elongated in y)", 171 | "default": 1.0 172 | }, 173 | "x0": { 174 | "type": "float", 175 | "doc": "x coordinate of the wavepacket (origin is at bottom left corner of the domain)", 176 | "default": 0.5 177 | }, 178 | "y0": { 179 | "type": "float", 180 | "doc": "y coordinate of the wavepacket (origin is at bottom left corner of the domain)", 181 | "default": 0.5 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /fourier.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import warnings 3 | 4 | warnings.filterwarnings("ignore") 5 | 6 | 7 | class Fourier(object): 8 | def __init__(self, param): 9 | list_param = ['Lx', 'Ly', 'nx', 'ny', 10 | 'typewave', 'g', 'H', 'BVF', 11 | 'beta', 'Rd', 'f0', 'ageos', 12 | 'gammarho'] 13 | param.copy(self, list_param) 14 | 15 | self.x, self.kx = set_x_and_k(self.nx, self.Lx) 16 | self.y, self.ky = set_x_and_k(self.ny, self.Ly) 17 | 18 | self.xx, self.yy = np.meshgrid(self.x, self.y) 19 | self.kxx, self.kyy = np.meshgrid(self.kx, self.ky) 20 | self.ktot = np.sqrt(self.kxx**2+self.kyy**2) 21 | 22 | if self.typewave == 'gw': 23 | self.omega = np.sqrt(self.g*self.ktot*np.tanh(self.H*self.ktot)) 24 | self.p2u = self.kxx/self.omega 25 | self.p2v = self.kyy/self.omega 26 | self.p2u[self.omega == 0] = 0. 27 | self.p2v[self.omega == 0] = 0. 28 | 29 | if self.typewave == 'gwshort': 30 | self.omega = np.sqrt(self.g*self.ktot) 31 | self.p2u = self.kxx/self.omega 32 | self.p2v = self.kyy/self.omega 33 | self.p2u[self.omega == 0] = 0. 34 | self.p2v[self.omega == 0] = 0. 35 | 36 | if self.typewave == 'capillary': 37 | self.omega = np.sqrt(self.g*self.ktot+self.gammarho*self.ktot**3) 38 | self.p2u = self.kxx/self.omega 39 | self.p2v = self.kyy/self.omega 40 | self.p2u[self.omega == 0] = 0. 41 | self.p2v[self.omega == 0] = 0. 42 | 43 | if self.typewave == 'gwlong': 44 | self.omega = np.sqrt(self.g*self.H)*self.ktot 45 | self.p2u = self.kxx/self.omega 46 | self.p2v = self.kyy/self.omega 47 | self.p2u[self.omega == 0] = 0. 48 | self.p2v[self.omega == 0] = 0. 49 | 50 | if self.typewave == 'inertiagravity': 51 | self.omega = np.sqrt(self.f0**2 + self.g*self.H*self.ktot**2) 52 | self.p2u = (self.omega*self.kxx+1j*self.f0*self.kyy) / (self.g*self.H*self.ktot**2) 53 | self.p2v = (self.omega*self.kyy-1j*self.f0*self.kxx) / (self.g*self.H*self.ktot**2) 54 | self.p2u[self.ktot == 0] = 0. 55 | self.p2v[self.ktot == 0] = 0. 56 | 57 | if self.typewave == 'internal': 58 | self.omega = self.BVF*np.abs(self.kxx)/self.ktot 59 | self.omega[self.ktot == 0] = 0. 60 | self.p2u = self.kxx/self.omega 61 | self.p2v = self.kyy/(self.omega-self.BVF**2/self.omega) 62 | self.p2u[self.omega == 0] = 0 63 | self.p2v[self.omega == 0] = 0 64 | self.p2v[self.omega == self.BVF] = 0 65 | 66 | if self.typewave == 'rossby': 67 | self.omega = -self.beta*self.kxx/(self.ktot**2+self.Rd**-2) 68 | self.p2u = +1j*self.kyy/self.f0 69 | self.p2v = -1j*self.kxx/self.f0 70 | if self.ageos: 71 | pp2u = -1j*self.p2v*self.omega/self.f0 72 | self.p2v = +1j*self.p2u*self.omega/self.f0 73 | self.p2u = pp2u 74 | 75 | def compute_all_variables(self, hphi): 76 | var = {} 77 | if self.typewave in ['gw', 'gwshort', 'gwlong', 'internal', 'rossby', 'inertiagravity']: 78 | pp = np.fft.ifft2(hphi) 79 | p = np.real(pp) 80 | amp = np.abs(pp) 81 | u = np.real(np.fft.ifft2(hphi*self.p2u)) 82 | v = np.real(np.fft.ifft2(hphi*self.p2v)) 83 | var['p'] = p 84 | var['abs'] = amp 85 | var['u'] = u 86 | var['v'] = v 87 | var['up'] = u*p 88 | var['vp'] = v*p 89 | 90 | return var 91 | 92 | def compute_balanced_wake(self, hphi, U, epsilon=0.3): 93 | """ ref: Raphael and de Gennes, PRE 1996 """ 94 | ktot = self.ktot 95 | omega = self.omega 96 | kxx = self.kxx 97 | 98 | def zeta_epsilon(hphi, eps0): 99 | den = omega**2-(U*kxx)**2 - 2*1j*eps0*U*kxx 100 | hzeta = hphi*ktot/den 101 | hzeta[den == 0] = 0. 102 | return -np.real(np.fft.ifft2(hzeta)) 103 | 104 | zeta1 = zeta_epsilon(hphi, epsilon*1.01) 105 | zeta0 = zeta_epsilon(hphi, epsilon*0.99) 106 | return (zeta1 - zeta0)/(0.02*epsilon) 107 | 108 | 109 | def set_x_and_k(n, L): 110 | k = ((n//2+np.arange(n)) % n) - n//2 111 | return (np.arange(n)+0.5)*L/n, 2*np.pi*k/L 112 | -------------------------------------------------------------------------------- /initial.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import namelist 3 | import wave2d 4 | 5 | import matplotlib.pyplot as plt 6 | plt.ion() 7 | 8 | # to get the list of all parameters and their available values 9 | # use param.manall() in your IPython console or in your Jupyter notebook 10 | 11 | param = namelist.Namelist() 12 | param.typewave = 'gwshort' # 'gwshort', 'inertiagravity', 'internal', 'rossby' 13 | param.generation = 'initial' 14 | param.omega0 = 5. 15 | param.waveform = 'gaussian' 16 | 17 | param.sigma = 0.02*param.Lx 18 | param.aspect_ratio = 1. 19 | 20 | param.varplot = 'p' 21 | 22 | param.nx, param.ny = 128*2, 128 23 | param.Lx, param.Ly = 4, 2 24 | param.x0, param.y0 = 1., 1.0 25 | 26 | param.g = 1. 27 | param.H = 1. 28 | param.f0 = 20. 29 | param.beta = 500. 30 | param.Rd = 0.05 31 | cg = 1.0 32 | param.beta = cg/param.Rd**2 33 | param.BVF = 20. 34 | 35 | param.tend = 2. 36 | param.tplot = .02 37 | param.plotvector = 'None' 38 | param.vectorscale = 10. # larger 'vectorscale' makes the arrows shorter 39 | param.dt = 1e-2 40 | 41 | param.netcdf = True # set it to True to save the NetCDF file 42 | 43 | param.U = .6 44 | param.alphaU = 0*np.pi/180. 45 | 46 | param.cax = np.asarray([-1., 1.]) 47 | param.figwidth = 1080 48 | 49 | model = wave2d.Wave2d(param) 50 | model.set_fourier_space(param) 51 | model.set_wave_packet(param) 52 | model.run(param) 53 | 54 | #plt.figure(2) 55 | #plt.plot(model.energy) 56 | -------------------------------------------------------------------------------- /internal_forced.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import namelist 3 | import wave2d 4 | 5 | import matplotlib.pyplot as plt 6 | plt.ion() 7 | 8 | param = namelist.Namelist() 9 | param.typewave = 'internal' 10 | param.generation = 'oscillator' 11 | param.omega0 = 6. 12 | param.waveform = 'gaussian' 13 | param.sigma = 0.05*param.Lx/8 14 | param.x0, param.y0 = 1.5, 1. # wavemaker location 15 | param.aspect_ratio = 1. 16 | 17 | param.varplot = 'p' 18 | 19 | param.nx, param.ny = 128*4, 128*4 20 | param.Lx, param.Ly = 3, 2 21 | 22 | param.BVF = 20. 23 | 24 | param.tend = 4. 25 | param.tplot = .05 26 | param.plotvector = 'None' 27 | param.vectorscale = 1. 28 | param.dt = 1e-2 29 | 30 | param.U = .4 31 | param.alphaU = -30*np.pi/180. 32 | 33 | param.cax = np.asarray([-1., 1.]) 34 | param.figwidth = 1080 35 | 36 | model = wave2d.Wave2d(param) 37 | model.set_fourier_space(param) 38 | model.set_wave_packet(param) 39 | model.run(param) 40 | -------------------------------------------------------------------------------- /namelist.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | class Namelist(object): 5 | def __init__(self): 6 | with open('defaults.json') as f: 7 | namelist = json.load(f) 8 | self.set_parameters(namelist) 9 | 10 | def set_parameters(self, namelist): 11 | avail = {} 12 | doc = {} 13 | for d in namelist.keys(): 14 | dd = namelist[d] 15 | for name in dd.keys(): 16 | val = dd[name]['default'] 17 | # print(name, val) 18 | setattr(self, name, val) 19 | if 'avail' in dd[name]: 20 | avail[name] = dd[name]['avail'] 21 | if 'doc' in dd[name]: 22 | doc[name] = dd[name]['doc'] 23 | self.avail = avail 24 | self.doc = doc 25 | 26 | def man(self, name): 27 | if name in self.doc: 28 | helpstr = self.doc[name] 29 | if name in self.avail: 30 | availstr = ', '.join([str(l) for l in self.avail[name]]) 31 | helpstr += ' / available values = ['+availstr+']' 32 | else: 33 | helpstr = 'no manual for this parameter' 34 | print('manual for %s : %s' % (name, helpstr)) 35 | 36 | def manall(self): 37 | ps = self.listall() 38 | for p in ps: 39 | self.man(p) 40 | 41 | def checkall(self): 42 | for p, avail in self.avail.items(): 43 | if getattr(self, p) in avail: 44 | # the parameter 'p' is well set 45 | pass 46 | else: 47 | msg = 'parameter "%s" should in ' % p 48 | msg += str(avail) 49 | raise ValueError(msg) 50 | 51 | def listall(self): 52 | """ return the list of all the parameters""" 53 | ps = [d for d in self.__dict__ if not(d in ['avail', 'doc'])] 54 | return ps 55 | 56 | def copy(self, obj, list_param): 57 | """ copy attributes listed in list_param to obj 58 | 59 | On output it returns missing attributes 60 | """ 61 | missing = [] 62 | for k in list_param: 63 | if hasattr(self, k): 64 | setattr(obj, k, getattr(self, k)) 65 | else: 66 | missing.append(k) 67 | return missing 68 | 69 | 70 | if __name__ == "__main__": 71 | param = Namelist() 72 | print('liste of parameters') 73 | print(param.listall()) 74 | 75 | # to have the documentation on one particular parameter 76 | param.man('beta') 77 | 78 | # to get the documentation on all the parameters 79 | param.manall() 80 | 81 | # to check that all parameters that should a value taken from a list 82 | # have an acceptable value 83 | param.checkall() 84 | -------------------------------------------------------------------------------- /nc_tools.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from netCDF4 import Dataset 3 | 4 | class NcTools(object): 5 | def __init__(self, variables, sizes, attrs, ncfilename="history.nc"): 6 | self.attrs = attrs 7 | self.variables = variables 8 | self.sizes = sizes 9 | self.ncfilename = ncfilename 10 | 11 | def createhisfile(self): 12 | with Dataset(self.ncfilename, "w") as nc: 13 | nc.setncatts(self.attrs) 14 | nc.createDimension("time") 15 | for dim, size in self.sizes.items(): 16 | nc.createDimension(f"{dim}", size) 17 | # 18 | for var in self.variables: 19 | shortn = var["short"] 20 | longn = var["long"] 21 | units = var["units"] 22 | dims = var["dims"] 23 | 24 | v = nc.createVariable(shortn, float, dims) 25 | v.long_name = longn 26 | v.units = units 27 | 28 | if __name__ == "__main__": 29 | nx, ny = 100, 50 30 | shape = (ny, nx) 31 | 32 | attrs = {"model": "wave2d", 33 | "wave": "gwlong"} 34 | 35 | sizes = {"y": ny, "x": nx} 36 | 37 | variables = [{"short": "time", 38 | "long": "time", 39 | "units": "s", 40 | "dims": ("time")}, 41 | {"short": "p", 42 | "long": "pressure anomaly", 43 | "units": "m s^-1", 44 | "dims": ("time", "y", "x")}] 45 | 46 | nct = NcTools(variables, sizes, attrs) 47 | nct.createhisfile() 48 | 49 | dt = 0.1 50 | p0 = np.random.uniform(size=shape) 51 | with Dataset(nct.ncfilename, "r+") as nc: 52 | for kt in range(50): 53 | t = kt*dt 54 | nc.variables["time"][kt] = t 55 | dx = 4*t 56 | dy = 10*np.sin(t/2) 57 | p = np.roll(np.roll(p0, int(dy), axis=0), int(dx), axis=1) 58 | nc.variables["p"][kt,:,:] = p 59 | -------------------------------------------------------------------------------- /netCDF_Reader.py: -------------------------------------------------------------------------------- 1 | import xarray as xr 2 | 3 | import numpy as np 4 | 5 | import matplotlib 6 | import matplotlib.pyplot as plt 7 | 8 | import matplotlib.animation as animation 9 | 10 | 11 | class Wave: 12 | 13 | """ 14 | A class to represent a wave builded with wave2d 15 | 16 | ... 17 | 18 | Attributes 19 | ---------- 20 | data : str 21 | data extracted from netCDF file 22 | 23 | Methods 24 | ------- 25 | variable_list(self): 26 | Print the list of available variables 27 | 28 | animate(self, variable, args): 29 | Build an animation of a given variable 30 | """ 31 | 32 | def __init__(self, data=None, 33 | **kwargs): 34 | self.data = data 35 | 36 | def variable_list(self): 37 | 38 | """ 39 | Print the list of available variables 40 | 41 | """ 42 | print(self.data.data_vars) 43 | 44 | def animate(self, variable, fps=10, xmin=0, xmax=4, ymin=0, ymax=2, cmap='coolwarm', cmin=-1, cmax=1, output='animation', figsize=(12,5), save=True): 45 | 46 | """ 47 | Build an animation 48 | 49 | Parameters 50 | ---------- 51 | variable: str 52 | Variable to display 53 | 54 | fps: int, default=10 55 | Number of images per second 56 | 57 | xmin, xmax, ymin, ymax: 4* float, default= 0, 4, 0, 2 58 | Limits of the domain 59 | 60 | cmap: str, default='coolwarm' 61 | Colormap to use 62 | 63 | cmin, cmax: 2* float, default=-1, 1 64 | Limits of colorbar 65 | 66 | output: str, default='animation' 67 | Name of the output saved file 68 | 69 | figsize: tuple, default=(12,5) 70 | Size of the figure 71 | 72 | save: boolean, default=True, 73 | Is the animation has to be saved? 74 | 75 | Returns 76 | ------- 77 | Animation 78 | Saved it into a gif if save==True 79 | 80 | """ 81 | 82 | snapshots = self.data[variable] 83 | 84 | # First set up the figure, the axis, and the plot element we want to animate 85 | fig = plt.figure( figsize=figsize) 86 | a = snapshots[0] 87 | im = plt.imshow(a.T, cmap=cmap, extent=[xmin,xmax,ymin,ymax]) 88 | plt.xlabel('X') 89 | plt.ylabel('Y') 90 | plt.clim(cmin,cmax) #Set up here the limits of colorbar 91 | plt.colorbar(label=variable) 92 | 93 | def animate_func(i): 94 | if i % int(fps) == 0: 95 | print( '.', end ='' ) 96 | 97 | im.set_array(snapshots[i]) 98 | return [im] 99 | 100 | anim = animation.FuncAnimation( 101 | fig, 102 | animate_func, 103 | frames = len(self.data[variable]), 104 | interval = 1000 / int(fps), # in ms 105 | ) 106 | if save == True: 107 | anim.save(output+'.gif', writer='PillowWriter') 108 | 109 | print('Done!') 110 | 111 | 112 | def import_Wave(file): 113 | 114 | """ 115 | Build a Wave object 116 | 117 | Parameters 118 | ---------- 119 | file: str 120 | File to read 121 | 122 | Returns 123 | ------- 124 | Wave Object 125 | 126 | """ 127 | 128 | ds = xr.open_dataset(file) 129 | 130 | return Wave(ds) 131 | 132 | 133 | -------------------------------------------------------------------------------- /plot_wake_directly.py: -------------------------------------------------------------------------------- 1 | """It is possible to compute the ship wake pattern directly, thanks 2 | to a beautiful mathematical trick found in a Pierre Gilles de Gennes 3 | paper (French 1991 Nobel Prize). See the fourier.py module. """ 4 | 5 | # to activate it, copy-paste this script in your Ipython window, after 6 | # you've run the main wave2d script 7 | # 8 | # The wake tip starts at param.x0 9 | # 10 | # to have a good rendering -> param.x0 = 1.5 11 | # 12 | 13 | 14 | import plotting as pt 15 | fig = pt.Plotting(param) 16 | # epsilon controls the wake extension (larger epsilon->shorter wake) 17 | p = model.fspace.compute_balanced_wake(model.hphi0, param.U, epsilon=0.8) 18 | fig.init_figure(p) 19 | -------------------------------------------------------------------------------- /plotting.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from mpl_toolkits.axes_grid1 import make_axes_locatable 3 | import matplotlib 4 | import fourier 5 | font = {'size': 16} 6 | 7 | # matplotlib.use('TkAgg') 8 | matplotlib.rc('font', **font) 9 | 10 | 11 | # the backend 'TkAgg' has to be set before pyplot is imported 12 | import matplotlib.pyplot as plt 13 | plt.ion() 14 | 15 | 16 | class Plotting(object): 17 | def __init__(self, param): 18 | list_param = ['Lx', 'Ly', 'cax', 'figwidth', 19 | 'varplot', 'typewave', 'vectorscale', 'macuser'] 20 | param.copy(self, list_param) 21 | self.d = 8 # how many points are skept for quiver plot 22 | 23 | def init_figure(self, field2d, u=None, v=None): 24 | """create the figure 25 | this is where you adapt the figure to your needs the function 26 | should return the graphical objects to update 27 | """ 28 | # best youtube aspect ratio is 16:9 29 | #fig_size = np.array([1280, 720]) 30 | fig_size = np.array([self.figwidth, self.figwidth//16*9]) 31 | my_dpi = 100 32 | zoom_factor = 1 33 | 34 | self.title_string = '%s / variable = %s / time = %.2f' 35 | 36 | self.fig = plt.figure(figsize=fig_size/my_dpi, dpi=my_dpi) 37 | self.fig.clf() 38 | 39 | field_size = np.shape(field2d) 40 | zoom_factor = (0.8*fig_size[0])//field_size[1] 41 | # zoom_factor = (fig_size[0])//field_size[0] 42 | # print("each grid cell is (%i, %i) pixels" % (zoom_factor, zoom_factor)) 43 | rectangle = [0.1, 0.1, 44 | field_size[1]/fig_size[0]*zoom_factor, 45 | field_size[0]/fig_size[1]*zoom_factor] 46 | #ax = plt.axes(rectangle) 47 | ax = self.fig.add_subplot(1, 1, 1) 48 | self.ax = ax 49 | self.im = ax.imshow(field2d, cmap=plt.get_cmap('RdBu_r', lut=21), 50 | vmin=self.cax[0], vmax=self.cax[1], 51 | extent=[0, self.Lx, 0, self.Ly], 52 | origin='lower', interpolation='nearest') 53 | 54 | time = 0. 55 | self.ti = ax.set_title(self.title_string % ( 56 | self.typewave, self.varplot, time)) 57 | ax.set_xlabel('X') 58 | if self.typewave == 'internal': 59 | ax.set_ylabel('Z') 60 | else: 61 | ax.set_ylabel('Y') 62 | 63 | divider = make_axes_locatable(ax) 64 | cbax = divider.append_axes("right", size="3%", pad=0.1) 65 | 66 | if not(u is None) and not(v is None): 67 | ny, nx = np.shape(field2d) 68 | self.x, _ = fourier.set_x_and_k(nx, self.Lx) 69 | self.y, _ = fourier.set_x_and_k(ny, self.Ly) 70 | maxu = max(np.max(np.abs(u.ravel())), np.max(np.abs(v.ravel()))) 71 | self.quiv = ax.quiver(self.x[::self.d], self.y[::self.d], 72 | u[::self.d, ::self.d], v[::self.d, ::self.d], 73 | scale=maxu*10*self.vectorscale) 74 | 75 | # pos = [rectangle[0]+rectangle[2]+0.02, rectangle[1], 0.05, rectangle[3]] 76 | cb = self.fig.colorbar(self.im, cax=cbax) 77 | cb.formatter.set_powerlimits((-3, 3)) 78 | pos = np.array(cb.ax.get_position().bounds) 79 | pos[1], pos[3] = rectangle[1], rectangle[3] 80 | 81 | self.fig.tight_layout() 82 | self.fig.show() 83 | if self.macuser: 84 | plt.pause(1e-4) 85 | else: 86 | self.fig.canvas.draw() 87 | plt.pause(1e-4) 88 | 89 | def update(self, kt, time, field2d, u=None, v=None): 90 | """ update the figure during the loop 91 | 92 | read/compute the field before and update the imshow object 'im' 93 | also update the title object 'ti' with the time """ 94 | 95 | self.im.set_array(field2d) 96 | self.ti.set_text(self.title_string % ( 97 | self.typewave, self.varplot, time)) 98 | if not(u is None) and not(v is None): 99 | self.quiv.set_UVC(u[::self.d, ::self.d], v[::self.d, ::self.d]) 100 | maxu = max(np.max(np.abs(u.ravel())), np.max(np.abs(v.ravel()))) 101 | maxu = max(np.std(u.ravel()), np.std(v.ravel())) 102 | self.quiv.scale = maxu*10*self.vectorscale 103 | 104 | if self.macuser: 105 | plt.pause(1e-4) 106 | else: 107 | self.fig.canvas.draw() 108 | plt.pause(1e-4) 109 | 110 | 111 | def plotvar(param, field2d, varname, cax=None): 112 | param.varplot = varname 113 | if cax is None: 114 | maxi = np.max(np.abs(field2d.ravel())) 115 | param.cax = [-maxi, maxi] 116 | else: 117 | param.cax = cax 118 | 119 | fig = Plotting(param) 120 | fig.init_figure(field2d) 121 | fig.update(0, 0., field2d) 122 | return fig.ax 123 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | python =>3.10,<3.12 2 | netCDF4 3 | matplotlib 4 | numpy 5 | scipy 6 | ipython 7 | -------------------------------------------------------------------------------- /rossby.py: -------------------------------------------------------------------------------- 1 | """Rossby waves 2 | 3 | The deformation radius param.Rd is chosen such that the initial wave 4 | packet width is in the short wavelength regime 5 | 6 | \omega \sim -\frac{\beta k_x}{k_x^2 + k_y^2} 7 | 8 | in which waves are dispersive. 9 | 10 | TODO: 11 | 12 | 1) run the code as is and observe the banana shape pattern 13 | 14 | 2) set param.plotvector = 'velocity' and rerun. The velocity is along 15 | the isobares. 16 | 17 | 3) set param.plotvector = 'velocity' and param.ageostrophic = 18 | True. Observe the difference 19 | 20 | 4) set param.plotvector = 'energyflux' and param.ageostrophic = 21 | False. Observe that there is no energy flux! How's that possible?... 22 | 23 | 5) set param.plotvector = 'energyflux' and param.ageostrophic = 24 | True. Now you see the energy flux! and it is ... eastward because it 25 | is the short wave regime. 26 | 27 | """ 28 | import numpy as np 29 | import namelist 30 | import wave2d 31 | 32 | 33 | param = namelist.Namelist() 34 | param.typewave = 'rossby' 35 | param.generation = 'initial' 36 | param.waveform = 'gaussian' 37 | 38 | param.sigma = 0.05*param.Lx 39 | param.aspect_ratio = 1. 40 | 41 | param.varplot = 'p' 42 | 43 | param.nx, param.ny = 128*4, 128*2 44 | param.Lx, param.Ly = 4, 2 45 | param.x0, param.y0 = 2., 1.0 46 | 47 | param.g = 20. 48 | param.H = .01 49 | param.beta = 1. 50 | param.Rd = .1 51 | cg = 1.0 52 | param.beta = cg/param.Rd**2 53 | param.omega0 = 5. 54 | 55 | param.tend = 3. 56 | param.tplot = .05 57 | 58 | param.plotvector = 'None' # 'energyflux' , 'velocity' , 'None' 59 | 60 | # False -> geostrophic velocity 61 | # True -> ageostrophic velocity 62 | param.ageos = False 63 | 64 | param.vectorscale = 10. 65 | param.dt = 1e-2 66 | 67 | param.U = .6 68 | param.alphaU = 0*np.pi/180. 69 | 70 | param.cax = np.asarray([-1., 1.])*2e-1 71 | param.figwidth = 1080 72 | 73 | model = wave2d.Wave2d(param) 74 | model.set_fourier_space(param) 75 | model.set_wave_packet(param) 76 | model.run(param) 77 | -------------------------------------------------------------------------------- /screenshots/wake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pvthinker/wave2d/0871a0603ac90685aa3bb189aeb6b9ed3d6b94b2/screenshots/wake.png -------------------------------------------------------------------------------- /script_read_netcdf.py: -------------------------------------------------------------------------------- 1 | import xarray as xr 2 | 3 | import numpy as np 4 | 5 | import matplotlib 6 | import matplotlib.pyplot as plt 7 | 8 | import matplotlib.animation as animation 9 | 10 | file = 'history.nc' 11 | variable = 'p' 12 | xmin, xmax = 0, 4 13 | ymin, ymax = 0, 2 14 | 15 | ds = xr.open_dataset(file) 16 | 17 | # This part is a fast adaptation of the window shape 18 | ny, nx = ds[variable][0].shape 19 | if nx == ny: 20 | shape_window = (8,8) 21 | else: 22 | shape_window = (12,5) 23 | 24 | fps = 10 #Set up the number of image / second (animation speed) 25 | snapshots = ds[variable] 26 | 27 | # First set up the figure, the axis, and the plot element we want to animate 28 | fig = plt.figure( figsize=shape_window ) 29 | a = snapshots[0] 30 | im = plt.imshow(a.T, cmap='coolwarm', extent=[xmin,xmax,ymin,ymax]) 31 | plt.xlabel('X') 32 | plt.ylabel('Y') 33 | plt.clim(-1,1) #Set up here the limits of colorbar 34 | plt.colorbar(label=variable) 35 | 36 | def animate_func(i): 37 | if i % fps == 0: 38 | print( '.', end ='' ) 39 | 40 | im.set_array(snapshots[i]) 41 | return [im] 42 | 43 | anim = animation.FuncAnimation( 44 | fig, 45 | animate_func, 46 | frames = len(ds[variable]), 47 | interval = 1000 / fps, # in ms 48 | ) 49 | anim.save('animation.gif', writer='PillowWriter') 50 | 51 | print('Done!') -------------------------------------------------------------------------------- /shipwake.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import namelist 3 | import wave2d 4 | import pickle 5 | 6 | import matplotlib.pyplot as plt 7 | plt.ion() 8 | 9 | param = namelist.Namelist() 10 | param.typewave = 'gwshort' 11 | 12 | 13 | param.nx, param.ny = 128*8, 128*4 14 | param.Lx, param.Ly = 2, 1 15 | param.g = 1. 16 | param.H = .1 17 | param.sigma = 0.01*param.Lx # ship length 18 | param.tend = 2.5 19 | param.tplot = .1 20 | param.dt = 1e-2 21 | 22 | param.alphaU = 0*np.pi/180. 23 | 24 | param.cax = np.asarray([-1., 1.])*20 25 | param.figwidth = 1080 26 | param.plotvector = 'None'#'energyflux' 27 | param.vectorscale = 10. # larger 'vectorscale' makes the arrows shorter 28 | 29 | param.aspect_ratio = 4. # for the ship, between x and y lengths 30 | param.U = 0.3 31 | param.x0 = 0.5 32 | 33 | model = wave2d.Wave2d(param) 34 | model.run(param, anim=True) 35 | 36 | dt = param.dt 37 | en = model.energy 38 | time = np.arange(0, len(en))*dt 39 | drag = np.mean(np.diff(en)[-50:]/dt) / param.U 40 | print('Estimated drag: %.3g / U = %.2f' % (drag, param.U)) 41 | -------------------------------------------------------------------------------- /shipwake_arbitrary.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import namelist 3 | import wave2d 4 | import pickle 5 | 6 | import matplotlib.pyplot as plt 7 | plt.ion() 8 | 9 | param = namelist.Namelist() 10 | param.typewave = 'gwshort' 11 | 12 | 13 | param.nx, param.ny = 128*4, 128*2 14 | param.Lx, param.Ly = 2, 1 15 | param.g = 1. 16 | param.H = .1 17 | param.sigma = 0.005*param.Lx # ship length 18 | param.tend = 4*np.pi 19 | param.tplot = .1 20 | param.dt = 10e-3 21 | param.x0 = 1. 22 | 23 | param.alphaU = 30*np.pi/180. 24 | 25 | param.cax = np.asarray([-1., 1.]) 26 | param.figwidth = 1080 27 | 28 | param.aspect_ratio = 4. # for the ship, between x and y lengths 29 | param.U = 0.15 30 | param.r = 0.3 31 | 32 | param.motion = "circular_uniform" 33 | 34 | 35 | class Trajectory(object): 36 | def __init__(self, param): 37 | self.param = param 38 | self.dt = 1e-6 39 | self.omega = param.U/param.r 40 | 41 | def get_position(self, time): 42 | if self.param.motion == "circular_uniform": 43 | theta = self.omega*time 44 | x = self.param.r * np.cos(theta) 45 | y = self.param.r * np.sin(theta) 46 | elif self.param.motion == "rect_uniform": 47 | theta = self.param.alphaU 48 | x = self.param.U*np.cos(theta)*time 49 | y = self.param.U*np.sin(theta)*time 50 | elif self.param.motion == "wavy": 51 | theta = self.omega*time*2 52 | x = self.param.U*time 53 | y = self.param.U/10*np.sin(theta)*time 54 | else: 55 | raise ValueError(f"{param.motion} is not defined") 56 | # add you own motion here ! 57 | return (x, y) 58 | 59 | def get_velocity(self, time): 60 | x1, y1 = self.get_position(time+self.dt) 61 | x0, y0 = self.get_position(time-self.dt) 62 | vx = (x1-x0)/(2*self.dt) 63 | vy = (y1-y0)/(2*self.dt) 64 | return (vx, vy) 65 | 66 | 67 | model = wave2d.Wave2d(param) 68 | model.traj = Trajectory(param) 69 | model.run(param, anim=True) 70 | 71 | # dt = param.dt 72 | # en = model.energy 73 | # time = np.arange(0, len(en))*dt 74 | # drag = np.mean(np.diff(en)[-50:]/dt) / param.U 75 | # print('Estimated drag: %.3g / U = %.2f' % (drag, param.U)) 76 | -------------------------------------------------------------------------------- /starter.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import namelist 3 | import wave2d 4 | 5 | import matplotlib.pyplot as plt 6 | plt.ion() 7 | 8 | # to get the list of all parameters and their available values 9 | # use param.manall() in your IPython console or in your Jupyter notebook 10 | 11 | param = namelist.Namelist() 12 | param.typewave = 'gwlong' 13 | param.generation = 'initial' 14 | param.omega0 = 40. 15 | param.waveform = 'gaussian' 16 | 17 | param.sigma = 0.02*param.Lx 18 | param.aspect_ratio = 1. 19 | 20 | param.varplot = 'p' 21 | 22 | param.nx, param.ny = 128*4, 128*2 23 | param.Lx, param.Ly = 4, 2 24 | param.x0, param.y0 = 2., 1. 25 | 26 | param.g = 1. 27 | param.H = 1. 28 | param.beta = 500. 29 | param.Rd = 0.05 30 | cg = 1.0 31 | param.beta = cg/param.Rd**2 32 | param.BVF = 20. 33 | 34 | param.tend = 1.5 35 | param.tplot = .04 # smaller 'tplot' makes the animation smoother 36 | param.plotvector = 'None' # 'velocity' 37 | param.vectorscale = 20. # larger 'vectorscale' makes the arrows shorter 38 | param.dt = 0.02 39 | param.macuser = False # <- try True if the animation does not work 40 | 41 | param.U = .2 42 | param.alphaU = 0*np.pi/180. 43 | 44 | param.cax = np.asarray([-1., 1.])*.25 45 | param.figwidth = 1080 46 | 47 | model = wave2d.Wave2d(param) 48 | model.set_fourier_space(param) 49 | model.set_wave_packet(param) 50 | model.run(param) 51 | 52 | -------------------------------------------------------------------------------- /wave2d.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import numpy as np 3 | 4 | import plotting 5 | import fourier 6 | import wavepackets as wp 7 | import nc_tools as nct 8 | from netCDF4 import Dataset 9 | 10 | import matplotlib.pyplot as plt 11 | plt.ion() 12 | 13 | 14 | class Wave2d(object): 15 | def __init__(self, param): 16 | param.checkall() 17 | self.set_fourier_space(param) 18 | self.set_wave_packet(param) 19 | 20 | def set_fourier_space(self, param): 21 | fspace = fourier.Fourier(param) 22 | self.fspace = fspace 23 | 24 | def set_wave_packet(self, param, heading=None): 25 | if heading is None: 26 | alphaU = param.alphaU 27 | else: 28 | alphaU = heading 29 | sigma = param.sigma 30 | aspect_ratio = param.aspect_ratio 31 | x0, y0 = param.x0, param.y0 32 | xx, yy = self.fspace.xx, self.fspace.yy 33 | z = (xx-x0) + 1j*(yy-y0) 34 | z = z*np.exp(-1j*alphaU) 35 | 36 | if param.waveform == 'gaussian': 37 | d2 = np.real(z)**2 + (aspect_ratio*np.imag(z))**2 38 | phi0 = np.exp(-d2/(2*sigma**2)) 39 | 40 | elif param.waveform == 'triangle': 41 | phi0 = wp.triangle(np.real(z), aspect_ratio * 42 | np.imag(z), param.sigma) 43 | 44 | elif param.waveform == 'square': 45 | phi0 = wp.square(np.real(z), aspect_ratio*np.imag(z), param.sigma) 46 | 47 | elif param.waveform == 'packet': 48 | kxx = self.fspace.kxx 49 | kyy = self.fspace.kyy 50 | 51 | Lx = param.Lx 52 | k0x = self.fspace.kx[60] 53 | k0y = self.fspace.kx[30] 54 | sigma = 10. 55 | 56 | d2 = ((kxx-k0x)*k0x+(kyy-k0y)*k0y)**2 57 | d2 += 20*((kxx-k0x)*k0y-(kyy-k0y)*k0x)**2 58 | d2 /= (k0x**2+k0y**2) 59 | 60 | hphi = np.exp(-d2/(2*sigma**2)) * np.exp(1j*(kxx*x0+kyy*y0)) 61 | 62 | hphi *= np.sqrt(param.nx*param.ny) 63 | self.hphi0 = hphi 64 | 65 | phi0 = np.real(np.fft.ifft2(hphi)) 66 | 67 | self.phi0 = phi0 68 | if param.waveform != 'packet': 69 | self.hphi0 = np.fft.fft2(self.phi0) 70 | self.boat = self.hphi0 71 | 72 | def run(self, param, anim=True): 73 | 74 | if param.generation in ['wake', 'oscillator']: 75 | hphi = self.hphi0.copy()*0 76 | 77 | else: 78 | hphi = self.hphi0.copy() 79 | 80 | if anim: 81 | self.plot = plotting.Plotting(param) 82 | var = self.fspace.compute_all_variables(hphi) 83 | if param.plotvector == 'velocity': 84 | self.plot.init_figure(self.phi0, u=var['u'], v=var['v']) 85 | 86 | elif param.plotvector == 'energyflux': 87 | self.plot.init_figure(self.phi0, u=var['up'], v=var['vp']) 88 | 89 | else: 90 | self.plot.init_figure(self.phi0) 91 | 92 | tend = param.tend 93 | dt = param.dt 94 | nt = int(tend/dt) 95 | kxx, kyy = self.fspace.kxx, self.fspace.kyy 96 | omega = self.fspace.omega 97 | propagator = np.exp(-1j*omega*dt) 98 | 99 | sigma = param.sigma 100 | aspect_ratio = param.aspect_ratio 101 | x0, y0 = param.x0, param.y0 102 | xx, yy = self.fspace.xx, self.fspace.yy 103 | 104 | time = 0. 105 | kplot = np.ceil(param.tplot/dt) 106 | xb, yb = param.x0+param.Lx/2, param.y0+param.Ly/2 107 | xb, yb = 0, 0 # param.x0, param.y0 108 | energy = np.zeros((nt,)) 109 | if param.netcdf: 110 | attrs = {"model": "wave2d", 111 | "wave": param.typewave} 112 | 113 | sizes = {"y": param.ny, "x": param.nx} 114 | 115 | variables = [{"short": "time", 116 | "long": "time", 117 | "units": "s", 118 | "dims": ("time")}, 119 | {"short": "p", 120 | "long": "pressure anomaly", 121 | "units": "m^2 s^-2", 122 | "dims": ("time", "y", "x")}, 123 | {"short": "u", 124 | "long": "velocity x-component", 125 | "units": "m s^-1", 126 | "dims": ("time", "y", "x")}, 127 | {"short": "v", 128 | "long": "velocity y-component (or z-)", 129 | "units": "m s^-1", 130 | "dims": ("time", "y", "x")}, 131 | {"short": "up", 132 | "long": "up flux x-component", 133 | "units": "m^3 s^-3", 134 | "dims": ("time", "y", "x")}, 135 | {"short": "vp", 136 | "long": "vp flux y-component", 137 | "units": "m^3 s^-3", 138 | "dims": ("time", "y", "x")} 139 | ] 140 | 141 | fid = nct.NcTools(variables, sizes, attrs, 142 | ncfilename=param.filename) 143 | fid.createhisfile() 144 | ktio = 0 145 | 146 | for kt in range(nt): 147 | energy[kt] = 0.5*np.mean(np.abs(hphi.ravel())**2) 148 | hphi = hphi*propagator 149 | 150 | if param.generation == 'wake': 151 | 152 | if hasattr(self, "traj"): 153 | 154 | xb, yb = self.traj.get_position(time) 155 | vx, vy = self.traj.get_velocity(time) 156 | 157 | kalpha = vx*kxx+vy*kyy 158 | # recompute self.boat (complex Fourier amplitude) 159 | # to account for the new heading 160 | heading = np.angle(vx+1j*vy) 161 | self.set_wave_packet(param, heading) 162 | # shift the source at the boat location (xb,yb) 163 | shift = np.exp(-1j*(kxx*xb+kyy*yb)) 164 | # add the source term to hphi 165 | hphi -= 1j*dt*self.boat*kalpha*shift 166 | else: 167 | if kt == 0: 168 | kalpha = np.cos(param.alphaU)*kxx + \ 169 | np.sin(param.alphaU)*kyy 170 | hphi -= (1j*1e2*dt*self.boat*param.U*kalpha) * \ 171 | np.exp(-1j*(kxx*xb+kyy*yb)) 172 | xb += dt*param.U*np.cos(param.alphaU) 173 | yb += dt*param.U*np.sin(param.alphaU) 174 | 175 | elif param.generation == 'oscillator': 176 | hphi += (1e2*dt*self.boat)*np.exp(-1j*time*param.omega0) 177 | 178 | kt += 1 179 | time += dt 180 | 181 | if anim: 182 | if (kt % kplot == 0): 183 | var = self.fspace.compute_all_variables(hphi) 184 | z2d = var[param.varplot] 185 | self.var = var 186 | if param.plotvector == 'velocity': 187 | self.plot.update(kt, time, z2d, u=var['u'], v=var['v']) 188 | elif param.plotvector == 'energyflux': 189 | self.plot.update( 190 | kt, time, z2d, u=var['up'], v=var['vp']) 191 | else: 192 | self.plot.update(kt, time, z2d) 193 | 194 | if param.netcdf: 195 | with Dataset(param.filename, "r+") as nc: 196 | nc.variables["time"][ktio] = time 197 | nc.variables["p"][ktio, :, :] = z2d 198 | for v in ["u", "v", "up", "vp"]: 199 | nc.variables[v][ktio, :, :] = var[v] 200 | 201 | ktio += 1 202 | else: 203 | print('\rkt=%i / %i' % (kt, nt), end='') 204 | 205 | var = self.fspace.compute_all_variables(hphi) 206 | self.energy = energy 207 | self.var = var 208 | -------------------------------------------------------------------------------- /wavepackets.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | def square(xr, yr, sigma): 4 | phi = np.zeros_like(xr) 5 | s2 = sigma/2. 6 | phi[(xr >= -s2) & (xr <= s2) & (yr >= -s2) & (yr <= s2)] = 1. 7 | return phi 8 | 9 | def triangle(xr, yr, sigma): 10 | j = np.exp(1j*2*np.pi/3) 11 | z = xr + 1j*yr 12 | z = (z/sigma) 13 | 14 | p1x = np.real(j**0) 15 | p1y = np.imag(j**0) 16 | p2x = np.real(j**1) 17 | p2y = np.imag(j**1) 18 | p0x = np.real(j**2) 19 | p0y = np.imag(j**2) 20 | px = np.real(z) 21 | py = np.imag(z) 22 | 23 | area = 0.5*(-p1y*p2x + p0y*(-p1x + p2x) + p0x*(p1y - p2y) + p1x*p2y) 24 | s = 1/(2*area)*(p0y*p2x - p0x*p2y + (p2y - p0y)*px + (p0x - p2x)*py) 25 | t = 1/(2*area)*(p0x*p1y - p0y*p1x + (p0y - p1y)*px + (p1x - p0x)*py) 26 | 27 | phi = np.zeros_like(xr) 28 | phi[(t > 0) & (s > 0) & (1-t-s > 0)] = 1. 29 | 30 | return phi 31 | --------------------------------------------------------------------------------