├── koreviz ├── __init__.py ├── plotlib.py ├── writeVts.py ├── libkoreviz.py └── kmode.py ├── tests ├── jones2000 │ ├── reference.jones │ └── find_Rac.py ├── dormy2004 │ ├── reference.dormy04 │ └── find_Rac.py ├── spinover │ └── reference.eig ├── test_spinover.py ├── koretest.py └── test_convection_bouss.py ├── docs ├── assets │ ├── test1.png │ ├── test2.png │ ├── kore_logo.png │ ├── new_logo.png │ └── kore_logo_white.png ├── javascripts │ └── mathjax.js ├── index.md ├── page2.md └── page3.md ├── tools ├── dosubs.sh ├── install-shtns.sh ├── animated_gif.py ├── read_operators.py ├── kore2sprouts.py ├── m2petsc.py ├── dodirs.sh ├── reap.sh ├── ramp.sh ├── ell_spectrum.py ├── shear_layer_profile.py ├── subramp.sh ├── getresults.py ├── plot_rad_con_tor_profile.py ├── plot_field.py ├── get_data.py └── plot_poltor.py ├── runKore.sh ├── .github └── workflows │ ├── ci.yml │ └── main.yml ├── bin ├── targets.py ├── kparser.py ├── compute_profiles.py ├── autocompute.py ├── find_Rac_pbs.py ├── bc_variables.py ├── mesa_profiles.py ├── radial_profiles.py ├── solve.py └── parameters.py ├── mkdocs.yml ├── .gitignore └── README.md /koreviz/__init__.py: -------------------------------------------------------------------------------- 1 | from .kmode import * 2 | from .writeVts import * -------------------------------------------------------------------------------- /tests/jones2000/reference.jones: -------------------------------------------------------------------------------- 1 | 6.325e-05 0.00 4.66986e+06 9 -1.93444e-02 2 | -------------------------------------------------------------------------------- /tests/dormy2004/reference.dormy04: -------------------------------------------------------------------------------- 1 | 2.000e-05 0.35 1.65404e+06 9 -1.10162e-02 2 | -------------------------------------------------------------------------------- /tests/spinover/reference.eig: -------------------------------------------------------------------------------- 1 | -1.019947097016500742e-01 1.005733690103750355e+00 2 | -------------------------------------------------------------------------------- /docs/assets/test1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/repepo/kore/HEAD/docs/assets/test1.png -------------------------------------------------------------------------------- /docs/assets/test2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/repepo/kore/HEAD/docs/assets/test2.png -------------------------------------------------------------------------------- /docs/assets/kore_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/repepo/kore/HEAD/docs/assets/kore_logo.png -------------------------------------------------------------------------------- /docs/assets/new_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/repepo/kore/HEAD/docs/assets/new_logo.png -------------------------------------------------------------------------------- /docs/assets/kore_logo_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/repepo/kore/HEAD/docs/assets/kore_logo_white.png -------------------------------------------------------------------------------- /docs/javascripts/mathjax.js: -------------------------------------------------------------------------------- 1 | window.MathJax = { 2 | tex: { 3 | inlineMath: [["\\(", "\\)"]], 4 | displayMath: [["\\[", "\\]"]], 5 | processEscapes: true, 6 | processEnvironments: true 7 | }, 8 | options: { 9 | ignoreHtmlClass: ".*|", 10 | processHtmlClass: "arithmatex" 11 | } 12 | }; 13 | 14 | document$.subscribe(() => { 15 | MathJax.typesetPromise() 16 | }) 17 | -------------------------------------------------------------------------------- /tools/dosubs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Submits a series of jobs to a PBS-managed cluster 4 | # 5 | # use exactly the same arguments as used when 6 | # using the dodirs.sh script 7 | 8 | 9 | pref=$1 10 | var=$2 11 | exp=$3 12 | 13 | for k in $(seq $4 $5 $6) 14 | do 15 | 16 | folder=$pref$k 17 | 18 | cd ~/data/$folder 19 | 20 | sleep 0.2 21 | qsub tools/subramp.sh 22 | 23 | done 24 | -------------------------------------------------------------------------------- /runKore.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ncpus=4 4 | 5 | opts='-st_type sinvert -eps_error_relative ::ascii_info_detail' 6 | #opts='-ksp_type preonly -pc_type lu' 7 | 8 | if [ "$1" == "purge" ]; then 9 | echo "Purging old matrices..." 10 | rm *.mtx *.npz 11 | fi 12 | 13 | ./bin/submatrices.py $ncpus 14 | mpiexec -n $ncpus ./bin/assemble.py 15 | mpiexec -n $ncpus ./bin/solve.py $opts 16 | #./postprocess.py 17 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: docs 2 | on: 3 | push: 4 | branches: 5 | - main 6 | permissions: 7 | contents: write 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: actions/setup-python@v4 14 | with: 15 | python-version: 3.x 16 | - uses: actions/cache@v4 17 | with: 18 | key: ${{ github.ref }} 19 | path: .cache 20 | - run: pip install mkdocs-material 21 | - run: mkdocs gh-deploy --force 22 | -------------------------------------------------------------------------------- /tools/install-shtns.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ver="3.5.2" 4 | 5 | wget https://bitbucket.org/nschaeff/shtns/downloads/shtns-$ver.tar.gz 6 | tar -xvf shtns-$ver.tar.gz 7 | rm shtns-$ver.tar.gz 8 | 9 | if [ -d "shtns-$ver" ] 10 | then 11 | mv shtns-$ver shtns 12 | fi 13 | 14 | cd shtns 15 | 16 | opts="--prefix=$HOME/.local --enable-ishioka --enable-openmp --enable-python" 17 | 18 | if [[ -n $MKLROOT ]] 19 | then 20 | echo "MKL found, installing with MKL" 21 | opts="$opts --enable-mkl" 22 | else 23 | echo "MKL not found, will try to install with FFTW" 24 | fi 25 | 26 | ./configure $opts 27 | 28 | if [ `echo $CC` ] 29 | then 30 | sed -i "s/shtcc=gcc/shtcc=${CC}/" Makefile 31 | fi 32 | 33 | make -j 34 | make install -j 35 | 36 | # Python installation will fail because of sudo, install as --user 37 | 38 | python3 setup.py install --user 39 | -------------------------------------------------------------------------------- /tools/animated_gif.py: -------------------------------------------------------------------------------- 1 | import imageio 2 | from pygifsicle import optimize 3 | 4 | from koreviz import kmode as k 5 | # u = k.kmode('u', 0, nr=500, nphi=500, phase=0) 6 | 7 | n = 48 8 | angles = linspace(0,360,n+1)[:-1] 9 | 10 | #limits = [u.ur.min(), u.ur.max()] 11 | limits = [-0.0095,0.0095] 12 | 13 | filenames = [] 14 | for j,angle in enumerate(angles): 15 | 16 | u = k.kmode('u', solnum=0, ntheta = 1440, phase=angle*np.pi/180) 17 | u.merid(comp='phi',azim=0,colbar=False,limits=limits) 18 | filename = f'{j}.png' 19 | savefig(filename,dpi=120) 20 | filenames.append(filename) 21 | close('all') 22 | 23 | # build gif 24 | with imageio.get_writer('imode.gif', mode='I') as writer: 25 | for filename in filenames: 26 | image = imageio.imread(filename) 27 | writer.append_data(image) 28 | 29 | 30 | optimize('imode.gif') 31 | -------------------------------------------------------------------------------- /bin/targets.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | # some useful targets for the solver 4 | 5 | def som(alpha,E): 6 | ''' 7 | spin-over freq and damping according to Zhang (2004) 8 | ''' 9 | eps2 = 2*alpha - alpha**2 10 | reG = -2.62047 - 0.42634 * eps2 11 | imG = 0.25846 + 0.76633 * eps2 12 | sigma = 1./(2.-eps2) #inviscid freq 13 | G = reG + 1.j*imG 14 | return 1j*( 2*sigma - 1j*G * E**0.5 ) 15 | 16 | 17 | def wattr(n,Ek): 18 | ''' 19 | Useful to set targets when reproducing Figs 3 and 4 of Rieutord & Valdettaro, JFM (2018) 20 | See also equation (3.2) in that paper 21 | ''' 22 | w0 = 0.782413 23 | tau1 = 0.485 24 | phi1 = -np.pi/3 25 | tau2 = 1.82 26 | phi2 = -np.pi/4 27 | out0 = 1j*w0 28 | out1 = -2*tau1*( np.cos(phi1)+1j*np.sin(phi1) )*(Ek**(1/3)) 29 | out2 = -(n+0.5)*np.sqrt(2)*tau2*( np.cos(phi2)+1j*np.sin(phi2) )*(Ek**(1/2)) 30 | return out0+out1+out2 31 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: User Manual 2 | 3 | theme: 4 | name: material 5 | logo: assets/test1.png 6 | language: en 7 | features: 8 | - navigation.tracking 9 | - navigation.tabs 10 | - navigation.tabs.sticky 11 | - navigation.sections 12 | - navigation.top 13 | - content.code.copy 14 | 15 | palette: 16 | 17 | # Palette toggle for light mode 18 | - scheme: default 19 | toggle: 20 | icon: material/brightness-7 21 | name: Switch to dark mode 22 | 23 | # Palette toggle for dark mode 24 | - scheme: slate 25 | toggle: 26 | icon: material/brightness-4 27 | name: Switch to light mode 28 | 29 | markdown_extensions: 30 | - pymdownx.arithmatex: 31 | generic: true 32 | - pymdownx.highlight: 33 | anchor_linenums: true 34 | line_spans: __span 35 | pygments_lang_class: true 36 | - pymdownx.inlinehilite 37 | - pymdownx.snippets 38 | - pymdownx.superfences 39 | 40 | 41 | 42 | extra_javascript: 43 | - javascripts/mathjax.js 44 | - https://polyfill.io/v3/polyfill.min.js?features=es6 45 | - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js 46 | -------------------------------------------------------------------------------- /tests/test_spinover.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import numpy as np 6 | from koretest import KoreTest 7 | 8 | class TestSpinover(KoreTest): 9 | 10 | def test_spinover(self): 11 | """ Test the spinover mode at Ek=1e-3 12 | """ 13 | self.dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'spinover') 14 | os.system('cp -r %s/bin %s/' % (self.kore_dir,self.dir)) 15 | 16 | os.chdir(self.dir) 17 | os.system('cp -r params.spinover ./bin/parameters.py') 18 | os.system("./bin/submatrices.py %d > /dev/null" % self.ncpus) 19 | os.system("mpiexec -n %d ./bin/assemble.py > /dev/null" % self.ncpus) 20 | os.system("mpiexec -n %d ./bin/solve.py %s" % (self.ncpus, self.solve_opts)) 21 | datRef = np.loadtxt('reference.eig') 22 | datTmp = np.loadtxt('eigenvalues0.dat') 23 | idx = np.argmax(datTmp[:,0]) 24 | datTmp = datTmp[idx,:] 25 | os.chdir(self.startDir) 26 | self.cleanDir(self.startDir, self.dir) 27 | 28 | return np.testing.assert_allclose(datRef, datTmp, rtol=self.precision, 29 | atol=1e-20) -------------------------------------------------------------------------------- /tools/read_operators.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import re 3 | import numpy as np 4 | import sys 5 | 6 | ''' 7 | Returns a list of all the operators with names matching the convention used in Kore in a file given as argument. 8 | The second list returned gives the parity guessed for each operator. 9 | Throws a warning message when more than one parity present. 10 | ''' 11 | 12 | regex = r"(?:(?:r|q)[0-9]_)?(?:h[0-9]_)?(?:(?:\w{3}[0-9]_)*)?(?:D[0-9]_)(?:[uvfghi])" 13 | r = re.compile(regex) 14 | 15 | filename = sys.argv[1] 16 | f = open(filename,'r') 17 | lines = f.readlines() 18 | 19 | l = [] 20 | p = [] 21 | 22 | for iline, line in enumerate(lines): 23 | 24 | matches = r.finditer(line.strip()) 25 | sorted_matches = sorted(matches, key=lambda m: len(m.group(0)), reverse=True) 26 | 27 | for m in sorted_matches: 28 | l.append(m.group(0)[:-2]) # append to list and drop section name from and of str 29 | 30 | l = np.unique(np.array(l)).tolist() 31 | print(l) 32 | 33 | for str in l: 34 | if sum([int(i) for i in list(map(lambda x: x.replace('dr', '-1'), re.findall('(?:dr|[0-9])',str)))])%2: 35 | p.append("odd") 36 | else: 37 | p.append("even") 38 | 39 | print(p) 40 | 41 | if len(np.unique(np.array(p)).tolist())>1: 42 | print("> Caution: check operators parity") -------------------------------------------------------------------------------- /tools/kore2sprouts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import numpy as np 4 | import sys 5 | import scipy.sparse as ss 6 | import scipy.io as sio 7 | 8 | sys.path.insert(1,'bin/') 9 | import utils as ut 10 | import parameters as par 11 | 12 | 13 | 14 | 15 | def main(Anpz): 16 | ''' 17 | reshuffle kore matrices to match sprouts ordering 18 | ''' 19 | 20 | A = ut.load_csr(str(Anpz)) 21 | 22 | N = par.N 23 | 24 | neq = 3 25 | 26 | ll = int(A.shape[0]/(neq*N)) 27 | 28 | gb = np.array([4,2,2]) # the basis order for each section 29 | 30 | N1 = N-gb # number of rows without bc's for each section 31 | 32 | nbc = ll*np.sum(gb) # total number of bc rows 33 | 34 | Aout = ss.dok_matrix(A.shape, dtype=complex) 35 | 36 | 37 | for j in range(neq): #loop over the four sections 38 | 39 | for k in range(ll): 40 | 41 | row0 = gb[j] + k*N + j*ll*N #initial row for kore 42 | 43 | row1 = nbc + k*N1[j] + sum(ll*N1[:j]) #initial row for sprouts 44 | 45 | Aout[ row1 : row1 + N1[j], : ] = A[ row0 : row0 + N1[j], : ] 46 | 47 | sio.mmwrite('out.mtx', Aout) 48 | 49 | 50 | return 0 51 | 52 | 53 | if __name__ == '__main__': 54 | import sys 55 | sys.exit(main(sys.argv[1])) 56 | -------------------------------------------------------------------------------- /tests/koretest.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import shutil 6 | from glob import glob 7 | 8 | class KoreTest: 9 | 10 | precision = 1e-8 11 | ncpus = 2 12 | solve_opts = "-st_type sinvert -eps_error_relative ::ascii_info_detail" 13 | startDir = os.getcwd() 14 | kore_dir = os.path.join(os.path.dirname( 15 | os.path.abspath(__file__)),'..' 16 | ) 17 | 18 | print('\n') 19 | print(' Running test suite using %d MPI ranks ' % ncpus) 20 | print('-----------------------------------------') 21 | print("\n") 22 | print(" _ __ ") 23 | print("| | / / ") 24 | print("| |/ / ___ _ __ ___ ") 25 | print(r"| \ / _ \| '__/ _ \\") 26 | print(r"| |\ \ (_) | | | __/") 27 | print(r"\_| \_/\___/|_| \___|") 28 | print("\n") 29 | 30 | def cleanDir(self,startDir,dir): 31 | """ Clean up the test directory 32 | """ 33 | os.chdir(dir) 34 | for f in glob("%s/*.mtx" % dir): 35 | os.remove(f) 36 | for f in glob("%s/*.npz" % dir): 37 | os.remove(f) 38 | for f in glob("%s/*.field" % dir): 39 | os.remove(f) 40 | for f in glob("%s/*.dat" % dir): 41 | os.remove(f) 42 | shutil.rmtree('./bin') 43 | os.chdir(startDir) 44 | -------------------------------------------------------------------------------- /tools/m2petsc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys, petsc4py 4 | petsc4py.init(sys.argv) 5 | from petsc4py import PETSc 6 | import scipy.io as sio 7 | import scipy.sparse as ss 8 | 9 | import utils as u 10 | 11 | 12 | def main(): 13 | 14 | 15 | Print = PETSc.Sys.Print 16 | rank = PETSc.COMM_WORLD.getRank() 17 | size = PETSc.COMM_WORLD.getSize() 18 | opts = PETSc.Options() 19 | 20 | input_matrix = sys.argv[1] 21 | output_matrix = sys.argv[2] 22 | 23 | # read and prepare matrix A 24 | #A = sio.mmread('A_ns_ch.mtx') 25 | #A = sio.mmread('A1.mtx') 26 | #A = ss.csr_matrix(A) 27 | A = u.load_csr(input_matrix) 28 | nb_l,nb_c = A.shape 29 | 30 | 31 | 32 | MA = PETSc.Mat() 33 | MA.create(PETSc.COMM_WORLD) 34 | MA.setSizes([nb_l,nb_l]) 35 | MA.setType('mpiaij') 36 | MA.setFromOptions() 37 | MA.setUp() 38 | 39 | Istart,Iend = MA.getOwnershipRange() 40 | indptrA = A[Istart:Iend,:].indptr 41 | indicesA = A[Istart:Iend,:].indices 42 | dataA = A[Istart:Iend,:].data 43 | del A 44 | 45 | MA.setPreallocationCSR(csr=(indptrA,indicesA)) 46 | MA.setValuesCSR(indptrA,indicesA,dataA) 47 | MA.assemblyBegin() 48 | MA.assemblyEnd() 49 | del indptrA,indicesA,dataA 50 | print('Done reading and preparing input matrix') 51 | 52 | 53 | 54 | viewer = PETSc.Viewer().createBinary(output_matrix, 'w',PETSc.COMM_WORLD) 55 | viewer(MA) 56 | 57 | MA.destroy() 58 | viewer.destroy() 59 | 60 | 61 | PETSc.COMM_WORLD.Barrier() 62 | print('Done writing binary matrix') 63 | 64 | if __name__ == '__main__': 65 | main() 66 | -------------------------------------------------------------------------------- /tests/test_convection_bouss.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import numpy as np 6 | from koretest import KoreTest 7 | 8 | class TestConvectionBouss(KoreTest): 9 | 10 | def test_jones(self): 11 | """ Test the onset of convection from Jones et al. 2000 12 | """ 13 | self.dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'jones2000') 14 | os.system('cp -r %s/bin %s/' % (self.kore_dir,self.dir)) 15 | 16 | os.chdir(self.dir) 17 | os.system('cp params.jones ./bin/parameters.py') 18 | os.system('./find_Rac.py') 19 | datRef = np.loadtxt('reference.jones') 20 | datTmp = np.loadtxt('critical_params.dat') 21 | os.chdir(self.startDir) 22 | self.cleanDir(self.startDir, self.dir) 23 | 24 | return np.testing.assert_allclose(datRef, datTmp, rtol=self.precision, 25 | atol=1e-20) 26 | 27 | def test_dormy(self): 28 | """ Test the onset of convection from Dormy et al. 2004 29 | """ 30 | self.dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dormy2004') 31 | os.system('cp -r %s/bin %s/' % (self.kore_dir,self.dir)) 32 | 33 | os.chdir(self.dir) 34 | os.system('cp params.dormy04 ./bin/parameters.py') 35 | os.system('./find_Rac.py') 36 | datRef = np.loadtxt('reference.dormy04') 37 | datTmp = np.loadtxt('critical_params.dat') 38 | os.chdir(self.startDir) 39 | self.cleanDir(self.startDir, self.dir) 40 | 41 | return np.testing.assert_allclose(datRef, datTmp, rtol=self.precision, 42 | atol=1e-20) -------------------------------------------------------------------------------- /bin/kparser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import numpy as np 4 | import re 5 | 6 | # regex = r"(?Pr[1-9]?)?(?Ph[1-9]?)?(?P\w{3}[1-9]?)?(?PD[1-9]|I)(?P
[uvfghi])" 7 | regex = r"(?P(?:r|q)[1-9]?)?(?Ph[1-9]?)?(?P\w{3}[1-9]?)?(?PD[1-9]|I)(?P
[uvfghi])" 8 | p = re.compile(regex) 9 | 10 | f = open('operators.py','r') 11 | lines = f.readlines() 12 | # lines = [lines[210]] 13 | lines_new = [] 14 | 15 | for iline, line in enumerate(lines): 16 | 17 | matches = p.finditer(line.strip()) 18 | sorted_matches = sorted(matches, key=lambda m: len(m.group(0)), reverse=True) 19 | 20 | for m in sorted_matches: 21 | old_op = m.group(0) 22 | mdict = m.groupdict() 23 | new_op = '' 24 | if mdict['rpow']: 25 | if len(mdict['rpow']) == 1: 26 | new_op += mdict['rpow'] + '1_' 27 | else: 28 | new_op += mdict['rpow'] + '_' 29 | else: 30 | new_op += 'r0_' 31 | 32 | if mdict['hpow']: 33 | if len(mdict['hpow']) == 1: 34 | new_op += 'h0_' 35 | else: 36 | new_op += mdict['hpow'] + '_' 37 | 38 | if mdict['prof']: 39 | if len(mdict['prof']) == 3: 40 | new_op += mdict['prof'] + '0_' 41 | else: 42 | new_op += mdict['prof'] + '_' 43 | 44 | if mdict['deriv']: 45 | if mdict['deriv'] == 'I': 46 | new_op += 'D0_' 47 | else: 48 | new_op += mdict['deriv'] + '_' 49 | 50 | new_op += mdict['section'] 51 | 52 | line = line.replace(old_op,new_op) 53 | 54 | lines_new.append(line) 55 | 56 | g = open('operators.py','w') 57 | g.writelines(lines_new) 58 | g.close() 59 | f.close() -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: build 4 | 5 | # Controls when the workflow will run 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the master branch 8 | push: 9 | branches: [ main ] 10 | pull_request: 11 | branches: [ main ] 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | # This workflow contains a single job called "build" 19 | build: 20 | # The type of runner that the job will run on 21 | runs-on: ubuntu-latest 22 | 23 | # Steps represent a sequence of tasks that will be executed as part of the job 24 | steps: 25 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 26 | - uses: actions/checkout@v3 27 | 28 | # Runs a single command using the runners shell 29 | - name: Test shell 30 | run: echo "Kore test with shell type $SHELL" 31 | 32 | # Runs a set of commands using the runners shell 33 | - name: Install pre-requisites 34 | run: | 35 | sudo apt-get update 36 | sudo apt-get -y install python3-scipy python3-mpi4py 37 | sudo apt-get -y install libpetsc-complex-dev libslepc-complex-dev 38 | sudo apt-get -y install python3-petsc4py-complex python3-slepc4py-complex 39 | sudo apt-get -y install python3-pytest 40 | 41 | - name: Run test 42 | run: | 43 | ulimit -s unlimited 44 | cd ${{github.workspace}}/tests 45 | export PETSC_DIR=/usr/lib/petscdir/petsc3.19/x86_64-linux-gnu-complex 46 | export SLEPC_DIR=/usr/lib/slepcdir/slepc3.19/x86_64-linux-gnu-complex 47 | pytest . -v -s -------------------------------------------------------------------------------- /tools/dodirs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Script to generate run directories under ~/data/ 4 | # (modify as neccessary below) 5 | # useful to submit jobs to a PBS managed cluster 6 | # 7 | # Use as 8 | # 9 | # ./dodirs.sh somename var d startvalue step endvalue 10 | # 11 | # It will generate a series of directories with names 12 | # beginning with 'somename' and ending with a numerical string 13 | # corresponding to the value assigned to the variable 'var' 14 | # in the parameters.py file. 15 | # 16 | # Example: 17 | # 18 | # ./dodirs.sh run_Ek_ Ek e -5 -0.1 -6 19 | # 20 | # will generate directories named run_Ek_-5.0, run_Ek_-5.1, ... 21 | # and so on up to run_Ek_-6.0. In each directory the file parameters.py 22 | # will have the appropriate value assigned to the parameter Ek, the Ekman number, 23 | # ranging from Ek =10**-5.0 to Ek =10**-6.0 across all the directories. 24 | # 25 | # If the argument 'e' is changed to 'd' then 'var' will have values 26 | # that change linearly instead of as powers of ten. For example: 27 | # 28 | # ./dodirs.sh run_ricb_ ricb d 0.35 0.01 0.5 29 | # 30 | # will generate directories named run_ricb_0.35, run_ricb_0.36, ... 31 | # up to run_ricb_0.50. The variable 'ricb', the inner core radius, 32 | # will have values ranging from 0.35 to 0.50 in each parameters.py 33 | # file across all the directories generated. 34 | 35 | 36 | pref=$1 37 | var=$2 38 | exp=$3 39 | 40 | for k in $(seq $4 $5 $6) 41 | do 42 | 43 | folder=$pref$k 44 | if [ $exp = 'e' ]; then 45 | value='10**'$k # powers of ten 46 | else 47 | value=$k # linear 48 | fi 49 | echo $folder $var=$value 50 | 51 | mkdir ~/data/$folder 52 | 53 | cd ~/data/$folder 54 | 55 | cp -r ~/kore/* . # copies the source files 56 | #cp ~/kore/underflow.py . 57 | 58 | # modify variables 59 | sed -i 's,^\('$var'[ ]*=\).*,\1'$value',' bin/parameters.py 60 | 61 | sed -i 's,^\(#PBS -N \).*,\1'$folder',' tools/subramp.sh 62 | 63 | sed -i 's,^\(dir=\).*,\1'$folder',' tools/subramp.sh 64 | 65 | done 66 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | 4 | KOR-ee, from the greek Κόρη, the queen of the underworld, daughter of Zeus and Demeter. Kore is a numerical tool to study the core flow within rapidly rotating planets or other rotating fluids contained within near-spherical boundaries. The current version solves the linear Navier-Stokes and induction equations for a viscous, incompressible and conductive fluid with an externally imposed magnetic field, and enclosed within a rotating spherical shell. 5 | 6 | Kore assumes all dynamical variables to oscillate in a harmonic fashion. The oscillation frequency can be imposed externally, as is the case when doing forced motion studies (e.g. tidal forcing), or it can be obtained as part of the solution to an eigenvalue problem. In Kore's current implementation, the eigenmodes are the inertial modes of the rotating fluid. Inertial modes are the global modes of a rotating flow in which the Coriolis force participates prominently in the restoring force balance. 7 | 8 | Kore's distinctive feature is the use of a very efficient spectral method employing Gegenbauer (also known as ultraspherical) polynomials as a basis in the radial direction. This approach leads to sparse matrices representing the differential equations, as opposed to dense matrices, as in traditional Chebyshev colocation methods. Sparse matrices have smaller memory-footprint and are more suitable for systematic core flow studies at extremely low viscosities (or small Ekman numbers). 9 | 10 | Kore is free for everyone to use, with no restrictions. Too often in the scientific literature the numerical methods used are presented with enough detail to guarantee reproducibility, but only in principle. Without access to the actual implementation of those methods, which would require a significant amount of work and time to develop, readers are left effectively without the possibility to reproduce or verify the results presented. This leads to very slow scientific progress. We share our code to avoid this. 11 | 12 | If this code is useful for your research, we invite you to cite the relevant papers (coming soon) and hope that you can also contribute to the project. 13 | -------------------------------------------------------------------------------- /tools/reap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Script to collect results from a series of run directories 4 | # 5 | # Use as: 6 | # ./reap.sh somename 7 | # 8 | # where 'somename' is the prefix used when creating the 9 | # directories with dodirs.sh. It will produce the files: 10 | # 11 | # somename.par, collecting all parameters, 12 | # somename.flo, with flow data, (if hydro = 1) 13 | # somename.eig, with the eigenvalues, (if forcing = 0) 14 | # somename.mag, with magnetic field data (if magnetic = 1) 15 | # somename.thm, with thermal data (if thermal = 1) 16 | # 17 | # Use the script getresults.py to further 18 | # process the results. 19 | 20 | 21 | #cd ~/data/ 22 | p=".par" 23 | f=".flo" 24 | m=".mag" 25 | e=".eig" 26 | s=".sla" 27 | t=".thm" 28 | 29 | for d in $(ls -1d $1*) 30 | do 31 | 32 | if [ -f $d/flow.dat ] 33 | then 34 | lines=`grep -c ^ $d/flow.dat` 35 | head -n $lines $d/flow.dat >> $1$f 36 | #cat $d/flow.dat >> $1$f 37 | fi 38 | 39 | if [ -f $d/eigenvalues.dat ] 40 | then 41 | head -n $lines $d/eigenvalues.dat >> $1$e 42 | fi 43 | 44 | if [ -f $d/params.dat ] 45 | then 46 | head -n $lines $d/params.dat >> $1$p 47 | fi 48 | 49 | if [ -f $d/magnetic.dat ] 50 | then 51 | head -n $lines $d/magnetic.dat >> $1$m 52 | fi 53 | 54 | if [ -f $d/thermal.dat ] 55 | then 56 | head -n $lines $d/thermal.dat >> $1$t 57 | fi 58 | 59 | if [ -f $d/slayer.dat ] 60 | then 61 | head -n $lines $d/slayer.dat >> $1$s 62 | fi 63 | 64 | if [ -f $d/lions.out ] 65 | then 66 | head -n $lines $d/lions.out >> $1$f 67 | fi 68 | 69 | if [ -f $d/params.out ] 70 | then 71 | head -n $lines $d/params.out >> $1$p 72 | fi 73 | 74 | done 75 | 76 | 77 | if [ -f $1$p ] 78 | then 79 | echo "Parameters written to $1$p" 80 | fi 81 | 82 | if [ -f $1$f ] 83 | then 84 | echo "Flow velocity data written to $1$f" 85 | fi 86 | 87 | if [ -f $1$m ] 88 | then 89 | echo "Magnetic field data written to $1$m" 90 | fi 91 | 92 | if [ -f $1$e ] 93 | then 94 | echo "Eigenvalues written to $1$e" 95 | fi 96 | 97 | if [ -f $1$t ] 98 | then 99 | echo "Thermal data written to $1$t" 100 | fi 101 | 102 | if [ -f $1$s ] 103 | then 104 | echo "Shear layer data written to $1$s" 105 | fi 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /tools/ramp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | ncpus=24 5 | 6 | 7 | #./bin/submatrices.py $ncpus >> out00 8 | #mpiexec -n $ncpus ./bin/assemble.py >> out0 9 | 10 | 11 | # For forced problems use: 12 | 13 | #export opts='-ksp_type preonly -pc_type lu' 14 | 15 | #export opts='-ksp_type gmres -pc_type lu -pc_factor_mat_solver_type mumps -ksp_monitor_true_residual -ksp_monitor -ksp_converged_reason' 16 | 17 | #export opts='-ksp_type preonly -pc_type lu -pc_factor_mat_solver_type superlu_dist -ksp_monitor -ksp_converged_reason' 18 | 19 | #export opts='-ksp_type preonly -pc_type lu -pc_factor_mat_solver_type superlu_dist -ksp_monitor -ksp_converged_reason -mat_superlu_dist_iterrefine 1 -mat_superlu_dist_colperm PARMETIS -mat_superlu_dist_parsymbfact 1' 20 | 21 | 22 | # For eigenvalue problems use: 23 | 24 | #export opts='-st_type cayley -eps_error_relative ::ascii_info_detail' 25 | 26 | #export opts='-st_type sinvert -eps_error_relative ::ascii_info_detail' 27 | 28 | export opts='-st_type sinvert -eps_error_relative ::ascii_info_detail -eps_balance twoside -pc_factor_mat_solver_type mumps -mat_mumps_icntl_14 1000' 29 | 30 | #export opts='-st_type sinvert -eps_error_relative ::ascii_info_detail -pc_factor_mat_solver_type mumps -mat_mumps_icntl_14 1000 -mat_mumps_icntl_23 8000' 31 | 32 | #export opts='-st_type sinvert -st_ksp_type preonly -st_pc_type lu -st_pc_factor_mat_solver_type superlu_dist' 33 | 34 | #export opts='-st_type sinvert -st_ksp_type preonly -st_pc_type lu -st_pc_factor_mat_solver_type superlu_dist' 35 | 36 | #export opts='-st_type sinvert -st_ksp_type preonly -st_pc_type lu -eps_error_relative ::ascii_info_detail -st_pc_factor_mat_solver_type superlu_dist -mat_superlu_dist_iterrefine 1 -mat_superlu_dist_colperm PARMETIS -mat_superlu_dist_parsymbfact 1' 37 | 38 | 39 | for i in $(seq 1 1 1) 40 | 41 | do 42 | #sed -i 's,^\(itau[ ]*=\).*,\1'$i',g' bin/parameters.py 43 | #sed -i 's,^\(Ek[ ]*=\).*,\1'10**$i',g' bin/parameters.py 44 | #sed -i 's,^\(mu[ ]*=\).*,\1'$i',g' bin/parameters.py 45 | #sed -i 's,^\(delta[ ]*=\).*,\1'$i',g' bin/parameters.py 46 | #sed -i 's,^\(ricb[ ]*=\).*,\1'$i',g' bin/parameters.py 47 | 48 | #echo $i 49 | 50 | ./submatrices.py $ncpus >> out00 51 | #mpiexec -n $ncpus ./bin/assemble.py >> out0 52 | #mpiexec -n $ncpus ./bin/solve.py $opts >> out1 53 | 54 | #for k in $(seq 0 0.002 0.008) 55 | for k in $(seq 1 1 1) 56 | do 57 | #rnd1=$(echo | awk -v seed=$RANDOM 'srand(seed) {print (2*rand()-1)}') 58 | #echo $rnd1 59 | #rnd2=$(echo | awk -v seed=$RANDOM 'srand(seed) {print (2*rand()-1)}') 60 | #echo $rnd2 61 | #sed -i 's,^\(Em[ ]*=\).*,\1'10**$k',g' bin/parameters.py 62 | #sed -i 's,^\(itau[ ]*=\).*,\1'$k',g' bin/parameters.py 63 | #sed -i 's,^\(rnd1[ ]*=\).*,\1'$rnd1',g' bin/parameters.py 64 | #sed -i 's,^\(rnd2[ ]*=\).*,\1'$rnd2',g' bin/parameters.py 65 | sed -i 's,^\(delta[ ]*=\).*,\1'$k',g' bin/parameters.py 66 | #sed -i 's,^\(ricb[ ]*=\).*,\1'$k',g' bin/parameters.py 67 | #sed -i 's,^\(Le[ ]*=\).*,\1'10**$k',g' bin/parameters.py 68 | 69 | #echo Solving... 70 | #./submatrices.py $ncpus >> out00 71 | mpiexec -n $ncpus ./bin/assemble.py >> out0 72 | mpiexec -n $ncpus ./bin/solve.py $opts >> out1 73 | done 74 | 75 | done 76 | -------------------------------------------------------------------------------- /bin/compute_profiles.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import numpy as np 4 | from scipy.io import savemat 5 | import parameters as par 6 | import utils as ut 7 | 8 | keys = [] 9 | values = [] 10 | tol = 1e-9 11 | 12 | twozone = ((par.thermal == 1) and (par.heating == 'two zone')) # boolean 13 | userdef = ((par.thermal == 1) and (par.heating == 'user defined')) # boolean 14 | cdipole = ((par.magnetic == 1) and (par.B0 == 'dipole') and (par.ricb > 0)) # boolean 15 | inviscid = (((par.Ek == 0) or (par.ViscosD == 0)) and (par.ricb == 0)) # boolean 16 | quadrupole = ((par.B0 == 'Luo_S2') or ((par.B0 == 'FDM') and (par.B0_l == 2))) # boolean 17 | anelastic = (par.anelastic==1) 18 | boussinesq = par.anelastic==0 and par.thermal==1 19 | 20 | if anelastic: 21 | if par.interior_model=='mesa': 22 | import mesa_profiles as rap 23 | else: 24 | import radial_profiles as rap 25 | else: 26 | import radial_profiles as rap 27 | 28 | if boussinesq: 29 | if twozone: 30 | cd_ent = ut.chebco_rf(rap.twozone, rpower=1, N=par.N, ricb=par.ricb, rcmb=ut.rcmb, tol=tol, args=par.args).reshape([par.N,1]) 31 | elif userdef: 32 | cd_ent = ut.chebco_rf(rap.BVprof, rpower=1, N=par.N, ricb=par.ricb, rcmb=ut.rcmb, tol=tol, args=par.args).reshape([par.N,1]) 33 | if twozone or userdef: 34 | keys.append('cd_ent') 35 | values.append(cd_ent) 36 | 37 | elif anelastic: ## 38 | 39 | cd_rho = ut.chebify( rap.density, 2, tol) 40 | cd_lho = ut.chebify( rap.log_density, 4, tol) 41 | cd_vsc = ut.chebify( rap.viscosity, 2, tol) 42 | cd_krT = ut.chebify( rap.krT, 1, tol) 43 | cd_roT = ut.chebify( rap.roT, 0, tol) 44 | 45 | keys.append('cd_rho') 46 | keys.append('cd_lho') 47 | keys.append('cd_vsc') 48 | keys.append('cd_krT') 49 | keys.append('cd_roT') 50 | 51 | values.append(cd_rho) 52 | values.append(cd_lho) 53 | values.append(cd_vsc) 54 | values.append(cd_krT) 55 | values.append(cd_roT) 56 | 57 | if par.thermal == 1: 58 | 59 | cd_lnT = ut.chebify( rap.log_temperature, 1, tol) 60 | cd_kho = ut.chebify( rap.kappa_rho, 1, tol) 61 | cd_tds = ut.chebify( rap.tds, 0, tol) 62 | 63 | if par.autograv: 64 | cd_buo = ut.chebco_rf(rap.buoFac,0,par.N,par.ricb,ut.rcmb,tol) 65 | cd_buo = ut.cheb2Product(cd_buo,ac.gravCoeff(),tol).reshape([par.N,1]) 66 | else: 67 | cd_buo = ut.chebco_rf(rap.buoFac,0,par.N,par.ricb,ut.rcmb,tol).reshape([par.N,1]) 68 | 69 | keys.append('cd_lnT') 70 | keys.append('cd_kho') 71 | keys.append('cd_tds') 72 | keys.append('cd_buo') 73 | 74 | values.append(cd_lnT) 75 | values.append(cd_kho) 76 | values.append(cd_tds) 77 | values.append(cd_buo) 78 | 79 | if par.magnetic: 80 | 81 | cd_eta = ut.chebify( rap.magnetic_diffusivity, 1, tol) 82 | 83 | keys.append('cd_eta') 84 | values.append(cd_eta) 85 | 86 | if par.anelastic: 87 | cd_eho = ut.chebify( rap.eta_rho, 1, tol) 88 | 89 | keys.append('cd_eho') 90 | values.append(cd_eho) 91 | 92 | dict_prof = dict(zip(keys,values)) 93 | 94 | savemat('radProfs.mat',dict_prof) 95 | rap.write_profiles() 96 | 97 | print("Profiles generated:",keys) -------------------------------------------------------------------------------- /tools/ell_spectrum.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import sys 3 | import matplotlib.pyplot as plt 4 | import numpy.polynomial.chebyshev as ch 5 | 6 | sys.path.insert(1,'bin/') 7 | 8 | import utils as ut 9 | import parameters as par 10 | 11 | ''' 12 | Script to compute the ell-spectrum at a given radius 13 | Use as: 14 | python3 tools/ell_spectrum.py nsol radius field 15 | 16 | nsol : solution number 17 | radius : radial location 18 | field : 'u' or 'b' for flow or magnetic field 19 | ''' 20 | 21 | solnum = int(sys.argv[1]) 22 | radius = float(sys.argv[2]) 23 | field = sys.argv[3] 24 | 25 | m = par.m 26 | lmax = par.lmax 27 | ricb = par.ricb 28 | rcmb = 1 29 | gap = rcmb - ricb 30 | n = ut.n 31 | nr = par.N 32 | 33 | 34 | # set the radial grid 35 | i = np.arange(0,nr) 36 | x = np.cos( (i+0.5)*np.pi/nr ) 37 | r = 0.5*gap*(x+1) + ricb; 38 | 39 | if ricb == 0 : 40 | x0 = 0.5 + x/2 41 | else : 42 | x0 = x 43 | 44 | # matrix with Chebyshev polynomials at every x point for all degrees: 45 | chx = ch.chebvander(x0,par.N-1) # this matrix has nr rows and N-1 cols 46 | 47 | # read fields from disk 48 | if field == 'u': 49 | a = np.loadtxt('real_flow.field',usecols=solnum) 50 | b = np.loadtxt('imag_flow.field',usecols=solnum) 51 | vsymm = par.symm 52 | elif field == 'b': 53 | a = np.loadtxt('real_magnetic.field',usecols=solnum) 54 | b = np.loadtxt('imag_magnetic.field',usecols=solnum) 55 | vsymm = par.symm * ut.symmB0 56 | 57 | # Rearrange and separate poloidal and toroidal parts 58 | Plj0 = a[:n] + 1j*b[:n] # N elements on each l block 59 | Tlj0 = a[n:n+n] + 1j*b[n:n+n] # N elements on each l block 60 | 61 | lm1 = lmax-m+1 62 | Plj0 = np.reshape(Plj0,(int(lm1/2),ut.N1)) 63 | Tlj0 = np.reshape(Tlj0,(int(lm1/2),ut.N1)) 64 | 65 | Plj = np.zeros((int(lm1/2),par.N),dtype=complex) 66 | Tlj = np.zeros((int(lm1/2),par.N),dtype=complex) 67 | 68 | if ricb == 0 : 69 | iP = (m + 1 - ut.s)%2 70 | iT = (m + ut.s)%2 71 | for k in np.arange(int(lm1/2)) : 72 | Plj[k,iP::2] = Plj0[k,:] 73 | Tlj[k,iT::2] = Tlj0[k,:] 74 | else : 75 | Plj = Plj0 76 | Tlj = Tlj0 77 | 78 | s = int(vsymm*0.5+0.5) # s=0 if antisymm, s=1 if symm 79 | if m>0: 80 | idp = np.arange( 1-s, lm1, 2) 81 | idt = np.arange( s , lm1, 2) 82 | ll = np.arange( m, lmax+1 ) 83 | elif m==0: 84 | idp = np.arange( s , lm1, 2) 85 | idt = np.arange( 1-s, lm1, 2) 86 | ll = np.arange( m+1, lmax+2 ) 87 | 88 | # init arrays 89 | Plr = np.zeros( (lm1, nr), dtype=complex ) 90 | Tlr = np.zeros( (lm1, nr), dtype=complex ) 91 | 92 | # populate Plr and Tlr 93 | Plr[idp,:] = np.matmul( Plj, chx.T) 94 | Tlr[idt,:] = np.matmul( Tlj, chx.T) 95 | 96 | ir = np.argmin(abs(r-radius)) 97 | 98 | # plot only the coeffs that are not zero 99 | kp = abs(Plr[:,ir]) > 0 100 | kt = abs(Tlr[:,ir]) > 0 101 | 102 | plt.figure() 103 | plt.yscale('log') 104 | plt.xlim(0,50) 105 | plt.plot(ll[kp],abs(Plr[kp,ir]),'o-',ms=3,lw=1,label=r'Poloidal') 106 | plt.plot(ll[kt],abs(Tlr[kt,ir]),'o-',ms=3,lw=1,label=r'Toroidal') 107 | plt.xlabel(r'Angular degree $\ell$',size=14) 108 | plt.ylabel(f'Spectral amplitude at $r={radius}$',size=14) 109 | plt.legend() 110 | 111 | plt.tight_layout() 112 | plt.show() 113 | -------------------------------------------------------------------------------- /bin/autocompute.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import numpy as np 4 | import scipy.sparse as ss 5 | # from scipy.linalg import solve 6 | from scipy.sparse.linalg import spsolve 7 | import numpy.polynomial.chebyshev as ch 8 | import bc_variables as bv 9 | import parameters as par 10 | import utils as ut 11 | import radial_profiles as rap 12 | 13 | 14 | def balance_heat_flux(): 15 | 16 | bc_val_bot = par.bci_thermal_val 17 | bc_val_top = par.bco_thermal_val 18 | 19 | tol = 1e-9 20 | 21 | cd_eps = ut.chebco_f(rap.epsilon_h,par.N,par.ricb,ut.rcmb,tol) 22 | epsInt = ch.chebint(cd_eps) 23 | 24 | return eps0, bc_val_top, bc_val_bot 25 | 26 | def get_equilibrium_entropy(): 27 | ''' 28 | Function to compute equilibrium entropy gradient profile by solving 29 | Div(rho T kappa grad S) = Q 30 | ''' 31 | 32 | # Balance heat fluxes 33 | eps0, bc_val_top, bc_val_bot = balance_heat_flux() 34 | 35 | tol = 1e-9 36 | 37 | r0 = ut.chebco(0, par.N, tol, par.ricb, ut.rcmb) 38 | r1 = ut.chebco(1, par.N, tol, par.ricb, ut.rcmb) 39 | cd_eps = ut.chebco_f(rap.epsilon_h,par.N,par.ricb,ut.rcmb,tol,args=eps0) 40 | 41 | D1 = ut.Dlam(1, par.N) 42 | D2 = ut.Dlam(2, par.N) 43 | S0 = ut.Slam(0, par.N) # From the Chebyshev basis ( C^(0) basis ) to C^(1) basis 44 | S1 = ut.Slam(1, par.N) # From C^(1) basis to C^(2) basis 45 | 46 | S10 = S1*S0 47 | 48 | r0_D1 = ut.Mlam(S10*r0,2,0) * (S1*D1) 49 | r1_D2 = ut.Mlam(S10*r1,2,0) * D2 50 | eps_h = S10*cd_eps 51 | 52 | if par.anelastic: 53 | cd_lnT = ut.chebify( rap.log_temperature, 1, tol) 54 | cd_lho = ut.chebify( rap.log_density, 1, tol) 55 | cd_lnk = ut.chebify( rap.log_thermal_diffusivity, 1, tol) 56 | 57 | r1_lnT1 = ut.cheb2Product( r1, cd_lnT[:,1], tol) 58 | r1_lho1 = ut.cheb2Product( r1, cd_lho[:,1], tol) 59 | r1_lnk1 = ut.cheb2Product( r1, cd_lnk[:,1], tol) 60 | 61 | r1_lnT1_D1 = ut.Mlam(S10*r1_lnT1,2,0) * (S1*D1) 62 | r1_lho1_D1 = ut.Mlam(S10*r1_lho1,2,0) * (S1*D1) 63 | r1_lnk1_D1 = ut.Mlam(S10*r1_lnk1,2,0) * (S1*D1) 64 | 65 | 66 | z2 = ss.csr_matrix((2,ut.N1)) 67 | chop = 2 68 | 69 | r0_D1 = ss.vstack( [ z2, r0_D1[:-chop,:] ], format='csr' ) 70 | r1_D2 = ss.vstack( [ z2, r1_D2[:-chop,:] ], format='csr' ) 71 | 72 | if par.anelastic: 73 | r1_lnT1_D1 = ss.vstack( [ z2, r1_lnT1_D1[:-chop,:] ], format='csr' ) 74 | r1_lho1_D1 = ss.vstack( [ z2, r1_lho1_D1[:-chop,:] ], format='csr' ) 75 | r1_lnk1_D1 = ss.vstack( [ z2, r1_lnk1_D1[:-chop,:] ], format='csr' ) 76 | 77 | if par.anelastic: 78 | Amat = 2*r0_D1 + r1_lnT1_D1 + r1_lho1_D1 + r1_lnk1_D1 + r1_D2 79 | else: 80 | Amat = 2*r0_D1 + r1_D2 81 | 82 | if par.bci_thermal == 0: #Constant entropy 83 | Amat[0,:] = bv.Ta[:,0] 84 | elif par.bci_thermal == 1: #Constant flux 85 | Amat[0,:] = bv.Ta[:,1] 86 | 87 | if par.bco_thermal == 0: #Constant entropy 88 | Amat[1,:] = bv.Tb[:,0] 89 | elif par.bco_thermal == 1: #Constant flux 90 | Amat[1,:] = bv.Tb[:,1] 91 | 92 | bci = par.bci_thermal_val 93 | bco = par.bco_thermal_val 94 | 95 | Bmat = np.r_[bci,bco,eps_h[:-chop]] 96 | 97 | # Amat = Amat.todense() 98 | 99 | Scheb = spsolve(Amat,Bmat) 100 | 101 | dScheb = ut.Dcheb(Scheb,par.ricb,ut.rcmb) 102 | 103 | return dScheb 104 | 105 | 106 | def gravCoeff(): 107 | ''' 108 | Integrates density profile in Cheb space and gives Cheb 109 | coefficients of gravity profile, normalized to the value 110 | at the outer boundary. This works. We checked. Again. 111 | ''' 112 | ck = ut.chebco_f(rap.density,par.N,par.ricb,ut.rcmb,par.tol_tc) 113 | 114 | # x0 = -(par.ricb + ut.rcmb)/(ut.rcmb - par.ricb) 115 | if par.ricb > 0: 116 | gk = (ut.rcmb - par.ricb)/2. * ch.chebint(ck,lbnd=-1) 117 | 118 | g_ref = ch.chebval(1,gk) 119 | out = gk/g_ref 120 | 121 | out[0] += par.g_icb # Value of g at ricb normalized by value at rcmb 122 | else: 123 | gk = ut.rcmb * ch.chebint(ck,lbnd=0) # g at origin goes to zero 124 | g_ref = ch.chebval(1,gk) 125 | out = gk/g_ref 126 | 127 | return out[:par.N] -------------------------------------------------------------------------------- /docs/page2.md: -------------------------------------------------------------------------------- 1 | # Installation Notes 2 | 3 | To run **`kore`** we need the PETSc/SLEPc packages and their python bindings petsc4py/slepc4py. PETSc needs to be compiled with support for complex scalars. MUMPS and SuperLU_dist are external packages that need to be installed together with PETSc. 4 | 5 | If you are interested in problems that involve very large matrices, for instance when considering extremely small viscosities, we recommend doing so with a machine with at least 128 GB of memory. The number of processing cores is not critical, eight are fine, 24 are plenty. Small to moderate size matrices can be solved using a laptop, depending on available memory. 6 | 7 | From our experience the PETSc version that has allowed us to solve the largest problems is version 3.9.4. That version requires python 3.7, however. Newer PETSc versions seem to have much larger memory footprint, although they can still handle medium size problems without issues. So, our advice is to install PETSc version 3.9.3 if the problem involves very large matrices, otherwise it is better to stick to the most recent PETSc release. 8 | 9 | ## Installing PETSc/SLEPc on MacOS 10 | 11 | It is convenient to have a dedicated python environment to use with **`kore`**. It is quite simple to create and activate it: 12 | 13 | ```Shell 14 | python3 -m venv kore_env 15 | source kore_env/bin/activate 16 | ``` 17 | 18 | Then we need to install scipy and cython in that environment: 19 | ```Shell 20 | pip3 install scipy cython 21 | ``` 22 | 23 | Now we download the latest PETSc/SLEPc releases using `git` 24 | ```Shell 25 | git clone -b release https://gitlab.com/petsc/petsc.git petsc 26 | git clone -b release https://gitlab.com/slepc/slepc slepc 27 | ``` 28 | 29 | We go now into the newly created `petsc` folder and configure PETSc: 30 | ```Shell 31 | cd petsc 32 | ./configure --with-petsc4py --download-mpi4py --download-mpich --with-scalar-type=complex --download-mumps --download-parmetis --download-metis --download-scalapack --download-fblaslapack --with-debugging=0 --download-superlu_dist --download-ptscotch CXXOPTFLAGS='-O3 -march=native' FOPTFLAGS='-O3 -march=native' COPTFLAGS='-O3 -march=native' --download-bison 33 | ``` 34 | Then we build PETSc: 35 | ```Shell 36 | make PETSC_DIR=/path/to/petsc PETSC_ARCH=arch-darwin-c-opt all 37 | ``` 38 | where you must replace `/path/to/petsc` with the actual path for your case. Before checking that everything works we must tell python where to find `petsc4py` and `mpi4py`, which were compiled along in the step above: 39 | ```Shell 40 | export PYTHONPATH=/path/to/petsc/arch-darwin-c-opt/lib 41 | ``` 42 | where again we must replace `/path/to/petsc` with the actual path. Now we test the installation: 43 | ```Shell 44 | make PETSC_DIR=/path/to/petsc PETSC_ARCH=arch-darwin-c-opt check 45 | ``` 46 | If no errors appear then you can proceed to install SLEPc. If an error is reported due to python not being able to find PETSc (even though we just updated the `PYTHONPATH`), but the other MPI tests were successful, then don't worry, it is safe to ignore the error. 47 | 48 | We need to setup some environment variables before installing SLEPc: 49 | ```Shell 50 | export PETSC_DIR=/path/to/petsc 51 | export PETSC_ARCH=arch-darwin-c-opt 52 | export SLEPC_DIR=/path/to/slepc 53 | ``` 54 | 55 | Now go to the slepc folder, configure and build SLEPc: 56 | ```Shell 57 | cd $SLEPC_DIR 58 | ./configure 59 | make 60 | make check 61 | ``` 62 | The last step is to install slepc4py, which is distributed along with SLEPc: 63 | ```Shell 64 | cd $SLEPC_DIR/src/binding/slepc4py 65 | python3 setup.py build 66 | python3 setup.py install 67 | ``` 68 | Almost there now. We need to update the `PATH` environment variable so that we can use the MPI library provided by PETSc instead of the one provided by your system, if any: 69 | ```Shell 70 | export PATH=$PETSC_DIR/$PETSC_ARCH/bin:$PATH 71 | ``` 72 | 73 | It is a good idea to keep the necessary commands for initialization in a separate file. It should contain the following lines: 74 | ```Shell 75 | source /path/to/kore_env/bin/activate 76 | export PETSC_DIR=/path/to/petsc 77 | export PETSC_ARCH=arch-darwin-c-opt 78 | export SLEPC_DIR=/path/to/slepc 79 | export PYTHONPATH=$PETSC_DIR/$PETSC_ARCH/lib 80 | export PATH=$PETSC_DIR/$PETSC_ARCH/bin:$PATH 81 | ``` 82 | We can write that to a file named e.g. `kore_env.sh` in your home directory, so every time you need to prepare to run **`kore`** we do first: 83 | ```Shell 84 | source $HOME/kore_env.sh 85 | ``` 86 | 87 | 88 | ## Installing on Linux 89 | Coming soon 90 | 91 | 92 | ## SHTns 93 | Coming soon 94 | -------------------------------------------------------------------------------- /bin/find_Rac_pbs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import numpy as np 4 | import os 5 | import sys 6 | from scipy.optimize import brentq 7 | from timeit import default_timer as timer 8 | import datetime 9 | 10 | sys.path.insert(1,os.getcwd()+'/bin') 11 | import parameters as par 12 | 13 | ''' 14 | Script to find the critical Rayleigh number for unstable convection. 15 | Needs a Ra_gap variable in parameters.py. 16 | ''' 17 | 18 | opts='-st_type sinvert -st_pc_factor_mat_solver_type mumps -mat_mumps_icntl_14 1000 -eps_true_residual -eps_balance twoside' 19 | optsRes = opts + ' -eps_error_relative ::ascii_info_detail' 20 | 21 | Ramin = 2.e5 22 | 23 | mmin = 5 24 | mmax = 9 25 | 26 | marr = np.arange(mmin,mmax+1) 27 | 28 | def runKoreRes(Rac,opts): # Print residuals once Rac is found 29 | Ra = 10**Rac 30 | 31 | os.system('sed -i "0,/Ra_gap.*/s//Ra_gap=%f/" ./bin/parameters.py' %Ra) 32 | os.system('mpiexec -n %d ./bin/assemble.py' %par.ncpus) 33 | os.system('mpiexec -n %d ./bin/solve.py %s' %(par.ncpus,opts)) 34 | # os.system('./bin/postprocess.py') 35 | 36 | def get_sigma(Ra,ncpus, opts): 37 | Ra = 10**Ra 38 | 39 | print("Ra = %e" %Ra,flush=True) 40 | 41 | if Ra in ra_cache: 42 | return ra_cache[Ra] 43 | else: 44 | os.system('sed -i "0,/Ra_gap.*/s//Ra_gap=%f/" ./bin/parameters.py' %Ra) 45 | os.system('mpiexec -n %d ./bin/assemble.py > /dev/null' %ncpus) 46 | os.system('mpiexec -n %d ./bin/solve.py %s > /dev/null' %(ncpus,opts)) 47 | eig0 = np.loadtxt('eigenvalues0.dat') 48 | eig = np.reshape(eig0, (-1, 2)) 49 | Idx = np.argmax(eig[:,0]) 50 | sigma_c = eig[Idx,0] 51 | ra_cache[Ra] = sigma_c 52 | 53 | os.remove('eigenvalues0.dat') 54 | 55 | return sigma_c 56 | 57 | 58 | # Function copied from SINGE - looks for Ra bounds 59 | def bracket_brentq(f, x1, x2=None, dx=0.01, tol=1e-6, maxiter=200, args=None): 60 | y1 = f(x1, *args) 61 | dx = abs(dx) # dx must be positive. 62 | if x2 is not None: # check that we actually have a bracket 63 | y2 = get_sigma(x2, *args) 64 | if y2*y1 > 0: # we don't have a bracket !! 65 | if (abs(y2) < abs(y1)): 66 | x1,y1 = x2,y2 # start from the value closest to the root 67 | x2 = None # search needed. 68 | if x2 is None: # search for a bracket 69 | x2 = x1 70 | if y1>0: dx = -dx # up or down ? 71 | while True: 72 | x2 += dx 73 | y2 = f(x2, *args) 74 | if y2*y1 < 0: break 75 | x1,y1 = x2,y2 76 | # Now that we know that the root is between x1 and x2, we use Brent's method: 77 | x0 = brentq(f, x1, x2, maxiter=maxiter, xtol=tol, rtol=tol, args=args) 78 | return x0 79 | 80 | 81 | print("###############################",flush=True) 82 | print("# Kore linear convection mode #",flush=True) 83 | print("###############################",flush=True) 84 | print("",flush=True) 85 | 86 | tic1 = timer() 87 | 88 | # -------------------------------------------------------------------------------- Compute Rac 89 | 90 | for m in marr: 91 | 92 | print("=======",flush=True) 93 | print(" m = %d" %m,flush=True) 94 | print("=======\n",flush=True) 95 | 96 | ra_cache = {} 97 | 98 | os.system('sed -i "0,/m =.*/s//m = %d/" ./bin/parameters.py' %m) 99 | os.system('./bin/submatrices.py %d > /dev/null' %par.ncpus) 100 | Rac = bracket_brentq(get_sigma,np.log10(Ramin),args=(par.ncpus,opts)) 101 | runKoreRes(Rac,optsRes) 102 | Rac=10**Rac 103 | eig0 = np.loadtxt('eigenvalues0.dat') 104 | eig = np.reshape(eig0, (-1, 2)) 105 | idx_c = np.argmax(eig[:,0]) 106 | sigma_c,omega_c = eig[idx_c,:] 107 | 108 | X = np.array([par.Ek_gap , par.ricb , Rac , int(m) , sigma_c , omega_c]) 109 | fmt = ['%.3e','%.2f','%.5e','%d','%.5e','%.5e'] 110 | 111 | with open('critical_params.dat','a') as dcrit: 112 | np.savetxt(dcrit, X.reshape(1,X.shape[0]), fmt=fmt) 113 | 114 | toc2 = timer() 115 | 116 | tsinglem = str(datetime.timedelta(seconds=toc2 - tic1)) 117 | 118 | print("Rac for m=%d computed in %s\n\n" %(m, tsinglem)) 119 | 120 | toc1 = timer() 121 | tform = str(datetime.timedelta(seconds=toc1 - tic1)) 122 | 123 | print("\n======================================",flush=True) 124 | print("Rac for m=%d to m=%d found in %s" %(mmin,mmax,tform),flush=True) 125 | print("======================================\n\n",flush=True) 126 | -------------------------------------------------------------------------------- /tests/dormy2004/find_Rac.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import numpy as np 4 | import os 5 | import sys 6 | from scipy.optimize import brentq 7 | from timeit import default_timer as timer 8 | import datetime 9 | 10 | sys.path.insert(1,os.getcwd()+'/bin') 11 | import parameters as par 12 | 13 | ''' 14 | Script to find the critical Rayleigh number for unstable convection. 15 | Needs a Ra_gap variable in parameters.py. 16 | ''' 17 | 18 | opts='-st_type sinvert -st_pc_factor_mat_solver_type mumps -mat_mumps_icntl_14 1000 -eps_true_residual -eps_balance twoside' 19 | optsRes = opts + ' -eps_error_relative ::ascii_info_detail' 20 | 21 | Ramin = 1.6e6 22 | 23 | mmin = 9 24 | mmax = 9 25 | 26 | marr = np.arange(mmin,mmax+1) 27 | 28 | def runKoreRes(Rac,opts): # Print residuals once Rac is found 29 | Ra = 10**Rac 30 | 31 | os.system('sed -i "0,/Ra_gap.*/s//Ra_gap=%f/" ./bin/parameters.py' %Ra) 32 | os.system('mpiexec -n %d ./bin/assemble.py' %par.ncpus) 33 | os.system('mpiexec -n %d ./bin/solve.py %s' %(par.ncpus,opts)) 34 | # os.system('./bin/postprocess.py') 35 | 36 | def get_sigma(Ra,ncpus, opts): 37 | Ra = 10**Ra 38 | 39 | print("Ra = %e" %Ra,flush=True) 40 | 41 | if Ra in ra_cache: 42 | return ra_cache[Ra] 43 | else: 44 | os.system('sed -i "0,/Ra_gap.*/s//Ra_gap=%f/" ./bin/parameters.py' %Ra) 45 | os.system('mpiexec -n %d ./bin/assemble.py > /dev/null' %ncpus) 46 | os.system('mpiexec -n %d ./bin/solve.py %s > /dev/null' %(ncpus,opts)) 47 | eig0 = np.loadtxt('eigenvalues0.dat') 48 | eig = np.reshape(eig0, (-1, 2)) 49 | Idx = np.argmax(eig[:,0]) 50 | sigma_c = eig[Idx,0] 51 | ra_cache[Ra] = sigma_c 52 | 53 | os.remove('eigenvalues0.dat') 54 | 55 | return sigma_c 56 | 57 | 58 | # Function copied from SINGE - looks for Ra bounds 59 | def bracket_brentq(f, x1, x2=None, dx=0.01, tol=1e-6, maxiter=200, args=None): 60 | y1 = f(x1, *args) 61 | dx = abs(dx) # dx must be positive. 62 | if x2 is not None: # check that we actually have a bracket 63 | y2 = get_sigma(x2, *args) 64 | if y2*y1 > 0: # we don't have a bracket !! 65 | if (abs(y2) < abs(y1)): 66 | x1,y1 = x2,y2 # start from the value closest to the root 67 | x2 = None # search needed. 68 | if x2 is None: # search for a bracket 69 | x2 = x1 70 | if y1>0: dx = -dx # up or down ? 71 | while True: 72 | x2 += dx 73 | y2 = f(x2, *args) 74 | if y2*y1 < 0: break 75 | x1,y1 = x2,y2 76 | # Now that we know that the root is between x1 and x2, we use Brent's method: 77 | x0 = brentq(f, x1, x2, maxiter=maxiter, xtol=tol, rtol=tol, args=args) 78 | return x0 79 | 80 | 81 | print("\n",flush=True) 82 | print("###############################",flush=True) 83 | print("# Kore linear convection mode #",flush=True) 84 | print("###############################",flush=True) 85 | print("",flush=True) 86 | 87 | tic1 = timer() 88 | 89 | # -------------------------------------------------------------------------------- Compute Rac 90 | 91 | for m in marr: 92 | 93 | print("=======",flush=True) 94 | print(" m = %d" %m,flush=True) 95 | print("=======\n",flush=True) 96 | 97 | ra_cache = {} 98 | 99 | os.system('sed -i "0,/m =.*/s//m = %d/" ./bin/parameters.py' %m) 100 | os.system('./bin/submatrices.py %d > /dev/null' %par.ncpus) 101 | Rac = bracket_brentq(get_sigma,np.log10(Ramin),args=(par.ncpus,opts)) 102 | runKoreRes(Rac,optsRes) 103 | Rac=10**Rac 104 | eig0 = np.loadtxt('eigenvalues0.dat') 105 | eig = np.reshape(eig0, (-1, 2)) 106 | idx_c = np.argmax(eig[:,0]) 107 | sigma_c,omega_c = eig[idx_c,:] 108 | 109 | X = np.array([par.Ek , par.ricb , Rac , int(m) , omega_c]) 110 | fmt = ['%.3e','%.2f','%.5e','%d','%.5e'] 111 | 112 | with open('critical_params.dat','a') as dcrit: 113 | np.savetxt(dcrit, X.reshape(1,X.shape[0]), fmt=fmt) 114 | 115 | toc2 = timer() 116 | 117 | tsinglem = str(datetime.timedelta(seconds=toc2 - tic1)) 118 | 119 | print("Rac for m=%d computed in %s\n\n" %(m, tsinglem)) 120 | 121 | toc1 = timer() 122 | tform = str(datetime.timedelta(seconds=toc1 - tic1)) 123 | 124 | print("\n======================================",flush=True) 125 | print("Rac for m=%d to m=%d found in %s" %(mmin,mmax,tform),flush=True) 126 | print("======================================\n\n",flush=True) 127 | -------------------------------------------------------------------------------- /tests/jones2000/find_Rac.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import numpy as np 4 | import os 5 | import sys 6 | from scipy.optimize import brentq 7 | from timeit import default_timer as timer 8 | import datetime 9 | 10 | sys.path.insert(1,os.getcwd()+'/bin') 11 | import parameters as par 12 | 13 | ''' 14 | Script to find the critical Rayleigh number for unstable convection. 15 | Needs a Ra_gap variable in parameters.py. 16 | ''' 17 | 18 | opts='-st_type sinvert -st_pc_factor_mat_solver_type mumps -mat_mumps_icntl_14 1000 -eps_true_residual -eps_balance twoside' 19 | optsRes = opts + ' -eps_error_relative ::ascii_info_detail' 20 | 21 | Ramin = 4.6e6 22 | 23 | mmin = 9 24 | mmax = 9 25 | 26 | marr = np.arange(mmin,mmax+1) 27 | 28 | def runKoreRes(Rac,opts): # Print residuals once Rac is found 29 | Ra = 10**Rac 30 | 31 | os.system('sed -i "0,/Ra_gap.*/s//Ra_gap=%f/" ./bin/parameters.py' %Ra) 32 | os.system('mpiexec -n %d ./bin/assemble.py' %par.ncpus) 33 | os.system('mpiexec -n %d ./bin/solve.py %s' %(par.ncpus,opts)) 34 | # os.system('./bin/postprocess.py') 35 | 36 | def get_sigma(Ra,ncpus, opts): 37 | Ra = 10**Ra 38 | 39 | print("Ra = %e" %Ra,flush=True) 40 | 41 | if Ra in ra_cache: 42 | return ra_cache[Ra] 43 | else: 44 | os.system('sed -i "0,/Ra_gap.*/s//Ra_gap=%f/" ./bin/parameters.py' %Ra) 45 | os.system('mpiexec -n %d ./bin/assemble.py > /dev/null' %ncpus) 46 | os.system('mpiexec -n %d ./bin/solve.py %s > /dev/null' %(ncpus,opts)) 47 | eig0 = np.loadtxt('eigenvalues0.dat') 48 | eig = np.reshape(eig0, (-1, 2)) 49 | Idx = np.argmax(eig[:,0]) 50 | sigma_c = eig[Idx,0] 51 | ra_cache[Ra] = sigma_c 52 | 53 | os.remove('eigenvalues0.dat') 54 | 55 | return sigma_c 56 | 57 | 58 | # Function copied from SINGE - looks for Ra bounds 59 | def bracket_brentq(f, x1, x2=None, dx=0.01, tol=1e-6, maxiter=200, args=None): 60 | y1 = f(x1, *args) 61 | dx = abs(dx) # dx must be positive. 62 | if x2 is not None: # check that we actually have a bracket 63 | y2 = get_sigma(x2, *args) 64 | if y2*y1 > 0: # we don't have a bracket !! 65 | if (abs(y2) < abs(y1)): 66 | x1,y1 = x2,y2 # start from the value closest to the root 67 | x2 = None # search needed. 68 | if x2 is None: # search for a bracket 69 | x2 = x1 70 | if y1>0: dx = -dx # up or down ? 71 | while True: 72 | x2 += dx 73 | y2 = f(x2, *args) 74 | if y2*y1 < 0: break 75 | x1,y1 = x2,y2 76 | # Now that we know that the root is between x1 and x2, we use Brent's method: 77 | x0 = brentq(f, x1, x2, maxiter=maxiter, xtol=tol, rtol=tol, args=args) 78 | return x0 79 | 80 | 81 | print("\n",flush=True) 82 | print("###############################",flush=True) 83 | print("# Kore linear convection mode #",flush=True) 84 | print("###############################",flush=True) 85 | print("",flush=True) 86 | 87 | tic1 = timer() 88 | 89 | # -------------------------------------------------------------------------------- Compute Rac 90 | 91 | for m in marr: 92 | 93 | print("=======",flush=True) 94 | print(" m = %d" %m,flush=True) 95 | print("=======\n",flush=True) 96 | 97 | ra_cache = {} 98 | 99 | os.system('sed -i "0,/m =.*/s//m = %d/" ./bin/parameters.py' %m) 100 | os.system('./bin/submatrices.py %d > /dev/null' %par.ncpus) 101 | Rac = bracket_brentq(get_sigma,np.log10(Ramin),args=(par.ncpus,opts)) 102 | runKoreRes(Rac,optsRes) 103 | Rac=10**Rac 104 | eig0 = np.loadtxt('eigenvalues0.dat') 105 | eig = np.reshape(eig0, (-1, 2)) 106 | idx_c = np.argmax(eig[:,0]) 107 | sigma_c,omega_c = eig[idx_c,:] 108 | 109 | X = np.array([par.Ek , par.ricb , Rac , int(m) , omega_c]) 110 | fmt = ['%.3e','%.2f','%.5e','%d','%.5e'] 111 | 112 | with open('critical_params.dat','a') as dcrit: 113 | np.savetxt(dcrit, X.reshape(1,X.shape[0]), fmt=fmt) 114 | 115 | toc2 = timer() 116 | 117 | tsinglem = str(datetime.timedelta(seconds=toc2 - tic1)) 118 | 119 | print("Rac for m=%d computed in %s\n\n" %(m, tsinglem)) 120 | 121 | toc1 = timer() 122 | tform = str(datetime.timedelta(seconds=toc1 - tic1)) 123 | 124 | print("\n======================================",flush=True) 125 | print("Rac for m=%d to m=%d found in %s" %(mmin,mmax,tform),flush=True) 126 | print("======================================\n\n",flush=True) 127 | -------------------------------------------------------------------------------- /bin/bc_variables.py: -------------------------------------------------------------------------------- 1 | # Variables involved with the boundary conditions 2 | 3 | import numpy as np 4 | import utils as ut 5 | import parameters as par 6 | 7 | 8 | 9 | def Tk(x, N, lamb_max) : 10 | ''' 11 | Chebyshev polynomial from order 0 to N (as rows) 12 | and its derivatives ** with respect to r **, up to lamb_max (as columns), 13 | evaluated at x=-1 (r=ricb) or x=1 (r=rcmb). 14 | ''' 15 | 16 | if par.ricb == 0 : 17 | ric = -ut.rcmb 18 | else : 19 | ric = par.ricb 20 | 21 | out = np.zeros((N+1,lamb_max+1)) 22 | 23 | for k in range(0,N+1): 24 | 25 | out[k,0] = x**k 26 | 27 | tmp = 1. 28 | for i in range(0, lamb_max): 29 | tmp = tmp * ( k**2 - i**2 )/( 2*i + 1 ) 30 | out[k,i+1] = x**(k+i+1) * tmp * (2/(ut.rcmb - ric))**(i+1) 31 | 32 | return out 33 | 34 | 35 | def Tcenter(N) : 36 | ''' 37 | Chebyshev polynomial evaluated at x=0, from order 0 to N 38 | Useful to set boundary condition at the center if no inner core present 39 | i.e. when the Chebyshev domain is [-1,1] 40 | ''' 41 | 42 | out = np.zeros((N+1,1)) 43 | for k in range (0,N+1): 44 | out[k] = np.cos( k * np.pi/2 ) 45 | out[abs(out)<0.1] = 0 46 | 47 | return out 48 | 49 | 50 | 51 | 52 | 53 | # to use in the b.c. and the torque calculation 54 | Ta = Tk(-1, par.N-1, 4) 55 | Tb = Tk( 1, par.N-1, 5) 56 | Tc = Tcenter(par.N-1) 57 | 58 | ''' 59 | # For the ellipsoidal case 60 | if (par.bco == 0 or par.bco == 1): 61 | mua1 = 1. # 62 | mua2 = 1. # will use spherical boundaries but will compute torques 63 | mua3 = 1. # as in an spheroid if alpha is different from zero 64 | elif bco == 2: 65 | mua1 = 1. 66 | mua2 = 0. 67 | mua3 = 0. 68 | elif bco == 3: 69 | mua1 = 1. 70 | mua2 = 1. 71 | mua3 = 0. 72 | elif bco == 4: 73 | mua1 = 1. 74 | mua2 = 1. 75 | mua3 = 1. 76 | 77 | a1 = mua1*ut.alpha 78 | a2 = mua2*ut.alpha**2 79 | a3 = mua3*ut.alpha**3 80 | 81 | akj = np.zeros((4,4)) 82 | # terms (r-1)^k / k! for the Taylor expansion, this one sets the long semiaxis a = 1 83 | akj[0,:] = [ 1. , 0. , 0. , 0. ] 84 | akj[1,:] = [ -a1/3. - a2/5. - (13*a3)/105. , (-2*a1)/3. - a2/7. + a3/21. , (12*a2)/35. + (96*a3)/385. , (-40*a3)/231. ] 85 | akj[2,:] = [ a2/10. + (3*a3)/35. , (2*a2)/7. + a3/7. , (4*a2)/35. - (48*a3)/385. , (-8*a3)/77. ] 86 | akj[3,:] = [ -a3/42. , (-5*a3)/63. , (-4*a3)/77. , (-8*a3)/693. ] 87 | 88 | ''' 89 | 90 | 91 | 92 | # Poloidals, Toroidals and derivatives at r = ricb 93 | 94 | P0_icb = Ta[:,0] 95 | P1_icb = Ta[:,1] 96 | P2_icb = Ta[:,2] 97 | P3_icb = Ta[:,3] 98 | P4_icb = Ta[:,4] 99 | 100 | T0_icb = Ta[:,0] 101 | T1_icb = Ta[:,1] 102 | T2_icb = Ta[:,2] 103 | T3_icb = Ta[:,3] 104 | 105 | # 0 component and its derivatives, it only has poloidals: 106 | u0_icb = np.zeros((par.N, 4)) 107 | u0_icb[:,0] = P0_icb 108 | u0_icb[:,1] = P1_icb - P0_icb 109 | u0_icb[:,2] = P2_icb - 2*P1_icb + 2*P0_icb 110 | u0_icb[:,3] = P3_icb - 3*P2_icb + 6*P1_icb - 6*P0_icb 111 | 112 | # +-1 component and derivatives. Poloidal part: 113 | u1P_icb = np.zeros((par.N, 4)) 114 | u1P_icb[:,0] = P1_icb + P0_icb 115 | u1P_icb[:,1] = P2_icb + P1_icb - P0_icb 116 | u1P_icb[:,2] = P3_icb + P2_icb - 2*P1_icb + 2*P0_icb 117 | u1P_icb[:,3] = P4_icb + P3_icb - 3*P2_icb + 6*P1_icb - 6*P0_icb 118 | 119 | # +-1 component and derivatives. Toroidal part: 120 | u1T_icb = np.zeros((par.N, 4)) 121 | u1T_icb[:,0] = T0_icb 122 | u1T_icb[:,1] = T1_icb 123 | u1T_icb[:,2] = T2_icb 124 | u1T_icb[:,3] = T3_icb 125 | 126 | 127 | 128 | # Poloidals, Toroidals and derivatives at r = rcmb 129 | 130 | P0_cmb = Tb[:,0] 131 | P1_cmb = Tb[:,1] 132 | P2_cmb = Tb[:,2] 133 | P3_cmb = Tb[:,3] 134 | P4_cmb = Tb[:,4] 135 | 136 | T0_cmb = Tb[:,0] 137 | T1_cmb = Tb[:,1] 138 | T2_cmb = Tb[:,2] 139 | T3_cmb = Tb[:,3] 140 | 141 | # 0 component and its derivatives, it only has poloidals: 142 | u0_cmb = np.zeros((par.N, 4)) 143 | u0_cmb[:,0] = P0_cmb 144 | u0_cmb[:,1] = P1_cmb - P0_cmb 145 | u0_cmb[:,2] = P2_cmb - 2*P1_cmb + 2*P0_cmb 146 | u0_cmb[:,3] = P3_cmb - 3*P2_cmb + 6*P1_cmb - 6*P0_cmb 147 | 148 | # +-1 component and derivatives. Poloidal part: 149 | u1P_cmb = np.zeros((par.N, 4)) 150 | u1P_cmb[:,0] = P1_cmb + P0_cmb 151 | u1P_cmb[:,1] = P2_cmb + P1_cmb - P0_cmb 152 | u1P_cmb[:,2] = P3_cmb + P2_cmb - 2*P1_cmb + 2*P0_cmb 153 | u1P_cmb[:,3] = P4_cmb + P3_cmb - 3*P2_cmb + 6*P1_cmb - 6*P0_cmb 154 | 155 | # +-1 component and derivatives. Toroidal part: 156 | u1T_cmb = np.zeros((par.N, 4)) 157 | u1T_cmb[:,0] = T0_cmb 158 | u1T_cmb[:,1] = T1_cmb 159 | u1T_cmb[:,2] = T2_cmb 160 | u1T_cmb[:,3] = T3_cmb 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.mtx 3 | *.npz 4 | *.field 5 | *.dat 6 | *__pycache__* 7 | 8 | 9 | # From GitHub template 10 | # Byte-compiled / optimized / DLL files 11 | __pycache__/ 12 | *.py[codz] 13 | *$py.class 14 | 15 | # C extensions 16 | *.so 17 | 18 | # Distribution / packaging 19 | .Python 20 | build/ 21 | develop-eggs/ 22 | dist/ 23 | downloads/ 24 | eggs/ 25 | .eggs/ 26 | lib/ 27 | lib64/ 28 | parts/ 29 | sdist/ 30 | var/ 31 | wheels/ 32 | share/python-wheels/ 33 | *.egg-info/ 34 | .installed.cfg 35 | *.egg 36 | MANIFEST 37 | 38 | # PyInstaller 39 | # Usually these files are written by a python script from a template 40 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 41 | *.manifest 42 | *.spec 43 | 44 | # Installer logs 45 | pip-log.txt 46 | pip-delete-this-directory.txt 47 | 48 | # Unit test / coverage reports 49 | htmlcov/ 50 | .tox/ 51 | .nox/ 52 | .coverage 53 | .coverage.* 54 | .cache 55 | nosetests.xml 56 | coverage.xml 57 | *.cover 58 | *.py.cover 59 | .hypothesis/ 60 | .pytest_cache/ 61 | cover/ 62 | 63 | # Translations 64 | *.mo 65 | *.pot 66 | 67 | # Django stuff: 68 | *.log 69 | local_settings.py 70 | db.sqlite3 71 | db.sqlite3-journal 72 | 73 | # Flask stuff: 74 | instance/ 75 | .webassets-cache 76 | 77 | # Scrapy stuff: 78 | .scrapy 79 | 80 | # Sphinx documentation 81 | docs/_build/ 82 | 83 | # PyBuilder 84 | .pybuilder/ 85 | target/ 86 | 87 | # Jupyter Notebook 88 | .ipynb_checkpoints 89 | 90 | # IPython 91 | profile_default/ 92 | ipython_config.py 93 | 94 | # pyenv 95 | # For a library or package, you might want to ignore these files since the code is 96 | # intended to run in multiple environments; otherwise, check them in: 97 | # .python-version 98 | 99 | # pipenv 100 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 101 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 102 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 103 | # install all needed dependencies. 104 | #Pipfile.lock 105 | 106 | # UV 107 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. 108 | # This is especially recommended for binary packages to ensure reproducibility, and is more 109 | # commonly ignored for libraries. 110 | #uv.lock 111 | 112 | # poetry 113 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 114 | # This is especially recommended for binary packages to ensure reproducibility, and is more 115 | # commonly ignored for libraries. 116 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 117 | #poetry.lock 118 | #poetry.toml 119 | 120 | # pdm 121 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 122 | # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. 123 | # https://pdm-project.org/en/latest/usage/project/#working-with-version-control 124 | #pdm.lock 125 | #pdm.toml 126 | .pdm-python 127 | .pdm-build/ 128 | 129 | # pixi 130 | # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. 131 | #pixi.lock 132 | # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one 133 | # in the .venv directory. It is recommended not to include this directory in version control. 134 | .pixi 135 | 136 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 137 | __pypackages__/ 138 | 139 | # Celery stuff 140 | celerybeat-schedule 141 | celerybeat.pid 142 | 143 | # SageMath parsed files 144 | *.sage.py 145 | 146 | # Environments 147 | .env 148 | .envrc 149 | .venv 150 | env/ 151 | venv/ 152 | ENV/ 153 | env.bak/ 154 | venv.bak/ 155 | 156 | # Spyder project settings 157 | .spyderproject 158 | .spyproject 159 | 160 | # Rope project settings 161 | .ropeproject 162 | 163 | # mkdocs documentation 164 | /site 165 | 166 | # mypy 167 | .mypy_cache/ 168 | .dmypy.json 169 | dmypy.json 170 | 171 | # Pyre type checker 172 | .pyre/ 173 | 174 | # pytype static type analyzer 175 | .pytype/ 176 | 177 | # Cython debug symbols 178 | cython_debug/ 179 | 180 | # PyCharm 181 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 182 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 183 | # and can be added to the global gitignore or merged into this file. For a more nuclear 184 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 185 | #.idea/ 186 | 187 | # Abstra 188 | # Abstra is an AI-powered process automation framework. 189 | # Ignore directories containing user credentials, local state, and settings. 190 | # Learn more at https://abstra.io/docs 191 | .abstra/ 192 | 193 | # Visual Studio Code 194 | # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore 195 | # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore 196 | # and can be added to the global gitignore or merged into this file. However, if you prefer, 197 | # you could uncomment the following to ignore the entire vscode folder 198 | # .vscode/ 199 | 200 | # Ruff stuff: 201 | .ruff_cache/ 202 | 203 | # PyPI configuration file 204 | .pypirc 205 | 206 | # Marimo 207 | marimo/_static/ 208 | marimo/_lsp/ 209 | __marimo__/ 210 | 211 | # Streamlit 212 | .streamlit/secrets.toml 213 | -------------------------------------------------------------------------------- /koreviz/plotlib.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import matplotlib.colors as colors 3 | import numpy as np 4 | import matplotlib 5 | 6 | mplMaj, mplMin, _ = matplotlib.__version__.split('.') 7 | 8 | 9 | def add_colorbar(im, aspect=40, pad_fraction=0.5, **kwargs): 10 | """Add a vertical color bar to an image plot.""" 11 | from mpl_toolkits import axes_grid1 12 | divider = axes_grid1.make_axes_locatable(im.axes) 13 | width = axes_grid1.axes_size.AxesY(im.axes, aspect=1./aspect) 14 | pad = axes_grid1.axes_size.Fraction(pad_fraction, width) 15 | current_ax = plt.gca() 16 | cax = divider.append_axes("right", size=width, pad=pad) 17 | plt.sca(current_ax) 18 | return im.axes.figure.colorbar(im, cax=cax, **kwargs) 19 | 20 | def default_cmap(field): 21 | field = field.lower() 22 | if field[0] == 'u' or field[:4]=='vort': 23 | try: 24 | import cmasher as cmr 25 | cm = cmr.prinsenvlag_r 26 | except: 27 | cm = 'seismic' 28 | elif field in ['ener','energy','ke','e']: 29 | try: 30 | import cmasher as cmr 31 | cm = cmr.ember 32 | except: 33 | cm = 'magma' 34 | elif field[0] in ['b','j']: 35 | try: 36 | import cmasher as cmr 37 | cm = cmr.holly_r 38 | except: 39 | cm = 'PRGn_r' 40 | elif field in ['t','temp','temperature']: 41 | try: 42 | import cmasher as cmr 43 | cm = cmr.sunburst 44 | except: 45 | cm = 'afmhot' 46 | elif field in ['comp','composition']: 47 | try: 48 | import cmasher as cmr 49 | cm = cmr.ocean 50 | except: 51 | cm = 'Blues_r' 52 | 53 | return cm 54 | 55 | def get_col_lims(dat,clim): 56 | if clim[0] == clim[1]: 57 | if (dat.min()<0) and (dat.max()>0) : 58 | datMax = (np.abs(dat)).max() 59 | datMin = -datMax 60 | else: 61 | datMax = dat.max() 62 | datMin = dat.min() 63 | else: 64 | datMin = min(clim) 65 | datMax = max(clim) 66 | 67 | datCenter = (datMin+datMax)/2 68 | 69 | return datMin,datCenter,datMax 70 | 71 | def hammer2cart(ttheta, pphi, colat=False): 72 | """ 73 | This function is used to define the Hammer projection used when 74 | plotting surface contours 75 | """ 76 | 77 | if not colat: # for lat and phi \in [-pi, pi] 78 | xx = ( 2.*np.sqrt(2.) * np.cos(ttheta)*np.sin(pphi/2.) 79 | /np.sqrt(1.+np.cos(ttheta)*np.cos(pphi/2.)) ) 80 | yy = ( np.sqrt(2.) * np.sin(ttheta) 81 | /np.sqrt(1.+np.cos(ttheta)*np.cos(pphi/2.)) ) 82 | else: # for colat and phi \in [0, 2pi] 83 | xx = ( -2.*np.sqrt(2.) * np.sin(ttheta)*np.cos(pphi/2.) 84 | /np.sqrt(1.+np.sin(ttheta)*np.sin(pphi/2.)) ) 85 | yy = ( np.sqrt(2.) * np.cos(ttheta) 86 | /np.sqrt(1.+np.sin(ttheta)*np.sin(pphi/2.)) ) 87 | return xx, yy 88 | 89 | 90 | def radContour(theta,phi,dat,levels=30,cmap='RdBu_r',clim=[0,0]): 91 | 92 | phi2D, theta2D = np.meshgrid(phi,theta,indexing='ij') 93 | xx,yy = hammer2cart(theta2D,phi2D,colat=True) 94 | 95 | datMin,datCenter,datMax = get_col_lims(dat,clim) 96 | 97 | divnorm = colors.TwoSlopeNorm(vmin=datMin, vcenter=datCenter, vmax=datMax) 98 | cont = plt.contourf(xx,yy,dat,levels,cmap=cmap,norm=divnorm) 99 | 100 | if mplMaj == '3' and int(mplMin) < 8: 101 | # matplotlib > 3.8 : cont is one collection 102 | # matplotlib < 3.8 : cont is a list of collections 103 | for c in cont.collections: 104 | c.set_edgecolor("face") 105 | else: 106 | cont.set_edgecolor("face") 107 | 108 | thB = np.linspace(np.pi/2, -np.pi/2, len(theta)) 109 | xxout, yyout = hammer2cart(thB, -np.pi-1e-3) 110 | xxin, yyin = hammer2cart(thB, np.pi+1e-3) 111 | 112 | plt.plot(xxout,yyout,'k',lw=0.6) 113 | plt.plot(xxin,yyin,'k',lw=0.6) 114 | 115 | return cont 116 | 117 | 118 | def merContour(r,theta,dat,levels=30,cmap='RdBu_r',clim=[0,0]): 119 | 120 | theta2D, r2D = np.meshgrid(theta,r,indexing='ij') 121 | xx = r2D * np.sin(theta2D) 122 | yy = r2D * np.cos(theta2D) 123 | 124 | datMin,datCenter,datMax = get_col_lims(dat,clim) 125 | 126 | divnorm = colors.TwoSlopeNorm(vmin=datMin, vcenter=datCenter, vmax=datMax) 127 | cont = plt.contourf(xx,yy,dat,levels,cmap=cmap,norm=divnorm) 128 | 129 | plt.plot(r[0]*np.sin(theta),r[0]*np.cos(theta),'k',lw=0.6) 130 | plt.plot(r[-1]*np.sin(theta),r[-1]*np.cos(theta),'k',lw=0.6) 131 | plt.plot([0,0], [ r.min(),r.max() ], 'k', lw=0.6) 132 | plt.plot([0,0], [ -r.max(),-r.min() ], 'k', lw=0.6) 133 | 134 | if mplMaj == '3' and int(mplMin) < 8: 135 | # matplotlib > 3.8 : cont is one collection 136 | # matplotlib < 3.8 : cont is a list of collections 137 | for c in cont.collections: 138 | c.set_edgecolor("face") 139 | else: 140 | cont.set_edgecolor("face") 141 | 142 | return cont 143 | 144 | 145 | def eqContour(r,phi,dat,levels=30,cmap='RdBu_r',clim=[0,0]): 146 | 147 | phi2D, r2D = np.meshgrid(phi,r,indexing='ij') 148 | xx = r2D * np.cos(phi2D) 149 | yy = r2D * np.sin(phi2D) 150 | 151 | datMin,datCenter,datMax = get_col_lims(dat,clim) 152 | 153 | divnorm = colors.TwoSlopeNorm(vmin=datMin, vcenter=datCenter, vmax=datMax) 154 | cont = plt.contourf(xx,yy,dat,levels,cmap=cmap,norm=divnorm) 155 | 156 | plt.plot(r[0]*np.cos(phi), r[0]*np.sin(phi),'k',lw=0.6) 157 | plt.plot(r[-1]*np.cos(phi), r[-1]*np.sin(phi),'k',lw=0.6) 158 | 159 | if mplMaj == '3' and int(mplMin) < 8: 160 | # matplotlib > 3.8 : cont is one collection 161 | # matplotlib < 3.8 : cont is a list of collections 162 | for c in cont.collections: 163 | c.set_edgecolor("face") 164 | else: 165 | cont.set_edgecolor("face") 166 | 167 | return cont 168 | -------------------------------------------------------------------------------- /tools/shear_layer_profile.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy.sparse as ss 3 | import scipy.sparse.linalg as ssl 4 | import sys 5 | import matplotlib.pyplot as plt 6 | import matplotlib 7 | import matplotlib.tri as tri 8 | import numpy.polynomial.chebyshev as ch 9 | 10 | import utils as ut 11 | import parameters as par 12 | 13 | ''' 14 | 15 | Script to plot meridional cuts of the flow 16 | Use as: 17 | 18 | python3 shear_layer_profile.py nsol z0 nl 19 | 20 | nsol: solution number 21 | z0 : profile intersection with z axis 22 | nl : number of points along profile 23 | 24 | it will compute the profile between 25 | -5*E**(1/3) and 5*E**(1/3) 26 | as measured from the characteristic 27 | coming from the inner core critical latitude 28 | 29 | ''' 30 | 31 | 32 | solnum = int(sys.argv[1]) 33 | z0 = float(sys.argv[2]) 34 | nl = int(sys.argv[3]) 35 | w0 = ut.wf 36 | 37 | lmax = par.lmax 38 | m = par.m 39 | symm = par.symm 40 | N = par.N 41 | Ek = par.Ek 42 | ricb = par.ricb 43 | rcmb = 1 44 | n = ut.n 45 | 46 | gap = rcmb-ricb 47 | 48 | a = np.loadtxt('real_flow.field',usecols=solnum) 49 | b = np.loadtxt('imag_flow.field',usecols=solnum) 50 | 51 | if m > 0 : 52 | symm1 = symm 53 | if symm == 1: 54 | m_top = m 55 | m_bot = m+1 # equatorially symmetric case (symm=1) 56 | lmax_top = lmax 57 | lmax_bot = lmax+1 58 | elif symm == -1: 59 | m_top = m+1 60 | m_bot = m # equatorially antisymmetric case (symm=-1) 61 | lmax_top = lmax+1 62 | lmax_bot = lmax 63 | elif m == 0 : 64 | symm1 = -symm 65 | if symm == 1: 66 | m_top = 2 67 | m_bot = 1 # equatorially symmetric case (symm=1) 68 | lmax_top = lmax+2 69 | lmax_bot = lmax+1 70 | elif symm == -1: 71 | m_top = 1 72 | m_bot = 2 # equatorially antisymmetric case (symm=-1) 73 | lmax_top = lmax+1 74 | lmax_bot = lmax+2 75 | 76 | 77 | 78 | Plj0 = a[:n] + 1j*b[:n] # N elements on each l block 79 | Tlj0 = a[n:n+n] + 1j*b[n:n+n] # N elements on each l block 80 | 81 | Plj = np.reshape(Plj0,(int((lmax-m+1)/2),N)) 82 | Tlj = np.reshape(Tlj0,(int((lmax-m+1)/2),N)) 83 | dPlj = np.zeros(np.shape(Plj),dtype=complex) 84 | 85 | Plr = np.zeros((int((lmax-m+1)/2), nl),dtype=complex) 86 | dP = np.zeros((int((lmax-m+1)/2), nl),dtype=complex) 87 | rP = np.zeros((int((lmax-m+1)/2), nl),dtype=complex) 88 | Qlr = np.zeros((int((lmax-m+1)/2), nl),dtype=complex) 89 | Slr = np.zeros((int((lmax-m+1)/2), nl),dtype=complex) 90 | Tlr = np.zeros((int((lmax-m+1)/2), nl),dtype=complex) 91 | 92 | ll = np.arange(m_top,lmax_top,2) 93 | L = ss.diags(ll*(ll+1),0) 94 | 95 | for k in range(np.size(ll)): 96 | dPlj[k,:] = ut.Dcheb(Plj[k,:], ricb, rcmb) 97 | 98 | 99 | m1 = max(m,1) 100 | if m == 0 : 101 | lmax1 = lmax+1 102 | else: 103 | lmax1 = lmax 104 | l1 = np.arange(m1,lmax1+1) # vector with all l's allowed whether for T or P 105 | 106 | clm = np.zeros((lmax-m+2,1)) 107 | for i,l in enumerate(l1): 108 | clm[i] = np.sqrt((l-m)*(l+m)) 109 | 110 | # start index for l. Do not confuse with indices for the Cheb expansion! 111 | idP = int( (1-symm1)/2 ) 112 | idT = int( (1+symm1)/2 ) 113 | 114 | plx = idP+lmax-m+1 115 | tlx = idT+lmax-m+1 116 | 117 | 118 | phi = 0. # select meridional cut 119 | theta_c = np.arcsin(w0/2) 120 | 121 | #matplotlib.rcParams['text.usetex'] = True 122 | #matplotlib.rcParams['image.cmap'] = 'rainbow' 123 | 124 | #fig=plt.figure(figsize=(12,5)) 125 | #ax1=fig.add_subplot(111) 126 | #ax1.set_title(r'Velocity profile',size=14) 127 | 128 | ur = np.zeros( nl, dtype=complex) 129 | utheta = np.zeros( nl, dtype=complex) 130 | uphi = np.zeros( nl, dtype=complex) 131 | 132 | 133 | #for k,z0 in enumerate(np.linspace(za,zb,nz)): 134 | 135 | l0 = ricb - z0*(w0/2) 136 | 137 | la = -5*Ek**(1/3) 138 | lb = 5*Ek**(1/3) 139 | 140 | lc = np.linspace(la,lb,nl) + l0 141 | 142 | theta = np.array([np.arccos( (z0 + l3*np.sin(theta_c))/np.sqrt(z0**2+l3**2+2*z0*l3*np.sin(theta_c))) for l3 in lc]) 143 | 144 | r = np.array([z0*(np.cos(tht)+np.sin(tht)*np.tan(tht+theta_c)) for tht in theta]) 145 | 146 | x = np.array([2.*(rr-ricb)/gap - 1. for rr in r]) 147 | 148 | chx = ch.chebvander(x,par.N-1) # this matrix has nl rows and N-1 cols 149 | 150 | 151 | np.matmul( Plj, chx.T, Plr ) 152 | np.matmul( Tlj, chx.T, Tlr ) 153 | 154 | rI = ss.diags(r**-1,0) 155 | 156 | np.matmul(dPlj, chx.T, dP) 157 | 158 | rP = Plr * ss.diags(r**-1,0) 159 | Qlr = ss.diags(ll*(ll+1),0) * rP 160 | Slr = rP + dP 161 | 162 | for j in range(nl): 163 | 164 | ylm = np.r_[ut.Ylm_full(lmax, m, theta[j], phi),0] 165 | 166 | ur[j] = np.dot( Qlr[:,j], ylm[idP:plx:2] ) 167 | 168 | tmp1 = np.dot( -(l1[idP:plx:2]+1) * Slr[:,j]/np.tan(theta[j]), ylm[idP:plx:2] ) 169 | tmp2 = np.dot( clm[idP+1:plx+1:2,0] * Slr[:,j]/np.sin(theta[j]), ylm[idP+1:plx+1:2] ) 170 | tmp3 = np.dot( 1j*m * Tlr[:,j]/np.sin(theta[j]), ylm[idT:tlx:2] ) 171 | utheta[j] = tmp1+tmp2+tmp3 172 | 173 | tmp1 = np.dot( (l1[idT:tlx:2]+1) * Tlr[:,j]/np.tan(theta[j]), ylm[idT:tlx:2] ) 174 | tmp2 = np.dot( -clm[idT+1:tlx+1:2,0] * Tlr[:,j]/np.sin(theta[j]), ylm[idT+1:tlx+1:2] ) 175 | tmp3 = np.dot( 1j*m * Slr[:,j]/np.sin(theta[j]), ylm[idP:plx:2] ) 176 | uphi[j] = tmp1+tmp2+tmp3 177 | 178 | #ax1.plot(lc-l0,np.absolute(ur[:,k]),'r-',label=r'$|u_r|$') 179 | #ax1.plot(lc-l0,np.absolute(utheta[:,k]),'g-',label=r'$|u_\theta|$') 180 | #ax1.plot(lc-l0,np.absolute(uphi[:,k]),'b-',label=r'$|u_\phi|$') 181 | 182 | #ax1.legend() 183 | 184 | #plt.tight_layout() 185 | #plt.show() 186 | 187 | u = np.absolute(ur) 188 | 189 | umax = np.max(u) 190 | 191 | print('Max ur =',umax) 192 | 193 | k0 = u==umax 194 | dtat = lc[k0]-l0 195 | print('Pos =',dtat[0]) 196 | 197 | k = u>=0.7*umax 198 | tmp = lc[k] 199 | slw7 = np.max(tmp)-np.min(tmp) 200 | print('Width(0.7) =',slw7) 201 | 202 | k = u>=0.8*umax 203 | tmp = lc[k] 204 | slw8 = np.max(tmp)-np.min(tmp) 205 | print('Width(0.8) =',slw8) 206 | 207 | k = u>=0.9*umax 208 | tmp = lc[k] 209 | slw9 = np.max(tmp)-np.min(tmp) 210 | print('Width(0.9) =',slw9) 211 | 212 | with open('slayer.dat','ab') as sla: 213 | np.savetxt(sla,np.c_[umax, dtat[0], slw7, slw8, slw9]) 214 | 215 | 216 | -------------------------------------------------------------------------------- /tools/subramp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ###PBS -l nodes=1:ppn=24 3 | ###PBS -l mem=245g 4 | ###PBS -l select=1:ncpus=24:mem=54gb 5 | #PBS -l select=1:ncpus=24:mem=248gb 6 | #PBS -N somename 7 | #PBS -l walltime=03:00:00 8 | 9 | 10 | 11 | source $HOME/venv00/bin/activate 12 | 13 | 14 | ncpus=24 15 | 16 | dir=somedir 17 | 18 | cd $HOME/data/$dir 19 | 20 | 21 | 22 | if [ -f no_conv_solution ]; then 23 | rm no_conv_solution 24 | fi 25 | if [ -f big_error ]; then 26 | rm big_error 27 | fi 28 | 29 | #./submatrices.py $ncpus >> out00 30 | #mpiexec -n $ncpus ./assemble.py >> out0 31 | 32 | #rm *.dat out* 33 | 34 | # For forced problems use the following options: 35 | 36 | #export opts='-ksp_type preonly -pc_type lu' 37 | #export opts='-ksp_type gmres -pc_type lu -pc_factor_mat_solver_type mumps -ksp_monitor_true_residual -ksp_monitor -ksp_converged_reason' 38 | #export opts='-ksp_type preonly -pc_type lu -pc_factor_mat_solver_type mumps -mat_mumps_icntl_14 2000 -ksp_monitor_true_residual -ksp_monitor -ksp_converged_reason' 39 | #export opts='-ksp_type preonly -pc_type lu -pc_factor_mat_solver_type superlu_dist -ksp_monitor -ksp_converged_reason' 40 | #export opts='-ksp_type gmres -pc_type lu -pc_factor_mat_solver_type superlu_dist -ksp_monitor -ksp_converged_reason -mat_superlu_dist_iterrefine 1 -mat_superlu_dist_colperm PARMETIS -mat_superlu_dist_parsymbfact 1' 41 | #export opts='-ksp_type preonly -pc_type lu -pc_factor_mat_solver_type superlu_dist -ksp_monitor -ksp_converged_reason -mat_superlu_dist_iterrefine 1 -mat_superlu_dist_colperm PARMETIS -mat_superlu_dist_parsymbfact 1' 42 | 43 | 44 | # For eigenvalue problems use: 45 | 46 | #export opts='-st_type cayley -eps_error_relative ::ascii_info_detail' 47 | #export opts='-st_type sinvert -eps_error_relative ::ascii_info_detail' 48 | #export opts='-st_type sinvert -eps_error_relative ::ascii_info_detail -eps_balance oneside -pc_factor_mat_solver_type mumps -mat_mumps_icntl_14 1000' 49 | #export opts='-st_type sinvert -eps_error_relative ::ascii_info_detail -pc_factor_mat_solver_type mumps -mat_mumps_icntl_14 1000 -mat_mumps_icntl_23 8000' 50 | export opts='-st_type sinvert -eps_error_relative ::ascii_info_detail -pc_factor_mat_solver_type mumps -mat_mumps_icntl_14 10000 -eps_balance twoside' 51 | #export opts='-st_type sinvert -st_ksp_type preonly -st_pc_type lu -st_pc_factor_mat_solver_type superlu_dist' 52 | #export opts='-st_type sinvert -st_ksp_type preonly -st_pc_type lu -st_pc_factor_mat_solver_type superlu_dist' 53 | #export opts='-st_type sinvert -st_ksp_type preonly -st_pc_type lu -eps_error_relative ::ascii_info_detail -st_pc_factor_mat_solver_type superlu_dist -mat_superlu_dist_iterrefine 1 -mat_superlu_dist_colperm PARMETIS -mat_superlu_dist_parsymbfact 1' 54 | #export opts='-st_type sinvert -st_ksp_type preonly -st_pc_type lu -eps_error_relative ::ascii_info_detail -st_pc_factor_mat_solver_type superlu_dist -mat_superlu_dist_iterrefine 1 -mat_superlu_dist_colperm PARMETIS -mat_superlu_dist_parsymbfact 1 -eps_converged_reason -eps_conv_rel -eps_monitor_conv -eps_true_residual 1 -eps_balance oneside' 55 | #export opts='-st_type sinvert -st_ksp_type preonly -st_pc_type lu -eps_error_relative ::ascii_info_detail -st_pc_factor_mat_solver_type superlu_dist -mat_superlu_dist_iterrefine 1 -mat_superlu_dist_colperm PARMETIS -mat_superlu_dist_parsymbfact 1 -eps_converged_reason -eps_conv_rel -eps_monitor_conv -eps_true_residual 1' 56 | 57 | 58 | #for j in $(seq 4 1 15) 59 | for j in $(seq 1 1 1) 60 | #for j in $(seq 0.67 0.003 0.99) 61 | do 62 | 63 | #sed -i 's,^\(ricb[ ]*=\).*,\1'$j',g' bin/parameters.py 64 | 65 | #./bin/submatrices.py $ncpus >> out00 66 | #mpiexec -n $ncpus ./bin/assemble.py >> out0 67 | 68 | 69 | #for i in $(seq 0.05 0.05 1.0) 70 | for i in $(seq 1 1 1) 71 | do 72 | #sed -i 's,^\(itau[ ]*=\).*,\1'$i',g' bin/parameters.py 73 | #sed -i 's,^\(Ek[ ]*=\).*,\1'10**$i',g' bin/parameters.py 74 | #sed -i 's,^\(m[ ]*=\).*,\1'$i',g' bin/parameters.py 75 | #sed -i 's,^\(delta[ ]*=\).*,\1'$i',g' bin/parameters.py 76 | #sed -i 's,^\(ricb[ ]*=\).*,\1'$i',g' bin/parameters.py 77 | 78 | #echo $i 79 | 80 | ./bin/submatrices.py $ncpus >> out00 81 | mpiexec -n $ncpus ./bin/assemble.py >> out0 82 | #mpiexec -n $ncpus ./bin/solve.py $opts >> out1 83 | 84 | #for k in $(seq 1 1 1) 85 | for k in $(seq 1 1 3 ) 86 | do 87 | 88 | #if [ -f no_conv_solution ] && [ -f track_target ]; then 89 | # echo 'No converged solution, stopping' 90 | # break 91 | #fi 92 | #if [ -f big_error ] && [ -f track_target ]; then 93 | # echo 'Error too big, stopping' 94 | # break 95 | #fi 96 | 97 | rnd1=$(echo | awk -v seed=$RANDOM 'srand(seed) {print (2*rand()-1)}') 98 | #echo $rnd1 99 | rnd2=$(echo | awk -v seed=$RANDOM 'srand(seed) {print (2*rand()-1)}') 100 | #echo $rnd2 101 | #sed -i 's,^\(Ek[ ]*=\).*,\1'10**$k',g' bin/parameters.py 102 | #sed -i 's,^\(itau0[ ]*=\).*,\1'$k',g' bin/parameters.py 103 | sed -i 's,^\(rnd1[ ]*=\).*,\1'$rnd1',g' bin/parameters.py 104 | sed -i 's,^\(rnd2[ ]*=\).*,\1'$rnd2',g' bin/parameters.py 105 | #sed -i 's,^\(delta[ ]*=\).*,\1'$k',g' bin/parameters.py 106 | #sed -i 's,^\(N[ ]*=\).*,\1'$k',g' bin/parameters.py 107 | #sed -i 's,^\(Lambda[ ]*=\).*,\1'$k',g' bin/parameters.py 108 | #sed -i 's,^\(Le[ ]*=\).*,\1'10**$k',g' bin/parameters.py 109 | #sed -i 's,^\(m[ ]*=\).*,\1'$k',g' bin/parameters.py 110 | #sed -i 's,^\(rc[ ]*=\).*,\1'$k',g' bin/parameters.py 111 | 112 | #echo 'Solving for Pm = 10**'$k >> out1 113 | 114 | #rm *.dat out* 115 | 116 | #./bin/submatrices.py $ncpus >> out00 117 | #mpiexec -n $ncpus ./bin/assemble.py >> out0 118 | 119 | #python3 tools/just_induction.py 120 | #mpiexec -n $ncpus ./bin/solve_ind.py $opts >> out2 121 | 122 | mpiexec -n $ncpus ./bin/solve.py $opts >> out1 123 | 124 | #cp tools/underflow.py . 125 | #deactivate 126 | #source $HOME/venv_shtns2/bin/activate 127 | #export PYTHONPATH=$HOME/venv_shtns2/lib64/python-3.6/site-packages 128 | 129 | #mv real_magnetic_ind.field real_magnetic.field 130 | #mv imag_magnetic_ind.field imag_magnetic.field 131 | 132 | #rm *max.dat 133 | #python3 underflow.py 134 | 135 | #python3 bin/find_Rac_pbs.py >> out2 136 | 137 | done 138 | 139 | done 140 | 141 | done 142 | 143 | rm *.npz *.mtx 144 | 145 | -------------------------------------------------------------------------------- /tools/getresults.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import numpy as np 3 | 4 | ''' 5 | Reads results collected with the reap.sh script into python. 6 | Using IPython do: 7 | 8 | %run -i getresults.py somename 9 | 10 | where 'somename' is the prefix used when creating and collecting 11 | the results, i.e. with dodirs.sh and reap.sh 12 | ''' 13 | 14 | ''' 15 | params: [Ek, m, symm, ricb, bci, bco, projection, forcing, \ 16 | forcing_amplitude, forcing_frequency, magnetic, Em, Le2, N, lmax, toc1-tic, ncpus] 17 | 18 | ken_dis: [KP, KT, internal_dis, rekin_dis, imkin_dis, repower, impower] 19 | ''' 20 | 21 | if len(sys.argv) == 2: 22 | u = np.loadtxt(sys.argv[1]+'.flo') # flow data 23 | p = np.loadtxt(sys.argv[1]+'.par') # parameters 24 | else: 25 | u = np.loadtxt('flow.dat') # flow data 26 | p = np.loadtxt('params.dat') # parameters 27 | 28 | if len(u.shape)==1: 29 | u = u.reshape((-1,len(u))) 30 | p = p.reshape((-1,len(p))) 31 | 32 | KP = u[:,0] # Poloidal kinetic energy 33 | KT = u[:,1] # Toroidal kinetic energy 34 | K = KP + KT 35 | t2p = KT/KP 36 | p2t = KP/KT 37 | 38 | ricb = p[:,3] # inner core radius 39 | wf = p[:,9] # forcing frequency 40 | Ek = p[:,0] # Ekman number 41 | ek = np.log10(Ek).round(decimals=6) 42 | 43 | Dkin0 = u[:,3] # Kinetic energy dissipation 44 | Dint0 = u[:,2] # Internal energy dissipation 45 | rpow = u[:,5] # Input power for forced problems, real part 46 | ipow = u[:,6] # Input power for forced problems, imaginary part 47 | 48 | 49 | forcing = p[:,7] 50 | 51 | bci = p[:,4] # inner core boundary condition 52 | bco = p[:,5] # cmb boundary condition 53 | amp = p[:,8] # forcing amplitude (cmb) 54 | 55 | if np.shape(p)[1]>=15: 56 | N = p[:,13] 57 | lmax = p[:,14] 58 | 59 | 60 | if sum(forcing) == 0: # reads eigenvalue data 61 | 62 | if len(sys.argv) == 2: 63 | w = np.loadtxt(sys.argv[1]+'.eig') 64 | else: 65 | w = np.loadtxt('eigenvalues.dat') 66 | if len(w.shape)==1: 67 | w = w.reshape((-1,len(w))) 68 | 69 | sigma = w[:,0] 70 | pss = np.zeros( np.shape(p)[0] ) 71 | pvf = np.zeros( np.shape(p)[0] ) 72 | scd = -sigma/np.sqrt(Ek) 73 | 74 | elif sum(forcing) == 7*np.shape(p)[0]: # libration, boundary forcing 75 | sigma = np.zeros( np.shape(p)[0] ) 76 | pvf = np.zeros( np.shape(p)[0] ) 77 | pss = rpow 78 | 79 | elif sum(forcing) == 8*np.shape(p)[0]: # libration, volume forcing 80 | sigma = np.zeros( np.shape(p)[0] ) 81 | pss = np.zeros( np.shape(p)[0] ) 82 | pvf = rpow 83 | 84 | elif sum(forcing) == 9*np.shape(p)[0]: # m=2 boundary forcing, needs fixing 85 | sigma = np.zeros( np.shape(p)[0] ) 86 | pss = rpow 87 | pvf = np.zeros( np.shape(p)[0] ) 88 | 89 | #if shape(u)[1]>=10: 90 | # viscous dissipation in the bulk, without boundary layers 91 | #vd1 = Dint - (u[:,7] + u[:,8]) 92 | #vd2 = Dint - (u[:,7] + u[:,9]) 93 | 94 | if np.shape(u)[1]>=12: 95 | # cmb torque, use 2*real part of this 96 | trq = u[:,10] + 1j*u[:,11] 97 | 98 | if np.shape(u)[1]>=14: 99 | # icb torque, use 2*real part of this 100 | trq_icb = u[:,12] + 1j*u[:,13] 101 | 102 | if np.shape(p)[1]>=17: 103 | tsol = p[:,15] 104 | ncpus = p[:,16] 105 | 106 | if np.shape(p)[1]>=21: 107 | tol = p[:,17] 108 | thermal = p[:,18] 109 | Prandtl = p[:,19] 110 | Brunt = p[:,20] 111 | 112 | # use this for the Ra_c, rXX_E-7 runs in the cluster 113 | #Ra_gap = p[:,18] 114 | #Prandtl = p[:,19] 115 | #thermal = p[:,20] 116 | 117 | 118 | magnetic = p[:,10] 119 | if np.shape(p)[1]>=30: 120 | time_scale = p[:,29] 121 | else: 122 | time_scale = 0 123 | 124 | 125 | if sum(magnetic) == np.shape(p)[0]: # reads magnetic data 126 | 127 | if len(sys.argv) == 2: 128 | b = np.loadtxt(sys.argv[1]+'.mag') 129 | else: 130 | b = np.loadtxt('magnetic.dat') 131 | 132 | if len(b.shape)==1: 133 | b = b.reshape((-1,len(b))) 134 | 135 | M0 = b[:,0] + b[:,1] # Total magnetic field energy 136 | Le2 = p[:,12] # Lehnert number squared 137 | Em = p[:,11] # Magnetic Ekman number 138 | Dohm0 = (b[:,2]+b[:,3]) # Ohmic dissipation 139 | Le = np.sqrt(Le2) # Lehnert number 140 | Pm = Ek/Em # Magnetic Prandtl number 141 | Lam = Le2/Em # Elsasser number 142 | 143 | pm = np.log10(Pm).round(decimals=4) 144 | ss = np.log10(Lam).round(decimals=4); 145 | 146 | #if shape(b)[1]>=7 : 147 | # od1 = Dohm - (b[:,4] + b[:,5]) 148 | # od2 = Dohm - (b[:,4] + b[:,6]) 149 | # d1 = od1/vd1 # dissip ratio in the bulk, without boundary layers 150 | # d2 = od2/vd2 # a bit deeper in the bulk 151 | 152 | else: 153 | 154 | M0 = np.zeros( np.shape(p)[0] ) 155 | Dohm0 = np.zeros( np.shape(p)[0] ) 156 | 157 | Le2 = p[:,12] # Lehnert number squared 158 | Em = p[:,11] # Magnetic Ekman number 159 | 160 | if time_scale == 0: 161 | M = M0*( Le2 ) 162 | 163 | Dint = Dint0*( Ek ) 164 | Dkin = Dkin0*( Ek ) 165 | Dohm = Dohm0*( Le2*Em ) 166 | elif time_scale == 1: 167 | M = M0 168 | 169 | Dint = Dint0*( Ek/Le ) 170 | Dkin = Dkin0*( Ek/Le ) 171 | Dohm = Dohm0*( Em/Le ) 172 | elif time_scale == 2: 173 | M = M0* ( Le2/(Ek**2) ) 174 | 175 | Dint = Dint0 176 | Dkin = Dkin0 177 | Dohm = Dohm0* ( Le2*Em/(Ek**2) ) 178 | 179 | d = Dohm/Dint # Ohmic to viscous dissipation ratio 180 | 181 | 182 | 183 | if sum(thermal) == np.shape(p)[0]: # reads thermal data 184 | 185 | if len(sys.argv) == 2: 186 | th = np.loadtxt(sys.argv[1]+'.thm') 187 | else: 188 | th = np.loadtxt('thermal.dat') 189 | 190 | #if len(th.shape)==1: 191 | # th = th.reshape((-1,len(th))) 192 | # Dtemp = th[:,0] 193 | #elif size(th)==1: 194 | # Dtemp = array([th]) 195 | Dtemp = np.array([th])[0] 196 | 197 | else: 198 | 199 | Dtemp = np.zeros( np.shape(p)[0] ) 200 | 201 | 202 | resid1 = abs( Dint + Dkin - pss ) / np.amax( [ abs(Dint), abs(Dkin), abs(pss) ], 0 ) 203 | 204 | resid2 = abs( 2*sigma*(K + M) - Dkin - Dtemp + Dohm - pvf ) / \ 205 | np.amax( [ abs(2*sigma*(K+M)), abs(Dkin), abs(Dohm), abs(Dtemp), abs(pvf) ], 0 ) 206 | 207 | 208 | 209 | 210 | 211 | 212 | -------------------------------------------------------------------------------- /bin/mesa_profiles.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import utils as ut 3 | import parameters as par 4 | #import pygyre as gy 5 | import scipy.interpolate as si 6 | import mesa_reader as mr 7 | 8 | 9 | #------------------------------------------------------------------------------------------------------------- 10 | # -------------------------------------------------------------------------- Mesa-derived background profiles 11 | #------------------------------------------------------------------------------------------------------------- 12 | 13 | def density(r): 14 | ''' 15 | Density, normalized by its value at the star's center. 16 | ''' 17 | #m = gy.read_model(par.mesa_file) 18 | #xm = m['x'] 19 | #ym = m['rho/rho_0'] 20 | prf = mr.MesaData(par.mesa_file) 21 | xm = np.flip(prf.data('radius_cm')) 22 | ym = np.flip(prf.data('density')) 23 | interp = si.Akima1DInterpolator(xm/xm[-1], ym/ym[0]) 24 | out = np.zeros_like(r) 25 | for i,x in enumerate(r): 26 | if x>=0: 27 | out[i] = interp(x) 28 | else: 29 | out[i] = interp(-x) # even function of r 30 | return out #even 31 | 32 | 33 | def gravity(r): 34 | ''' 35 | Magnitude of the gravitational acceleration, normalized by its value at the star's surface. 36 | ''' 37 | #m = gy.read_model(par.mesa_file) 38 | #xm = m['x'] 39 | #ym = m['dtheta'] 40 | prf = mr.MesaData(par.mesa_file) 41 | xm = np.flip(prf.data('radius_cm')) 42 | ym = np.flip(prf.data('grav')) 43 | interp = si.Akima1DInterpolator(xm/xm[-1], ym/ym[-1]) 44 | out = np.zeros_like(r) 45 | for i,x in enumerate(r): 46 | if x>=0: 47 | out[i] = interp(x) 48 | else: 49 | out[i] = -interp(-x) # odd function of r 50 | return out # odd 51 | 52 | 53 | def temperature(r): 54 | ''' 55 | Temperature, normalized by its value at the star's center. 56 | ''' 57 | #m = gy.read_model(par.mesa_file) 58 | #xm = m['x'] 59 | #ym = m['theta'] 60 | prf = mr.MesaData(par.mesa_file) 61 | xm = np.flip(prf.data('radius_cm')) 62 | ym = np.flip(prf.data('temperature')) 63 | interp = si.Akima1DInterpolator(xm/xm[-1], ym/ym[0]) 64 | out = np.zeros_like(r) 65 | for i,x in enumerate(r): 66 | if x>=0: 67 | out[i] = interp(x) 68 | else: 69 | out[i] = interp(-x) # even function of r 70 | return out 71 | 72 | 73 | def BruVa2(r): 74 | ''' 75 | The squared, dimensionless Brunt-Vaisala frequency, in units of GM/R^3. 76 | ''' 77 | #m = gy.read_model(par.mesa_file) 78 | #xm = m['x'] 79 | #ym = m['dtheta'] 80 | 81 | prf = mr.MesaData(par.mesa_file) 82 | xm = np.flip(prf.data('radius_cm')) 83 | ym = 3 * np.flip(prf.data('brunt_N2_dimensionless')) # MESA uses 3*GM/R^3 instead of GM/R^3 84 | ym[xm/xm[-1]>par.r_cutoff] = 0 # zero out the atmosphere, stinkin atmosphere 85 | ym[ym<0]=0 # zero out convective zones 86 | 87 | interp = si.Akima1DInterpolator(xm/xm[-1], ym) 88 | out = np.zeros_like(r) 89 | for i,x in enumerate(r): 90 | if x>=0: 91 | out[i] = interp(x) 92 | else: 93 | out[i] = interp(-x) # even function of r 94 | #out = r**2-r**4 # even 95 | return out # even 96 | 97 | # --------------------------------------------------------------------------------------- 98 | # The functions below are directly linked to the ones above, no user intervention needed. 99 | # --------------------------------------------------------------------------------------- 100 | 101 | def thermal_diffusivity(r): 102 | out = np.ones_like(r) 103 | return out 104 | 105 | def log_thermal_diffusivity(r): 106 | out = np.log(thermal_diffusivity(r)) 107 | return out 108 | 109 | def buoFac(r): 110 | 111 | #Profile of buoyancy = rho*alpha*T*g 112 | #Ideal gas : alpha = 1/T 113 | 114 | out = density(r)*gravity(r) 115 | return out 116 | 117 | def krT(r): 118 | ''' 119 | Thermal diffusivity * density * temperature 120 | ''' 121 | out = thermal_diffusivity(r) * density(r) * temperature(r) 122 | return out # even*even*even = even function of r 123 | 124 | 125 | def roT(r): 126 | ''' 127 | density * temperature 128 | ''' 129 | out = density(r) * temperature(r) 130 | return out # even*even = even function of r 131 | 132 | 133 | # def dTdr(r): 134 | # ''' 135 | # Gradient of temperature. 136 | # ''' 137 | # #dck = ut.chebify(tempe,1,par.tol)[:,1] 138 | # #out = ut.funcheb( dck, r, par.ricb, ut.rcmb, 0) 139 | # m = gy.read_model(par.mesa_file) 140 | # xm = m['x'] 141 | # ym = m['dtheta'] * m['z'][-1] 142 | # interp = si.make_interp_spline(xc, ym, k=3) 143 | # out = np.zeros_like(r) 144 | # for i,x in enumerate(r): 145 | # if x>=0: 146 | # out[i] = interp(x) 147 | # else: 148 | # out[i] = -interp(-x) # odd 149 | # return out # odd function of r 150 | 151 | 152 | def TdS(r): 153 | ''' 154 | r * temperature * entropy gradient (Glatzmaier2014, eq. 12.9), 155 | par.gamma is the adiabatic index (e.g. use par.gamma=5/3 for a monoatomic perfect gas) 156 | ''' 157 | # m = gy.read_model(par.mesa_file) 158 | # z0 = m['z'][-1] 159 | # dtheta0 = m['dtheta'][-1] 160 | # n = m['n_poly'][-1] # we assume there's just one single polytrope 161 | # gamma0 = (n+1) * z0 * (-dtheta0) * (1-1/par.gamma) 162 | # out = dTdr(r) + gamma0 * gravity(r) # odd+odd = odd 163 | 164 | prf = mr.MesaData(par.mesa_file) 165 | 166 | xm = np.flip(prf.data('radius_cm')) 167 | r0 = xm[-1] # star radius 168 | 169 | g = np.flip(prf.data('grav')) 170 | g0 = g[-1] # gravity at the surface 171 | 172 | T = np.flip(prf.data('temperature')) 173 | T0 = T[0] # temperature at the center 174 | 175 | N2 = np.flip(prf.data('brunt_N2'))/(g0/r0) # dimensionless BV freq squared (i.e. in units of GM/R^3) 176 | N2[xm/r0>par.r_cutoff] = 0 # zero out the atmosphere, stinkin atmosphere 177 | 178 | ym = N2 * (T/T0) / (g/g0) # if all variables dimensionless then T*(ds/dr) = N2*T/g 179 | ym[ym<0]=0 # zero out convective zones 180 | 181 | interp = si.Akima1DInterpolator(xm/r0, ym) 182 | out = np.zeros_like(r) 183 | for i,x in enumerate(r): 184 | if x>=0: 185 | out[i] = interp(x) 186 | else: 187 | out[i] = -interp(-x) # N2*T/g is an odd function of r 188 | 189 | return out # odd function of r 190 | 191 | 192 | def tds(r): 193 | out = BruVa2(r) * temperature(r) / gravity(r) 194 | return out # even * even / odd = odd 195 | 196 | def log_density(r): 197 | out = np.log(density(r)) 198 | return out 199 | 200 | def viscosity(r): 201 | return np.zeros_like(r) 202 | 203 | def log_temperature(r): 204 | out = np.log(temperature(r)) 205 | return out 206 | 207 | def kappa_rho(r): 208 | out = thermal_diffusivity(r)*density(r) 209 | return out 210 | -------------------------------------------------------------------------------- /docs/page3.md: -------------------------------------------------------------------------------- 1 | # Non-dimensionalization 2 | 3 | 4 | For numerical convenience we use *dimensionless* variables when solving the Navier Stokes equation, the induction equation, and the heat equation. A non-dimensionalization procedure is always possible thanks to the Buckingham-$\pi$ theorem. 5 | 6 | 7 | 8 | ## The momentum equation 9 | 10 | In a reference frame rotating with angular speed $\Omega$, the dimensional form of the *linear* momentum equation (i.e. the Navier-Stokes equation) describing the acceleration $\partial_t \mathbf{u}$ of a small fluid parcel with density $\rho$, including buoyancy and the Lorentz force, is: 11 | 12 | $$ 13 | \rho\partial_t \mathbf{u} +2\rho\,\mathbf{\Omega}\times\mathbf{u}=-\nabla p +\frac{1}{\mu_0}(\nabla \times \mathbf{B})\times \mathbf{B} + \rho\mathbf{g}+\rho\nu\nabla^2{\mathbf{u}}, 14 | $$ 15 | 16 | where $p$ is the reduced pressure, $\mathbf{B}$ the magnetic field, and $\nu$ is the kinematic viscosity of the fluid. Assume small density perturbations $\rho'$ following 17 | 18 | $$ 19 | \rho=\rho_0+\rho'=\rho_0-\rho_0\alpha\theta, 20 | $$ 21 | 22 | where $\alpha$ is the fluid's thermal expansion coefficient and $\theta$ is the temperature variation from the isentropic temperature profile $T(r)$. 23 | Assume also a gravitational acceleration following 24 | 25 | $$ 26 | \mathbf{g}=-g_0\frac{r}{R}\mathbf{\hat r}. 27 | $$ 28 | 29 | Within the *Boussinesq approximation* the density variations enter only through the buoyancy force, so 30 | 31 | $$ 32 | \rho\mathbf{g} \longrightarrow \rho' \mathbf{g} = \rho_0 \alpha g_0 \frac{r}{R} \theta \mathbf{\hat r}. 33 | $$ 34 | 35 | Now we make the dimensional units explicit. With $L$ being the unit of length, $\tau$ the unit of time, $\theta^*$ the unit of temperature, $P^*$ the unit of pressure, and $\mathbf{\hat z}$ the unit vector along $\mathbf{\Omega}$, then the momentum equation, after dividing by $\rho_0$, is 36 | 37 | $$ 38 | \frac{L}{\tau^2}\,\partial_t \mathbf{u} +L\frac{\Omega}{\tau}\, 2\mathbf{\hat z}\times\mathbf{u}=-\frac{P^*}{\rho_0 L} \nabla p + \frac{B_0^2}{L\rho_0 \mu_0}(\nabla \times \mathbf{B})\times \mathbf{B} + \frac{\alpha g_0 \theta^*}{R} L r \theta\mathbf{\hat r}+\frac{\nu }{\tau L}\nabla^2 \mathbf{u}, 39 | $$ 40 | 41 | where it is understood that the variables $r, t, \mathbf{u}, p, \theta, \mathbf{B}$ are now *dimensionless*. Multiply the equation by $\tau^2/L$ and get 42 | 43 | $$ 44 | \partial_t \mathbf{u} + 2\,\Omega\tau\,\mathbf{\hat z}\times\mathbf{u}=- \nabla p + \frac{\tau^2 B_0^2}{L^2\rho_0 \mu_0}(\nabla \times \mathbf{B})\times \mathbf{B} + \tau^2 \frac{\alpha g_0 \theta^*}{R} r \theta \mathbf{\hat r}+\tau\frac{\nu}{L^2}\nabla^2 \mathbf{u}. 45 | $$ 46 | 47 | Above we have chosen the pressure scale as $P^*=\rho_0 L^2/\tau^2$. For the time being we leave the temperature scale $\theta^*$ unspecified, and define the *Ekman number* $E$ as 48 | 49 | $$ 50 | E \equiv \frac{\nu}{\Omega L^2}, 51 | $$ 52 | 53 | The *Lehnert number* $Le$ as 54 | 55 | $$ 56 | Le \equiv \frac{B_0}{\Omega L \sqrt{\rho_0\mu_0}}, 57 | $$ 58 | 59 | the *Rayleigh number* $Ra$ as 60 | 61 | $$ 62 | Ra \equiv \frac{\alpha g_0 \theta^* L^4}{\nu\kappa R}, 63 | $$ 64 | 65 | and the *Prandtl number* $Pr$ as 66 | 67 | $$ 68 | Pr \equiv \frac{\nu}{\kappa}, 69 | $$ 70 | 71 | where $\kappa$ is the thermal diffusivity of the fluid So, the momentum equation becomes 72 | 73 | $$ 74 | \partial_t \mathbf{u} + 2\,(\Omega\tau)\,\mathbf{\hat z}\times\mathbf{u}=-\nabla p + (\Omega\tau)^2 Le^2(\nabla \times \mathbf{B})\times \mathbf{B} + (\Omega\tau)^2 E^2\frac{Ra}{Pr}\,\theta\, r\, \mathbf{\hat r}+(\Omega\tau)\,E\,\nabla^2 \mathbf{u}. 75 | $$ 76 | 77 | Alternatively, if we deal with problems without viscosity or thermal diffusion where the Rayleigh number diverges, it is better to define a *reference Brunt-Väisälä frequency* $N_0$ such that 78 | 79 | $$ 80 | N_0^2 \equiv -\frac{\alpha g_0 \theta^*}{R}. 81 | $$ 82 | 83 | If the Rayleigh number is finite then we can write 84 | 85 | $$ 86 | E^2\frac{Ra}{Pr} = -\frac{N_0^2}{\Omega^2}. 87 | $$ 88 | 89 | The momentum equation, using the reference Brunt-Väisälä frequency reads 90 | 91 | $$ 92 | \partial_t \mathbf{u} + 2\,(\Omega\tau)\,\mathbf{\hat z}\times\mathbf{u}=-\nabla p + (\Omega\tau)^2 Le^2(\nabla \times \mathbf{B})\times \mathbf{B} - (\Omega\tau)^2 \frac{N_0^2}{\Omega^2}\,\theta\, r\, \mathbf{\hat r}+(\Omega\tau)\,E\,\nabla^2 \mathbf{u}. 93 | $$ 94 | 95 | 96 | A common choice for the time scale is the rotation time scale, so $\tau=1/\Omega$ and the $(\Omega\tau)$ factors go away. Another choice is the viscous diffusion time scale, with $\tau=L^2/\nu$, in which case $\Omega\tau=1/E$. Yet another choice is the Alfvén wave time scale, with $\tau=L \sqrt{\mu_0\rho_0}/B_0$ so that $\Omega\tau=1/Le$. 97 | 98 | 99 | 100 | ## The induction equation 101 | 102 | The induction equation in dimensional form is 103 | 104 | $$ 105 | \partial_t \mathbf{B} = \nabla \times (\mathbf{u} \times \mathbf{B}) + \eta \nabla^2 \mathbf{B}, 106 | $$ 107 | 108 | where $\eta$ is the magnetic diffusivity. Making the dimensional scale factors explicit we get 109 | 110 | $$ 111 | \frac{B_0}{\tau} \partial_t \mathbf{B} = \frac{B_0}{\tau} \nabla \times (\mathbf{u} \times \mathbf{B}) + \eta \frac{B_0}{L^2} \nabla^2\mathbf{B}, 112 | $$ 113 | 114 | where $\mathbf{u}, \mathbf{B}, t$ are now dimensionless. Multiply now by $\tau/B_0$ and obtain 115 | 116 | $$ 117 | \partial_t \mathbf{B} = \nabla \times (\mathbf{u} \times \mathbf{B}) + (\Omega\tau)E_\eta \nabla^2 \mathbf{B}, 118 | $$ 119 | 120 | where $E_\eta$ is the *magnetic Ekman number* defined as 121 | 122 | $$ 123 | E_\eta \equiv \frac{\eta}{\Omega L^2}. 124 | $$ 125 | 126 | 127 | 128 | ## The heat equation 129 | 130 | The heat equation in its dimensional form is 131 | 132 | $$ 133 | \partial_t \theta=-\mathbf{u}\cdot\nabla T+\kappa \nabla^2 \theta. 134 | $$ 135 | 136 | We assume that an isentropic temperature background $T(r)$ exists, which is only a function of radius. Its gradient is then $\nabla T=\mathbf{\hat r}\,\mathrm{d}T/\mathrm{d}r$. Now we write $\mathrm{d}T/\mathrm{d}r=C\,f(r)$, where $C$ is a scale factor for the gradient (can be negative) and $f(r)$ is a dimensionless function of $r$ (with $r$ also dimensionless). Then we can write the heat equation, using this time dimensionless variables exclusively as 137 | 138 | $$ 139 | \partial_t \theta=-\frac{LC}{\theta^*}\,u_r\,f(r)+(\Omega\tau)\frac{E}{Pr} \nabla^2 \theta. 140 | $$ 141 | 142 | In **`kore`** we choose always the temperature scale as $\theta^*=-LC$ so that the heat equation reads simply 143 | 144 | $$ 145 | \partial_t \theta=u_r\,f(r)+(\Omega\tau)\frac{E}{Pr} \nabla^2 \theta. 146 | $$ 147 | 148 | A temperature profile with a linear gradient is common in the literature. In that case $\partial_r T=-\beta r$ (dimensional). In dimensionless variables this is $-\beta L r$ ($r$ now dimensionless), so that the temperature scale is $\theta^*=\beta L^2$. And if the length scale is the CMB radius, i.e. $L=R$, then the Rayleigh number becomes 149 | 150 | $$ 151 | Ra = \frac{\alpha g_0 \beta R^5}{\nu\kappa}. 152 | $$ 153 | -------------------------------------------------------------------------------- /koreviz/writeVts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: iso-8859-15 -*- 3 | 4 | import numpy as np 5 | 6 | try: 7 | try: # Version 2 changed naming convention of functions 8 | from evtk.hl import structuredToVTK 9 | gridToVTK = structuredToVTK 10 | except: 11 | import evtk 12 | gridToVTK = evtk.hl.gridToVTK 13 | except: 14 | print("If you need 3D visualization:") 15 | print("writeVts requires the use of evtk library.") 16 | print("You can get it from https://github.com/paulo-herrera/PyEVTK") 17 | 18 | def get_grid(r,theta,phi): 19 | 20 | r3D,th3D,phi3D = np.meshgrid(r,theta,phi,indexing='ij') 21 | 22 | s3D = r3D * np.sin(th3D) 23 | x3D = s3D * np.cos(phi3D) 24 | y3D = s3D * np.sin(phi3D) 25 | z3D = r3D * np.cos(th3D) 26 | 27 | return r3D,th3D,phi3D, x3D,y3D,z3D, s3D 28 | 29 | def get_cart(vr,vt,vp,th3D,p3D): 30 | 31 | vs = vr * np.sin(th3D) + vt *np.cos(th3D) 32 | vz = vr * np.cos(th3D) - vt *np.sin(th3D) 33 | 34 | vx = vs * np.cos(p3D) - vp * np.sin(p3D) 35 | vy = vs * np.sin(p3D) + vp * np.cos(p3D) 36 | 37 | return vx,vy,vz 38 | 39 | def tile_and_fix(data,m,nr,ntheta,nphi,step,potextra,bfield=False): 40 | 41 | if potextra: 42 | if not bfield: 43 | data = data[::-1,...] 44 | scal = np.zeros([nr,ntheta,nphi]) 45 | scal_tile = (np.tile(data,m))[::step,::step,:] 46 | scal[:data.shape[0],:,:-1] = scal_tile 47 | scal[:data.shape[0],:,-1] = scal_tile[...,0] 48 | scal = np.asfortranarray(scal) 49 | else: 50 | scal = np.zeros([nr,ntheta,nphi]) 51 | scal_tile = (np.tile(data,m)) 52 | scal[:data.shape[0],:,:-1] = scal_tile 53 | scal[:data.shape[0],:,-1] = scal_tile[...,0] 54 | scal = np.asfortranarray(scal) 55 | del scal_tile 56 | return scal 57 | else: 58 | scal = np.zeros([nr,ntheta,nphi]) 59 | scal_tile = (np.tile(data,m))[::step,::step,:] 60 | scal[:data.shape[0],:,:-1] = scal_tile 61 | scal[:data.shape[0],:,-1] = scal_tile[...,0] 62 | scal = np.asfortranarray(scal) 63 | 64 | del scal_tile 65 | 66 | return scal 67 | 68 | def writeVts(mode, scals=[],vecs=[],potextra=False, 69 | nrout=32,radratio=2.0,step=5): 70 | 71 | # Make everything case insensitive 72 | 73 | for k in range(len(scals)): 74 | scals[k] = scals[k].lower() 75 | 76 | for k in range(len(vecs)): 77 | vecs[k] = vecs[k].lower() 78 | 79 | # Figure out if magnetic field needs plotting 80 | 81 | plotb = ( any(elem in ['br','bphi','bp','bt','btheta'] for elem in scals) or 82 | any(elem in ["b"] for elem in vecs) ) 83 | btile = True 84 | # Potential extrapolation? radratio = r_surface/r_cmb 85 | 86 | if plotb and potextra: 87 | rout = np.linspace(mode.r[0],radratio,nrout) 88 | brout,btout,bpout = mode.potextra(mode.br[0,...],mode.r[0],rout) 89 | r = np.concatenate((mode.r[::step][::-1],rout)) 90 | nr = len(r) 91 | br = mode.br[::step,::step,:] 92 | btheta = mode.btheta[::step,::step,:] 93 | bphi = mode.bphi[::step,::step,:] 94 | br = np.concatenate((br[::-1,...], brout[:,::step,:]),axis=0) 95 | btheta = np.concatenate((btheta[::-1,...],btout[:,::step,:]),axis=0) 96 | bphi = np.concatenate((bphi[::-1,...], bpout[:,::step,:]),axis=0) 97 | elif plotb and not potextra: 98 | r = mode.r[::step] 99 | nr = len(r) 100 | br = mode.br 101 | btheta = mode.btheta 102 | bphi = mode.bphi 103 | else: 104 | r = mode.r[::step] 105 | nr = len(r) 106 | 107 | theta = mode.theta[::step] 108 | ntheta = len(theta) 109 | nphi = mode.phi.shape[0] 110 | 111 | r3D,th3D,p3D, x3D,y3D,z3D, s3D = get_grid(r,theta,mode.phi) 112 | 113 | keys = [] 114 | values = [] 115 | 116 | keys.append("radius") 117 | keys.append("cyl_radius") 118 | 119 | values.append(r3D) 120 | values.append(s3D) 121 | 122 | 123 | 124 | if any(elem in ["u","v"] for elem in vecs): 125 | 126 | # Tiling and steps 127 | 128 | ur = tile_and_fix(mode.ur,mode.m,nr,ntheta,nphi,step,potextra) 129 | ut = tile_and_fix(mode.utheta,mode.m,nr,ntheta,nphi,step,potextra) 130 | up = tile_and_fix(mode.uphi,mode.m,nr,ntheta,nphi,step,potextra) 131 | 132 | ux,uy,uz = get_cart(ur,ut,up,th3D,p3D) 133 | 134 | ux = np.asfortranarray(ux) 135 | uy = np.asfortranarray(uy) 136 | uz = np.asfortranarray(uz) 137 | 138 | keys.append("vecV") 139 | values.append((ux,uy,uz)) 140 | 141 | if any(elem in ["b"] for elem in vecs): 142 | 143 | # Tiling and steps 144 | 145 | br = tile_and_fix(br,mode.m,nr,ntheta,nphi,step,potextra,bfield=True) 146 | bt = tile_and_fix(btheta,mode.m,nr,ntheta,nphi,step,potextra,bfield=True) 147 | bp = tile_and_fix(bphi,mode.m,nr,ntheta,nphi,step,potextra,bfield=True) 148 | 149 | bx,by,bz = get_cart(br,bt,bp,th3D,p3D) 150 | 151 | bx = np.asfortranarray(bx) 152 | by = np.asfortranarray(by) 153 | bz = np.asfortranarray(bz) 154 | 155 | keys.append("vecB") 156 | values.append((bx,by,bz)) 157 | btile = False 158 | 159 | if any(elem in ["ur", "vr"] for elem in scals): 160 | ur = tile_and_fix(mode.ur,mode.m,nr,ntheta,nphi,step,potextra) 161 | keys.append("Radial vel") 162 | values.append(ur) 163 | 164 | if any(elem in ["ut", "utheta", "vt", "vtheta"] for elem in scals): 165 | utheta = tile_and_fix(mode.utheta,mode.m,nr,ntheta,nphi,step,potextra) 166 | keys.append("U theta") 167 | values.append(utheta) 168 | 169 | if any(elem in ["up","uphi","vp","vphi"] for elem in scals): 170 | uphi = tile_and_fix(mode.uphi,mode.m,nr,ntheta,nphi,step,potextra) 171 | keys.append("Zonal flow") 172 | values.append(uphi) 173 | 174 | if any(elem in ["us","vs"] for elem in scals): 175 | us = np.zeros_like(mode.ur) 176 | for k,ktheta in enumerate(mode.theta): 177 | us[:,k,:] = ( mode.ur[:,k,:]*np.sin(ktheta) 178 | +mode.utheta[:,k,:]*np.cos(ktheta) ) 179 | us = tile_and_fix(us,mode.m,nr,ntheta,nphi,step,potextra) 180 | keys.append("Cyl rad vel") 181 | values.append(us) 182 | 183 | if any(elem in ["br"] for elem in scals): 184 | if btile: 185 | br = tile_and_fix(br,mode.m,nr,ntheta,nphi,step,potextra,bfield=True) 186 | 187 | keys.append("Radial mag. field") 188 | values.append(br) 189 | 190 | if any(elem in ["bt", "btheta"] for elem in scals): 191 | if btile: 192 | btheta = tile_and_fix(btheta,mode.m,nr,ntheta,nphi,step,potextra,bfield=True) 193 | 194 | keys.append("B_theta") 195 | values.append(btheta) 196 | 197 | if any(elem in ["bp","bphi"] for elem in scals): 198 | if btile: 199 | bphi = tile_and_fix(bphi,mode.m,nr,ntheta,nphi,step,potextra,bfield=True) 200 | 201 | keys.append("Zonal mag. field") 202 | values.append(bphi) 203 | 204 | if any(elem in ["t","temp","temperature"] for elem in scals): 205 | temperature = tile_and_fix(mode.temperature,mode.m, 206 | nr,ntheta,nphi,step,potextra) 207 | keys.append("Temperature") 208 | values.append(temperature) 209 | 210 | if any(elem in ["c","xi","comp","compositon","chem"] for elem in scals): 211 | composition= tile_and_fix(mode.composition,mode.m, 212 | nr,ntheta,nphi,step,potextra) 213 | composition = np.asfortranarray(composition) 214 | keys.append("Composition") 215 | values.append(composition) 216 | 217 | dataDict = dict(zip(keys,values)) 218 | 219 | gridToVTK("out",x3D,y3D,z3D,pointData= dataDict) 220 | 221 | print("Output written to out.vts!") 222 | 223 | return 0 224 | -------------------------------------------------------------------------------- /tools/plot_rad_con_tor_profile.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy.sparse as ss 3 | import sys 4 | import matplotlib.pyplot as plt 5 | import matplotlib 6 | import numpy.polynomial.chebyshev as ch 7 | 8 | sys.path.insert(1,'bin/') 9 | 10 | import utils as ut 11 | import parameters as par 12 | 13 | ''' 14 | Computes the radial profile of kinetic energy, internal energy dissipation, 15 | and kinetic energy dissipation (theta,phi integrated). 16 | 17 | Use as 18 | %run -i tools/plot_rad_con_tor_profile.py sol nR R1 R2 19 | solnum is the solution muber (0 if forced problem) 20 | nR is the number of points in radius 21 | R1 from radius R1 22 | R2 to radius R2 23 | ''' 24 | 25 | 26 | solnum = int(sys.argv[1]) 27 | nR = int(sys.argv[2]) # number of radial points 28 | R1 = float(sys.argv[3]) # From radius R1 29 | R2 = float(sys.argv[4]) # To radius R2 30 | 31 | lmax = par.lmax 32 | m = par.m 33 | symm = par.symm 34 | N = par.N 35 | Ek = par.Ek 36 | ricb = par.ricb 37 | rcmb = 1 38 | n = ut.n 39 | 40 | 41 | # xk are the colocation points, from -1 to 1 42 | i = np.arange(0,nR) 43 | xk = np.cos( (i+0.5)*np.pi/nR ) 44 | x1 = ( (R2-R1)*xk + (R1+R2) - (ricb+rcmb) )/(rcmb-ricb) 45 | # rk are the radial colocation points, from Ra to Rb 46 | r = 0.5*(rcmb-ricb)*( x1 + 1 ) + ricb 47 | r2 = r**2 48 | 49 | Inv_r = ss.diags(1/r,0) 50 | 51 | 52 | 53 | # matrix with Chebishev polynomials at every x point for all degrees: 54 | chx = ch.chebvander(x1,par.N-1) # this matrix has nR rows and N-1 cols 55 | 56 | 57 | u = np.loadtxt('flow.dat') # flow data 58 | if len(u.shape)==1: 59 | u = u.reshape((-1,len(u))) 60 | KP = u[:,0] # Poloidal kinetic energy 61 | KT = u[:,1] # Toroidal kinetic energy 62 | K = KP + KT 63 | 64 | # w = np.loadtxt('eigenvalues.dat') 65 | # if len(w.shape)==1: 66 | # w = w.reshape((-1,len(w))) 67 | 68 | 69 | reu = np.loadtxt('real_flow.field',usecols=solnum) 70 | imu = np.loadtxt('imag_flow.field',usecols=solnum) 71 | 72 | 73 | Plj0 = reu[:n] + 1j*imu[:n] # N elements on each l block 74 | Tlj0 = reu[n:n+n] + 1j*imu[n:n+n] # N elements on each l block 75 | Plj = np.reshape(Plj0,(-1,N)) 76 | Tlj = np.reshape(Tlj0,(-1,N)) 77 | 78 | 79 | # l-indices for u and b: 80 | lmm = 2*np.shape(Plj)[0] -1 # this should be =lmax-m 81 | s = int(symm*0.5+0.5) # s=0 if u is antisymm, s=1 if u is symm 82 | if m>0: 83 | lup = np.arange( m+1-s, m+1-s +lmm, 2) # u pol 84 | lut = np.arange( m+s , m+s +lmm, 2) # u tor 85 | elif m==0: 86 | lup = np.arange( 1+s, 1+s +lmm, 2) # u pol 87 | lut = np.arange( 2-s, 2-s +lmm, 2) # u tor 88 | 89 | 90 | Lup = ss.diags(lup*(lup+1),0) 91 | Lut = ss.diags(lut*(lut+1),0) 92 | Inv_Lup = ss.diags(1/(lup*(lup+1)),0) 93 | Inv_Lut = ss.diags(1/(lut*(lut+1)),0) 94 | 95 | 96 | d1Plj = np.zeros(np.shape(Plj),dtype=complex) 97 | d2Plj = np.zeros(np.shape(Plj),dtype=complex) 98 | d3Plj = np.zeros(np.shape(Plj),dtype=complex) 99 | 100 | d1Tlj = np.zeros(np.shape(Tlj),dtype=complex) 101 | d2Tlj = np.zeros(np.shape(Tlj),dtype=complex) 102 | 103 | P0 = np.zeros((int((lmm+1)/2), nR),dtype=complex) 104 | P1 = np.zeros((int((lmm+1)/2), nR),dtype=complex) 105 | P2 = np.zeros((int((lmm+1)/2), nR),dtype=complex) 106 | P3 = np.zeros((int((lmm+1)/2), nR),dtype=complex) 107 | 108 | T0 = np.zeros((int((lmm+1)/2), nR),dtype=complex) 109 | T1 = np.zeros((int((lmm+1)/2), nR),dtype=complex) 110 | T2 = np.zeros((int((lmm+1)/2), nR),dtype=complex) 111 | 112 | Q0 = np.zeros((int((lmm+1)/2), nR),dtype=complex) 113 | S0 = np.zeros((int((lmm+1)/2), nR),dtype=complex) 114 | 115 | 116 | np.matmul( Plj, chx.T, P0 ) 117 | np.matmul( Tlj, chx.T, T0 ) 118 | 119 | for k in range(np.size(lup)): 120 | d1Plj[k,:] = ut.Dcheb( Plj[k,:], ricb, rcmb) 121 | d2Plj[k,:] = ut.Dcheb( d1Plj[k,:], ricb, rcmb) 122 | d3Plj[k,:] = ut.Dcheb( d2Plj[k,:], ricb, rcmb) 123 | np.matmul(d1Plj, chx.T, P1) 124 | np.matmul(d2Plj, chx.T, P2) 125 | np.matmul(d3Plj, chx.T, P3) 126 | 127 | for k in range(np.size(lup)): 128 | d1Tlj[k,:] = ut.Dcheb( Tlj[k,:], ricb, rcmb) 129 | d2Tlj[k,:] = ut.Dcheb( d1Tlj[k,:], ricb, rcmb) 130 | np.matmul(d1Tlj, chx.T, T1) 131 | np.matmul(d2Tlj, chx.T, T2) 132 | 133 | Q0 = Lup * P0 * Inv_r 134 | Q1 = ( Lup * P1 - Q0 ) * Inv_r 135 | Q2 = ( Lup * P2 - 2*Q1 ) * Inv_r 136 | 137 | S0 = P1 + P0 * Inv_r 138 | S1 = P2 + Inv_Lup * Q1 139 | S2 = P3 + Inv_Lup * Q2 140 | 141 | 142 | ke_pol = np.zeros((int((lmm+1)/2), nR),dtype=complex) 143 | ke_rad = np.zeros((int((lmm+1)/2), nR),dtype=complex) 144 | ke_con = np.zeros((int((lmm+1)/2), nR),dtype=complex) 145 | ke_tor = np.zeros((int((lmm+1)/2), nR),dtype=complex) 146 | 147 | #di_pol = np.zeros((int((lmm+1)/2), nR),dtype=complex) 148 | #di_tor = np.zeros((int((lmm+1)/2), nR),dtype=complex) 149 | 150 | #dk_pol = np.zeros((int((lmm+1)/2), nR),dtype=complex) 151 | #dk_tor = np.zeros((int((lmm+1)/2), nR),dtype=complex) 152 | 153 | 154 | ## Poloidal components 155 | for k,l in enumerate(lup): 156 | 157 | L = l*(l+1) 158 | 159 | q0 = Q0[k,:] 160 | q1 = Q1[k,:] 161 | q2 = Q2[k,:] 162 | 163 | s0 = S0[k,:] 164 | s1 = S1[k,:] 165 | s2 = S2[k,:] 166 | 167 | f0 = 4*np.pi/(2*l+1) 168 | 169 | # kinetic energy 170 | f1 = r2*np.absolute( q0 )**2 171 | f2 = r2*L*np.absolute( s0 )**2 172 | ke_pol[k,:] = f0*(f1+f2) 173 | ke_rad[k,:] = f0*(f1) 174 | ke_con[k,:] = f0*(f2) 175 | 176 | # Dint: Internal energy dissipation 177 | #f1 = L*np.absolute(q0 + r*s1 - s0)**2 178 | #f2 = 3*np.absolute(r*q1)**2 179 | #f3 = L*(l-1)*(l+2)*np.absolute(s0)**2 180 | #di_pol[k,:] = 2*f0*( f1+f2+f3 ) 181 | 182 | # Dkin: Kinetic energy dissipation 183 | #f1 = L * r2 * np.conj(s0) * s2 184 | #f2 = 2 * r * L * np.conj(s0) * s1 185 | #f3 = -(L**2)*( np.conj(s0)*s0 ) - (l**2+l+2) * ( np.conj(q0)*q0 ) 186 | #f4 = 2 * r * np.conj(q0)*q1 + r2 * np.conj(q0) * q2 187 | #f5 = 2 * L *( np.conj(q0)*s0 + q0*np.conj(s0) ) 188 | #dk_pol[k,:] = 2*f0*( f1+f2+f3+f4+f5 ) 189 | 190 | 191 | 192 | ## Toroidal components 193 | for k,l in enumerate(lut): 194 | 195 | L = l*(l+1) 196 | 197 | t0 = T0[k,:] 198 | t1 = T1[k,:] 199 | t2 = T2[k,:] 200 | 201 | f0 = 4*np.pi/(2*l+1) 202 | 203 | # kinetic energy 204 | f1 = (r2)*L*np.absolute(t0)**2 205 | ke_tor[k,:] = f0*f1 206 | 207 | # Dint: Internal energy dissipation 208 | #f1 = L*np.absolute( r*t1-t0 )**2 209 | #f2 = L*(l-1)*(l+2)*np.absolute( t0 )**2 210 | #di_tor[k,:] = 2*f0*( f1+f2 ) 211 | 212 | # Dkin: Kinetic energy dissipation 213 | #f1 = L * r2 * np.conj(t0) * t2 214 | #f2 = 2 * r * L * np.conj(t0) * t1 215 | #f3 = -(L**2)*( np.conj(t0)*t0 ) 216 | #dk_tor[k,:] = 2*f0*(f1+f2+f3) 217 | 218 | ke = np.real(sum(ke_pol+ke_tor,0)) 219 | krad = np.real(sum(ke_rad,0)) 220 | kcon = np.real(sum(ke_con,0)) 221 | ktor = np.real(sum(ke_tor,0)) 222 | 223 | #dint = np.real(sum(di_pol+di_tor,0)*par.Ek) 224 | #dkin = np.real(sum(dk_pol+dk_tor,0)*par.Ek) 225 | 226 | totK = K[solnum] 227 | 228 | k = np.argsort(r) 229 | tmp = ke 230 | print('Estimated kinetic energy:', np.trapz(tmp[k],r[k])) 231 | print(' Actual kinetic energy:', K[solnum]) 232 | 233 | 234 | 235 | # plt.figure(); 236 | 237 | # plt.plot(r,ke/totK,'--',lw=1,color='gray', label=r'Total kinetic energy') 238 | # plt.plot(r,krad/totK, label=r'Radial') 239 | # plt.plot(r,kcon/totK, label=r'Consoidal') 240 | # plt.plot(r,ktor/totK, label=r'Toroidal') 241 | 242 | # plt.yscale('log') 243 | # plt.xlabel(r'$r$',size=12) 244 | # plt.legend() 245 | # plt.tight_layout() 246 | # plt.show() 247 | 248 | 249 | fig, ax = subplots(nrows=2,ncols=1,figsize=(5,5)) 250 | 251 | ax[0].plot(r,ke/totK,'--',lw=1,color='gray', label=r'Total kinetic energy') 252 | ax[0].plot(r,krad/totK, label=r'Radial') 253 | ax[0].plot(r,kcon/totK, label=r'Consoidal') 254 | ax[0].plot(r,ktor/totK, label=r'Toroidal') 255 | 256 | ax[0].set_ylim(-1,18) 257 | ax[0].set_xlabel(r'$r/R_\odot$',size=12) 258 | ax[0].set_ylabel(r'$\left_{\theta\phi}$',size=12) 259 | ax[0].legend() 260 | 261 | 262 | k = np.argmin(np.abs(r-0.99)) 263 | 264 | ax[1].plot(lup,np.abs(ke_rad[:,k])/ke[k],'.-',lw=0.3,label=r'Radial') 265 | ax[1].plot(lup,np.abs(ke_con[:,k])/ke[k],'.-',lw=0.3,label=r'Consoidal') 266 | ax[1].plot(lut,np.abs(ke_tor[:,k])/ke[k],'.-',lw=0.3,label=r'Toroidal') 267 | 268 | ax[1].set_yscale('log') 269 | ax[1].set_xlabel(r'Spherical harmonic degree $l$',size=12) 270 | ax[1].set_ylabel(r'Spectral energy at $r=0.99R_\odot$',size=12) 271 | ax[1].set_xlim(7,35) 272 | ax[1].set_ylim(1e-5,1) 273 | #ax[1].text(8,2e-5,r'$r=0.99R_\odot$',size=14) 274 | 275 | 276 | ax[1].legend() 277 | 278 | 279 | 280 | tight_layout() 281 | show() 282 | -------------------------------------------------------------------------------- /tools/plot_field.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy.sparse as ss 3 | import scipy.sparse.linalg as ssl 4 | import sys 5 | import matplotlib.pyplot as plt 6 | import matplotlib 7 | import matplotlib.tri as tri 8 | import numpy.polynomial.chebyshev as ch 9 | 10 | sys.path.insert(1,'bin/') 11 | 12 | import utils as ut 13 | import parameters as par 14 | import utils4pp as upp 15 | 16 | ''' 17 | Script to plot meridional cuts of a solution field 18 | Use as: 19 | 20 | python3 plot_field.py nsol nR ntheta theta0 theta1 field opt 21 | 22 | nsol : solution number 23 | nR : number of points in radius 24 | ntheta : number of points in the theta direction 25 | theta0 : starting colatitude 26 | theta1 : final colatitude 27 | field : whether to plot flow velocity or magnetic field ('u' or 'b') 28 | opt : 'raw' for real part (phase and phi dependent!), or 'abs' for the magnitude 29 | ''' 30 | 31 | # Uncomment the following line to use TeX fonts. Requires TeX obviously. 32 | #plt.rc('text', usetex=True) 33 | 34 | solnum = int(sys.argv[1]) 35 | 36 | if sys.argv[6] == 'u': 37 | a0 = np.loadtxt('real_flow.field',usecols=solnum) 38 | b0 = np.loadtxt('imag_flow.field',usecols=solnum) 39 | vsymm = par.symm 40 | if sys.argv[7] == 'raw': 41 | titlelabels = [r'$\mathbf{\hat r}\cdot\mathrm{Re}\left(\mathbf{u_0}\right)$', \ 42 | r'$\mathbf{\hat \theta}\cdot\mathrm{Re}\left(\mathbf{u_0}\right)$', \ 43 | r'$\mathbf{\hat \phi}\cdot\mathrm{Re}\left(\mathbf{u_0}\right)$'] 44 | elif sys.argv[7] == 'abs': 45 | titlelabels = [r'$\mathbf{\hat r}\cdot\left|\mathbf{u_0}\right|$', \ 46 | r'$\mathbf{\hat \theta}\cdot\left|\mathbf{u_0}\right|$', \ 47 | r'$\mathbf{\hat \phi}\cdot\left|\mathbf{u_0}\right|$'] 48 | cmap = 'rainbow' 49 | elif sys.argv[6] == 'b': 50 | a0 = np.loadtxt('real_magnetic.field',usecols=solnum) 51 | b0 = np.loadtxt('imag_magnetic.field',usecols=solnum) 52 | if sys.argv[7] == 'raw': 53 | titlelabels = [r'$\mathbf{\hat r}\cdot\mathrm{Re}\left(\mathbf{b_0}\right)$', \ 54 | r'$\mathbf{\hat \theta}\cdot\mathrm{Re}\left(\mathbf{b_0}\right)$', \ 55 | r'$\mathbf{\hat \phi}\cdot\mathrm{Re}\left(\mathbf{b_0}\right)$'] 56 | elif sys.argv[7] == 'abs': 57 | titlelabels = [r'$\mathbf{\hat r}\cdot\left|\mathbf{b_0}\right|$', \ 58 | r'$\mathbf{\hat \theta}\cdot\left|\mathbf{b_0}\right|$', \ 59 | r'$\mathbf{\hat \phi}\cdot\left|\mathbf{b_0}\right|$'] 60 | vsymm = ut.bsymm 61 | cmap = 'plasma' 62 | 63 | 64 | lmax = par.lmax 65 | m = par.m 66 | symm = par.symm 67 | N = par.N 68 | Ek = par.Ek 69 | ricb = par.ricb 70 | rcmb = 1 71 | n = ut.n 72 | n0 = ut.n0 73 | 74 | nR = int(sys.argv[2]) # number of radial points 75 | Ntheta = int(sys.argv[3]) # number of points in the theta direction 76 | 77 | # setup radial grid 78 | gap = rcmb-ricb 79 | r = np.linspace(ricb,rcmb,nR) 80 | if ricb == 0: 81 | r = r[1:] 82 | nR = nR - 1 83 | x = upp.xcheb(r,ricb,rcmb) 84 | 85 | 86 | phi = 0. # select meridional cut 87 | 88 | 89 | # matrix with Chebyshev polynomials at every x point for all degrees: 90 | chx = ch.chebvander(x,par.N-1) # this matrix has nR rows and N-1 cols 91 | 92 | # expand solution in case ricb=0 93 | aib = upp.expand_sol(a0+1j*b0,vsymm) 94 | a = np.real(aib) 95 | b = np.imag(aib) 96 | 97 | Plj0 = a[:n0] + 1j*b[:n0] # N elements on each l block 98 | Tlj0 = a[n0:n0+n0] + 1j*b[n0:n0+n0] # N elements on each l block 99 | 100 | Plj = np.reshape(Plj0,(int((lmax-m+1)/2),N)) 101 | Tlj = np.reshape(Tlj0,(int((lmax-m+1)/2),N)) 102 | dPlj = np.zeros(np.shape(Plj),dtype=complex) 103 | 104 | Plr = np.zeros((int((lmax-m+1)/2), nR),dtype=complex) 105 | dP = np.zeros((int((lmax-m+1)/2), nR),dtype=complex) 106 | rP = np.zeros((int((lmax-m+1)/2), nR),dtype=complex) 107 | Qlr = np.zeros((int((lmax-m+1)/2), nR),dtype=complex) 108 | Slr = np.zeros((int((lmax-m+1)/2), nR),dtype=complex) 109 | Tlr = np.zeros((int((lmax-m+1)/2), nR),dtype=complex) 110 | 111 | np.matmul( Plj, chx.T, Plr ) 112 | np.matmul( Tlj, chx.T, Tlr ) 113 | 114 | rI = ss.diags(r**-1,0) 115 | 116 | ll0 = ut.ell(m,lmax,vsymm) 117 | llpol = ll0[0] 118 | lltor = ll0[1] 119 | ll = ll0[2] 120 | 121 | for k in range(np.size(llpol)): 122 | dPlj[k,:] = ut.Dcheb(Plj[k,:], ricb, rcmb) 123 | 124 | np.matmul(dPlj, chx.T, dP) 125 | 126 | rP = Plr * ss.diags(r**-1,0) 127 | Qlr = ss.diags(llpol*(llpol+1),0) * rP 128 | Slr = rP + dP 129 | 130 | # setup the latitudinal grid 131 | theta = np.linspace(float(sys.argv[4])*np.pi/180,float(sys.argv[5])*np.pi/180,Ntheta+2) 132 | theta = theta[1:-1] 133 | 134 | s = np.zeros( nR*Ntheta ) 135 | z = np.zeros( nR*Ntheta ) 136 | 137 | #ur2 = np.zeros( (nR)*Ntheta ) 138 | #ut2 = np.zeros( (nR)*Ntheta ) 139 | #up2 = np.zeros( (nR)*Ntheta ) 140 | 141 | ur = np.zeros( (nR)*Ntheta, dtype=complex) 142 | utheta = np.zeros( (nR)*Ntheta, dtype=complex) 143 | uphi = np.zeros( (nR)*Ntheta, dtype=complex) 144 | 145 | clm = np.zeros((lmax-m+2,1)) 146 | for i,l in enumerate(ll): 147 | clm[i] = np.sqrt((l-m)*(l+m)) 148 | 149 | # start index for l. Do not confuse with indices for the Cheb expansion! 150 | sy = int( vsymm*0.5 + 0.5 ) # sy=0 if antisymm, sy=1 if symm 151 | idP = (np.sign(m)+sy )%2 152 | idT = (np.sign(m)+sy+1)%2 153 | plx = idP+lmax-m+1 154 | tlx = idT+lmax-m+1 155 | 156 | 157 | k=0 158 | for kt in range(Ntheta): 159 | 160 | ylm = np.r_[ut.Ylm_full(lmax, m, theta[kt], phi),0] 161 | for kr in range(0,nR): 162 | 163 | s[k] = r[kr]*np.sin(theta[kt]) 164 | z[k] = r[kr]*np.cos(theta[kt]) 165 | 166 | ur[k] = np.dot( Qlr[:,kr], ylm[idP:plx:2] ) 167 | #ur2[k] = absolute(dot( Qlm[:,kr], ylm[idP:plx:2] ))**2 168 | 169 | tmp1 = np.dot( -(llpol+1) * Slr[:,kr]/np.tan(theta[kt]), ylm[idP:plx:2] ) 170 | tmp2 = np.dot( clm[idP+1:plx+1:2,0] * Slr[:,kr]/np.sin(theta[kt]), ylm[idP+1:plx+1:2] ) 171 | tmp3 = np.dot( 1j*m * Tlr[:,kr]/np.sin(theta[kt]), ylm[idT:tlx:2] ) 172 | utheta[k] = tmp1+tmp2+tmp3 173 | #ut2[k] = absolute(tmp1+tmp2+tmp3)**2 174 | 175 | tmp1 = np.dot( (lltor+1) * Tlr[:,kr]/np.tan(theta[kt]), ylm[idT:tlx:2] ) 176 | tmp2 = np.dot( -clm[idT+1:tlx+1:2,0] * Tlr[:,kr]/np.sin(theta[kt]), ylm[idT+1:tlx+1:2] ) 177 | tmp3 = np.dot( 1j*m * Slr[:,kr]/np.sin(theta[kt]), ylm[idP:plx:2] ) 178 | uphi[k] = tmp1+tmp2+tmp3 179 | #up2[k] = absolute(tmp1+tmp2+tmp3)**2 180 | 181 | #uz[k] = ur[k]*cos(theta[kt]) - ut[k]*sin(theta[kt]) 182 | k=k+1 183 | 184 | # Mask the inner core 185 | a = 1. 186 | c = 1. 187 | id_in = np.where((s**2/(a**2)) + (z**2/(c**2)) < 1.) 188 | s1 = s[id_in] 189 | z1 = z[id_in] 190 | triang = tri.Triangulation(s1, z1) 191 | xmid = s1[triang.triangles].mean(axis=1) 192 | x2 = xmid*xmid 193 | ymid = z1[triang.triangles].mean(axis=1) 194 | y2 = ymid*ymid 195 | mask = np.where( (x2 + y2 <= ricb**2), 1, 0) 196 | triang.set_mask(mask) 197 | 198 | 199 | fig=plt.figure(figsize=(14,7)) 200 | # ------------------------------------------------------------------- ur 201 | ax1=fig.add_subplot(131) 202 | ax1.set_title(titlelabels[0],size=20) 203 | #ax1.text(0.1,0,titlelabels[0],size=20) 204 | if sys.argv[7] == 'raw': 205 | im1=ax1.tricontourf( triang, np.real(ur[id_in]), 70, cmap=cmap) 206 | elif sys.argv[7] == 'abs': 207 | im1=ax1.tricontourf( triang, np.absolute(ur[id_in]), 70, cmap=cmap) 208 | for c in im1.collections: 209 | c.set_edgecolor('face') 210 | ax1.set_aspect('equal') 211 | plt.colorbar(im1,aspect=70) 212 | 213 | # --------------------------------------------------------------- utheta 214 | ax2=fig.add_subplot(132) 215 | ax2.set_title(titlelabels[1],size=20) 216 | #ax2.text(0.1,0,titlelabels[1],size=20) 217 | if sys.argv[7] == 'raw': 218 | im2=ax2.tricontourf( triang, np.real(utheta[id_in]), 70, cmap=cmap) 219 | elif sys.argv[7] == 'abs': 220 | im2=ax2.tricontourf( triang, np.absolute(utheta[id_in]), 70, cmap=cmap) 221 | for c in im2.collections: 222 | c.set_edgecolor('face') 223 | ax2.set_aspect('equal') 224 | plt.colorbar(im2,aspect=70) 225 | 226 | # ----------------------------------------------------------------- uphi 227 | ax3=fig.add_subplot(133) 228 | ax3.set_title(titlelabels[2],size=20) 229 | #ax3.text(0.1,0,titlelabels[2],size=20) 230 | if sys.argv[7] == 'raw': 231 | im3=ax3.tricontourf( triang, np.real(uphi[id_in]), 70, cmap=cmap) 232 | elif sys.argv[7] == 'abs': 233 | im3=ax3.tricontourf( triang, np.absolute(uphi[id_in]), 70, cmap=cmap) 234 | for c in im3.collections: 235 | c.set_edgecolor('face') 236 | ax3.set_aspect('equal') 237 | plt.colorbar(im3,aspect=70) 238 | 239 | # ---------------------------------------------------------------------- 240 | plt.tight_layout() 241 | plt.show() 242 | -------------------------------------------------------------------------------- /tools/get_data.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import numpy.lib.scimath as scimath 4 | import sys 5 | from os.path import exists 6 | 7 | 8 | ''' 9 | Reads results produced by solve.py or collected by reap.sh into a pandas dataframe 10 | Use as: 11 | python3 $KORE_DIR/tools/get_data.py filename 12 | 13 | filename : prefix used when collecting data via reap.sh (leave empty when reading results directly from solve.py) 14 | ''' 15 | 16 | def load_results(filename): 17 | df = pd.DataFrame() 18 | if exists(filename + '.par'): 19 | par = pd.read_csv(filename + '.par', sep=' ', names=["Ek", "m", "symm", "ricb", "bci", "bco", "projection", 20 | "forcing", "Af_cmb", "wf", "magnetic", "Em", "Le2", "N", 21 | "lmax", "solve_time", "ncpus", "tol", "thermal", "Prandtl", 22 | "Ra", "compositional", "Schmidt", "Ra_comp", "Af_icb", 23 | "rc", "h", "mbci", "c", "c1", "mu", "B0_norm", 24 | "time_scale"]) 25 | df = pd.concat([df, par], axis=1) 26 | if exists(filename + '.flo'): 27 | flo = pd.read_csv(filename + '.flo', sep=' ', names=["KP", "KT", "Dint", "rDkin", "iDkin", "rpow", "ipow", 28 | "Dint1", "Dint2", "Dint3", "rvtorq_cmb", "ivtorq_cmb", 29 | "rvtorq_icb", "ivtorq_icb"]) 30 | df = pd.concat([df, flo], axis=1) 31 | if exists(filename + '.mag'): 32 | mag = pd.read_csv(filename + '.mag', sep=' ', names=["MP", "MT", "DohmP", "DohmT", "Dohm1", "Dohm2", 33 | "Dohm3", "rmtorq", "imtorq"]) 34 | df = pd.concat([df, mag], axis=1) 35 | if exists(filename + '.tmp'): 36 | tmp = pd.read_csv(filename + '.tmp', sep=' ', names=['Dbuoy', 'TE', 'Dtemp', 'Dadv']) 37 | df = pd.concat([df, tmp], axis=1) 38 | if exists(filename + '.cmp'): 39 | cmp = pd.read_csv(filename + '.cmp', sep=' ', names=['Dbuoy_comp', 'CE', 'Dcomp', 'Dadv_comp']) 40 | df = pd.concat([df, cmp], axis=1) 41 | if exists(filename + '.eig'): 42 | err = pd.read_csv(filename + '.eig', sep=' ', names=['resid1', 'resid2']) 43 | df = pd.concat([df, err], axis=1) 44 | if exists(filename + '.eig'): 45 | eig = pd.read_csv(filename + '.eig', sep=' ', names=['rtau', 'itau']) 46 | df = pd.concat([df, eig], axis=1) 47 | return df 48 | 49 | 50 | def load_result(): 51 | df = pd.DataFrame() 52 | if exists('params.dat'): 53 | par = pd.read_csv('params.dat', sep=' ', names=["Ek", "m", "symm", "ricb", "bci", "bco", "projection", 54 | "forcing", "Af_cmb", "wf", "magnetic", "Em", "Le2", "N", "lmax", 55 | "solve_time", "ncpus", "tol", "thermal", "Prandtl", "Ra", 56 | "compositional", "Schmidt", "Ra_comp", "Af_icb", "rc", "h", 57 | "mbci", "c", "c1", "mu", "B0_norm", "tA"]) 58 | df = pd.concat([df, par], axis=1) 59 | if exists('flow.dat'): 60 | flo = pd.read_csv('flow.dat', sep=' ', names=["KP", "KT", "Dint", "rDkin", "iDkin", "rpow", "ipow", "Dint1", 61 | "Dint2", "Dint3", "rvtorq_cmb", "ivtorq_cmb", "rvtorq_icb", 62 | "ivtorq_icb"]) 63 | df = pd.concat([df, flo], axis=1) 64 | if exists('magnetic.dat'): 65 | mag = pd.read_csv('magnetic.dat', sep=' ', names=["MP", "MT", "DohmP", "DohmT", "Dohm1", "Dohm2", 66 | "Dohm3", "rmtorq", "imtorq"]) 67 | df = pd.concat([df, mag], axis=1) 68 | if exists('thermal.dat'): 69 | tmp = pd.read_csv('thermal.dat', sep=' ', names=['Dbuoy', 'TE', 'Dtemp', 'Dadv']) 70 | df = pd.concat([df, tmp], axis=1) 71 | if exists('compositional.dat'): 72 | cmp = pd.read_csv('compositional.dat', sep=' ', names=['Dbuoy_comp', 'CE', 'Dcomp', 'Dadv_comp']) 73 | df = pd.concat([df, cmp], axis=1) 74 | if exists('error.dat'): 75 | err = pd.read_csv('error.dat', sep=' ', names=['resid1', 'resid2']) 76 | df = pd.concat([df, err], axis=1) 77 | if exists('eigenvalues.dat'): 78 | eig = pd.read_csv('eigenvalues.dat', sep=' ', names=['rtau', 'itau']) 79 | df = pd.concat([df, eig], axis=1) 80 | return df 81 | 82 | 83 | # --- INITIALIZATION --- 84 | # load results from the solver(s) 85 | if len(sys.argv) == 2: 86 | filename = sys.argv[1] 87 | df = load_results(filename) 88 | else: 89 | df = load_result() 90 | 91 | # --- COMPUTATION --- 92 | # scaling factors 93 | if (df['time_scale'] == 1).all(): 94 | df['scale_factor'] = 1/df['Ek'] 95 | elif (df['time_scale'] == 2).all(): 96 | df['scale_factor'] = 1/np.sqrt(df['Le2']) 97 | else: 98 | df['scale_factor'] = np.ones(len(df)) 99 | 100 | # kinetic energy parameters 101 | df['KE'] = df['KP'] + df['KT'] 102 | 103 | df['t2p'] = df['KT']/df['KP'] 104 | df['p2t'] = df['KP']/df['KT'] 105 | 106 | df['Dkin'] = df['rDkin']*df['Ek']*df['scale_factor'] 107 | df['Dint'] = df['Dint']*df['Ek']*df['scale_factor'] 108 | 109 | if (df['Dint1'] != 0).all() or (df['Dint2'] != 0).all() or (df['Dint3'] != 0).all(): 110 | df['Dint_bulk1'] = df['Dint'] - df['Dint1'] - df['Dint2'] 111 | df['Dint_bulk2'] = df['Dint'] - df['Dint1'] - df['Dint3'] 112 | del df['Dint1'] 113 | del df['Dint2'] 114 | del df['Dint3'] 115 | 116 | df['vtorq_cmb'] = df['rvtorq_cmb'] + 1j*df['ivtorq_cmb'] 117 | df['vtorq_icb'] = df['rvtorq_icb'] + 1j*df['ivtorq_icb'] 118 | 119 | # forcing parameters 120 | if (df['forcing'] == 0).all(): 121 | del df['Af_icb'] 122 | del df['Af_cmb'] 123 | df['freq'] = df['itau'] 124 | df['damp'] = df['rtau'] 125 | df['pss'] = np.zeros(len(df)) 126 | df['pvf'] = np.zeros(len(df)) 127 | else: 128 | df['freq'] = df['wf'] 129 | if (df['forcing'] == 1).all(): 130 | df['pss'] = np.zeros(len(df)) 131 | df['pvf'] = df['rpow'] 132 | elif (df['forcing'] == 7).all(): 133 | df['pvf'] = 0 134 | df['pss'] = df['rpow'] 135 | elif (df['forcing'] == 8).all(): 136 | df['pss'] = np.zeros(len(df)) 137 | df['pvf'] = df['rpow'] 138 | elif (df['forcing'] == 9).all(): 139 | df['pvf'] = np.zeros(len(df)) 140 | df['pss'] = df['rpow'] 141 | 142 | del df['wf'] 143 | del df['rtau'] 144 | del df['itau'] 145 | 146 | # magnetic parameters 147 | if (df['magnetic'] == 0).all(): 148 | del df['Em'] 149 | del df['Le2'] 150 | del df['mu'] 151 | del df['B0_norm'] 152 | df['Dohm'] = np.zeros(len(df)) 153 | else: 154 | df['Le'] = np.sqrt(df['Le2']) 155 | df['Pm'] = df['Ek']/df['Em'] 156 | df['Lam'] = df['Le2']/df['Em'] 157 | 158 | df['ME'] = (df['MP'] + df['MT'])*df['Le2']*(df['scale_factor']**2) 159 | df['mtorq'] = df['rmtorq'] + 1j*df['imtorq'] 160 | df['Dohm'] = (df['DohmP'] + df['DohmT'])*df['Le2']*df['Em']*(df['scale_factor']**2) 161 | 162 | df['o2v'] = df['Dohm']/df['Dint'] 163 | 164 | if (df['Dohm1'] != 0).all() or (df['Dohm2'] != 0).all() or (df['Dohm3'] != 0).all(): 165 | df['Dohm_bulk1'] = df['Dohm'] - df['Dohm1'] - df['Dohm2'] 166 | df['Dohm_bulk2'] = df['Dohm'] - df['Dohm1'] - df['Dohm3'] 167 | df['o2v1'] = df['Dohm_bulk1']/df['Dint_bulk1'] 168 | df['o2v2'] = df['Dohm_bulk2']/df['Dint_bulk2'] 169 | del df['Dohm1'] 170 | del df['Dohm2'] 171 | del df['Dohm3'] 172 | 173 | # thermal parameters 174 | if (df['thermal'] == 0).all(): 175 | del df['Prandtl'] 176 | del df['Ra'] 177 | del df['rc'] 178 | del df['h'] 179 | df['Dbuoy'] = np.zeros(len(df)) 180 | else: 181 | df['Brunt'] = scimath.sqrt(-df['Ra']/df['Prandtl'])*df['Ek'] 182 | df['Dbuoy'] = df['Dbuoy']*(df['scale_factor']**2)*(df['Ek']**2)*df['Ra']/df['Prandtl'] 183 | df['Dtemp'] = df['Dtemp']*df['scale_factor']*df['Ek']/df['Prandtl'] 184 | 185 | # compositional parameters 186 | if (df['compositional'] == 0).all(): 187 | del df['Schmidt'] 188 | del df['Ra_comp'] 189 | df['Dbuoy_comp'] = np.zeros(len(df)) 190 | else: 191 | df['Brunt_comp'] = scimath.sqrt(-df['Ra_comp']/df['Schmidt'])*df['Ek'] 192 | df['Dbuoy_comp'] = df['Dbuoy_comp']*(df['scale_factor']**2)*(df['Ek']**2)*df['Ra_comp']/df['Schmidt'] 193 | df['Dcomp'] = df['Dcomp']*df['scale_factor']*df['Ek']/df['Schmidt'] 194 | 195 | # additional parameters 196 | df['Dtot'] = df['Dint'] + df['Dohm'] + df['Dbuoy'] + df['Dbuoy_comp'] 197 | df['Q'] = df['KE']/df['Dtot'] 198 | 199 | # save results to comma-separated-value file 200 | if len(sys.argv) == 2: 201 | df.to_csv(filename + '.csv') 202 | else: 203 | df.to_csv('results.csv') 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kore 2 | 3 | [![Build workflow](https://github.com/repepo/kore/actions/workflows/main.yml/badge.svg)](https://github.com/repepo/kore/actions/workflows/main.yml) 4 | [![docs](https://github.com/repepo/kore/actions/workflows/ci.yml/badge.svg)](https://github.com/repepo/kore/actions/workflows/ci.yml) 5 | [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) 6 | 7 | *KOR-ee*, from the greek **Κόρη**, the queen of the underworld, daughter of Zeus and Demeter. Kore is a numerical tool to study the core flow within rapidly rotating planets or other rotating fluids contained within near-spherical boundaries. The current version solves the *linear* Navier-Stokes equation and optionally the induction and thermal/compositional (Boussinesq) equations for a viscous, incompressible and conductive fluid with an externally imposed magnetic field, and enclosed within a near-spherical boundary. A solid inner core can be included optionally. 8 | 9 | Kore assumes all dynamical variables to oscillate in a harmonic fashion. The oscillation frequency can be imposed externally, as is the case when doing forced motion studies (e.g. *tidal* forcing), or it can be obtained as part of the solution to an eigenvalue problem. In Kore's current implementation the eigenmodes comprise the inertial mode set (resoring force is Coriolis), the gravity modes or g mode set (restoring force is buoyancy), the torsional Alfvén mode set (restoring force is the Lorentz force), and the magneto-Archimedes-Coriolis (MAC) set (combination of Coriolis, Lorentz, and buoyancy as restoring forces). 10 | 11 | Kore's distinctive feature is the use of a very efficient spectral method employing Gegenbauer (also known as ultraspherical) polynomials as a basis in the radial direction. This approach leads to *sparse* matrices representing the differential equations, as opposed to *dense* matrices, as in traditional Chebyshev colocation methods. Sparse matrices have smaller memory-footprint and are more suitable for systematic core flow studies at extremely low viscosities (or small Ekman numbers). 12 | 13 | Kore is free for everyone to use under the GPL v3 license. Too often in the scientific literature the numerical methods used are presented with enough detail to guarantee reproducibility, but only *in principle*. Without access to the actual implementation of those methods, which would require a significant amount of work and time to develop, readers are left effectively without the possibility to reproduce or verify the results presented. This leads to very slow scientific progress. We share our code to avoid this. 14 | 15 | If this code is useful for your research, we invite you to cite the relevant papers (coming soon) and hope that you can also contribute to the project. 16 | 17 | ## Getting Started 18 | 19 | ### Prerequisites 20 | 21 | * python3 22 | * [PETSc](https://www.mcs.anl.gov/petsc/) with complex scalars, mumps and superlu_dist. 23 | * [SLEPc](http://slepc.upv.es/) 24 | * [petsc4py](https://bitbucket.org/petsc/petsc4py/src/master/) 25 | * [slepc4py](https://bitbucket.org/slepc/slepc4py/src/master/) 26 | * [mpi4py](https://bitbucket.org/mpi4py/mpi4py/src/master/) 27 | * [wigxjpf](http://fy.chalmers.se/subatom/wigxjpf/) 28 | 29 | #### Installing PETSc 30 | 31 | Download PETSc release 3.12.5. This release supports SuperLU_DIST version 5.4.0, which has much lower memory footprint than the newest version. We need to download SuperLU_DIST (no need to unpack it) and then download and unpack PETSc: 32 | ``` 33 | wget https://portal.nersc.gov/project/sparse/superlu/superlu_dist_5.4.0.tar.gz 34 | wget http://ftp.mcs.anl.gov/pub/petsc/release-snapshots/petsc-lite-3.12.5.tar.gz 35 | tar xvf petsc-lite-3.12.5.tar.gz 36 | cd petsc-3.12.5 37 | ``` 38 | We need PETSc built with support for complex scalars. We need also the external packages `superlu_dist` (which we just downloaded) and `mumps`. 39 | Therefore the configure command should include the options: 40 | ``` 41 | --with-scalar-type=complex --download-mumps=1 --download-superlu_dist=../superlu_dist_5.4.0.tar.gz 42 | ``` 43 | Additional options might be needed according to your specific system, please consult the PETSc installation documentation [here](https://www.mcs.anl.gov/petsc/documentation/installation.html). PETSc requires a working MPI installation, either `mpich` or `openmpi`. In our own experience, it saves a lot of headache if we include `mpich` as an external package to be installed along with PETSc. Therefore we include the option `--download-mpich=1` 44 | Just to provide an example, the configure command needed in our own computing cluster is (get yourself some coffee, this step takes several minutes to complete): 45 | ``` 46 | ./configure --download-mpich --with-scalar-type=complex --download-mumps=1 --download-parmetis --download-metis --download-scalapack=1 --download-fblaslapack=1 --with-debugging=0 --download-superlu_dist=../superlu_dist_5.4.0.tar.gz --download-ptscotch=1 CXXOPTFLAGS='-O3 -march=native' FOPTFLAGS='-O3 -march=native' COPTFLAGS='-O3 -march=native' --with-cxx-dialect=C++11 47 | ``` 48 | If everything goes well then you can build the libraries (modify `/path/to` as needed): 49 | ``` 50 | make PETSC_DIR=/path/to/petsc-3.12.5 PETSC_ARCH=slu540 51 | ``` 52 | then test the libraries: 53 | ``` 54 | make PETSC_DIR=/path/to/petsc-3.12.5 PETSC_ARCH=slu540 check 55 | ``` 56 | The MPI executables are now installed under `/path/to/petsc-3.12.5/slu540/bin/` so we need to prepend that directory to the `$PATH` variable. A good place to do that could be in your `.profile`. Include the following lines: 57 | ``` 58 | export PETSC_DIR=/path/to/petsc-3.12.5 59 | export PETSC_ARCH=slu540 60 | export PATH=$PETSC_DIR/$PETSC_ARCH/bin:$PATH 61 | ``` 62 | PETSc and MPI are now ready! 63 | 64 | #### Installing SLEPc 65 | Download release 3.12.2. Unpack and cd to the installation directory: 66 | ``` 67 | cd 68 | wget http://slepc.upv.es/download/distrib/slepc-3.12.2.tar.gz 69 | tar xvf slepc-3.12.2.tar.gz 70 | cd slepc-3.12.2 71 | ``` 72 | Make sure the environment variables `PETSC_DIR` and `PETSC_ARCH` are exported already: if you modified your `.profile` as suggested above then simply do 73 | ``` 74 | source ~/.profile 75 | ``` 76 | Then configure, build and test SLEPc (modify `/path/to` as needed): 77 | ``` 78 | ./configure 79 | make SLEPC_DIR=/path/to/slepc-3.12.2 PETSC_DIR=/path/to/petsc-3.12.5 PETSC_ARCH=slu540 80 | make SLEPC_DIR=/path/to/slepc-3.12.2 PETSC_DIR=/path/to/petsc-3.12.5 check 81 | ``` 82 | Finally, export the variable `SLEPC_DIR`. (Adding this line to your `.profile` is a good idea) 83 | ``` 84 | export SLEPC_DIR=/path/to/slepc-3.12.2 85 | ``` 86 | SLEPc is now ready. 87 | 88 | #### Installing petsc4py, slepc4py and mpi4py 89 | Get the petsc4py tarball and unpack: 90 | ``` 91 | cd 92 | wget https://bitbucket.org/petsc/petsc4py/downloads/petsc4py-3.12.0.tar.gz 93 | tar xvf petsc4py-3.12.0.tar.gz 94 | ``` 95 | Then build and install to python3: 96 | ``` 97 | cd petsc4py-3.12.0 98 | python3 setup.py build 99 | python3 setup.py install --user 100 | ``` 101 | Follow a completely analogous procedure for slepc4py and mpi4py. Download the tarballs with: 102 | ``` 103 | cd 104 | wget https://bitbucket.org/slepc/slepc4py/downloads/slepc4py-3.12.0.tar.gz 105 | wget https://bitbucket.org/mpi4py/mpi4py/downloads/mpi4py-3.0.3.tar.gz 106 | ``` 107 | 108 | #### Installing wigxjpf 109 | This is a library to compute Wigner-3j and 6j symbols, useful to compute the products of spherical harmonics. Download and unpack: 110 | ``` 111 | cd 112 | wget http://fy.chalmers.se/subatom/wigxjpf/wigxjpf-1.11.tar.gz 113 | tar xvf wigxjpf-1.11.tar.gz 114 | ``` 115 | Then build and install: 116 | ``` 117 | cd wigxjpf-1.11 118 | make 119 | python3 setup.py install --user 120 | ``` 121 | 122 | 123 | ### Installing and running `Kore` 124 | Clone the repository with 125 | ``` 126 | git clone https://bitbucket.org/repepo/kore.git 127 | ``` 128 | Or download and unzip the tar file under the downloads section. 129 | ```sh 130 | cd 131 | wget https://bitbucket.org/repepo/Kore/downloads/kore-0.2.tar.gz 132 | tar xvf kore-0.2.tar.gz 133 | ``` 134 | For regular work, make a copy of the source directory, keeping the original source clean. For example: 135 | 136 | ```sh 137 | cp -r kore-0.2 kwork1 138 | cd kwork1 139 | ``` 140 | 141 | Modify the `parameters.py` under `kwork1/bin/` file as desired. 142 | 143 | Then generate the submatrices: 144 | ```sh 145 | ./bin/submatrices.py ncpus 146 | ``` 147 | where `ncpus` is the number of cpu's (cores) in your system. 148 | 149 | To assemble the main matrices do: 150 | ```sh 151 | mpiexec -n ncpus ./bin/assemble.py 152 | ``` 153 | 154 | If you are solving an *eigenvalue* problem, do the following export: 155 | ```sh 156 | export opts="-st_type sinvert -eps_error_relative ::ascii_info_detail" 157 | ``` 158 | 159 | If you are solving a *forced* problem then do: 160 | ```sh 161 | export opts='-ksp_type preonly -pc_type lu' 162 | ``` 163 | Others options might be required depending on the size of the matrices. 164 | 165 | 166 | To solve the problem do 167 | ```sh 168 | mpiexec -n ncpus ./bin/solve.py $opts 169 | ``` 170 | 171 | The result is written/appended to the file `flow.dat`, and the parameters used are written/appended to the file `params.dat`, one line for each solution. If solving an eigenvalue problem, the eigenvalues are written/appended to the file `eigenvalues.dat`. If solving with magnetic fields, an additional file `magnetic.dat` is created/appended. 172 | 173 | We include a set of scripts in the `tools` folder: 174 | ``` 175 | dodirs.sh 176 | dosubs.sh 177 | reap.sh 178 | getresults.py 179 | ``` 180 | to aid in submitting/collecting results of a large number of runs to/from a PBS-managed cluster. The code itself can run however even on a single cpu machine (albeit slowly). 181 | 182 | ## Authors 183 | 184 | * **Santiago Andres Triana** - *This implementation* 185 | * **Jeremy Rekier** - *Sparse spectral method* 186 | * **Antony Trinh** - *Tensor calculus* 187 | * **Ankit Barik** - *Convection branch, visualization* 188 | * **Fleur Seuren** - *Buoyancy* 189 | 190 | ## License 191 | 192 | GPLv3 193 | 194 | -------------------------------------------------------------------------------- /tools/plot_poltor.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy.sparse as ss 3 | import scipy.sparse.linalg as ssl 4 | import sys 5 | import matplotlib.pyplot as plt 6 | import matplotlib 7 | import matplotlib.tri as tri 8 | import numpy.polynomial.chebyshev as ch 9 | import cmasher as cmr 10 | 11 | sys.path.insert(1,'bin/') 12 | 13 | import utils as ut 14 | import parameters as par 15 | 16 | from matplotlib import rc 17 | rc('text', usetex=True) 18 | 19 | cmap = 'cool' 20 | cmap2 = cmr.neon_r 21 | 22 | ''' 23 | 24 | Script to plot meridional cuts of the flow 25 | Use as: 26 | 27 | python3 plot_poltor.py nsol nR ntheta theta0 theta1 28 | 29 | nsol : solution number 30 | nR : number of points in radius 31 | ntheta : number of points in the theta direction 32 | theta0 : starting colatitude 33 | theta1 : final colatitude 34 | 35 | ''' 36 | 37 | 38 | solnum = int(sys.argv[1]) 39 | 40 | lmax = par.lmax 41 | m = par.m 42 | symm = par.symm 43 | N = par.N 44 | Ek = par.Ek 45 | ricb = par.ricb 46 | rcmb = 1 47 | n = ut.n 48 | 49 | nR = int(sys.argv[2]) # number of radial points 50 | Ntheta = int(sys.argv[3]) # number of points in the theta direction 51 | 52 | gap = rcmb-ricb 53 | r = np.linspace(ricb,rcmb,nR) 54 | if ricb == 0: 55 | r = r[1:] 56 | nR = nR - 1 57 | x = r/gap 58 | else : 59 | x = 2.*(r-ricb)/gap - 1. 60 | 61 | chx = ch.chebvander(x,par.N-1) # this matrix has nR rows and N-1 cols 62 | 63 | #-------------------------------- 64 | phi = 0. # select meridional cut 65 | #-------------------------------- 66 | 67 | a = np.loadtxt('real_flow.field',usecols=solnum) 68 | b = np.loadtxt('imag_flow.field',usecols=solnum) 69 | 70 | if m > 0 : 71 | symm1 = symm 72 | if symm == 1: 73 | m_top = m 74 | m_bot = m+1 # equatorially symmetric case (symm=1) 75 | lmax_top = lmax 76 | lmax_bot = lmax+1 77 | elif symm == -1: 78 | m_top = m+1 79 | m_bot = m # equatorially antisymmetric case (symm=-1) 80 | lmax_top = lmax+1 81 | lmax_bot = lmax 82 | elif m == 0 : 83 | symm1 = -symm 84 | if symm == 1: 85 | m_top = 2 86 | m_bot = 1 # equatorially symmetric case (symm=1) 87 | lmax_top = lmax+2 88 | lmax_bot = lmax+1 89 | elif symm == -1: 90 | m_top = 1 91 | m_bot = 2 # equatorially antisymmetric case (symm=-1) 92 | lmax_top = lmax+1 93 | lmax_bot = lmax+2 94 | 95 | 96 | 97 | 98 | # matrix with Chebishev polynomials at every x point for all degrees: 99 | #chx = ch.chebvander(x,par.N-1) # this matrix has nR rows and N-1 cols 100 | 101 | Plj0 = a[:n] + 1j*b[:n] # N elements on each l block 102 | Tlj0 = a[n:n+n] + 1j*b[n:n+n] # N elements on each l block 103 | 104 | Plj0 = np.reshape(Plj0,(int((lmax-m+1)/2),ut.N1)) 105 | Tlj0 = np.reshape(Tlj0,(int((lmax-m+1)/2),ut.N1)) 106 | 107 | Plj = np.zeros((int((lmax-m+1)/2),N),dtype=complex) 108 | Tlj = np.zeros((int((lmax-m+1)/2),N),dtype=complex) 109 | 110 | if ricb == 0 : 111 | iP = (m + 1 - ut.s)%2 112 | iT = (m + ut.s)%2 113 | for k in np.arange(int((lmax-m+1)/2)) : 114 | Plj[k,iP::2] = Plj0[k,:] 115 | Tlj[k,iT::2] = Tlj0[k,:] 116 | else : 117 | Plj = Plj0 118 | Tlj = Tlj0 119 | 120 | 121 | 122 | dPlj = np.zeros(np.shape(Plj),dtype=complex) 123 | 124 | Plr = np.zeros((int((lmax-m+1)/2), nR),dtype=complex) 125 | dP = np.zeros((int((lmax-m+1)/2), nR),dtype=complex) 126 | rP = np.zeros((int((lmax-m+1)/2), nR),dtype=complex) 127 | Qlr = np.zeros((int((lmax-m+1)/2), nR),dtype=complex) 128 | Slr = np.zeros((int((lmax-m+1)/2), nR),dtype=complex) 129 | Tlr = np.zeros((int((lmax-m+1)/2), nR),dtype=complex) 130 | 131 | np.matmul( Plj, chx.T, Plr ) 132 | np.matmul( Tlj, chx.T, Tlr ) 133 | 134 | rI = ss.diags(r**-1,0) 135 | 136 | ll = np.arange(m_top,lmax_top,2) 137 | L = ss.diags(ll*(ll+1),0) 138 | 139 | for k in range(np.size(ll)): 140 | dPlj[k,:] = ut.Dcheb(Plj[k,:], ricb, rcmb) 141 | 142 | np.matmul(dPlj, chx.T, dP) 143 | 144 | rP = Plr * ss.diags(r**-1,0) 145 | Qlr = ss.diags(ll*(ll+1),0) * rP 146 | Slr = rP + dP 147 | 148 | 149 | 150 | theta = np.linspace(float(sys.argv[4])*np.pi/180,float(sys.argv[5])*np.pi/180,Ntheta+2) 151 | theta = theta[1:-1] 152 | 153 | s = np.zeros( nR*Ntheta ) 154 | z = np.zeros( nR*Ntheta ) 155 | 156 | #ur2 = np.zeros( (nR)*Ntheta ) 157 | #ut2 = np.zeros( (nR)*Ntheta ) 158 | #up2 = np.zeros( (nR)*Ntheta ) 159 | 160 | ur = np.zeros( (nR)*Ntheta, dtype=complex) 161 | utheta = np.zeros( (nR)*Ntheta, dtype=complex) 162 | uphi = np.zeros( (nR)*Ntheta, dtype=complex) 163 | 164 | pol = np.zeros( (nR)*Ntheta, dtype=complex) 165 | tor = np.zeros( (nR)*Ntheta, dtype=complex) 166 | 167 | 168 | #ylm = zeros((lmax-m+1,1),dtype=complex128) 169 | #lv = zeros((lmax-m+1,1)) 170 | 171 | m1 = max(m,1) 172 | if m == 0 : 173 | lmax1 = lmax+1 174 | else: 175 | lmax1 = lmax 176 | l1 = np.arange(m1,lmax1+1) # vector with all l's allowed whether for T or P 177 | 178 | 179 | clm = np.zeros((lmax-m+2,1)) 180 | for i,l in enumerate(l1): 181 | clm[i] = np.sqrt((l-m)*(l+m)) 182 | 183 | 184 | # start index for l. Do not confuse with indices for the Cheb expansion! 185 | idP = int( (1-symm1)/2 ) 186 | idT = int( (1+symm1)/2 ) 187 | 188 | plx = idP+lmax-m+1 189 | tlx = idT+lmax-m+1 190 | 191 | 192 | k=0 193 | for kt in range(Ntheta): 194 | 195 | ylm = np.r_[ut.Ylm_full(lmax, m, theta[kt], phi),0] 196 | for kr in range(0,nR): 197 | 198 | s[k] = r[kr]*np.sin(theta[kt]) 199 | z[k] = r[kr]*np.cos(theta[kt]) 200 | 201 | pol[k] = np.dot( Plr[:,kr], ylm[idP:plx:2] ) 202 | ur[k] = np.dot( Qlr[:,kr], ylm[idP:plx:2] ) 203 | #ur2[k] = absolute(dot( Qlm[:,kr], ylm[idP:plx:2] ))**2 204 | 205 | tmp1 = np.dot( -(l1[idP:plx:2]+1) * Slr[:,kr]/np.tan(theta[kt]), ylm[idP:plx:2] ) 206 | tmp2 = np.dot( clm[idP+1:plx+1:2,0] * Slr[:,kr]/np.sin(theta[kt]), ylm[idP+1:plx+1:2] ) 207 | tmp3 = np.dot( 1j*m * Tlr[:,kr]/np.sin(theta[kt]), ylm[idT:tlx:2] ) 208 | utheta[k] = tmp1+tmp2+tmp3 209 | #ut2[k] = absolute(tmp1+tmp2+tmp3)**2 210 | 211 | tor[k] = np.dot( Tlr[:,kr], ylm[idT:tlx:2] ) 212 | tmp1 = np.dot( (l1[idT:tlx:2]+1) * Tlr[:,kr]/np.tan(theta[kt]), ylm[idT:tlx:2] ) 213 | tmp2 = np.dot( -clm[idT+1:tlx+1:2,0] * Tlr[:,kr]/np.sin(theta[kt]), ylm[idT+1:tlx+1:2] ) 214 | tmp3 = np.dot( 1j*m * Slr[:,kr]/np.sin(theta[kt]), ylm[idP:plx:2] ) 215 | uphi[k] = tmp1+tmp2+tmp3 216 | #up2[k] = absolute(tmp1+tmp2+tmp3)**2 217 | 218 | #uz[k] = ur[k]*cos(theta[kt]) - ut[k]*sin(theta[kt]) 219 | k=k+1 220 | 221 | 222 | a = 1. 223 | c = 1. 224 | id_in = np.where((s**2/(a**2)) + (z**2/(c**2)) < 1.) 225 | s1 = s[id_in] 226 | z1 = z[id_in] 227 | 228 | triang = tri.Triangulation(s1, z1) 229 | # Mask off unwanted triangles (inner core) 230 | xmid = s1[triang.triangles].mean(axis=1) 231 | x2 = xmid*xmid 232 | ymid = z1[triang.triangles].mean(axis=1) 233 | y2 = ymid*ymid 234 | mask = np.where( (x2 + y2 <= ricb**2), 1, 0) 235 | triang.set_mask(mask) 236 | 237 | 238 | #matplotlib.rcParams['text.usetex'] = True 239 | #matplotlib.rcParams['image.cmap'] = 'rainbow' 240 | 241 | #fig=plt.figure(figsize=(14,5)) 242 | fig, ax = plt.subplots(nrows=1,ncols=5,sharey='row',figsize=(13,5)) 243 | #fig.subplots_adjust(hspace=0.01) 244 | 245 | 246 | 247 | # ------------------------------------------------------------------- ur 248 | #ax[0,0]=fig.add_subplot(151) 249 | #ax1.set_title(r'$|u_r|$',size=22) 250 | ax[0].text(0.31,0,r'$|\mathbf{\hat r}\cdot\mathbf{u}_0|$',size=17) 251 | im1=ax[0].tricontourf( triang, np.absolute(ur[id_in]), 70, cmap=cmap) 252 | for c in im1.collections: 253 | c.set_edgecolor('face') 254 | ax[0].plot(r[0]*np.sin(theta),r[0]*np.cos(theta),'k',lw=0.4) 255 | ax[0].plot(r[-1]*np.sin(theta),r[-1]*np.cos(theta),'k',lw=0.4) 256 | ax[0].plot([0,0], [ r.min(),r.max() ], 'k', lw=0.4) 257 | ax[0].plot([0,0], [ -r.max(),-r.min() ], 'k', lw=0.4) 258 | ax[0].set_aspect('equal') 259 | ax[0].set_axis_off() 260 | 261 | cax = ax[0].inset_axes([0.02,-0.55,0.02,1.1],transform=ax[0].transData) 262 | plt.colorbar(im1,aspect=70,ax=ax[0],cax=cax) 263 | 264 | # --------------------------------------------------------------- utheta 265 | #ax2=fig.add_subplot(152) 266 | #ax2.set_title(r'$|u_\theta|$',size=22) 267 | ax[1].text(0.31,0,r'$|\mathbf{\hat \theta}\cdot\mathbf{u}_0|$',size=17) 268 | im2=ax[1].tricontourf( triang, np.absolute(utheta[id_in]), 70, cmap=cmap) 269 | for c in im2.collections: 270 | c.set_edgecolor('face') 271 | ax[1].plot(r[0]*np.sin(theta),r[0]*np.cos(theta),'k',lw=0.4) 272 | ax[1].plot(r[-1]*np.sin(theta),r[-1]*np.cos(theta),'k',lw=0.4) 273 | ax[1].plot([0,0], [ r.min(),r.max() ], 'k', lw=0.4) 274 | ax[1].plot([0,0], [ -r.max(),-r.min() ], 'k', lw=0.4) 275 | ax[1].set_aspect('equal') 276 | ax[1].set_axis_off() 277 | 278 | cax = ax[1].inset_axes([0.02,-0.55,0.02,1.1],transform=ax[1].transData) 279 | plt.colorbar(im2,aspect=70,ax=ax[1],cax=cax) 280 | 281 | # ----------------------------------------------------------------- uphi 282 | #ax3=fig.add_subplot(153) 283 | #ax3.set_title(r'$|u_\phi|$',size=22) 284 | ax[2].text(0.31,0,r'$|\mathbf{\hat \phi}\cdot\mathbf{u}_0|$',size=17) 285 | im3=ax[2].tricontourf( triang, np.absolute(uphi[id_in]), 70, cmap=cmap) 286 | for c in im3.collections: 287 | c.set_edgecolor('face') 288 | ax[2].plot(r[0]*np.sin(theta),r[0]*np.cos(theta),'k',lw=0.4) 289 | ax[2].plot(r[-1]*np.sin(theta),r[-1]*np.cos(theta),'k',lw=0.4) 290 | ax[2].plot([0,0], [ r.min(),r.max() ], 'k', lw=0.4) 291 | ax[2].plot([0,0], [ -r.max(),-r.min() ], 'k', lw=0.4) 292 | ax[2].set_aspect('equal') 293 | ax[2].set_axis_off() 294 | 295 | cax = ax[2].inset_axes([0.02,-0.55,0.02,1.1],transform=ax[2].transData) 296 | plt.colorbar(im3,aspect=70,ax=ax[2],cax=cax) 297 | 298 | 299 | # ------------------------------------------------------------------- pol 300 | #ax4=fig.add_subplot(154) 301 | #ax1.set_title(r'$|u_r|$',size=22) 302 | ax[3].text(0.38,0,r'$|\mathcal{P}|$',size=17) 303 | im4=ax[3].tricontourf( triang, np.absolute(pol[id_in]), 70, cmap=cmap2) 304 | #im4=ax4.tricontourf( triang, np.real(pol[id_in]), 70, cmap=cmap) 305 | for c in im4.collections: 306 | c.set_edgecolor('face') 307 | ax[3].plot(r[0]*np.sin(theta),r[0]*np.cos(theta),'k',lw=0.4) 308 | ax[3].plot(r[-1]*np.sin(theta),r[-1]*np.cos(theta),'k',lw=0.4) 309 | ax[3].plot([0,0], [ r.min(),r.max() ], 'k', lw=0.4) 310 | ax[3].plot([0,0], [ -r.max(),-r.min() ], 'k', lw=0.4) 311 | ax[3].set_aspect('equal') 312 | ax[3].set_axis_off() 313 | 314 | cax = ax[3].inset_axes([0.02,-0.55,0.02,1.1],transform=ax[3].transData) 315 | plt.colorbar(im4,aspect=70,ax=ax[3],cax=cax) 316 | 317 | 318 | 319 | # ----------------------------------------------------------------- tor 320 | #ax5=fig.add_subplot(155) 321 | #ax3.set_title(r'$|u_\phi|$',size=22) 322 | ax[4].text(0.35,0,r'$|\mathcal{T}|$',size=17) 323 | im5=ax[4].tricontourf( triang, np.absolute(tor[id_in]), 70, cmap=cmap2) 324 | #im5=ax5.tricontourf( triang, np.real(tor[id_in]), 70, cmap=cmap) 325 | for c in im5.collections: 326 | c.set_edgecolor('face') 327 | ax[4].plot(r[0]*np.sin(theta),r[0]*np.cos(theta),'k',lw=0.4) 328 | ax[4].plot(r[-1]*np.sin(theta),r[-1]*np.cos(theta),'k',lw=0.4) 329 | ax[4].plot([0,0], [ r.min(),r.max() ], 'k', lw=0.4) 330 | ax[4].plot([0,0], [ -r.max(),-r.min() ], 'k', lw=0.4) 331 | ax[4].set_aspect('equal') 332 | ax[4].set_axis_off() 333 | 334 | cax = ax[4].inset_axes([0.02,-0.55,0.02,1.1],transform=ax[4].transData) 335 | plt.colorbar(im5,aspect=70,ax=ax[4],cax=cax) 336 | 337 | 338 | # ---------------------------------------------------------------------- 339 | plt.tight_layout() 340 | plt.show() 341 | 342 | 343 | -------------------------------------------------------------------------------- /koreviz/libkoreviz.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy.sparse as ss 3 | import shtns 4 | 5 | 6 | def spec2spat_vec(M,ut,chx,Plj,Tlj,vsymm,nthreads, 7 | vort=False,transform=True): 8 | 9 | lm1 = M.lmax-M.m+1 10 | 11 | # init arrays 12 | Plr = np.zeros( (lm1, M.nr), dtype=complex ) 13 | Qlr = np.zeros( (lm1, M.nr), dtype=complex ) 14 | Slr = np.zeros( (lm1, M.nr), dtype=complex ) 15 | Tlr = np.zeros( (lm1, M.nr), dtype=complex ) 16 | dP = np.zeros( (lm1, M.nr), dtype=complex ) 17 | rP = np.zeros( (lm1, M.nr), dtype=complex ) 18 | dPlj = np.zeros( np.shape(Plj), dtype=complex ) 19 | if vort: 20 | ddPlj = np.zeros( np.shape(Plj), dtype=complex ) 21 | dTlj = np.zeros( np.shape(Tlj), dtype=complex ) 22 | dT = np.zeros( (lm1, M.nr), dtype=complex ) 23 | ddP = np.zeros( (lm1, M.nr), dtype=complex ) 24 | 25 | # These are the l values (ll) and indices (idp,idt) 26 | s = int(vsymm*0.5+0.5) # s=0 if antisymm, s=1 if symm 27 | 28 | if M.m > 0: 29 | idp = np.arange( 1-s, lm1, 2) 30 | idt = np.arange( s , lm1, 2) 31 | ll = np.arange( M.m, M.lmax+1 ) 32 | elif M.m == 0: 33 | idp = np.arange( s , lm1, 2) 34 | idt = np.arange( 1-s, lm1, 2) 35 | ll = np.arange( M.m+1, M.lmax+2 ) 36 | 37 | # populate Plr and Tlr 38 | Plr[idp,:] = np.matmul( Plj, chx.T) 39 | Tlr[idt,:] = np.matmul( Tlj, chx.T) 40 | 41 | # populate dPlj and dP 42 | for k in range(int(lm1/2)): 43 | dPlj[k,:] = ut.Dcheb(Plj[k,:], M.ricb, M.rcmb) 44 | dP[idp,:] = np.matmul(dPlj, chx.T) 45 | if vort: 46 | for k in range(int(lm1/2)): 47 | dTlj[k,:] = ut.Dcheb(Tlj[k,:], M.ricb, M.rcmb) 48 | ddPlj[k,:] = ut.Dcheb(dPlj[k,:], M.ricb, M.rcmb) 49 | dT[idt,:] = np.matmul(dTlj, chx.T) 50 | ddP[idp,:] = np.matmul(ddPlj, chx.T) 51 | 52 | # populate Qlr and Slr 53 | rI = ss.diags(M.r**-1,0) 54 | L = ss.diags(ll*(ll+1),0) 55 | 56 | if vort: 57 | r2I = ss.diags(M.r**-2,0) 58 | r2P = Plr * r2I 59 | 60 | Qlr = L * Tlr # l(l+1) * T 61 | Slr = dT + Tlr * rI # T' + T/r 62 | Tlr = L * r2P - 2 * dP * rI - ddP # l(l+1) * P/r^2 - 2*P'/r - P" 63 | 64 | else: 65 | 66 | rP = Plr * rI # P/r 67 | Qlr = L * rP # l(l+1)*P/r 68 | Slr = rP + dP # P' + P/r 69 | 70 | # Now in these Q, S, T arrays, the first lmax+1 indices are for m=0 71 | # and the remaining lmax+1-m are for mres. 72 | # (this is the SHTns way with m=mres when m is not zero) 73 | 74 | lmax2 = int( M.lmax + 1 - np.sign(M.m) ) # the true max value of l 75 | # nlm = ( np.sign(M.m)+1 ) * (lmax2+1) - M.m 76 | 77 | if M.m == 0 : #pad with zeros for the l=0 component 78 | ql = np.r_[ np.zeros((1,M.nr)) ,Qlr ] 79 | sl = np.r_[ np.zeros((1,M.nr)) ,Slr ] 80 | tl = np.r_[ np.zeros((1,M.nr)) ,Tlr ] 81 | pl = np.r_[ np.zeros((1,M.nr)) ,Plr ] 82 | else : 83 | ql = Qlr 84 | sl = Slr 85 | tl = Tlr 86 | pl = Plr #Only for output, not needed for transforms 87 | 88 | 89 | # SHTns init 90 | norm = shtns.sht_schmidt | shtns.SHT_NO_CS_PHASE 91 | mmax = int( np.sign(M.m) ) 92 | mres = max(1,M.m) 93 | sh = shtns.sht( lmax2, mmax=mmax, mres=mres, norm=norm, nthreads=nthreads ) 94 | M.sh = sh 95 | 96 | # Create spectral arrays in SHTns style 97 | 98 | Q = np.zeros([M.nr, sh.nlm], dtype=complex) 99 | S = np.zeros([M.nr, sh.nlm], dtype=complex) 100 | T = np.zeros([M.nr, sh.nlm], dtype=complex) 101 | P = np.zeros([M.nr, sh.nlm], dtype=complex) 102 | 103 | mask = sh.m == M.m 104 | 105 | Q[:,mask] = ql.T 106 | S[:,mask] = sl.T 107 | T[:,mask] = tl.T 108 | P[:,mask] = pl.T 109 | 110 | if transform: 111 | ntheta, nphi = sh.set_grid( M.ntheta+M.ntheta%2, M.nphi, polar_opt=1e-10) 112 | M.theta = np.arccos(sh.cos_theta) 113 | M.phi = np.linspace(0., 2*np.pi, M.nphi*mres+1, endpoint=True) 114 | M.ntheta = ntheta 115 | M.nphi = nphi 116 | 117 | # init the spatial component arrays 118 | ur = np.zeros([M.nr, ntheta, nphi] ) 119 | utheta = np.zeros([M.nr, ntheta, nphi] ) 120 | uphi = np.zeros([M.nr, ntheta, nphi] ) 121 | 122 | # the final call to shtns for each radius 123 | for ir in range(M.nr): 124 | ur[ir,...], utheta[ir,...], uphi[ir,...] = sh.synth( Q[ir,:], S[ir,:], T[ir,:]) 125 | 126 | return Q,S,P,T,ur,utheta,uphi 127 | else: 128 | return Q,S,P,T 129 | 130 | 131 | def spec2spat_scal(M,chx,Plj,vsymm,nthreads,transform=True): 132 | 133 | lm1 = M.lmax-M.m+1 134 | 135 | # init arrays 136 | Plr = np.zeros( (lm1, M.nr), dtype=complex ) 137 | 138 | # These are the l values (ll) and indices (idp,idt) 139 | s = int(vsymm*0.5+0.5) # s=0 if antisymm, s=1 if symm 140 | 141 | if M.m > 0: 142 | idp = np.arange( 1-s, lm1, 2) 143 | elif M.m == 0: 144 | idp = np.arange( s , lm1, 2) 145 | 146 | # populate Plr 147 | Plr[idp,:] = np.matmul( Plj, chx.T) 148 | 149 | # (this is the SHTns way with m=mres when m is not zero) 150 | 151 | lmax2 = int( M.lmax + 1 - np.sign(M.m) ) # the true max value of l 152 | # nlm = ( np.sign(M.m)+1 ) * (lmax2+1) - M.m 153 | 154 | if M.m == 0 : #pad with zeros for the l=0 component 155 | ql = np.r_[ np.zeros((1,M.nr)) ,Plr ] 156 | else : 157 | ql = Plr 158 | 159 | norm = shtns.sht_schmidt | shtns.SHT_NO_CS_PHASE 160 | mmax = int( np.sign(M.m) ) 161 | mres = max(1,M.m) 162 | sh = shtns.sht( lmax2, mmax=mmax, mres=mres, norm=norm, nthreads=nthreads ) 163 | M.sh = sh 164 | 165 | Q = np.zeros([M.nr, sh.nlm], dtype=complex) 166 | mask = sh.m == M.m 167 | Q[:,mask] = ql.T 168 | 169 | if transform: 170 | # SHTns init 171 | #norm = shtns.sht_schmidt | shtns.SHT_NO_CS_PHASE 172 | ntheta, nphi = sh.set_grid( M.ntheta+M.ntheta%2, M.nphi, polar_opt=1e-10) 173 | M.theta = np.arccos(sh.cos_theta) 174 | M.phi = np.linspace(0., 2*np.pi, M.nphi*mres+1, endpoint=True) 175 | 176 | # init the spatial component arrays 177 | M.ntheta = ntheta 178 | M.nphi = nphi 179 | M.sh = sh 180 | scal = np.zeros([M.nr, ntheta, nphi] ) 181 | # the final call to shtns for each radius 182 | for ir in range(M.nr): 183 | scal[ir,...] = sh.synth( Q[ir,:]) 184 | return Q,scal 185 | else: 186 | return [Q] 187 | 188 | def get_ang_momentum(M,epsilon_cmb): 189 | 190 | l = M.sh.l 191 | m = M.sh.m 192 | r = M.r 193 | Slm = M.Slm[0,:] 194 | Tlm = M.Tlm[0,:] 195 | 196 | Gamma_tor = np.zeros(M.sh.nlm,dtype=np.complex128) 197 | 198 | Gamma_pol = 1j * m * Slm * M.rcmb * np.conjugate(epsilon_cmb) 199 | clm1 = (l+2)/(2*l+3) * np.sqrt((l+m+1)*(l-m+1)) 200 | clm2 = (l-1)/(2*l-1) * np.sqrt((l+m)*(l-m)) 201 | 202 | for mm in [0,M.m]: 203 | for ell in range(mm,M.lmax+1): 204 | k = M.sh.idx(ell,mm) 205 | if ell == mm: 206 | Gamma_tor[k] = M.rcmb * ( clm1[k] * Tlm[M.sh.idx(ell+1,mm)]) 207 | elif ell == M.lmax: 208 | Gamma_tor[k] = M.rcmb * ( -clm2[k] * Tlm[M.sh.idx(ell-1,mm)] ) 209 | else: 210 | Gamma_tor[k] = M.rcmb * ( clm1[k] * Tlm[M.sh.idx(ell+1,mm)] 211 | -clm2[k] * Tlm[M.sh.idx(ell-1,mm)] ) 212 | Gamma_tor[k] *= np.conjugate(epsilon_cmb[k]) 213 | 214 | torq_pollm = np.real( 4*np.pi/(2*l+1) * (Gamma_pol)) # elementwise (array) multiplication 215 | torq_torlm = np.real( 4*np.pi/(2*l+1) * (Gamma_tor)) 216 | mask = M.sh.m == 0 217 | torq_pollm[~mask] *= 2 218 | torq_torlm[~mask] *= 2 219 | torq_pol = np.sum(torq_pollm) 220 | torq_tor = np.sum(torq_torlm) 221 | 222 | return torq_pol, torq_tor 223 | 224 | def get_coriolis_torque(M,epsilon_cmb): 225 | 226 | l = M.sh.l 227 | m = M.sh.m 228 | r = M.r 229 | Qlm = M.Qlm[0,:] 230 | Slm = M.Slm[0,:] 231 | Tlm = M.Tlm[0,:] 232 | 233 | Gamma_rad = np.zeros(M.sh.nlm,dtype=np.complex128) 234 | Gamma_con = np.zeros(M.sh.nlm,dtype=np.complex128) 235 | Gamma_tor = np.zeros(M.sh.nlm,dtype=np.complex128) 236 | 237 | clm_rad_p2 = -2/(2*l+3)/(2*l+5) * np.sqrt((l+m+1)*(l-m+1)) * np.sqrt((l+m+2)*(l-m+2)) 238 | clm_rad_0 = 4*(l**2+l-1+m**2)/(4*l*(l+1)-3) 239 | clm_rad_m2 = 2/(4*l*(l-2)+3) * np.sqrt((l+m)*(l-m)) * np.sqrt((l-1)**2-m**2) 240 | 241 | for mm in [0,M.m]: 242 | for ell in range(mm,M.lmax+1): 243 | k = M.sh.idx(ell,mm) 244 | if ell <= mm+1: 245 | Gamma_rad[k] = M.rcmb * ( clm_rad_p2[k] * Qlm[M.sh.idx(ell+2,mm)] 246 | +clm_rad_0[k] * Qlm[M.sh.idx(ell,mm)] ) 247 | elif ell >= M.lmax-1: 248 | Gamma_rad[k] = M.rcmb * ( clm_rad_m2[k] * Qlm[M.sh.idx(ell-2,mm)] 249 | +clm_rad_0[k] * Qlm[M.sh.idx(ell,mm)] ) 250 | else: 251 | Gamma_rad[k] = M.rcmb * ( clm_rad_p2[k] * Qlm[M.sh.idx(ell+2,mm)] 252 | +clm_rad_0[k] * Qlm[M.sh.idx(ell,mm)] 253 | +clm_rad_m2[k] * Qlm[M.sh.idx(ell-2,mm)] ) 254 | Gamma_rad[k] *= np.conjugate(epsilon_cmb[k]) 255 | 256 | clm_con_p2 = -2*(l+3)/(2*l+3)/(2*l+5) * np.sqrt((l+m+1)*(l-m+1)) * np.sqrt((l+m+2)*(l-m+2)) 257 | clm_con_0 = -2*(l+l**2-3*m**2)/(4*l*(l+1)-3) 258 | clm_con_m2 = 2*(l-2)/(4*l*(l-2)+3) * np.sqrt((l+m)*(l-m)) * np.sqrt((l-1)**2-m**2) 259 | 260 | for mm in [0,M.m]: 261 | for ell in range(mm,M.lmax+1): 262 | k = M.sh.idx(ell,mm) 263 | if ell <= mm+1: 264 | Gamma_con[k] = M.rcmb * ( clm_con_p2[k] * Slm[M.sh.idx(ell+2,mm)] 265 | +clm_con_0[k] * Slm[M.sh.idx(ell,mm)] ) 266 | elif ell >= M.lmax-1: 267 | Gamma_con[k] = M.rcmb * ( clm_con_m2[k] * Slm[M.sh.idx(ell-2,mm)] 268 | +clm_con_0[k] * Slm[M.sh.idx(ell,mm)] ) 269 | else: 270 | Gamma_con[k] = M.rcmb * ( clm_con_p2[k] * Slm[M.sh.idx(ell+2,mm)] 271 | +clm_con_0[k] * Slm[M.sh.idx(ell,mm)] 272 | +clm_con_m2[k] * Slm[M.sh.idx(ell-2,mm)] ) 273 | Gamma_con[k] *= np.conjugate(epsilon_cmb[k]) 274 | 275 | clm_tor_p1 = 2*1j*m*(l+2)/(2*l+3) * np.sqrt((l+m+1)*(l-m+1)) 276 | clm_tor_m1 = 2*1j*m*(l-1)/(2*l-1) * np.sqrt((l+m)*(l-m)) 277 | 278 | for mm in [0,M.m]: 279 | for ell in range(mm,M.lmax+1): 280 | k = M.sh.idx(ell,mm) 281 | if ell == mm: 282 | Gamma_tor[k] = M.rcmb * ( clm_tor_p1[k] * Tlm[M.sh.idx(ell+1,mm)]) 283 | elif ell == M.lmax: 284 | Gamma_tor[k] = M.rcmb * ( clm_tor_m1[k] * Tlm[M.sh.idx(ell-1,mm)] ) 285 | else: 286 | Gamma_tor[k] = M.rcmb * ( clm_tor_p1[k] * Tlm[M.sh.idx(ell+1,mm)] 287 | +clm_tor_m1[k] * Tlm[M.sh.idx(ell-1,mm)] ) 288 | Gamma_tor[k] *= np.conjugate(epsilon_cmb[k]) 289 | 290 | torq_radlm = np.real( 4*np.pi/(2*l+1) * (Gamma_rad)) 291 | torq_conlm = np.real( 4*np.pi/(2*l+1) * (Gamma_con)) 292 | torq_torlm = np.real( 4*np.pi/(2*l+1) * (Gamma_tor)) 293 | mask = M.sh.m == 0 294 | torq_radlm[~mask] *= 2 295 | torq_conlm[~mask] *= 2 296 | torq_torlm[~mask] *= 2 297 | torq_rad = np.sum(torq_radlm) 298 | torq_con = np.sum(torq_conlm) 299 | torq_tor = np.sum(torq_conlm) 300 | 301 | return torq_rad, torq_con, torq_tor 302 | 303 | -------------------------------------------------------------------------------- /bin/radial_profiles.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import utils as ut 3 | import parameters as par 4 | import autocompute as ac 5 | 6 | def twozone(r,args): 7 | ''' 8 | Symmetrized dT/dr (dimensionless), extended to negative r 9 | rc is the transition radius, h the transition width, sym is 1 or -1 10 | depending on the radial parity desired 11 | ** Neutrally buoyant inner zone, stratified outer zone ** (Vidal2015) 12 | ''' 13 | rc = args[0] 14 | h = args[1] 15 | sym = args[2] 16 | 17 | out = np.zeros_like(r) 18 | for i,x in enumerate(r): 19 | if x >= 0 : 20 | out[i] = (1 + np.tanh( 2*(x-rc)/h ))/2 21 | elif x < 0 : 22 | out[i] = sym*(1 + np.tanh( 2*(abs(x)-rc)/h ))/2 23 | return out 24 | 25 | 26 | 27 | def BVprof(r,args=None): 28 | ''' 29 | Symmetrized dT/dr (dimensionless), extended to negative r. 30 | Define this function so that it is an odd function of r 31 | ''' 32 | out = np.zeros_like(r) 33 | for i,x in enumerate(r): 34 | out[i] = x # dT/dr propto r like in Dintrans1999 35 | #out[i] = x*abs(x) # dT/dr propto r^2 36 | #out[i] = x**3 # dT/dr propto r^3 37 | #rc = args[0] 38 | #h = args[1] 39 | #if abs(x) < rc/2 : 40 | # out[i] = np.tanh( 4*x/h ) 41 | #elif x >= rc/2 : 42 | # out[i] = 0.5*(1 - np.tanh( 4*(x-rc)/h )) 43 | #elif x <= -rc/2 : 44 | # out[i] = -0.5*(1 - np.tanh( 4*(abs(x)-rc)/h )) 45 | return out 46 | 47 | 48 | 49 | #------------------------------------------------------------------------------------------------------------- 50 | # -------------------------------------------------------------------------- User-provided background profiles 51 | #------------------------------------------------------------------------------------------------------------- 52 | def viscosity(r): 53 | ''' 54 | Kinematic viscosity (aka momentum diffusivity) 55 | ''' 56 | out = np.zeros_like(r) 57 | return out # even function of r 58 | 59 | #--------------------------------------------------------- 60 | # Get required parameters according to interior model 61 | #--------------------------------------------------------- 62 | 63 | def getPolyParams(): 64 | 65 | #This function defines useful parameters required to compute polytropic profiles 66 | #defined by rho = T^n , p = rho^(1 + 1/n) and ideas gas law : p = rho T 67 | 68 | 69 | radratio = par.ricb/ut.rcmb 70 | Nrhofac = np.exp(par.Nrho/par.polind) 71 | c0 = ( (-2*par.g2 - (2*par.g0 + par.g1 - 2*par.g2)*Nrhofac*radratio 72 | + 2*par.g0*radratio**2 + par.g1*radratio**3)/ 73 | ((-1 + radratio)*(2*par.g2 + radratio*(2*par.g0 + par.g1 + par.g1*radratio))) ) 74 | c1 = ( (-2*(-1 + Nrhofac)*radratio)/ 75 | ((-1 + radratio)*(2*par.g2 + radratio*(2*par.g0 + par.g1 + par.g1*radratio))*ut.rcmb) ) 76 | 77 | return c0,c1,Nrhofac 78 | 79 | 80 | def getEOSparams(): 81 | 82 | #This function allows the user to set custom profiles defined by polynomial 83 | #coefficients. Coefficients are ordered from highest order to constant, as 84 | #required by numpy.polyval . 85 | 86 | # Set constant values for any missing profiles 87 | 88 | coeffDens = [1]; coeffTemp = [1]; coeffAlpha = [1]; coeffGrav = [1] 89 | 90 | if par.interior_model == 'jupiter': 91 | coeffDens = [-3428.5223691617484,9297.45806689145,-9053.015978040878, 92 | 3692.8869368199303,-970.5219501076829,455.9692566566673] 93 | coeffTemp = [-234.89315693284604,629.7550411434713,-620.7785004558405, 94 | 264.5131393963175,-60.42859069469556,23.325397786820883] 95 | coeffAlpha = [ 7772.115489487784, -32570.155177385783,57445.909371048925, 96 | -55346.03609488902, 31580.21652524248,-10792.31056102935, 97 | 2120.720011656385,-212.80140120580023,2.8282797790308654] 98 | coeffGrav = [ 359.26949772994396, -1647.4951402273955, 3114.971302818801, 99 | -3121.9995586185805, 1768.8806269264637, -552.869900057094, 100 | 78.85393983296116, 1.8991569550910268, -0.5216321175523105] 101 | elif par.interior_model == 'saturn': 102 | coeffTemp= [ 2746322.598433222, -12819667.849508610, 24913613.036177561, 103 | -26159709.956658602, 16027297.768664116, -5718555.197998112, 104 | 1089237.928095523, -77880.376328818 ] 105 | coeffDens= [ 2746322.598433222, -12819667.849508610, 24913613.036177561, 106 | -26159709.956658602, 16027297.768664116, -5718555.197998112] 107 | coeffGrav= [ 2746322.598433222, -12819667.849508610, 24913613.036177561, 108 | 1089237.928095523, -77880.376328818 ] 109 | coeffAlpha= [ 2746322.598433222, -12819667.849508610, 24913613.036177561, 110 | -26159709.956658602, 16027297.768664116, -5718555.197998112, 111 | 1089237.928095523, -77880.376328818 ] 112 | # elif par.interior_model == 'pns': # To be implemented 113 | # pass 114 | 115 | return coeffDens, coeffTemp, coeffAlpha, coeffGrav 116 | 117 | 118 | if par.interior_model == 'polytrope': 119 | c0,c1,Nrhofac = getPolyParams() 120 | 121 | def gint(r): # Indefinite integral of gravity 122 | return par.g0*r + (par.g1*r**2)/(2.*ut.rcmb) - (par.g2*ut.rcmb**2)/r 123 | 124 | # Special case: Gravity g(r) = 1/r^2 125 | 126 | if par.g2 == 1 and (par.g0+par.g1+par.g2) == 1: 127 | def zeta(r): 128 | return -c1 * gint(r) + c0 129 | else: 130 | coeffDens, coeffTemp, coeffAlpha, coeffGrav = getEOSparams() 131 | 132 | #------------------------------------------------------------- 133 | # Compute profiles depending on interior model 134 | #------------------------------------------------------------- 135 | 136 | 137 | def entropy_gradient(r,args=None): 138 | if args is not None: 139 | ampStrat = args[0] 140 | rStrat = args[1] 141 | thickStrat=args[2] 142 | slopeStrat=args[3] 143 | 144 | out = ( 0.25 * (ampStrat+1.) * (1.+np.tanh(slopeStrat*(r-rStrat))) * 145 | (1.-np.tanh(slopeStrat*(r-rStrat-thickStrat))) 146 | - 1. ) 147 | elif ( par.interior_model == 'polytrope' and 148 | par.g2 == 1 and (par.g0+par.g1+par.g2) == 1): #Analytical solution is only possible when gravity is 1/r^2, there is probably a better way to write this. 149 | 150 | out = ( ((-1 + Nrhofac)*Nrhofac**par.polind * par.polind*par.ricb)/ 151 | ((-1 + Nrhofac**par.polind)*r**2*(-1 + par.ricb/ut.rcmb)) / 152 | zeta(r)**(par.polind+1) ) * (1-par.ricb) 153 | else: 154 | out = -par.ricb/(1-par.ricb) * 1/r**2 155 | return out 156 | 157 | 158 | def temperature(r): 159 | if par.interior_model is not None: 160 | if par.interior_model == 'polytrope': 161 | out = -c1*gint(r) + c0 162 | else: 163 | out = np.polyval(coeffTemp,par.r_cutoff * r) 164 | else: 165 | out = np.ones_like(r) 166 | return out 167 | 168 | def log_temperature(r): 169 | out = np.log(temperature(r)) 170 | return out 171 | 172 | 173 | 174 | def density(r): 175 | if par.interior_model is not None: 176 | if par.interior_model == 'polytrope': 177 | out = temperature(r)**par.polind 178 | else: 179 | out = np.polyval(coeffDens,par.r_cutoff * r) 180 | else: 181 | out = np.ones_like(r) 182 | return out 183 | 184 | def log_density(r): 185 | out = np.log(density(r)) 186 | return out 187 | 188 | 189 | def alpha(r): 190 | if par.interior_model is not None: 191 | if par.interior_model == 'polytrope': 192 | out = 1/temperature(r) 193 | else: 194 | out = np.exp(np.polyval(coeffAlpha,par.r_cutoff * r)) #Fit is to ln alpha 195 | else: 196 | out = np.ones_like(r) 197 | return out 198 | 199 | 200 | def thermal_diffusivity(r): 201 | out = np.ones_like(r) 202 | return out 203 | 204 | def log_thermal_diffusivity(r): 205 | out = np.log(thermal_diffusivity(r)) 206 | return out 207 | 208 | 209 | def kappa_rho(r): 210 | out = thermal_diffusivity(r)*density(r) 211 | return out 212 | 213 | def heat_source(r,eps0=0): 214 | out = eps0 215 | return out 216 | 217 | def epsilon_h(r,eps0=0): 218 | out = (eps0*r*heat_source(r,eps0)/ 219 | (density(r)*temperature(r)* 220 | thermal_diffusivity(r))) 221 | return out 222 | 223 | 224 | def gravity(r): 225 | if par.interior_model is not None: 226 | if par.interior_model == 'polytrope': 227 | out = par.g0 + par.g1 * r/ut.rcmb + par.g2 * ut.rcmb**2/r**2 228 | else: 229 | out = np.polyval(coeffGrav,par.r_cutoff * r) 230 | else: 231 | out = r 232 | return out 233 | 234 | 235 | def buoFac(r): 236 | 237 | #Profile of buoyancy = rho*alpha*T*g 238 | #If autocomputed, gravity is multiplied later in Cheb space 239 | 240 | if par.autograv: 241 | out = density(r)*alpha(r)*temperature(r) 242 | else: 243 | out = density(r)*alpha(r)*temperature(r)*gravity(r) 244 | return out 245 | 246 | 247 | 248 | #------------------------------------------ 249 | # Magnetic : Variable conductivity 250 | #------------------------------------------ 251 | 252 | def conductivity(r): 253 | ''' 254 | This function needs to be an even function of r when ricb=0 255 | ''' 256 | out = np.ones_like(r) 257 | return out 258 | 259 | 260 | def magnetic_diffusivity(r): 261 | out = 1./conductivity(r) 262 | return out 263 | 264 | 265 | def eta_rho(r): 266 | out = magnetic_diffusivity(r)*density(r) 267 | return out 268 | 269 | # --------------------------------------------------------------------------------------- 270 | # The functions below are directly linked to the ones above, no user intervention needed. 271 | # --------------------------------------------------------------------------------------- 272 | 273 | def rog(r): 274 | ''' 275 | density * gravitational acceleration 276 | ''' 277 | out = density(r) * gravity(r) # even * odd = odd 278 | return out # odd function of r 279 | 280 | 281 | def krT(r): 282 | ''' 283 | Thermal diffusivity * density * temperature 284 | ''' 285 | out = thermal_diffusivity(r) * density(r) * temperature(r) 286 | return out # even*even*even = even function of r 287 | 288 | 289 | def roT(r): 290 | ''' 291 | density * temperature 292 | ''' 293 | out = density(r) * temperature(r) 294 | return out # even*even = even function of r 295 | 296 | 297 | def tds(r): 298 | if par.entropyGrad == 'auto': 299 | cd_ent = ac.get_equilibrium_entropy() 300 | dsdr = ut.funcheb(cd_ent,r,par.ricb,ut.rcmb,par.N) 301 | out = temperature(r)*dsdr 302 | else: 303 | # out = BruVa2(r) * temperature(r) / gravity(r) 304 | out = temperature(r) * entropy_gradient(r) 305 | return out # even * even / odd = odd 306 | 307 | 308 | def write_profiles(): 309 | i = np.arange(0, par.N) 310 | xi = np.cos(np.pi * (i + 0.5) / par.N) 311 | 312 | if par.ricb > 0: 313 | ri = (par.ricb + (ut.rcmb - par.ricb) * (xi + 1) / 2.) 314 | elif par.ricb == 0 : 315 | ri = ut.rcmb * xi 316 | 317 | rho0 = density(ri) 318 | temp0= temperature(ri) 319 | alpha0=alpha(ri) 320 | grav = gravity(ri) 321 | dsdr = entropy_gradient(ri) 322 | cond = conductivity(ri) 323 | 324 | X = np.array([ri,rho0,temp0,alpha0,grav,dsdr,cond]).transpose() 325 | 326 | header = ["r", "rho0", "temp0", "alpha0", "grav", "dsdr", "cond"] 327 | 328 | # try: 329 | # import pandas as pd 330 | # df = pd.DataFrame(X,columns=header) 331 | # df.to_csv('profiles.dat',sep=' ',float_format='%.4f',index=False) 332 | # except: 333 | # np.savetxt('profiles.dat',X,fmt='%.4f',header=' '.join(header),delimiter=' ') 334 | 335 | np.savetxt('profiles.dat',X,fmt='%.4f',header=' '.join(header),delimiter=' ') 336 | #------------------------------------------ 337 | -------------------------------------------------------------------------------- /bin/solve.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | ''' 3 | kore solver script. Writes solutions to disk. 4 | 5 | To use, first export desired solver options: 6 | > export opts='...' 7 | 8 | and then execute: 9 | > mpiexec -n ncpus ./bin/solve.py $opts 10 | 11 | You can use the postprocess.py script after the solutions are written to disk. 12 | ''' 13 | 14 | import sys 15 | import slepc4py 16 | slepc4py.init(sys.argv) 17 | from petsc4py import PETSc 18 | from slepc4py import SLEPc 19 | import scipy.io as sio 20 | import scipy.sparse as ss 21 | from timeit import default_timer as timer 22 | import numpy as np 23 | 24 | import parameters as par 25 | import utils as ut 26 | 27 | 28 | 29 | def main(): 30 | 31 | Print = PETSc.Sys.Print 32 | rank = PETSc.COMM_WORLD.getRank() 33 | size = PETSc.COMM_WORLD.getSize() 34 | opts = PETSc.Options() 35 | 36 | if rank == 0: 37 | tic = timer() 38 | 39 | # ------------------------------------------------------------------ reads matrix A 40 | A = ut.load_csr('A.npz') 41 | nb_l,nb_c = A.shape 42 | 43 | MA = PETSc.Mat() 44 | MA.create(PETSc.COMM_WORLD) 45 | MA.setSizes([nb_l,nb_l]) 46 | MA.setType('mpiaij') 47 | MA.setFromOptions() 48 | MA.setUp() 49 | 50 | Istart,Iend = MA.getOwnershipRange() 51 | indptrA = A[Istart:Iend,:].indptr 52 | indicesA = A[Istart:Iend,:].indices 53 | dataA = A[Istart:Iend,:].data 54 | del A 55 | 56 | MA.setPreallocationCSR(csr=(indptrA,indicesA)) 57 | MA.setValuesCSR(indptrA,indicesA,dataA) 58 | MA.assemblyBegin() 59 | MA.assemblyEnd() 60 | del indptrA,indicesA,dataA 61 | # done reading and preparing A 62 | 63 | if par.forcing == 0: # --------------------------------------------- if eigenvalue problem, reads matrix B 64 | 65 | B = ut.load_csr('B.npz') 66 | nb_l,nb_c = B.shape 67 | nbl = opts.getInt('nbl',nb_l) 68 | 69 | MB = PETSc.Mat() 70 | MB.create(PETSc.COMM_WORLD) 71 | MB.setSizes([nbl,nbl]) 72 | MB.setType('mpiaij') 73 | MB.setFromOptions() 74 | MB.setUp() 75 | 76 | Istart,Iend = MB.getOwnershipRange() 77 | indptrB = B[Istart:Iend,:].indptr 78 | indicesB = B[Istart:Iend,:].indices 79 | dataB = B[Istart:Iend,:].data 80 | del B 81 | 82 | MB.setPreallocationCSR(csr=(indptrB,indicesB)) 83 | MB.setValuesCSR(indptrB,indicesB,dataB) 84 | MB.assemblyBegin() 85 | MB.assemblyEnd() 86 | del indptrB,indicesB,dataB 87 | # done reading and preparing B 88 | 89 | 90 | # -------------------------------------------------------------- setup eigenvalue solver 91 | E = SLEPc.EPS() 92 | E.create(SLEPc.COMM_WORLD) 93 | E.setOperators(MA,MB) 94 | E.setProblemType(SLEPc.EPS.ProblemType.GNHEP) 95 | #E.setDimensions(nev,ncv) 96 | E.setDimensions(par.nev) 97 | E.setTolerances(par.tol,par.maxit) 98 | 99 | wep = par.which_eigenpairs 100 | if wep == 'LM': 101 | E.setWhichEigenpairs(SLEPc.EPS.Which.LARGEST_MAGNITUDE) 102 | elif wep == 'SM': 103 | E.setWhichEigenpairs(SLEPc.EPS.Which.SMALLEST_MAGNITUDE) 104 | elif wep == 'LR': 105 | E.setWhichEigenpairs(SLEPc.EPS.Which.LARGEST_REAL) 106 | elif wep == 'SR': 107 | E.setWhichEigenpairs(SLEPc.EPS.Which.SMALLEST_REAL) 108 | elif wep == 'LI': 109 | E.setWhichEigenpairs(SLEPc.EPS.Which.LARGEST_IMAGINARY) 110 | elif wep == 'SI': 111 | E.setWhichEigenpairs(SLEPc.EPS.Which.SMALLEST_IMAGINARY) 112 | elif wep == 'TM': 113 | E.setWhichEigenpairs(SLEPc.EPS.Which.TARGET_MAGNITUDE) 114 | elif wep == 'TR': 115 | E.setWhichEigenpairs(SLEPc.EPS.Which.TARGET_REAL) 116 | elif wep == 'TI': 117 | E.setWhichEigenpairs(SLEPc.EPS.Which.TARGET_IMAGINARY) 118 | 119 | E.setTarget(par.tau) 120 | E.setFromOptions() 121 | # done setting up solver 122 | 123 | E.solve() # ---------------------------------------------------- solve and collect solution 124 | 125 | class solution: 126 | pass # class intentionally empty, this is just to have sol.*stuff* 127 | 128 | sol = solution() 129 | 130 | # recover results 131 | sol.its = E.getIterationNumber() 132 | sol.neps_type = E.getType() 133 | sol.tol, sol.maxit = E.getTolerances() 134 | sol.nev, sol.ncv, sol.mpd = E.getDimensions() 135 | sol.nconv = E.getConverged() 136 | sol.k = np.zeros((1,sol.nconv),dtype=complex) 137 | sol.vec = np.zeros((nb_l,sol.nconv),dtype=complex) 138 | sol.tau = E.getTarget() 139 | 140 | if sol.nconv > 0: 141 | 142 | # initialize eigenvector placeholder 143 | v = MA.createVecLeft() 144 | 145 | for i in range(0,sol.nconv): 146 | # gets eigenvalue and eigenvector for each solution found 147 | # note that petsc uses complex scalars, so k and v are generally complex 148 | # no need for separate real and imag (hence the None below) 149 | k = E.getEigenpair(i, v, None) 150 | # collects and assembles the vector in the zeroth processor 151 | tozero,V = PETSc.Scatter.toZero(v) 152 | tozero.begin(v,V) 153 | tozero.end(v,V) 154 | tozero.destroy() 155 | 156 | sol.k[0,i] = k 157 | if rank == 0: 158 | sol.vec[0:,i] = V[0:] 159 | 160 | if rank == 0: 161 | 162 | # Eigenvalues, 1 row per solution found, column 0 is the real part, column 1 is the imaginary part 163 | eigval = np.hstack([np.real(sol.k).transpose(),np.imag(sol.k).transpose()]) 164 | 165 | # Eigenvectors, each column is a solution 166 | rEigv = np.copy(np.real(sol.vec)) 167 | iEigv = np.copy(np.imag(sol.vec)) 168 | 169 | if par.hydro == 1: 170 | # each solution for u has 2*ut.n coeffs 171 | ru = rEigv[ :2*ut.n, : ] 172 | iu = iEigv[ :2*ut.n, : ] 173 | 174 | # each solution for b has 2*ut.n coefs 175 | if par.magnetic == 1: 176 | offset = 2*ut.n*par.hydro 177 | rb = rEigv[ offset : offset + 2*ut.n, : ] 178 | ib = iEigv[ offset : offset + 2*ut.n, : ] 179 | 180 | # each solution for the temperature has ut.n coeffs 181 | if par.thermal == 1: 182 | offset = 2*ut.n + par.magnetic * 2*ut.n 183 | rtemp = rEigv[ offset : offset + ut.n, : ] 184 | itemp = iEigv[ offset : offset + ut.n, : ] 185 | 186 | # each solution for the composition has ut.n coeffs 187 | if par.compositional == 1: 188 | offset = 2*ut.n + par.magnetic * 2*ut.n + par.thermal * ut.n 189 | rcomp = rEigv[ offset : offset + ut.n, : ] 190 | icomp = iEigv[ offset : offset + ut.n, : ] 191 | 192 | success = sol.nconv 193 | 194 | else: 195 | 196 | if rank == 0: 197 | success = 0 198 | print('No converged solution found') 199 | np.savetxt('no_conv_solution',[0]) 200 | 201 | MA.destroy() 202 | MB.destroy() 203 | 204 | 205 | 206 | 207 | else: # ------------------------------------------------------------ if forced problem, reads forcing vector 208 | 209 | b0 = ut.load_csr('B_forced.npz') 210 | x, bvec = MA.createVecs() 211 | 212 | #b.set(0) 213 | Istart,Iend = bvec.getOwnershipRange() 214 | bvec.setValues(range(Istart,Iend),b0[Istart:Iend,0].toarray()) 215 | bvec.assemblyBegin() 216 | bvec.assemblyEnd() 217 | del b0 218 | 219 | # -------------------------------------------------------------- setup, solve & collect 220 | K = PETSc.KSP() 221 | K.create(PETSc.COMM_WORLD) 222 | K.setOperators(MA) 223 | K.setTolerances(rtol=par.tol,max_it=par.maxit) 224 | K.setFromOptions() 225 | 226 | # solve 227 | K.solve(bvec, x) 228 | 229 | # collect result 230 | tozero,VR = PETSc.Scatter.toZero(x) 231 | tozero.begin(x,VR) 232 | tozero.end(x,VR) 233 | tozero.destroy() 234 | 235 | # cleanup 236 | K.destroy() 237 | MA.destroy() 238 | bvec.destroy() 239 | x.destroy() 240 | 241 | if rank == 0: 242 | 243 | if par.hydro == 1: 244 | offset = 0 245 | ru = np.reshape(np.real(VR[ offset : offset + 2*ut.n ]),(-1,1)) 246 | iu = np.reshape(np.imag(VR[ offset : offset + 2*ut.n ]),(-1,1)) 247 | 248 | if par.magnetic == 1: 249 | offset = par.hydro * 2*ut.n 250 | rb = np.reshape(np.real(VR[ offset : offset + 2*ut.n ]),(-1,1)) 251 | ib = np.reshape(np.imag(VR[ offset : offset + 2*ut.n ]),(-1,1)) 252 | 253 | if par.thermal == 1: 254 | offset = par.hydro * 2*ut.n + par.magnetic * 2*ut.n 255 | rtemp = np.reshape(np.real(VR[ offset : offset + ut.n ]),(-1,1)) 256 | itemp = np.reshape(np.imag(VR[ offset : offset + ut.n ]),(-1,1)) 257 | 258 | if par.compositional == 1: 259 | offset = par.hydro * 2*ut.n + par.magnetic * 2*ut.n + par.thermal * ut.n 260 | rcomp = np.reshape(np.real(VR[ offset : offset + ut.n ]),(-1,1)) 261 | icomp = np.reshape(np.imag(VR[ offset : offset + ut.n ]),(-1,1)) 262 | 263 | if np.sum([np.isnan(ru), np.isnan(iu), np.isinf(ru), np.isinf(iu)]) > 0: 264 | success = 0 265 | print('Solver crashed, got nan\'s!') 266 | else: 267 | success = 1 # got actual numbers ... but it could still be a bad solution ;) 268 | print('Solution(s) computed') 269 | 270 | #PETSc.COMM_WORLD.Barrier() 271 | 272 | 273 | # ------------------------------------------------------------------ write solution vector(s) to disk 274 | 275 | if rank == 0: 276 | 277 | if success > 0: 278 | 279 | if par.forcing == 0: 280 | with open('eigenvalues0.dat','wb') as deig: 281 | np.savetxt(deig, eigval) 282 | 283 | # one solution per column 284 | if par.hydro == 1: 285 | with open('real_flow.field','wb') as dflo1: 286 | np.savetxt(dflo1, ru) 287 | with open('imag_flow.field','wb') as dflo2: 288 | np.savetxt(dflo2, iu) 289 | 290 | if par.magnetic == 1: 291 | with open('real_magnetic.field','wb') as dmag1: 292 | np.savetxt(dmag1, rb) 293 | with open('imag_magnetic.field','wb') as dmag2: 294 | np.savetxt(dmag2, ib) 295 | 296 | if par.thermal == 1: 297 | with open('real_temperature.field','wb') as dtemp1: 298 | np.savetxt(dtemp1, rtemp) 299 | with open('imag_temperature.field','wb') as dtemp2: 300 | np.savetxt(dtemp2, itemp) 301 | 302 | if par.compositional == 1: 303 | with open('real_composition.field','wb') as dcomp1: 304 | np.savetxt(dcomp1, rcomp) 305 | with open('imag_composition.field','wb') as dcomp2: 306 | np.savetxt(dcomp2, icomp) 307 | 308 | toc2 = timer() 309 | print('Solve done in',toc2-tic,'seconds') 310 | with open('timing.dat','ab') as dtim: 311 | np.savetxt(dtim, np.array([toc2-tic])) 312 | 313 | # ------------------------------------------------------------------ done 314 | return 0 315 | 316 | 317 | 318 | if __name__ == "__main__": 319 | sys.exit(main()) 320 | 321 | 322 | -------------------------------------------------------------------------------- /koreviz/kmode.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | import numpy.polynomial.chebyshev as ch 4 | from glob import glob 5 | from .plotlib import add_colorbar,default_cmap,radContour,merContour,eqContour 6 | from .libkoreviz import spec2spat_vec,spec2spat_scal 7 | import sys 8 | import os 9 | 10 | 11 | class kmode: 12 | 13 | def __init__(self, vort=False,datadir='.',field='u', solnum=0, 14 | nr=None, nphi=None, nthreads=4, transform=True): 15 | 16 | sys.path.insert(0,datadir+'/bin') 17 | 18 | import parameters as par 19 | import utils as ut 20 | import utils4pp as upp 21 | if ( os.path.exists('radial_profiles.py') 22 | or os.path.exists('./bin/radial_profiles.py') ): 23 | import radial_profiles as rap 24 | l_prof = True 25 | else: 26 | l_prof = False 27 | print("No radial_profiles.py found, assuming not on main branch.") 28 | 29 | self.solnum = solnum 30 | self.lmax = par.lmax 31 | self.m = par.m 32 | self.symm = par.symm 33 | self.N = par.N 34 | self.ricb = par.ricb 35 | self.rcmb = ut.rcmb 36 | self.field = field 37 | gap = self.rcmb - self.ricb 38 | self.ut = ut 39 | self.par = par 40 | self.nthreads = nthreads 41 | 42 | if nr is None: 43 | self.nr = par.N + 2 44 | else: 45 | self.nr = nr 46 | 47 | if self.m == 0: 48 | 49 | if nphi is None: 50 | self.nphi = par.lmax * 3 # Orszag's 1/3 rule 51 | else: 52 | self.nphi = nphi 53 | self.ntheta = self.nphi // 2 54 | self.nphi = max(128,self.nphi) 55 | 56 | else: 57 | 58 | if nphi is None: 59 | self.nphi = par.lmax * 3 // self.m # Orszag's 1/3 rule 60 | else: 61 | self.nphi = nphi // self.m 62 | 63 | self.nphi = max(128//self.m,self.nphi) 64 | self.ntheta = (self.nphi * self.m) // 2 65 | 66 | # set the radial grid 67 | if self.ricb > 0: 68 | i = np.arange(0,self.nr-2) 69 | xk = np.r_[ 1, np.cos( (i+0.5)*np.pi/self.nr ), -1] # include endpoints ricb and rcmb 70 | elif self.ricb==0: 71 | i = np.arange(0,self.nr-1) 72 | xk = np.r_[ 1, np.cos( (i+0.5)*np.pi/self.nr ) ] # include rcmb but not the origin if ricb=0 73 | r = 0.5*gap*(xk+1) + self.ricb 74 | x0 = upp.xcheb(r,self.ricb,self.rcmb) 75 | self.r = r 76 | 77 | # matrix with Chebyshev polynomials at every x point for all degrees: 78 | chx = ch.chebvander(x0,par.N-1) # this matrix has nr rows and N-1 cols 79 | 80 | # read fields from disk 81 | if field == 'u': 82 | a = np.loadtxt('real_flow.field',usecols=solnum) 83 | b = np.loadtxt('imag_flow.field',usecols=solnum) 84 | vsymm = par.symm 85 | vec = True 86 | elif field == 'b': 87 | a = np.loadtxt('real_magnetic.field',usecols=solnum) 88 | b = np.loadtxt('imag_magnetic.field',usecols=solnum) 89 | vsymm = ut.bsymm 90 | vec = True 91 | elif field in ['t','temp','temperature']: 92 | if len(glob('*_temperature.field')) > 0: 93 | a = np.loadtxt('real_temperature.field',usecols=solnum) 94 | b = np.loadtxt('imag_temperature.field',usecols=solnum) 95 | elif len(glob('*_temp.field')) > 0: 96 | a = np.loadtxt('real_temp.field',usecols=solnum) 97 | b = np.loadtxt('imag_temp.field',usecols=solnum) 98 | field='temperature' 99 | vsymm = par.symm 100 | vec = False 101 | elif field in ['comp','composition']: 102 | a = np.loadtxt('real_composition.field',usecols=solnum) 103 | b = np.loadtxt('imag_composition.field',usecols=solnum) 104 | field='composition' 105 | vsymm = par.symm 106 | vec = False 107 | 108 | if vec: 109 | 110 | [Plj, Tlj] = upp.expand_reshape_sol(a+1j*b,vsymm) 111 | 112 | sol = spec2spat_vec(self,ut,chx,Plj,Tlj,vsymm,nthreads, 113 | vort=vort,transform=transform) 114 | 115 | self.Qlm,self.Slm,self.Plm,self.Tlm = sol[:4] 116 | 117 | if transform: 118 | if not vort: 119 | if field == 'u': 120 | self.ur, self.utheta, self.uphi = sol[4:] 121 | if hasattr(par,'anelastic'): 122 | if par.anelastic and l_prof: #Comment the block out if you want momentum/mass flux 123 | self.rho = rap.density(self.r) 124 | for irho in range(self.nr): 125 | self.ur[irho,...] /= self.rho[irho] 126 | self.utheta[irho,...] /= self.rho[irho] 127 | self.uphi[irho,...] /= self.rho[irho] 128 | elif field == 'b': 129 | self.br, self.btheta, self.bphi = sol[4:] 130 | else: 131 | if field == 'u': 132 | self.vort_r, self.vort_t, self.vort_p = sol[4:] 133 | elif field == 'b': 134 | self.jr, self.jtheta, self.jphi = sol[4:] 135 | 136 | del sol 137 | 138 | else: 139 | Plj = upp.expand_reshape_sol(a+1j*b,vsymm) 140 | sol = spec2spat_scal(self,chx,Plj,vsymm, 141 | nthreads,transform=transform) 142 | self.Qlm = sol[0] 143 | if transform: 144 | scal = sol[1] 145 | exec('self.'+field + '= scal') 146 | del scal 147 | del sol 148 | 149 | 150 | def get_data(self,field): 151 | 152 | if self.field in ['t','temp','temperature']: 153 | field = 'temperature' 154 | elif self.field in ['c','comp','composition']: 155 | field = 'composition' 156 | 157 | field = field.lower() 158 | 159 | if field in ['ur','vr','urad','vrad']: 160 | data = self.ur 161 | titl = r'$u_r$' 162 | 163 | if field in ['up','vp','uphi','vphi']: 164 | data = self.uphi 165 | titl = r'$u_\phi$' 166 | 167 | if field in ['ut','vt','utheta','vtheta']: 168 | data = self.utheta 169 | titl = r'$u_\theta$' 170 | 171 | if field in ['br','brad']: 172 | data = self.br 173 | titl = r'$B_r$' 174 | 175 | if field in ['bp','bphi']: 176 | data = self.bphi 177 | titl = r'$B_\phi$' 178 | 179 | if field in ['bt','btheta']: 180 | data = self.btheta 181 | titl = r'$B_\theta$' 182 | 183 | if field in ['t','temp','temperature']: 184 | data = self.temperature 185 | titl = r'Temperature' 186 | 187 | if field in ['c','comp','composition']: 188 | data = self.composition 189 | titl = r'Composition' 190 | 191 | if field in ['energy','ener','ke','e']: 192 | data = 0.5 * (self.ur**2 + self.utheta**2 + self.uphi**2) 193 | titl = r'Kinetic Energy' 194 | 195 | if field in ['vortz']: 196 | th3D = np.zeros_like(self.vort_r) 197 | for k in range(self.ntheta): 198 | th3D[:,k,:] = self.theta[k] 199 | 200 | data = self.vort_r * np.cos(th3D) - self.vort_t * np.sin(th3D) 201 | titl = r'$\omega_z$' 202 | 203 | return data, titl, field 204 | 205 | 206 | def surf(self, field='ur', r=0.5, levels=48, cmap=None, 207 | colbar=True, titl=True, clim=[0,0]): 208 | # Surface plot at constant radius 209 | 210 | if self.m == 0: 211 | data = np.zeros([ self.ntheta, self.nphi + 1]) 212 | ir = np.argmin(abs(self.r-r)) 213 | dat_tmp,titl,field = self.get_data(field=field) 214 | data[:,:-1] = dat_tmp[ir,...] 215 | data[:, -1] = data[:,0] 216 | else: 217 | data = np.zeros([ self.ntheta, self.nphi*self.m + 1]) 218 | ir = np.argmin(abs(self.r-r)) 219 | dat_tmp,titl,field = self.get_data(field=field) 220 | data[:,:-1] = np.tile( dat_tmp[ir,...], self.m ) 221 | data[:, -1] = data[:,0] 222 | 223 | plt.figure(figsize=(12,6)) 224 | 225 | if cmap is None: 226 | cmap = default_cmap(field) 227 | 228 | cont = radContour( self.theta, self.phi, data.T, 229 | levels=levels, cmap=cmap, clim=clim) 230 | 231 | if titl: 232 | titl = titl + r' at $r/r_o = %.2f$' %(self.r[ir]/self.rcmb) 233 | plt.title(titl,fontsize=30) 234 | plt.axis('equal') 235 | plt.axis('off') 236 | if colbar: 237 | cbar = add_colorbar(cont,aspect=40) 238 | 239 | plt.tight_layout() 240 | plt.show() 241 | 242 | 243 | def merid(self, field='ur', azim=0, levels=48, cmap=None, 244 | colbar=True, titl=True, clim=[0,0]): 245 | # Meridional cross section 246 | 247 | iphi = np.argmin(abs( self.phi - (azim*np.pi/180) )) % self.nphi 248 | dat_tmp,titl,field = self.get_data(field) 249 | data = dat_tmp[:,:,iphi] 250 | 251 | if field in ['energy','ener','e','ke']: 252 | #cmap = cmr.tropical_r 253 | data = np.log10(data) 254 | 255 | plt.figure(figsize=(6,9)) 256 | 257 | if cmap is None: 258 | cmap = default_cmap(field) 259 | 260 | cont = merContour( self.r, self.theta, data.T, 261 | levels=levels, cmap=cmap, clim=clim) 262 | 263 | if titl: 264 | titl = titl + r' at $\phi=%.1f^\circ$' %(self.phi[iphi] * 180/np.pi) 265 | plt.title(titl,fontsize=20) 266 | plt.axis('equal') 267 | plt.axis('off') 268 | if colbar: 269 | cbar = add_colorbar(cont,aspect=60) 270 | plt.tight_layout() 271 | plt.show() 272 | 273 | 274 | def equat(self, field='ur', levels=48, cmap=None, 275 | colbar=True, titl=True, clim=[0,0]): 276 | # Equatorial cross section 277 | 278 | if self.m == 0: 279 | data = np.zeros([ self.nr, self.nphi + 1]) 280 | itheta = np.argmin( abs( self.theta - np.pi/2 ) ) 281 | dat_tmp,titl,field = self.get_data(field) 282 | data[:,:-1] = dat_tmp[:,itheta,:] 283 | data[:, -1] = data[:,0] 284 | else: 285 | data = np.zeros([ self.nr, self.nphi*self.m + 1]) 286 | itheta = np.argmin( abs( self.theta - np.pi/2 ) ) 287 | dat_tmp,titl,field = self.get_data(field) 288 | print(np.shape(data),np.shape(dat_tmp)) 289 | data[:,:-1] = np.tile( dat_tmp[:,itheta,:], self.m ) 290 | data[:, -1] = data[:,0] 291 | 292 | plt.figure(figsize=(11,9)) 293 | 294 | if cmap is None: 295 | cmap = default_cmap(field) 296 | 297 | cont = eqContour(self.r, self.phi, data.T, 298 | levels=levels, cmap=cmap, clim=clim) 299 | 300 | if titl: 301 | titl = titl + ' at equator' 302 | plt.title(titl,fontsize=20) 303 | 304 | plt.axis('equal') 305 | plt.axis('off') 306 | if colbar: 307 | cbar = add_colorbar(cont,aspect=60) 308 | plt.tight_layout() 309 | plt.show() 310 | 311 | def potextra(self,brcmb,rcmb,rout): 312 | 313 | if self.field != 'b': 314 | print("Potential extrapolation only valid when field='b'") 315 | return 0 316 | 317 | try: 318 | import shtns 319 | except ImportError: 320 | print("Potential extrapolation requires the SHTns library") 321 | print("It can be obtained here: https://bitbucket.org/nschaeff/shtns") 322 | 323 | self.nrout = len(rout) 324 | polar_opt = 1e-10 325 | 326 | norm=shtns.sht_orthonormal | shtns.SHT_NO_CS_PHASE 327 | lmax2 = int( self.lmax + 1 - np.sign(self.m) ) 328 | mmax = int( np.sign(self.m) ) 329 | mres = max(1,self.m) 330 | sh = shtns.sht( lmax2, mmax=mmax, mres=mres, norm=norm, 331 | nthreads=self.nthreads ) 332 | ntheta, nphi = sh.set_grid(self.ntheta, self.nphi, 333 | polar_opt=polar_opt) 334 | 335 | L = sh.l * (sh.l + 1) 336 | 337 | brlm = sh.analys(brcmb) 338 | bpolcmb = np.zeros_like(brlm) 339 | bpolcmb[1:] = rcmb**2 * brlm[1:]/L[1:] 340 | btor = np.zeros_like(brlm) 341 | 342 | brout = np.zeros([ntheta,nphi,self.nrout]) 343 | btout = np.zeros([ntheta,nphi,self.nrout]) 344 | bpout = np.zeros([ntheta,nphi,self.nrout]) 345 | 346 | for k,radius in enumerate(rout): 347 | print(("%d/%d" %(k,self.nrout))) 348 | 349 | radratio = rcmb/radius 350 | bpol = bpolcmb * radratio**(sh.l) 351 | brlm = bpol * L/radius**2 352 | brout[...,k] = sh.synth(brlm) 353 | 354 | slm = -sh.l/radius * bpol 355 | 356 | btout[...,k], bpout[...,k] = sh.synth(slm,btor) 357 | 358 | brout = np.transpose(brout,(2,0,1)) 359 | btout = np.transpose(btout,(2,0,1)) 360 | bpout = np.transpose(bpout,(2,0,1)) 361 | 362 | return brout, btout, bpout 363 | -------------------------------------------------------------------------------- /bin/parameters.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import sys 3 | #import targets as tg 4 | 5 | 6 | def Ncheb(Ek): 7 | ''' 8 | Returns the truncation level N for the Chebyshev expansion according to the Ekman number 9 | Please experiment and adapt to your particular problem. N must be even. 10 | ''' 11 | if Ek !=0 : 12 | out = int(17*Ek**-0.2) 13 | else: 14 | out = 48 # 15 | 16 | return max(48, out + out%2) 17 | 18 | 19 | aux1 = 1.0 # Auxiliary variable, useful e.g. for ramps 20 | aux2 = 0 21 | 22 | 23 | # ---------------------------------------------------------------------------------------------------------------------- 24 | # ---------------------------------------------------------------------------------------------- Hydrodynamic parameters 25 | # ---------------------------------------------------------------------------------------------------------------------- 26 | hydro = 1 # set to 1 to include the Navier-Stokes equation for the flow velocity, set to 0 otherwise 27 | 28 | # Azimuthal wave number m (>=0) 29 | m = 1 30 | 31 | # Equatorial symmetry of the flow field. Use 1 for symmetric, -1 for antisymmetric. 32 | symm = -1 33 | 34 | # Inner core radius, surface/CMB radius is unity. 35 | ricb = 0.35 36 | 37 | # Inner core spherical boundary conditions 38 | # Use 0 for stress-free, 1 for no-slip or forced boundary flow. Ignored if ricb = 0 39 | bci = 1 40 | 41 | # CMB spherical boundary conditions 42 | # Use 0 for stress-free, 1 for no-slip or forced boundary flow 43 | bco = 1 44 | 45 | # Ekman number (use 2* to match Dintrans 1999). Ek can be set to 0 if ricb=0 46 | # CoriolisNumber = 1.2e3 47 | # Ek = 2/CoriolisNumber 48 | # Ek_gap = 2e-4 49 | # Ek = Ek_gap*(1-ricb)**2 50 | 51 | Ek = 1e-3 52 | 53 | anelastic = 0 54 | variable_viscosity = 0 55 | 56 | #--------------------------------------------------------------------------------------- 57 | # Options for setting interior profiles and options for a polytropic gas, Nrho and polind 58 | # are ignored when an interior model other than polytrope is used 59 | # All these options are only for anelastic models, ignored if anelastic=0 60 | #-------------------------------------------------------------------------------------- 61 | 62 | interior_model = 'polytrope' # Interior model, available options are: polytrope, jupiter, pns 63 | mesa_file='theprofile.data' 64 | gamma = 5./3. # adiabatic index 65 | r_cutoff = 0.99 # Cut-off radius for interior model 66 | 67 | Nrho = 2.0 # ln(\rho_i/\rho_o), number of density scale heights 68 | polind = 2.0 # Polytropic index : p = \rho^(1 + 1/n) , p = \rho T for ideal gas, R = 1 69 | autograv = 0 # Automatic computation of gravity 70 | g_icb = 0.0 # Value of g at icb, is set to zero when ricb = 0 71 | g0 = 0; g1 = 0; g2=1 # Easy way to control gravity, g(r) = g0 + g1 r/rcmb + g2 rcmb^2/r^2 72 | 73 | #-------------------------------------------------------------------------------------- 74 | 75 | 76 | forcing = 0 # Uncomment this line for eigenvalue problems 77 | # forcing = 1 # For Lin & Ogilvie 2018 tidal body force, m=2, symm. OK 78 | # forcing = 2 # For boundary flow forcing, use with bci=1 and bco=1. 79 | # forcing = 3 # For Rovira-Navarro 2018 tidal body forcing, m=0,2 must be symm, m=1 antisymm. Leaks power! 80 | # forcing = 4 # first test case, Lin body forcing with X(r)=(A*r^2 + B/r^3)*C, (using Jeremy's calculation), m=2,symm. OK 81 | # forcing = 5 # second test case, Lin body forcing with X(r)=1/r, m=0,symm. OK 82 | # forcing = 6 # Buffett2010 ICB radial velocity boundary forcing, m=1,antisymm 83 | # forcing = 7 # Longitudinal libration boundary forcing, m={0, 2}, symm, no-slip 84 | # forcing = 8 # Longitudinal libration as a Poincaré force (body force) in the mantle frame, m=0, symm, no-slip 85 | # forcing = 9 # Radial, symmetric, m=2 boundary flow forcing. 86 | 87 | # Forcing frequency (ignored if forcing == 0) 88 | forcing_frequency = 1.0 # negative is prograde 89 | 90 | # Forcing amplitude. Body forcing amplitude will use the cmb value 91 | forcing_amplitude_cmb = 1.0 92 | forcing_amplitude_icb = 0.0 93 | 94 | # if solving an eigenvalue problem, compute projection of eigenmode 95 | # and some hypothetical forcing. Cases as described above (available only for 1,3 or 4) 96 | projection = 1 97 | 98 | 99 | 100 | # ---------------------------------------------------------------------------------------------------------------------- 101 | # -------------------------------------------------------------------------------------------- Magnetic field parameters 102 | # ---------------------------------------------------------------------------------------------------------------------- 103 | magnetic = 0 # set to 1 if including the induction equation and the Lorentz force 104 | 105 | # Imposed background magnetic field 106 | B0 = 'axial' # Axial, uniform field along the spin axis 107 | # B0 = 'dipole' # classic dipole, singular at origin, needs ricb>0 108 | # B0 = 'G21 dipole' # Felix's dipole (Gerick GJI 2021) 109 | # B0 = 'Luo_S1' # Same as above, actually (Luo & Jackson PRSA 2022) 110 | # B0 = 'Luo_S2' # Quadrupole 111 | # B0 = 'FDM' # Free Poloidal Decay Mode (Zhang & Fearn 1994,1995; Schmitt 2012) 112 | beta = 3.0 # guess for FDM's beta 113 | B0_l = 1 # l number for the FDM mode 114 | 115 | # Magnetic boundary conditions at the ICB: 116 | innercore = 'insulator' 117 | # innercore = 'TWA' # Thin conductive wall layer (Roberts, Glatzmaier & Clune, 2010) 118 | c_icb = 0 # Ratio (h*mu_wall)/(ricb*mu_fluid) (if innercore='TWA') 119 | c1_icb = 0 # Thin wall to fluid conductance ratio (if innercore='TWA') 120 | # innercore = 'perfect conductor, material' # tangential *material* electric field jump [nxE']=0 across the ICB 121 | # innercore = 'perfect conductor, spatial' # tangential *spatial* electric field jump [nxE]=0 across the ICB 122 | # Note: 'perfect conductor, material' or 'perfect conductor, spatial' are identical if ICB is no-slip (bci = 1 above) 123 | 124 | # Magnetic boundary conditions at the CMB 125 | mantle = 'insulator' 126 | # mantle = 'TWA' # Thin conductive wall layer (Roberts, Glatzmaier & Clune, 2010) 127 | c_cmb = 0 # Ratio (h*mu_wall)/(rcmb*mu_fluid) (if mantle='TWA') 128 | c1_cmb = 0 # Thin wall to fluid conductance ratio (if mantle='TWA') 129 | 130 | # Relative permeability (fluid/vacuum) 131 | mu = 1.0 132 | 133 | # Magnetic field strength and magnetic diffusivity: 134 | # Either use the Elsasser number and the magnetic Prandtl number (i.e. Lambda and Pm: uncomment and set the following three lines): 135 | Lambda = 0.1 136 | Pm = 0.001 137 | Em = Ek/Pm; Le2 = Lambda*Em; Le = np.sqrt(Le2) 138 | # Or use the Lehnert number and the magnetic Ekman number (i.e. Le and Em: uncomment and set the following three lines): 139 | # Le = 1e-3; Lu=2e3 140 | # Em = Le/Lu 141 | # Le2 = Le**2 142 | 143 | # Normalization of the background magnetic field 144 | # cnorm = 'rms_cmb' # Sets the radial rms field at the CMB as unity 145 | cnorm = 'mag_energy' # Unit magnetic energy as in Luo & Jackson 2022 (I. Torsional oscillations) 146 | # cnorm = 'Schmitt2012' # as above but times 2 147 | # cnorm = 3.86375 # G101 of Schmitt 2012, ricb = 0.35 148 | # cnorm = 4.067144 # Zhang & Fearn 1994, ricb = 0.35 149 | # cnorm = 15*np.sqrt(21/(46*np.pi)) # G21 dipole, ricb = 0 150 | # cnorm = 1.09436 # simplest FDM, l=1, ricb = 0 151 | # cnorm = 3.43802 # simplest FDM, l=1, ricb = 0.001 152 | # cnorm = 0.09530048175738767 # Luo_S1 ricb = 0, unit mag_energy 153 | # cnorm = 0.6972166887783963 # Luo_S1 ricb = 0, rms_Bs=1 154 | # cnorm = 0.005061566801979833 # Luo_S2 ricb = 0, unit mag_energy 155 | # cnorm = 0.0158567582314039 # Luo_S2 ricb = 0, rms_Bs=1 156 | 157 | 158 | 159 | # ---------------------------------------------------------------------------------------------------------------------- 160 | # --------------------------------------------------------------------------------------------------- Thermal parameters 161 | # ---------------------------------------------------------------------------------------------------------------------- 162 | thermal = 0 # Use 1 or 0 to include or not the internal energy equation and the buoyancy force 163 | 164 | # "Thermal" Ekman number 165 | Prandtl = 1 166 | Etherm = Ek/Prandtl 167 | # Etherm = 0 168 | 169 | # Background isentropic temperature gradient dT/dr choices, uncomment the appropriate line below: 170 | # heating = 'internal' # dT/dr = -beta * r temp_scale = beta * ro**2 171 | heating = 'differential' # dT/dr = -beta * r**-2 temp_scale = Ti-To beta = (Ti-To)*ri*ro/(ro-ri) 172 | # heating = 'two zone' # dT/dr = K * ut.twozone() temp_scale = -ro * K 173 | # heating = 'user defined' # dT/dr = K * ut.BVprof() temp_scale = -ro * K 174 | 175 | # Rayleigh number as Ra = alpha * g0 * ro^3 * temp_scale / (nu*kappa), alpha is the thermal expansion coeff, 176 | # g0 the gravity accel at ro, ro is the cmb radius (the length scale), nu is viscosity, kappa is thermal diffusivity. 177 | Ra_gap=6e5 178 | Ra = Ra_gap / (1-ricb)**3 179 | # Ra_Silva = 0.0; Ra = Ra_Silva * (1/(1-ricb))**6 180 | # Ra_Monville = 0.0; Ra = 2*Ra_Monville 181 | 182 | # Alternatively, you can specify directly the squared ratio of a reference Brunt-Väisälä freq. and the rotation rate. 183 | # The reference Brunt-Väisälä freq. squared is defined as -alpha*g0*temp_scale/ro. See the non-dimensionalization notes 184 | # in the documentation. 185 | 186 | BV2 = -Ra * Ek**2 / Prandtl 187 | #BV2 = 0.0 188 | 189 | 190 | 191 | entropyGrad = None # Automatically compute equilibrium entropy gradient 192 | 193 | if entropyGrad == 'ssl': 194 | 195 | ampStrat = 0 196 | rStrat = 0.6 197 | thickStrat= 0.1 198 | slopeStrat= 75 199 | 200 | dent_args = [ampStrat,rStrat,thickStrat,slopeStrat] 201 | 202 | # Additional arguments for 'Two zone' or 'User defined' case (modify if needed). 203 | rc = 0.7 # transition radius 204 | h = 0.1 # transition width 205 | rsy = -1 # radial symmetry 206 | args = [rc, h, rsy] 207 | 208 | 209 | 210 | # Thermal boundary conditions 211 | # 0 for isothermal, theta=0 212 | # 1 for constant heat flux, (d/dr)theta=0 213 | bci_thermal = 0 # ICB 214 | bco_thermal = 0 # CMB 215 | 216 | ''' 217 | #Set boundary conditions to solve for equilibrium entropy gradient 218 | if entropyGrad == 'auto': 219 | 220 | bci_thermal_val = 1 221 | bco_thermal_val = 0 222 | ''' 223 | 224 | 225 | # ---------------------------------------------------------------------------------------------------------------------- 226 | # --------------------------------------------------------------------------------------------- Compositional parameters 227 | # ---------------------------------------------------------------------------------------------------------------------- 228 | compositional = 0 # Use 1 or 0 to include compositional transport or not (Boussinesq) 229 | 230 | # Schmidt number: ratio of viscous to compositional diffusivity 231 | Schmidt = 1.0 232 | # "Compositional" Ekman number 233 | Ecomp = Ek/Schmidt 234 | 235 | # Background isentropic composition gradient dC/dr choices, uncomment the appropriate line below: 236 | comp_background = 'internal' # dC/dr = -beta * r comp_scale = beta * ro**2 237 | # comp_background = 'differential' # dC/dr = -beta * r**-2 comp_scale = Ci-Co 238 | 239 | # Compositional Rayleigh number 240 | Ra_comp = 0.0 241 | # Ra_comp_Silva = 0.0; Ra_comp = Ra_comp_Silva * (1/(1-ricb))**6 242 | # Ra_comp_Monville = 0.0; Ra_comp = 2*Ra_comp_Monville 243 | 244 | # Alternatively, specify directly the squared ratio of a reference compositional Brunt-Väisälä frequency 245 | # and the rotation rate. 246 | # BV2_comp = -Ra_comp * Ek**2 / Schmidt 247 | BV2_comp = 0.0 248 | 249 | # Additional arguments for 'Two zone' or 'User defined' case (modify if needed). 250 | rcc = 0.7 # transition radius 251 | hc = 0.1 # transition width 252 | rsyc = -1 # radial symmetry 253 | args_comp = [rcc, hc, rsyc] 254 | 255 | # Compositional boundary conditions 256 | # 0 for constant composition, xi=0 257 | # 1 for constant flux, (d/dr)xi=0 258 | bci_compositional = 1 # ICB 259 | bco_compositional = 1 # CMB 260 | 261 | 262 | 263 | # ---------------------------------------------------------------------------------------------------------------------- 264 | # -------------------------------------------------------------------------------------- Unit of time and force switches 265 | # ---------------------------------------------------------------------------------------------------------------------- 266 | # Choose the time scale by specifying the dimensionless angular velocity using the desired time scale. Please see 267 | # the non-dimensionalization notes in the documentation. Uncomment your choice: 268 | # OmgTau = 1 # Rotation time scale 269 | # OmgTau = 1/Ek # Viscous diffusion time scale 270 | # OmgTau = 1/Le # Alfvén time scale 271 | # OmgTau = 1/Em # Magnetic diffusion time scale 272 | 273 | Gaspard = 1. #0.15 # Omega*Tau Coriolis force factor. Set to 1 for unit time Tau = 1/Omega 274 | Beyonce = BV2 # (N0*Tau)**2 Buoyancy force factor. Set to 1 for unit time Tau = 1/N0 = sqrt(r0/g0) 275 | Hendrik = Le2 # (Tau*B0/r0)**2/(rho0*mu0) Lorentz force factor. Set to 1 for Alfven time scale 276 | ViscosD = Ek # nu0 * Tau / r0**2 Viscous force factor. Set to 1 for viscous diffusion time scale 277 | ThermaD = Etherm # kappa0 * Tau / r0**2 Thermal diffusion factor. Set to 1 for thermal diffusion time scale 278 | MagnetD = Em # eta0 * Tau / r0**2 Magnetic diffusion factor. Set to 1 for magnetic diffusion time scale 279 | 280 | 281 | # Gaspard = 1/Ek #0.15 # Omega*Tau Coriolis force factor. Set to 1 for unit time Tau = 1/Omega 282 | # Beyonce = BV2 # (N0*Tau)**2 Buoyancy force factor. Set to 1 for unit time Tau = 1/N0 = sqrt(r0/g0) 283 | # Hendrik = Le2 # (Tau*B0/r0)**2/(rho0*mu0) Lorentz force factor. Set to 1 for Alfven time scale 284 | # ViscosD = 1. # nu0 * Tau / r0**2 Viscous force factor. Set to 1 for viscous diffusion time scale 285 | # ThermaD = 1/Prandtl # kappa0 * Tau / r0**2 Thermal diffusion factor. Set to 1 for thermal diffusion time scale 286 | # MagnetD = 1/Pm # eta0 * Tau / r0**2 Magnetic diffusion factor. Set to 1 for magnetic diffusion time scale 287 | 288 | # ---------------------------------------------------------------------------------------------------------------------- 289 | # ----------------------------------------------------------------------------------------------------------- Resolution 290 | # ---------------------------------------------------------------------------------------------------------------------- 291 | 292 | # Number of cpus 293 | ncpus = 4 294 | 295 | # Chebyshev polynomial truncation level. Use function def at top or set manually. N must be even if ricb = 0. 296 | N = Ncheb(Ek) 297 | # N = 48 298 | 299 | # Spherical harmonic truncation lmax and approx lmax/N ratio: 300 | g = 1.0 301 | lmax = int( 2*ncpus*( np.floor_divide( g*N, 2*ncpus ) ) + m - 1 ) 302 | # If manually setting the max angular degree lmax, then it must be even if m is odd, 303 | # and lmax-m+1 should be divisible by 2*ncpus 304 | # lmax = (2*ncpus*2 + m - 1) 305 | 306 | 307 | 308 | # ---------------------------------------------------------------------------------------------------------------------- 309 | # ------------------------------------------------------------------------------------------------- SLEPc solver options 310 | # ---------------------------------------------------------------------------------------------------------------------- 311 | 312 | # rnd1 = 0 313 | # Set track_target = 1 below to track an eigenvalue, 0 otherwise. 314 | # Assumes a preexisting 'track_target' file with target data 315 | # Set track_target = 2 to write initial 'track_target' file, see also postprocess.py 316 | track_target = 0 317 | if track_target == 1 : # read target from file and sets target accordingly 318 | tt = np.loadtxt('track_target') 319 | rtau = tt[0] 320 | itau = tt[1] 321 | else: # set target manually 322 | rtau = 0 323 | itau = 1.0 324 | 325 | # tau is the actual target for the solver 326 | # real part is damping 327 | # imaginary part is frequency (positive is retrograde) 328 | tau = rtau + itau*1j 329 | 330 | which_eigenpairs = 'TM' # Use 'TM' for shift-and-invert 331 | # L/S/T & M/R/I 332 | # L largest, S smallest, T target 333 | # M magnitude, R real, I imaginary 334 | 335 | # Number of desired eigenvalues 336 | nev = 5 337 | 338 | # Number of vectors in Krylov space for solver 339 | # ncv = 100 340 | 341 | # Maximum iterations to converge to an eigenvector 342 | maxit = 50 343 | 344 | # Tolerance for solver 345 | tol = 1e-15 346 | # Tolerance for the thermal/compositional matrix 347 | tol_tc = 1e-6 348 | 349 | 350 | # ---------------------------------------------------------------------------------------------------------------------- 351 | # ---------------------------------------------------------------------------------------------------------------------- 352 | # ---------------------------------------------------------------------------------------------------------------------- 353 | --------------------------------------------------------------------------------