├── requirements.txt ├── save_file.p ├── CLAuDE NOM.pdf ├── earth map.bmp ├── f0e1869820b56bcc79819dee3f35490e.jpg ├── tex-docs ├── figures │ └── potential_temperature.jpg ├── appendices │ ├── TTNMETAF.tex │ ├── vars.tex │ └── history.tex ├── CLAuDE.tex ├── topics │ ├── master.tex │ ├── control_panel.tex │ ├── advection.tex │ ├── velocity.tex │ └── util_funcs.tex └── references.bib ├── claude_setup.py ├── standard_atmosphere.txt ├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── LICENSE ├── README.md ├── CODE_OF_CONDUCT.md ├── .gitignore ├── BUILDING.md ├── claude_top_level_library.pyx ├── claude_low_level_library.pyx └── toy_model.py /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | matplotlib 3 | scipy -------------------------------------------------------------------------------- /save_file.p: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planet-Factory/legacy-claude/HEAD/save_file.p -------------------------------------------------------------------------------- /CLAuDE NOM.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planet-Factory/legacy-claude/HEAD/CLAuDE NOM.pdf -------------------------------------------------------------------------------- /earth map.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planet-Factory/legacy-claude/HEAD/earth map.bmp -------------------------------------------------------------------------------- /f0e1869820b56bcc79819dee3f35490e.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planet-Factory/legacy-claude/HEAD/f0e1869820b56bcc79819dee3f35490e.jpg -------------------------------------------------------------------------------- /tex-docs/figures/potential_temperature.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planet-Factory/legacy-claude/HEAD/tex-docs/figures/potential_temperature.jpg -------------------------------------------------------------------------------- /claude_setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | from Cython.Build import cythonize 3 | import numpy 4 | 5 | setup( 6 | include_dirs=[numpy.get_include()], 7 | ext_modules = cythonize("*.pyx", compiler_directives={'language_level' : "3"}) 8 | ) -------------------------------------------------------------------------------- /standard_atmosphere.txt: -------------------------------------------------------------------------------- 1 | 0 288.1 1.225E+0 101300 2 | 2 275.2 1.007E+0 79500 3 | 4 262.2 8.193E-1 61700 4 | 6 249.2 6.601E-1 47200 5 | 8 236.2 5.258E-1 35600 6 | 10 223.3 4.135E-1 26500 7 | 20 216.6 8.891E-2 5500 8 | 30 226.5 1.841E-2 1200 9 | 40 250.4 3.995E-3 290 10 | 50 270.6 1.027E-3 80 11 | 60 247.0 3.096E-4 20 12 | 70 219.6 8.281E-5 5 13 | 80 198.6 1.845E-5 0.1 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Simon Clark 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CLimate Analysis using Digital Estimations (CLAuDE) 2 | This is the project to model the climates of hypothetical planets, which could be Earth with slightly different parameters or entirely alien worlds. 3 | 4 | The tool which we'll use to accomplish this is CLimate Analysis using Digital Estimations (CLAuDE), an intuitive, 5 | simplistic model of atmospheric dynamics. 6 | 7 | **The purpose of CLAuDE is to be:** 8 | - versatile, such that users customise which modules of code will be used in a given simulation 9 | - a teaching tool, both for programming and climate physics 10 | - accessible, designed to run even on basic machines with appropriate parameters 11 | 12 | **Objectives** 13 | - Modular, so users can customise the complexity of the model and write their own modules. 14 | - To answer questions through YouTube videos about hypothetical planets, that would use the output of CLAuDE 15 | 16 | Beyond these goals, we hope that it can become a useful resource for teachers and students discussing climate 17 | and atmospheric dynamics. 18 | 19 | ### Building 20 | See the documentation on building in [BUILDING.md](https://github.com/Planet-Factory/claude/blob/master/BUILDING.md). 21 | 22 | ### Community 23 | 24 | **Discord** 25 | 26 | If you wish to discuss CLAuDE and other things related to climate and atmospheric dynamics, feel free 27 | to join the [Dr. Simon Clark Discord Server](https://discord.gg/SZu6e2F). 28 | 29 | **YouTube** 30 | 31 | Simon creates videos about climate and atmospheric dynamics, as well as videos hypothesising 32 | if fictional planets could be a reality, based of what we currently know. 33 | 34 | If you think you'd enjoy this videos, you can watch him [here](https://www.youtube.com/channel/UCRRr_xrOm66qaigIbwFLvbQ). 35 | 36 | **Twitch** 37 | 38 | CLAuDE is currently being developed on Wednesday [Twitch](https://twitch.tv/drsimonclark) streams every week! 39 | 40 | ### Licensing 41 | [MIT License](LICENSE) 42 | -------------------------------------------------------------------------------- /tex-docs/appendices/TTNMETAF.tex: -------------------------------------------------------------------------------- 1 | \appendix 2 | \appendixpage 3 | \section{Terms That Need More Explanation Then A Footnote} 4 | \subsection{Potential} \label{sec:potential} 5 | Potential is the energy change that occurs when the position of an object changes \cite{potential}. There are many potentials, like electric potential, gravitational potential and elastic 6 | potential. Let me explain the concept with an example. Say you are walking on a set of stairs in the upwards direction. As your muscles move to bring you one step upwards, energy that is used 7 | by your muscles is converted into gravitational potential. Now imagine you turn around and go downwards instead. Notice how that is easier? That is due to the gravitational potential being 8 | converted back into energy so your muscles have to deliver less energy to get you down. The potential is usually tied to a force, like the gravitational force. 9 | 10 | \subsection{Asymptotic Runtime} \label{sec:runtime} 11 | Asymptotic runtime is what we use in computer science to indicate how fast an algorithm works. We do it this way because concrete time indications (seconds, minutes, hours) are very machine 12 | dependent. It matters a lot if your CPU, RAM and GPU are fast or not for the runtime. Therefore, we needed something to compare algorithms by which is machine independent. That is what asymptotic 13 | runtime is. We have 3 notations for asymptotic runtime, $\Omega$ which is the lower bound of the runtime: not faster than; $O$ which is the upperbound of the runtime: not slower than; and we 14 | have $\Theta$ which is the tight bound: not slower but also not faster than. After these 3 notations we usually denote the runtime in algebraic letters which stand for the input size. $O(n)$ for 15 | instance means that for an input of size $n$ the algorithm will not run slower than $n$ operations. Whereas $\Omega(n^3)$ means that the algorithm needs for an input of size $n$ at least $n^3$ 16 | operations. Now this is not an exact match, as there are constants and other terms in the real runtime, but for asymptotic runtime we look at the most dominant factor, as that outgrows all the 17 | other factors if the input size increases. You can compare this by plotting the functions $y = x$ and $z = x^2$ on a graphical calculator. No matter which constant $a$ you put in front of the $x$, 18 | $z = x^2$ will at some point (note we don't specify when or where) be larger than $y = ax$. What you need to remember for all this is that polynomials are faster than exponentials ($n^2 < 2^n$) 19 | and logarithms are faster than polynomials ($\log(n) < n$). $n!$ is very slow, $\log(n)$ is very fast. 20 | 21 | \subsection{Complex Numbers} \label{sec:complex} 22 | As you all know in the real numbers ($\mathbb{R}$) negative roots are not allowed as they do not exist. But what would happen if we would allow them to exist? Then we move into the area of 23 | complex numbers. A complex number consists out of two parts, a real part and an imaginary part in the form $a + bi$ where $i = \sqrt{-1}$. Complex numbers have all kinds of properties, but what 24 | we need them for are rotations. This is captured in Euler's formula $e^{it} = \cos(x) + i\sin(x)$ \cite{eulerFormula}. Which means that for time $t$ we rotate around the origin (of the complex 25 | plane) forming a circle with radius one (the unit circle). Now if you would set $t = \pi$ then the result is $0$. What this means is that we have come full circle (hah) when $t = 2\pi$. -------------------------------------------------------------------------------- /tex-docs/appendices/vars.tex: -------------------------------------------------------------------------------- 1 | \section{List of Variables} 2 | Are you ever confused about what something is? Do you ever forget what a variable represents? Then I got the solution for you. The following overview will explain what each variable is and 3 | represents. I will try to not use one variable for the same thing, though that is sometimes very difficult to do. I'll do my best. In the meantime, enjoy this exstensive list. Note that this 4 | only applies to variables in code, every symbol in equations are explained at the equations themselves. 5 | 6 | \begin{itemize} 7 | \item $R$: The Gas Constant with value $8.3144621$ (\si{J(mol)^{-1}K}). 8 | \item $day$: Length of one day in seconds (\si{s}). 9 | \item $year$: Length of one year in seconds (\si{s}). 10 | \item $\delta t$: How much time is between each calculation run in seconds (\si{s}). 11 | \item $g:$ Magnitude of gravity on the planet in \si{ms^{-2}}. 12 | \item $\alpha$: By how many degrees the planet is tilted with respect to the star's plane, also called axial tilt. 13 | \item $top$: How high the top of the atmosphere is with respect to the planet surface in meters (\si{m}). 14 | \item $ins$: Amount of energy from the star that reaches the planet per unit area (\si{Jm^{-2}}). 15 | \item $\epsilon$: Absorbtivity of the atmosphere, fraction of how much of the total energy is absorbed (unitless). 16 | \item $resolution$: The amount of degrees on the latitude longitude grid that each cell has, with this setting each cell is 3 degrees latitude high and 3 degrees longitude wide. 17 | \item $nlevels \leftarrow 10$: The amount of layers in the atmosphere. 18 | \item $\delta t_s$: The time between calculation rounds during the spin up period in seconds (\si{s}). 19 | \item $t_s$: How long we let the planet spin up in seconds (\si{s}). 20 | \item $adv$: Whether we want to enable advection or not. 21 | \item $velocity$: Whether we want to calculate the air velocity or not. 22 | \item $adv\_boun$: How many cells away from the poles where we want to stop calculating the effects of advection. 23 | \item $nlon$: The amount of longitude gridpoints that we use, which depends on the resolution. 24 | \item $nlat$: The amount of latitude gridpoints that we use, which depends on the resolution. 25 | \item $T_p$: The temperature of the planet, a 2D array representing a latitude, longitude grid cell. 26 | \item $T_a$: The temperature of the atmosphere, a 3D array representing a grid cell on the latitude, longitude, atmospheric layer grid. 27 | \item $\sigma$: The Stefan-Boltzmann constant equal to $5.670373 \cdot 10^{-8} \ ($\si{Wm^{-2}K^{-4}}). 28 | \item $C_a$: Specific heat capacity of the air, equal to $1.0035$ \si{Jg^{-1}K^{-1}}. 29 | \item $C_p$: Specific heat capacity of the planet, equal to $1.0 \cdot 10^{6}$ \si{Jg^{-1}K^{-1}}. 30 | \item $a$: Albedo, the reflectiveness of a substance. Note that $a$ is used in general functions as an array that is supplied as input. If that is the case it can be read at the top of the 31 | algorithm. 32 | \item $\rho$: The density of the atmosphere, a 3D array representing a grid cell on the latitude, longitude, atmospheric layer grid. 33 | \item $\delta x$: How far apart the gridpoints are in the $x$ direction in degrees longitude. 34 | \item $\delta y$: How far apart the gridpoints are in the $y$ direction in degrees latitude. 35 | \item $p_z$ : The vertical pressure coordinate in Pascals (\si{Pa}). 36 | \item $\tau$: The optical depth for an atmospheric layer. 37 | \item $\tau_0$: The optical depth at the planet surface. 38 | \item $f_l$: The optical depth parameter. 39 | \item $pressureProfile$: The average pressure taken over all atmospheric layers in a latitude, longitude gridcell in Pascals (\si{Pa}). 40 | \item $densityProfile$:The average density taken over all atmospheric layers in a latitude, longitude gridcell. 41 | \item $temperatureProfile$: The average temperature taken over all atmospheric layers in a latitude, longitude gridcell in degrees Kelvin (\si{K}). 42 | \item $U$: Upward flux of radiation, 1D array representing an atmospheric layer. 43 | \item $D$: Downward flux of radiation, 1D array representing an atmospheric layer. 44 | \item $u$: The east to west air velocity in meters per second (\si{ms^{-1}}). 45 | \item $v$: The north to south air velocity in meters per second (\si{ms^{-1}}). 46 | \item $w$: The bottom to top air velocity in \si{ms^{-1}}. 47 | \item $f$: The coriolis parameter. 48 | \item $\Omega$: The rotation rate of the planet in \si{rads^{-1}}. 49 | \item $p$: The pressure of a latitude, longitude, atmospheric layer gridcell in Pascals (\si{Pa}). 50 | \item $p_0$: The pressure of a latitude, longitude, atmospheric layer gridcell from the previous calculation round in Pascals (\si{Pa}). 51 | \item $\alpha_a$: The thermal diffusivity constant for air. 52 | \item $\alpha_p$: The thermal diffusivity constant for the planet surface. 53 | \item $smooth_t$: The smoothing parameter for the temperature. 54 | \item $smooth_u$: The smoothing parameter for the $u$ component of the velocity. 55 | \item $smooth_v$: The smoothing parameter for the $v$ component of the velocity. 56 | \item $smooth_w$: The smoothing parameter for the $w$ component of the velocity. 57 | \item $smooth_{vert}$: The smoothing parameter for the vertical part of the velocities. 58 | \item $r$: Radius of the planet in \si{m}. 59 | \end{itemize} -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behaviour may be 62 | reported to the community leaders responsible for enforcement at 63 | https://forms.gle/ZP1BVat7QVenqCXH7. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /tex-docs/CLAuDE.tex: -------------------------------------------------------------------------------- 1 | \documentclass{article} 2 | 3 | \usepackage{hyperref} 4 | \usepackage{cite} 5 | \usepackage{amsmath} 6 | \usepackage{amsfonts} 7 | \usepackage[ruled]{algorithm2e} 8 | \usepackage[margin=1in]{geometry} 9 | \usepackage{appendix} 10 | \usepackage{float} 11 | \usepackage{verbatim} 12 | \usepackage[normalem]{ulem} 13 | \usepackage{graphicx} 14 | \usepackage{siunitx} 15 | \usepackage[section]{placeins} 16 | 17 | \setcounter{section}{-1} 18 | \newtheorem{lemma}{Lemma} 19 | \newcommand{\lemmaautorefname}{Lemma} 20 | 21 | \title{CLimate Analysis using Digital Estimations Non-Offical Manual (CLAuDE NOM)} 22 | \author{Sam "TechWizard" Baggen} 23 | \date{\today} 24 | 25 | \begin{document} 26 | 27 | \maketitle 28 | 29 | \tableofcontents 30 | 31 | \newpage 32 | 33 | \section{Introduction} 34 | 35 | The CLimate Analysis using Digital Estimations model is a simplified planetary climate model. It will be used to educate people on how climate physics works and to experiment with different 36 | parameters and see how much influence a tiny change can have (like for instance the rotation rate of the planet around its axis). It is built to be accessible to and runnable by everyone, 37 | whether they have a super computer or a dated laptop. The model is written in Python and written during the weekly streams of Dr. Simon Clark \cite{twitch}. There is a useful playlist on 38 | Simon's Twitch which has all the streams without ad breaks or interruptions \cite{playlist}. 39 | 40 | The manual itself is split up into distinct sections, each explaining one particular part of the model. Each section will be treating one topic, like radiation, advection or the control panel. 41 | Although many concepts cannot be seen in isolation, as the wind has influence on how much temperature is distributed throughout the atmosphere, the calculations can be split up. The manual is 42 | cumulative, starting with the basics and slowly building up to the current form of the algorithm. All changes to the algorithms can therefore be found here. An important distinction needs to be 43 | made regarding the changes though. If the changes only change one part of the calculations, then it is considered an evolution, which will be added to the relevant section. However if the changes 44 | are significant and not based on the previous code then the old alghorithms will be relocated to \autoref{sec:history}. Though the relevant theory will remain, as that is required to gain an 45 | understanding of what the algorithm does. Do note that the radiation \autoref{sec:rad} is an exception for the first calculations as this forms the basis of the beginning of CLAuDE and the 46 | fundamentals of the theory which I deem important enough to be left in place even if the calculations end up significantly different. 47 | 48 | This manual will provide an overview of the formulae used and will explain aspects of these formulae. For each equation each symbol will be explained what it is. In such an explanation, the 49 | units will be presented in SI units \cite{SI} between brackets like: $T$: The temperature of the planet (\si{K}). Which indicates that $T$ is the temperature of the planet in degrees Kelvin. If 50 | you need to relate SI units to your preferred system of units, please refer to the internet for help with that. There are great calculators online where you only need to plug in a number and 51 | select the right units. 52 | 53 | Within this manual we will not concern ourselves with plotting the data, instead we focus on the physics side of things and translating formulae into code. If you are interested in how the 54 | plotting of the data works, or how loading and saving data works, please refer to the relevant stream on Simon's Twitch page \cite{twitch}. 55 | 56 | This manual is for the toy model, which is as of now still in development. One important thing to note is that the layout may change significantly when new sections are added. This is due to the 57 | amount of code that is added/changed. If a lot of code changes, a lot of so called 'algorithm' blocks are present which have different placement rules than just plain text. Therefore it may 58 | occur that an algorithm is referenced even though it is one or two pages later. This is a pain to fix and if something later on changes, the whole layout may be messed up again and is a pain to 59 | fix again. Hence I opt to let \LaTeX (the software/typeset language used to create this manual) figure out the placement of the algorithm blocks, which may or may not be in the right places. 60 | 61 | One last important thing, I try to make this manual as generic as possible regarding the code. This means that I try to steer away from using Python specific notation. However, this manual is 62 | also the documentation for the project written in Python. Therefore it is sometimes unavoidable to use Python notation. Furthermore, to make the model run efficiently, the code is vectorised. 63 | Vectorised code is however more difficult to understand (as a programmer without the required linear algebra) than the For loops that it replaces. Therefore, it is not uncommon to see (parts of) 64 | algorithms repeated in their relevant sections, once as a standard for loop and once as the vectorised version. 65 | 66 | The manual is now up on the Planet Factory GitHub repository\cite{claudeGit}, together with all the source code. There is also a fork \cite{nomGit} that also contains the source code. 67 | The fork will usually be more up to date than the version on the Planet Factory repository as Simon needs to merge pull requests into the repository. However I can update the fork freely so if a 68 | particular stream is missing in the version on the Planet Factory repository, check the fork/Discord whether there is a newer version. If that is not the case, you just have to be a bit more 69 | patient, or you can start writing a part of the manual yourself! Don't forget to ping me in the Discord to notify me of any additions (GitHub refuses to send me emails so I have no other way of 70 | knowing). 71 | 72 | \FloatBarrier 73 | \newpage 74 | \input{topics/control_panel.tex} 75 | 76 | \FloatBarrier 77 | \newpage 78 | \input{topics/util_funcs.tex} 79 | 80 | \FloatBarrier 81 | \newpage 82 | \input{topics/radiation.tex} 83 | 84 | \FloatBarrier 85 | \newpage 86 | \input{topics/velocity.tex} 87 | 88 | \FloatBarrier 89 | \newpage 90 | \input{topics/advection.tex} 91 | 92 | \FloatBarrier 93 | \newpage 94 | \input{topics/planar.tex} 95 | 96 | \FloatBarrier 97 | \newpage 98 | \input{topics/master.tex} 99 | 100 | \FloatBarrier 101 | \newpage 102 | \input{appendices/TTNMETAF.tex} 103 | 104 | \FloatBarrier 105 | \newpage 106 | \input{appendices/history.tex} 107 | 108 | \FloatBarrier 109 | \newpage 110 | \input{appendices/vars.tex} 111 | 112 | \newpage 113 | \bibliography{references} 114 | \bibliographystyle{plain} 115 | \end{document} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/python,tex 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=python,tex 3 | 4 | ### Python ### 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | pip-wheel-metadata/ 28 | share/python-wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .nox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | *.py,cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | pytestdebug.log 58 | 59 | # Translations 60 | *.mo 61 | *.pot 62 | 63 | # Django stuff: 64 | *.log 65 | local_settings.py 66 | db.sqlite3 67 | db.sqlite3-journal 68 | 69 | # Flask stuff: 70 | instance/ 71 | .webassets-cache 72 | 73 | # Scrapy stuff: 74 | .scrapy 75 | 76 | # Sphinx documentation 77 | docs/_build/ 78 | doc/_build/ 79 | 80 | # PyBuilder 81 | target/ 82 | 83 | # Jupyter Notebook 84 | .ipynb_checkpoints 85 | 86 | # IPython 87 | profile_default/ 88 | ipython_config.py 89 | 90 | # pyenv 91 | .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 101 | __pypackages__/ 102 | 103 | # Celery stuff 104 | celerybeat-schedule 105 | celerybeat.pid 106 | 107 | # SageMath parsed files 108 | *.sage.py 109 | 110 | # Environments 111 | .env 112 | .venv 113 | env/ 114 | venv/ 115 | ENV/ 116 | env.bak/ 117 | venv.bak/ 118 | 119 | # Spyder project settings 120 | .spyderproject 121 | .spyproject 122 | 123 | # Rope project settings 124 | .ropeproject 125 | 126 | # mkdocs documentation 127 | /site 128 | 129 | # mypy 130 | .mypy_cache/ 131 | .dmypy.json 132 | dmypy.json 133 | 134 | # Pyre type checker 135 | .pyre/ 136 | 137 | # pytype static type analyzer 138 | .pytype/ 139 | 140 | ### TeX ### 141 | ## Core latex/pdflatex auxiliary files: 142 | *.aux 143 | *.lof 144 | *.lot 145 | *.fls 146 | *.out 147 | *.toc 148 | *.fmt 149 | *.fot 150 | *.cb 151 | *.cb2 152 | .*.lb 153 | 154 | ## Intermediate documents: 155 | *.dvi 156 | *.xdv 157 | *-converted-to.* 158 | # these rules might exclude image files for figures etc. 159 | # *.ps 160 | # *.eps 161 | # *.pdf 162 | 163 | ## Generated if empty string is given at "Please type another file name for output:" 164 | .pdf 165 | 166 | ## Bibliography auxiliary files (bibtex/biblatex/biber): 167 | *.bbl 168 | *.bcf 169 | *.blg 170 | *-blx.aux 171 | *-blx.bib 172 | *.run.xml 173 | 174 | ## Build tool auxiliary files: 175 | *.fdb_latexmk 176 | *.synctex 177 | *.synctex(busy) 178 | *.synctex.gz 179 | *.synctex.gz(busy) 180 | *.pdfsync 181 | 182 | ## Build tool directories for auxiliary files 183 | # latexrun 184 | latex.out/ 185 | 186 | ## Auxiliary and intermediate files from other packages: 187 | # algorithms 188 | *.alg 189 | *.loa 190 | 191 | # achemso 192 | acs-*.bib 193 | 194 | # amsthm 195 | *.thm 196 | 197 | # beamer 198 | *.nav 199 | *.pre 200 | *.snm 201 | *.vrb 202 | 203 | # changes 204 | *.soc 205 | 206 | # comment 207 | *.cut 208 | 209 | # cprotect 210 | *.cpt 211 | 212 | # elsarticle (documentclass of Elsevier journals) 213 | *.spl 214 | 215 | # endnotes 216 | *.ent 217 | 218 | # fixme 219 | *.lox 220 | 221 | # feynmf/feynmp 222 | *.mf 223 | *.mp 224 | *.t[1-9] 225 | *.t[1-9][0-9] 226 | *.tfm 227 | 228 | #(r)(e)ledmac/(r)(e)ledpar 229 | *.end 230 | *.?end 231 | *.[1-9] 232 | *.[1-9][0-9] 233 | *.[1-9][0-9][0-9] 234 | *.[1-9]R 235 | *.[1-9][0-9]R 236 | *.[1-9][0-9][0-9]R 237 | *.eledsec[1-9] 238 | *.eledsec[1-9]R 239 | *.eledsec[1-9][0-9] 240 | *.eledsec[1-9][0-9]R 241 | *.eledsec[1-9][0-9][0-9] 242 | *.eledsec[1-9][0-9][0-9]R 243 | 244 | # glossaries 245 | *.acn 246 | *.acr 247 | *.glg 248 | *.glo 249 | *.gls 250 | *.glsdefs 251 | *.lzo 252 | *.lzs 253 | 254 | # uncomment this for glossaries-extra (will ignore makeindex's style files!) 255 | # *.ist 256 | 257 | # gnuplottex 258 | *-gnuplottex-* 259 | 260 | # gregoriotex 261 | *.gaux 262 | *.gtex 263 | 264 | # htlatex 265 | *.4ct 266 | *.4tc 267 | *.idv 268 | *.lg 269 | *.trc 270 | *.xref 271 | 272 | # hyperref 273 | *.brf 274 | 275 | # knitr 276 | *-concordance.tex 277 | # TODO Comment the next line if you want to keep your tikz graphics files 278 | *.tikz 279 | *-tikzDictionary 280 | 281 | # listings 282 | *.lol 283 | 284 | # luatexja-ruby 285 | *.ltjruby 286 | 287 | # makeidx 288 | *.idx 289 | *.ilg 290 | *.ind 291 | 292 | # minitoc 293 | *.maf 294 | *.mlf 295 | *.mlt 296 | *.mtc[0-9]* 297 | *.slf[0-9]* 298 | *.slt[0-9]* 299 | *.stc[0-9]* 300 | 301 | # minted 302 | _minted* 303 | *.pyg 304 | 305 | # morewrites 306 | *.mw 307 | 308 | # nomencl 309 | *.nlg 310 | *.nlo 311 | *.nls 312 | 313 | # pax 314 | *.pax 315 | 316 | # pdfpcnotes 317 | *.pdfpc 318 | 319 | # sagetex 320 | *.sagetex.sage 321 | *.sagetex.py 322 | *.sagetex.scmd 323 | 324 | # scrwfile 325 | *.wrt 326 | 327 | # sympy 328 | *.sout 329 | *.sympy 330 | sympy-plots-for-*.tex/ 331 | 332 | # pdfcomment 333 | *.upa 334 | *.upb 335 | 336 | # pythontex 337 | *.pytxcode 338 | pythontex-files-*/ 339 | 340 | # tcolorbox 341 | *.listing 342 | 343 | # thmtools 344 | *.loe 345 | 346 | # TikZ & PGF 347 | *.dpth 348 | *.md5 349 | *.auxlock 350 | 351 | # todonotes 352 | *.tdo 353 | 354 | # vhistory 355 | *.hst 356 | *.ver 357 | 358 | # easy-todo 359 | *.lod 360 | 361 | # xcolor 362 | *.xcp 363 | 364 | # xmpincl 365 | *.xmpi 366 | 367 | # xindy 368 | *.xdy 369 | 370 | # xypic precompiled matrices and outlines 371 | *.xyc 372 | *.xyd 373 | 374 | # endfloat 375 | *.ttt 376 | *.fff 377 | 378 | # Latexian 379 | TSWLatexianTemp* 380 | 381 | ## Editors: 382 | # WinEdt 383 | *.bak 384 | *.sav 385 | 386 | # Texpad 387 | .texpadtmp 388 | 389 | # LyX 390 | *.lyx~ 391 | 392 | # Kile 393 | *.backup 394 | 395 | # gummi 396 | .*.swp 397 | 398 | # KBibTeX 399 | *~[0-9]* 400 | 401 | # TeXnicCenter 402 | *.tps 403 | 404 | # auto folder when using emacs and auctex 405 | ./auto/* 406 | *.el 407 | 408 | # expex forward references with \gathertags 409 | *-tags.tex 410 | 411 | # standalone packages 412 | *.sta 413 | 414 | # Makeindex log files 415 | *.lpz 416 | 417 | # REVTeX puts footnotes in the bibliography by default, unless the nofootinbib 418 | # option is specified. Footnotes are the stored in a file with suffix Notes.bib. 419 | # Uncomment the next line to have this generated file ignored. 420 | #*Notes.bib 421 | 422 | ### TeX Patch ### 423 | # LIPIcs / OASIcs 424 | *.vtc 425 | 426 | # glossaries 427 | *.glstex 428 | 429 | # End of https://www.toptal.com/developers/gitignore/api/python,tex 430 | 431 | # Platform specific - compiled python (through cython) 432 | claude_*_level_library*.pyd 433 | # and the code it's generated from 434 | claude_*_level_library.c 435 | -------------------------------------------------------------------------------- /BUILDING.md: -------------------------------------------------------------------------------- 1 | # Running CLAuDE 2 | 3 | CLAuDE is built using Python and Cython, a utility which compiles python code into C to speed up execution. 4 | 5 | The basic steps to run the program are as follows: 6 | 7 | 1. Use Cython to compile `claude_low_level_library` and `claude_top_level_library` 8 | ``` 9 | python claude_setup.py build_ext --inplace 10 | ``` 11 | 12 | 2. Run the model 13 | ``` 14 | python ./toy_model.py 15 | ``` 16 | 17 | Below are more specific instructions to get setup for various operating systems. 18 | 19 | ## Windows 20 | 21 | ### 1. Clone or download the repository 22 | If you plan on contributing to this project, you will need to use git in some way to clone the project. Otherwise, simply selecting to download the repo will do. 23 | Various git programs, such as [GitHub Desktop](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/cloning-a-repository-from-github-to-github-desktop), [GitKraken](https://support.gitkraken.com/working-with-repositories/open-clone-init/), and [git for Windows](https://gitforwindows.org/) are available to clone the repository with git. Each of these provide different levels of features and different levels of complexity. 24 | 25 | ### 2. Install Python, pip and a compiler 26 | To build this project, you will need Python, a compiler for the C code that Cython generates, and several python libraries that will be installed with pip. 27 | Pip is a package manager, a program which simplifies installation of software, in this case, python libraries. 28 | 29 | You can install python from [its website](https://www.python.org/downloads/windows/). 30 | 31 | If you do not already have pip installed (it should be installed with python if you have an up to date version), follow the instructions on the [pip website](https://pip.pypa.io/en/stable/installing/) to install it. 32 | 33 | #### Compiler 34 | A compiler must then be installed. According to [the cython documentation](https://cython.readthedocs.io/en/latest/src/quickstart/install.html), you can use either [MinGW](https://osdn.net/projects/mingw/releases/) or Microsoft Visual C. 35 | If you choose to use MinGW, follow the instructions in the [cython documentation](https://cython.readthedocs.io/en/latest/src/tutorial/appendix.html) to get it set up. 36 | If you choose to use Microsoft Visual C/C++, follow the instructions on the [Python website](https://wiki.python.org/moin/WindowsCompilers). 37 | 38 | ### 3. Use pip to install python libraries 39 | Using command prompt, install the pip packages [cython](https://pypi.org/project/Cython/), [numpy](https://pypi.org/project/numpy/), and [matplotlib](https://pypi.org/project/matplotlib/), as well as [setuptools](https://pypi.org/project/setuptools/), if for some reason it is not already installed. 40 | The syntax to install a package with pip is 41 | ``` 42 | pip install -r requirements.txt 43 | ``` 44 | Where `PACKAGE` is replaced with the name of the package. 45 | 46 | ### 4. Using Cython, compile `claude_low_level_library` and `claude_top_level_library` 47 | Simply open command prompt, navigate to directory containing this file, and run: 48 | ``` 49 | python claude_setup.py build_ext --inplace 50 | ``` 51 | This will convert the `.pyx` files in the repository to C files, then compile them. 52 | 53 | ### 5. Run the model 54 | Simply run this in your command prompt, while in the directory containing this file. 55 | ``` 56 | python toy_model.py 57 | ``` 58 | 59 | ## Linux 60 | 61 | The following programs and libraries are required to build CLAuDE: 62 | 63 | - git (to clone the repository) 64 | - python 65 | - setuptools 66 | - cython 67 | - numpy 68 | - matplotlib 69 | - scipy 70 | 71 | If you're using apt as your package manager, these correspond to the packages `git` `python3` `python3-setuptools` `cython3` `python3-numpy` `python3-matplotlib` `python-scipy`. 72 | 73 | Depending upon your system you may also need to install `tkinter` to view the graphics, which corresponds to the package `python3-tk`. 74 | 75 | ### 1. Clone the repository. 76 | Using the git command line, and cloning via https, this command will do it: 77 | ``` 78 | git clone https://github.com/Planet-Factory/claude.git 79 | ``` 80 | If using a GUI front-end for git, your method of doing it may vary. 81 | 82 | ### 2. Using Cython, compile `claude_low_level_library` and `claude_top_level_library` 83 | In your terminal, in the base `claude` directory, simply run 84 | ``` 85 | python3 claude_setup.py build_ext --inplace 86 | ``` 87 | This will convert the `.pyx` files in the repository to C files, then compile them. 88 | 89 | ### 3. Run the model 90 | In your terminal, run 91 | ``` 92 | python3 ./toy_model.py 93 | ``` 94 | 95 | #### 2.1 Troubleshooting 96 | For one reason or another, step 2 may fail. Here are some errors you might run in to: 97 | 98 | ##### Python.h: no such file or directory 99 | 100 | This means that you did not install the equivalent of apt's `python3-dev` package - you only have python for running programs, but lack the necessary headers for developing with it. If you are using apt and install the packages listed above, this should not occur. 101 | 102 | ##### some_other_file.h: no such file or directory 103 | 104 | This likely means that the compiler building the libraries is not being told to search the proper directories for header files to include. 105 | One such case where this can happen is when you have libxcrypt installed, so Python.h includes ``, but setuptools does not tell the compiler where to search for that. 106 | There is probably a proper solution to this, but I don't know it, so for now, you can work around this by telling it manually what to include. 107 | For example, with following error: 108 | ``` 109 | /usr/include/python3.8/Python.h:44:10: fatal error: crypt.h: No such file or directory 110 | 44 | #include 111 | | 112 | ``` 113 | You need to find where the crypt.h header is to tell setuptools where to include it, so you can use the aptly named `find`. 114 | You can use `find` in the format `find dir/ -name "pattern"` to recursively search directories from `dir/` and get the full path of the header file you are searching for. 115 | If the #include has a directory included (e.g. `#include `), use the directory which contains that directory (if `foo.h` is in `/usr/include/alice/blah/foo.h`, you want `/usr/include/alice`). 116 | 117 | So for example, you use `find /usr -name "crypt.h"` to find the full path of the crypt.h header that Python.h is looking to include. 118 | I got the output `/usr/include/tirpc/rpcsvc/crypt.h`, so now I know where that file is found, and I can instruct setuptools to include it. 119 | 120 | You instruct setuptools what directories to include with the `-I` option to the command, so in this case, it would be 121 | ``` 122 | python3 claude_setup.py build_ext --inplace -I/usr/include/tirpc/rpcsvc/ 123 | ``` 124 | Separate multiple includes with `:`, so including `/usr/include/foo` and `/usr/include/bar`, you do `-I/usr/include/foo:/usr/include/bar`. 125 | 126 | However, when you manually specify what directories to include, it will not automatically include numpy, so you will need to specify that too. You can wait until it gives you an error about that to find what you need to include. 127 | 128 | An actual example command is 129 | ``` 130 | python claude_setup.py build_ext --inplace -I/usr/include/tirpc:/usr/include/tirpc/rpcsvc:/usr/lib/python3.8/site-packages/numpy/core/include 131 | ``` 132 | 133 | ## OSX (not tested) 134 | 135 | As stated, these are not tested, but these are the hypothetical steps to set up and build CLAuDE on OSX. 136 | Please report if these do or do not work, so documentation can be updated. 137 | 138 | ### 1. Install HomeBrew 139 | HomeBrew is a package manager, software which simplifies the installation of various software. 140 | You can install HomeBrew with instructions from [its website](https://brew.sh/). 141 | 142 | ### 2. Clone the repository 143 | If you plan on contributing to this project, you will need to use git in some way to clone the project. Otherwise, simply selecting to download the repo will do. 144 | Various git programs, such as [GitHub Desktop](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/cloning-a-repository-from-github-to-github-desktop), [GitKraken](https://support.gitkraken.com/working-with-repositories/open-clone-init/), or just git are available to clone the repository. Each of these provide different levels of features and different levels of complexity. 145 | git can be installed through homebrew using `brew install git`. 146 | Using git, you can clone the repository via https with this command: 147 | ``` 148 | git clone https://github.com/Planet-Factory/claude.git 149 | ``` 150 | 151 | ### 3. Install dependencies 152 | To build the project, you require `python`, as well as the python libraries `setuptools`, `cython`, `numpy`, `matplotlib` and `scipy`. 153 | First, use brew to install python: 154 | ``` 155 | brew install python 156 | ``` 157 | Then, use `pip`, a python package manager that is installed with python to install the other dependencies. 158 | `setuptools` should automatically be included with pip, but if it is not, install it along with these packages. 159 | ``` 160 | pip install cython 161 | pip install numpy 162 | pip install matplotlib 163 | pip install scipy 164 | ``` 165 | 166 | ### 4. Using Cython, compile `claude_low_level_library` and `claude_top_level_library` 167 | In your terminal, in the base `claude` directory, simply run 168 | ``` 169 | python claude_setup.py build_ext --inplace 170 | ``` 171 | This will convert the `.pyx` files in the repository to C files, then compile them. 172 | 173 | ### 3. Run the model 174 | In your terminal, run 175 | ``` 176 | python ./toy_model.py 177 | ``` 178 | -------------------------------------------------------------------------------- /tex-docs/topics/master.tex: -------------------------------------------------------------------------------- 1 | \section{The Master File} 2 | The master file is the file that controls the model calculation. This file decides what calculations are used and what is done with the calculations (which is not the scope of this manual). 3 | In other words, the master file combines all the calculations and theory from the previous sections and puts it all together to form a model. As mentioned earlier, this structure enables the 4 | user to create their own version of the model. If one has their own calculations, or wants to use an older version of the calculations in this manual, then the user can define it themselves 5 | and call it instead of the calls that we use. The model is meant to be customisable, which this structure enables. 6 | 7 | \subsection{Adding a Spin-Up Time} 8 | Instead of having a static start (having the planet start from rest, so no rotations allowed) we will have the model start up for some time before we start simulating the climate extensively. 9 | To accomodate for this, we have to make some changes in the code. First we need to add two booleans (variables that can only take two values, either \texttt{TRUE} or \texttt{FALSE}) that we use 10 | to indicate to the model whether we want to simulate the wind and whether we want to simulate advection. This means that the main loop will have some changes made to it. After performing the 11 | radiation calculations we would calculate the velocities and afterwards we would calculate the advection. Instead let us change it to what is shown in 12 | \autoref{alg:stream4v1}. 13 | 14 | \begin{algorithm} 15 | \caption{Main loop that can simulate velocities and advection conditionally} 16 | \label{alg:stream4v1} 17 | \While{\texttt{TRUE}}{ 18 | Do the radiation calculations \; 19 | \If{$velocity$}{ 20 | Do the velocity calculations \; 21 | \If{$advection$}{ 22 | Do the advection calculations \; 23 | } 24 | } 25 | } 26 | \end{algorithm} 27 | 28 | Now to dynamically enable/disable the simulation of flow and advection we need to add the spin-up calculations to the main loop. So in \autoref{alg:stream4v1}, before 29 | \autoref{alg:temperature with density} we add \autoref{alg:spinup}. What it does is it changes the timestep when spinnning up and disables flow simulation, and when a week has passed it reduces 30 | the timestep and enables flow simulation. At this point in time, the advection is not dynamically enabled/disabled but it is done by the programmer. 31 | 32 | \begin{algorithm} 33 | \caption{The spin-up block dynamically enabling or disabling flow simulation} 34 | \label{alg:spinup} 35 | \eIf{$t < 7day$}{ 36 | $\delta t \leftarrow 60 \cdot 47$ \; 37 | $velocity \leftarrow \texttt{FALSE}$ \; 38 | }{ 39 | $\delta t \leftarrow 60 \cdot 9$ \; 40 | $velocity \leftarrow \texttt{TRUE}$ \; 41 | } 42 | \end{algorithm} 43 | 44 | \subsection{Varying the Albedo} 45 | The albdeo (reflectiveness of the planet's surface) is of course not the same over the whole planet. To account for this, we instead vary the albedo slightly for each point in the latitude 46 | longitude grid. The algorithm that does this is shown in \autoref{alg:albedo variance}. The uniform distribution basically says that each allowed value in the interval has an equal chance of 47 | being picked \cite{uniformdist}. 48 | 49 | \begin{algorithm} 50 | \caption{Varying the albedo of the planet} 51 | \label{alg:albedo variance} 52 | $V_a \leftarrow 0.02$ \; 53 | \For{$lat \in [-nlat, nlat]$}{ 54 | \For{$lon \in [0, nlon]$}{ 55 | $R \leftarrow \text{ Pick a random number (from the uniform distribution) in the interval } [-V_a, V_a]$ \; 56 | $a[lat, lon] \leftarrow a[lat, lon] + V_a \cdot R$\; 57 | } 58 | } 59 | \end{algorithm} 60 | 61 | \subsection{Non-uniform air density} 62 | While air density on the surface is in general consistent, this does not hold if you move up through the atmosphere. The planet will pull air down due to gravity, which means that more air is at 63 | the planet surface than at the top of the atmosphere. Hence the air density changes throughout the atmosphere and we need to account for that. This is done in \autoref{alg:density}. Because this 64 | is used in radiation, velocity and advection, we initialise this in the master file. Though one could argue it could be part of the control panel, we opt not to include any code other than 65 | variable declarations in the control panel for greater clarity. This also means that we give the user the option to have only one layer (by skipping implementing this algorithm). Note that the 66 | $\rho[:,: i]$ notation means that for every index in the first and second dimension, only change the value for the index $i$ in the third dimension. 67 | 68 | \begin{algorithm} 69 | \caption{Initialisation of the air density $\rho$} 70 | \label{alg:density} 71 | $\rho[:, :, 0] \leftarrow 1.3$ \; 72 | \For{$i \in [1, nlevels-1]$}{ 73 | $\rho[:, :, i] \leftarrow 0.1\rho[:, :, i - 1]$ 74 | } 75 | \end{algorithm} 76 | 77 | \subsection{Interpolating the Air Density} \label{sec:usatmosp} 78 | In order to interpolate (see \autoref{sec:interpolation}) the air density, to have a better estimation at each grid cell, we need data. However currently we are just guessing the air density at 79 | higher levels, instead of taking real values. So let us change that. For that we are going to use the U.S. Standard Atmosphere, an industry standard measure of the atmosphere on Earth 80 | \cite{usatmosp}. This data was provided in a text (\texttt{TXT}) file which of course needs to be read in order for the data to be used in the model. Here we only care for the density and the 81 | temperature at a specific height. So the text file only contains those two columns of the data (and the height in km of course as that is the index of the row, the property that uniquely 82 | identifies a row). 83 | 84 | With that in mind, let's get coding and importing the data. We do this in \autoref{alg:usatmosp}. As one can see we do not specify how to open the file or how to split the read line, as this 85 | is language specific and not interesting to describe in detail. I refer you to the internet to search for how to open a text file in the language you are working in. Keep in mind in which 86 | magnitude you are working and in which magnitude the data is. If you work with \si{km} for height and the data is in \si{m}, you need to account for that somewhere by either transforming the imported 87 | data or work in the other magnitude. 88 | 89 | \begin{algorithm} 90 | \caption{Loading in the U.S. Standard Atmosphere} 91 | \label{alg:usatmosp} 92 | $data \leftarrow \text{open text file containing the us standard atmosphere data}$ \; 93 | \ForEach{$line \in data$}{ 94 | Split $line$ into three components, $sh, st$ and $sd$, representing the height, temperature and density respectively \; 95 | $standardHeight.add(sh)$ \; 96 | $standardTemperature.add(st)$ \; 97 | } 98 | 99 | $temperatureProfile \leftarrow \texttt{interpolate}(p_z, standardTemperature)$ \; 100 | 101 | \For{$alt \in [0, nlevels]$}{ 102 | $\rho[:, :, alt] \leftarrow densityProfile[alt]$ \; 103 | $T_i[:, :, alt] \leftarrow temperatureProfile[alt]$ \; 104 | } 105 | \end{algorithm} 106 | 107 | Note that the function \texttt{interpolate} takes three arguments, the first one being the data points that we want to have values for, the second one is the data points that we know and the 108 | third one is the values for the data points that we know. This function may or may not exist in your programming language of choice, which might mean that you have to write it yourself. 109 | The formula that we use for interpolation can be found in \autoref{eq:interpolation}, though you still need to figure out what value you need for $\lambda$ (see \autoref{sec:interpolation}). 110 | This is left as an exercise for the reader. 111 | 112 | \subsection{Clamping the Velocities} 113 | Due to the boundaries in the advection calculations (see \autoref{sec:adv}) we get weird instabilities as the velocity calculations are executed on more cells. Which means that air is trying to 114 | displace temperature (advect it) by flowing faster to those cells, but actually don't carry any temperature because we turned it off for those cells. This is something that we need to fix to get 115 | rid of weirdness around the edges. This is done in \autoref{alg:velocity clamped}. Here the $bla:$ means from $bla$ to the last valid index, if the $:$ is in front of $bla$ then it means from 116 | the first valid index to $bla$. 117 | 118 | \begin{algorithm} 119 | \caption{Clamping the Velocities} 120 | \label{alg:velocity clamped} 121 | \autoref{alg:velocity} 122 | $u[(adv\_boun, -adv\_boun - 1), :, :] \leftarrow 0.5u[(adv\_boun, -adv\_boun - 1), :, :]$ \; 123 | $v[(adv\_boun, -adv\_boun - 1), :, :] \leftarrow 0.5v[(adv\_boun, -adv\_boun - 1), :, :]$ \; 124 | $w[(adv\_boun, -adv\_boun - 1), :, :] \leftarrow 0.5w[(adv\_boun, -adv\_boun - 1), :, :]$ \; 125 | 126 | $u[:adv\_boun, :, :] \leftarrow 0 $\; 127 | $v[:adv\_boun, :, :] \leftarrow 0 $\; 128 | $w[:adv\_boun, :, :] \leftarrow 0 $\; 129 | 130 | $u[-adv\_boun:, :, :] \leftarrow 0$ \; 131 | $v[-adv\_boun:, :, :] \leftarrow 0$ \; 132 | $w[-adv\_boun:, :, :] \leftarrow 0$ \; 133 | \end{algorithm} 134 | 135 | \subsection{Smoothing all the things} 136 | On a planet wide scale, you have a lot of variety in the data. To counteract that we filter out the high frequency data. Which means that we filter out the data that occurs sporadically. So we 137 | do not consider the data that occurs so infrequently that it means nothing. We do this for the radiation (temperature) and the velocity which is shown in \autoref{alg:smootht} and 138 | \autoref{alg:smoothv} respectively. It is worth mentioning that \autoref{alg:smootht} is executed after we do the calculations for $T_{pot}$ (shown in \autoref{alg:optical depth}). 139 | \autoref{alg:smoothv} is done after \autoref{alg:velocity} but before \autoref{alg:velocity clamped}. 140 | 141 | 142 | \begin{algorithm} 143 | \caption{Smoothing the atmospheric temperature} 144 | \label{alg:smootht} 145 | $T_{pot} \leftarrow \texttt{Smooth}(T_{pot}, smooth_t)$ \; 146 | \end{algorithm} 147 | 148 | \begin{algorithm} 149 | \caption{Smoothing the velocity} 150 | \label{alg:smoothv} 151 | $u \leftarrow \texttt{Smooth}(u, smooth_u)$ \; 152 | $v \leftarrow \texttt{Smooth}(v, smooth_v)$ \; 153 | $w \leftarrow \texttt{Smooth}(w, smooth_w, smooth_{vert})$ \; 154 | \end{algorithm} -------------------------------------------------------------------------------- /tex-docs/topics/control_panel.tex: -------------------------------------------------------------------------------- 1 | \section{Control Panel} \label{sec:cp} 2 | Before we dive in an start modelling the planet, let us first set up a control panel that will influence how the model will behave and effectively decides what type of planet we model. 3 | 4 | \subsection{The Beginning} 5 | In the beginning there was nothing, and then there was "Hello World!" Or at least that is how many projects start. Why? you might ask, which is a perfectly valid question. In Computer Science, 6 | "Hello World!" is very simple code that we use to test whether all the tools we need to get coding works. This checks whether the computer compiles the code and is able to execute it and whether 7 | the code editor (IDE, Integrated Development Environment) starts the right processes to get the code compiled and executed. Oh right we were talking about CLAuDE, ahem. 8 | 9 | Every project must have its beginning. And with CLAuDE I made the decision to start explaining the Control Panel first. This is to get you familiar with notation and to lay down some basics. To 10 | do that we start with the fixed part of the Control Panel, the physical constants. Many things vary from planet to planet, how much radiation they receive from their star, how strong their 11 | gravity is, how fast they spin around their axis and many many more. What does not change are the physical constants, well because they are constant. The Stefan-Boltzmann constant for instance 12 | does not change. Whether you are on Earth, in space or on Jupiter, the value of the Stefan-Boltzmann constant will remain the same. 13 | 14 | The Stefan-Boltzmann constant is denoted by $\sigma$ and has a value of $5.670373 \cdot 10^-8$ (\si{Wm^{-2}K^{-4}}) \cite{stefan-boltzmann}. The $\sigma$ is a greek letter called sigma. Greek 15 | letters are often used in mathematics, as well as in physics or any other discipline that relies on maths (spoiler alert, quite a lot). Treat it like a normal letter in maths, representing a 16 | number that you either do not know yet or is too long or cumbersome to write down every time. The Stefan-Boltzmann constant is denoted in scientific notation, a number followed by the order of 17 | magnitude. It is denoted as a multiplication, because that is what you have to do to get the real number. An example: $4.3 \cdot 10^2 = 430$ and $4.3 \cdot 10^{-2} = 0.043$. The 18 | letters behind the numbers are units, how we give meaning to the numbers. If I say that I am $1.67$ does not mean anything. Do I mean inches, centimeters, meters, miles? That is why we need units 19 | as they give meaning to the number. they tell us whether the number is talking about speed, distance, time, energy and many other things. In this manual we will use SI units. Behind all the 20 | letters you will find the following: [number]. This is a citation, a reference to an external source where you can check whether I can still read. If I pull a value out somewhere I will insert a 21 | citation to show that I am not making these numbers up. This is what scientists use to back up their claims if they do not want to redo the work that others have done. I mean what is the point 22 | of re-inventing the wheel if there is a tyre company next door? That is why scientists citate. 23 | 24 | So with that out of the way, let us write down some constants. Why do I do this here? Because a lot of constants are used everywhere and I am too lazy to replicate them every time. If you see a 25 | letter or symbol that is not explicitly explained, then it is most likely a constant that we discuss here in the control panel. 26 | 27 | \subsection{Physical Constants} 28 | As mentioned before, physical constants do not change based on where you are in the universe. Below you will find an overview of all the relevant constants together with their units. And a short 29 | explanation where they are used or what they represent. To see them in action, consult the other sections of this manual, you will find them in equations all throughout this document. 30 | 31 | \subsubsection{The Gas Constant} \label{sec:gas constant} 32 | The Gas constant, $R = 8.3144621$ (\si{JK^{-1}mol^{-1}}) \cite{idealGas} is the constant used to relate the temperature of the gas to the pressure and the volume. One would expect this constant to be 33 | different per gas, but under high enough temperatures and low enough pressure the gas constant is the same for all gases. 34 | 35 | \subsubsection{The Specific Heat Capacity} 36 | The specific heat capacity $c$ depicts how much energy is required to heat the object by one degree Kelvin per unit mass (\si{Jkg^{-1}K^{-1}}) \cite{specificHeat}. This varies per material 37 | and is usually indicated by a subscript. The specific heat capacity for water for instance is $c_w = 4190$ \si{Jkg^{-1}K^{-1}}. Specific heat capacities also exist in the form of 38 | \si{Jkg^{-1}K^{-1}}, \si{Jmol^{-1}K^{-1}} and \si{Jcm^{-3}K^{-1}} which you can use in various circumstances, depending on what information you have. 39 | 40 | \subsubsection{Mole} 41 | Mole is the amount of particles ($6.02214076 \cdot 10^{23}$) in a substance, where the average weight of one mole of particles in grams is about the same as the weight of one particle in atomic 42 | mass units (\si{u})\cite{mole}. This is not a physical constant perse, but more like a unit (\si{mol}). Though it is still important enough to be added here for future reference. All other units are 43 | way more intuitive and are assumed to be known. 44 | 45 | \subsubsection{The Stefan-Boltzmann Constant} 46 | The Stefan-Boltzmann constant, $\sigma = 5.670373 \cdot 10^-8$ \si{Wm^{-2}K^{-4})} \cite{stefan-boltzmann} is used in the Stefan-Boltzmann law (more on that in \autoref{sec:first thermolaw}). 47 | 48 | \subsection{Planet Specific Variables} 49 | The following set of variables vary per planet, that's why we call them variables since they vary. Makes sense right? We add them here as we will use them throughout the manual. The advantage 50 | of that is quite significant. If you want to test things for a different planet, you only need to change the values in one place, instead of all places where you use it. If there is one thing 51 | that we computer scientists hate is doing work, we like being lazy and defining things in one place means that we can be lazy if we need to change it. So we put in the extra work now, so we do 52 | not have to do the extra work in the future. That's actually a quite accurate description of computer scientists, doing hard work so that they can be lazy in the future. 53 | 54 | \subsubsection{The Passage of Time} 55 | On Earth we have various indications of how much time has passed. While most of them remain the same throughout the universe, like seconds, minutes and hours, others vary throughout the universe, 56 | like days, months and years. Here we specify how long the variable quantities of time are for the planet we want to consider as they are used in the code. This can be seen in 57 | \autoref{alg:time constants}. Here a $\leftarrow$ indicates that we assign a value to the variable name before it, so that we can use the variable name in the code instead of the value, which 58 | has the advantage I indicated before. // means that we start a comment, which is text that the code ignores and does not tell the cpu about. Not that the cpu would understand it, but that just 59 | means less work for the computer. Yes computers are lazy too. 60 | 61 | \begin{algorithm*} 62 | \caption{Definition of how much time it takes for a day and a year on a planet and how much time on the planet passes before we start another calculation run} 63 | \label{alg:time constants} 64 | \SetKwComment{Comment}{//}{} 65 | $day \leftarrow 60*60*24$ \Comment*[l]{Length of one day in seconds (\si{s})} 66 | $year \leftarrow 365*day$ \Comment*[l]{Length of one year in seconds (\si{s})} 67 | $\delta t \leftarrow 60 * 9$ \Comment*[l]{How much time is between each calculation run in seconds (\si{s})} 68 | \end{algorithm*} 69 | 70 | \subsubsection{The Planet Passport} 71 | Each planet is different, so why should they all have the same gravity? Oh wait, they don't. Just as they are not all the same size, tilted as much and their atmospheres differ. So here we define 72 | all the relevant variables that are unique to a planet, or well not necessarily unique but you get the idea. This can all be found in \autoref{alg:planet constants}. 73 | 74 | \begin{algorithm} 75 | \caption{Defining the constants that are specific to a planet} 76 | \label{alg:planet constants} 77 | \SetKwComment{Comment}{//}{} 78 | $g \leftarrow 9.81$ \Comment*[l]{Magnitude of gravity on the planet in \si{ms^{-2}}} 79 | $\alpha \leftarrow -23.5$ \Comment*[l]{By how many degrees the planet is tilted with respect to the star's plane} 80 | $top \leftarrow 50*10^3$ \Comment*[l]{How high the top of the atmosphere is with respect to the planet surface in meters (\si{m})} 81 | $ins \leftarrow 1370$ \Comment*[l]{Amount of energy from the star that reaches the planet per unit area (\si{Jm^{-2}})} 82 | $\epsilon \leftarrow 0.75$ \Comment*[l]{Absorbtivity of the atmosphere, fraction of how much of the total energy is absorbed (unitless)} 83 | $r \leftarrow 6.4*10^6$ \Comment*[l]{The radius of the planet in meters (\si{m})} 84 | $p_z \leftarrow [100000, 95000, 90000, 80000, 70000, 60000, 50000, 55000, 40000, 5000, 30000, 25000, 20000, 15000, 10000, 7500, 5000, 2500, 1000, 500, 200, 100]$ \Comment*[l]{The pressure 85 | of the different levels (\si{Pa}) noted in an array} 86 | \end{algorithm} 87 | 88 | \subsubsection{Model Specific Parameters} 89 | These parameters cannot be found out in the wild, they only exist within our model. They control things like the size of a cell on the latitude longitude grid (more on that in later sections), 90 | how much time the model gets to spin up. We need the model to spin up in order to avoid numerical instability. Numerical instability occurs when you first run the model. This is due to the nature of the equations. Nearly all equations are continuous, which means that they are always at work. However 91 | when you start the model, the equations were not at work yet. It is as if you suddenly give a random meteor an atmosphere, place it in orbit around a star and don't touch it for a bit. You will 92 | see that the whole system oscilates wildly as it adjusts to the sudden changes and eventually it will stabilise. We define the amount of time it needs to stabilise as the spin up time. All 93 | definitions can be found in \autoref{alg:model constants}. What the $adv$ boolean does is enabling or disabling advection, a process described in
which does not work yet. 94 | 95 | \begin{algorithm} 96 | \caption{Defining the paramters that only apply to the model} 97 | \label{alg:model constants} 98 | \SetKwComment{Comment}{//}{} 99 | $resolution \leftarrow 3$ \Comment*[l]{The amount of degrees on the latitude longitude grid that each cell has, with this setting each cell is 3 degrees latitude high and 3 degrees 100 | longitude wide} 101 | $nlevels \leftarrow 10$ \Comment*[l]{The amount of layers in the atmosphere} 102 | $\delta t_s \leftarrow 60*137$ \Comment*[l]{The time between calculation rounds during the spin up period in seconds (\si{s})} 103 | $t_s \leftarrow 5*day$ \Comment*[l]{How long we let the planet spin up in seconds (\si{s})} 104 | $adv \leftarrow \texttt{FALSE}$ \Comment*[l]{Whether we want to enable advection or not} 105 | $adv\_boun \leftarrow 8$ \Comment*[l]{How many cells away from the poles where we want to stop calculating the effects of advection} 106 | $C_a \leftarrow 287$ \Comment*[l]{Heat capacity of the atmosphere in \si{Jkg^{-1}K^{-1}}} 107 | $C_p \leftarrow 1 \cdot 10^6$ \Comment*[l]{Heat capacity of the planet in \si{Jkg^{-1}K^{-1}}} 108 | $\delta y \leftarrow \frac{2\pi r}{nlat}$ \Comment*[l]{How far apart the gridpoints in the y direction are (degrees latitude)} 109 | $\alpha_a \leftarrow 2 \cdot 10^{-5}$ \Comment*[l]{The diffusivity constant for the atmosphere} 110 | $\alpha_p \leftarrow 1.5 \cdot 10^{-6}$ \Comment*[l]{The diffusivity constant for the planet surface} 111 | $smooth_t \leftarrow 0.9$ \Comment*[l]{the smoothing parameter for the temperature} 112 | $smooth_u \leftarrow 0.8$ \Comment*[l]{The smoothing parameter for the $u$ component of the velocity} 113 | $smooth_v \leftarrow 0.8$ \Comment*[l]{The smoothing parameter for the $v$ component of the velocity} 114 | $smooth_w \leftarrow 0.3$ \Comment*[l]{The smoothing parameter for the $w$ component of the velocity} 115 | $smooth_{vert} \leftarrow 0.3$ \Comment*[l]{The smoothing parameter for the vertical part of the velocities} 116 | $count \leftarrow 0$ \; 117 | \For{$j \in [0, top]$}{ 118 | $heights[j] \leftarrow count$ \Comment*[l]{The height of a layer (\si{m})} 119 | $count \leftarrow count + \frac{top}{nlevels}$ \; 120 | } 121 | 122 | \For{$i \in [0, nlat]$}{ 123 | $\delta x[i] \leftarrow \delta y\cos(lat[i]\frac{\pi}{180})$ \Comment*[l]{How far apart the gridpoints in the x direction are (degrees longitude)} 124 | } 125 | 126 | \For{$k \in [0, nlevels - 1]$}{ 127 | $\delta z[k] \leftarrow p_z[k + 1] - p_z[k]$ \Comment*[l]{How far apart the gridpoints in the z direction are (\si{Pa})} 128 | } 129 | 130 | $\Pi \leftarrow$ Array with the same amount of elements as $p_z$ \Comment*[l]{Dimensionless pressure that is used to easily convert from potential temperature to absolute temperature and 131 | vice versa} 132 | $\kappa \leftarrow \frac{R}{C_p}$ \; 133 | \For{$i \leftarrow 0$ \KwTo $\Pi.length$}{ 134 | $\Pi[i] \leftarrow C_p(\frac{p_z[i]}{p_z[0]})^{\kappa}$ \; 135 | } 136 | $poleLowerLatLimit \leftarrow 4$ \Comment*[l]{Polar Plane Approximation Lower Grid Limit} 137 | $poleHigherLatLimit \leftarrow 4$ \Comment*[l]{Polar Plane Approximation Upper Grid Limit} 138 | \end{algorithm} -------------------------------------------------------------------------------- /tex-docs/appendices/history.tex: -------------------------------------------------------------------------------- 1 | \section{History of the Algorithms} \label{sec:history} 2 | Back when I was a young naive programmer, I made a thing. Now a few years down the line I made the thing again, but infinitely better. So I have no use for the old thing anymore. But fear not, 3 | old algorithms (used by CLAuDE) will be collected here. This is just for historical purposes. 4 | 5 | \subsection{Radiation} 6 | \subsubsection{Adding Layers} 7 | Remember \autoref{eq:atmos change}? We need this equation for every layer in the atmosphere. This also means that we have to adjust the main calculation of the code, which is described in 8 | \autoref{alg:temperature with density}. The $T_a$ needs to change, we need to either add a dimension (to indicate which layer of the atmosphere we are talking about) or we need to add different 9 | matrices for each atmosphere layer. We opt for adding a dimension as that costs less memory than defining new arrays 10 | \footnote{This has to do with pointers, creating a new object always costs a bit more space than adding a dimension as we need a pointer to the object and what type of object it is whereas with 11 | adding a dimension we do not need this additional information as it has already been defined}. So $T_a$, and all other matrices that have to do with the atmosphere (so not $T_p$ for instance) 12 | are no longer indexed by $lat, lon$ but are indexed by $lat, lon, layer$. We need to account for one more thing, the absorbtion of energy from another layer. The new equation is shown in 13 | \autoref{eq:atmos change layer}. Here $k$ is the layer of the atmosphere, $k = -1$ means that you use $T_p$ and $k = nlevels$ means that $T_{a_{nlevels}} = 0$ as that is space. Also, let us 14 | rewrite the equation a bit such that the variables that are repeated are only written once and stuff that is divided out is removed, which is done in \autoref{eq:atmos change layer improved}. 15 | Let us also clean up the equation for the change in the surface temperature (\autoref{eq:surface change}) in \autoref{eq:surface change improved}. 16 | 17 | \begin{subequations} 18 | \begin{equation} 19 | \label{eq:atmos change layer} 20 | \Delta T_{a_k} = \frac{\delta t (\sigma \epsilon_{k - 1}T_{a_{k - 1}}^4 + \sigma \epsilon_{k + 1}T_{a_{k + 1}}^4 - 2\epsilon_k\sigma T_{a_k}^4)}{C_a} 21 | \end{equation} 22 | \begin{equation} 23 | \label{eq:atmos change layer improved} 24 | \Delta T_{a_k} = \frac{\delta t \sigma (\epsilon_{k - 1}T_{a_{k - 1}}^4 + \epsilon_{k + 1}T_{a_{k + 1}}^4 - 2\epsilon_kT_{a_k}^4)}{C_a} 25 | \end{equation} 26 | \begin{equation} 27 | \label{eq:surface change improved} 28 | \Delta T_p = \frac{\delta t (S + \sigma(4\epsilon_pT_a^4 - 4T_p^4))}{4C_p} 29 | \end{equation} 30 | \end{subequations} 31 | 32 | With the changes made to the equation, we need to make those changes in the code as well. We need to add the new dimension to all matrices except $T_p$ and $a$ as they are unaffected (with 33 | regards to the storage of the values) by the addition of multiple atmospheric layers. Every other matrix is affected. The new code can be found in \autoref{alg:temperature layer}. $\delta z$ 34 | 35 | \begin{algorithm}[hbt] 36 | \caption{The main function for the temperature calculations} 37 | \label{alg:temperature layer} 38 | \SetAlgoLined 39 | \SetKwInput{Input}{Input} 40 | \SetKwInOut{Output}{Output} 41 | \Input{amount of energy that hits the planet $S$} 42 | \Output{Temperature of the planet $T_p$, temperature of the atmosphere $T_a$} 43 | \For{$lat \leftarrow -nlat$ \KwTo $nlat$}{ 44 | \For{$lon \leftarrow 0$ \KwTo $nlot$}{ 45 | \For{$layer \leftarrow 0$ \KwTo $nlevels$}{ 46 | $T_p[lat, lon] \leftarrow T_p[lat, lon] + \frac{\delta t ((1 - a[lat, lon])S + \sigma(4\epsilon[0](T_a[lat, lon, 0])^4 - 4(T_p[lat, lon])^4))} 47 | {4C_p[lat, lon]}$ \; 48 | \uIf{$layer = 0$}{ 49 | $T_a[lat, lon, layer] \leftarrow T_a[lat, lon, layer] + \frac{\delta t \sigma((T_p[lat, lon])^4 - 2\epsilon[layer](T_a[lat, lon, layer])^4)} 50 | {\rho[lat, lon, layer]C_a\delta z[layer]}$ \; 51 | }\uElseIf{$layer = nlevels - 1$}{ 52 | $T_a[lat, lon, layer] \leftarrow T_a[lat, lon, layer] + \frac{\delta t \sigma(\epsilon[layer - 1](T_a[lat, lon, layer - 1])^4 - 2\epsilon[layer](T_a[lat, lon, layer])^4)} 53 | {\rho[lat, lon, layer]C_a\delta z[layer]}$ \; 54 | }\uElse{ 55 | $T_a[lat, lon, layer] \leftarrow T_a[lat, lon, layer] + \frac{\delta t \sigma(\epsilon[layer - 1](T_a[lat, lon, layer - 1])^4 + \epsilon[layer + 1]T_a[lat, lon, layer + 1] 56 | - 2\epsilon[layer](T_a[lat, lon, layer])^4)}{\rho[lat, lon, layer]C_a\delta z[layer]}$ \; 57 | } 58 | } 59 | } 60 | } 61 | \end{algorithm} 62 | 63 | We also need to initialise the $\epsilon$ value for each layer. We do that in \autoref{alg:epsilon}. 64 | 65 | \begin{algorithm} 66 | \caption{Intialisation of the insulation of each layer (also known as $\epsilon$)} 67 | \label{alg:epsilon} 68 | $\epsilon[0] \leftarrow 0.75$ \; 69 | \For{$i \leftarrow 1$ \KwTo $nlevels$}{ 70 | $\epsilon[i] \leftarrow 0.5\epsilon[i - 1]$ 71 | } 72 | \end{algorithm} 73 | 74 | \subsection{Velocity} 75 | \subsubsection{The Primitive Equations and Geostrophy} \label{sec:primitive} 76 | The primitive equations (also known as the momentum equations) is what makes the air move. It is actually kind of an injoke between physicists as they are called the primitive equations but 77 | actually look quite complicated (and it says $fu$ at the end! \cite{simon}). The primitive equations are a set of equations dictating the direction in the $u$ and $v$ directions as shown in 78 | \autoref{eq:primitive u} and \autoref{eq:primitive v}. We can make the equations simpler by using and approximation called geostrophy which means that we have no vertical motion, such that the 79 | terms with $\omega$ in \autoref{eq:primitive u} and \autoref{eq:primitive v} become 0. We also assume that we are in a steady state, i.e. there is no acceleration which in turn means that the 80 | whole middle part of the equations are $0$. Hence we are left with \autoref{eq:primitive u final} and \autoref{eq:primitive v final}. 81 | 82 | \begin{subequations} 83 | \begin{equation} 84 | \label{eq:primitive u} 85 | \frac{du}{dt} = \frac{\delta u}{\delta t} + u\frac{\delta u}{ \delta x} + v\frac{\delta u}{\delta v} + \omega\frac{\delta u}{\delta p} = -\frac{\delta \Phi}{\delta x} + fv 86 | \end{equation} 87 | \begin{equation} 88 | \label{eq:primitive v} 89 | \frac{dv}{dt} = \frac{\delta v}{\delta t} + u\frac{\delta v}{ \delta x} + v\frac{\delta v}{\delta v} + \omega\frac{\delta v}{\delta p} = -\frac{\delta \Phi}{\delta y} - fu 90 | \end{equation} 91 | \begin{equation} 92 | \label{eq:primitive u final} 93 | 0 = -\frac{\delta \Phi}{\delta x} + fv 94 | \end{equation} 95 | \begin{equation} 96 | \label{eq:primitive v final} 97 | 0 = -\frac{\delta \Phi}{\delta y} - fu 98 | \end{equation} 99 | \end{subequations} 100 | 101 | \autoref{eq:primitive u final} can be split up into to parts, the $\frac{\delta \Phi}{\delta x}$ part (the gradient force) and the $fv$ part (the coriolis force). The same applies to 102 | \autoref{eq:primitive v final}. Effectively we have a balance between the gradient and the coriolis force as shown in \autoref{eq:pu simple} and \autoref{eq:pv simple}. The symbols in both of 103 | these equations are: 104 | 105 | \begin{itemize} 106 | \item $\Phi$: The geopotential, potential (more explanation in \autoref{sec:potential}) of the planet's gravity field ($Jkg^{-1}$). 107 | \item $x$: The change in the East direction along the planet surface ($m$). 108 | \item $y$: The change in the North direction along the planet surface ($m$). 109 | \item $f$: The coriolis parameter as described by \autoref{eq:coriolis}, where $\Omega$ is the rotation rate of the planet (for Earth $7.2921 \cdot 10^{-5}$) ($rad \ s^{-1}$) and $\theta$ is the 110 | latitude \cite{coriolis}. 111 | \item $u$: The velocity in the latitude ($ms^{-1}$). 112 | \item $v$: The velocity in the longitude ($ms^{-1}$). 113 | \end{itemize} 114 | 115 | \begin{subequations} 116 | \begin{equation} 117 | \label{eq:coriolis} 118 | f = 2\Omega\sin(\theta) 119 | \end{equation} 120 | \begin{equation} 121 | \label{eq:pu simple} 122 | \frac{\delta \Phi}{\delta x} = fv 123 | \end{equation} 124 | \begin{equation} 125 | \label{eq:pv simple} 126 | \frac{\delta \Phi}{\delta y} = -fu 127 | \end{equation} 128 | \begin{equation} 129 | \label{eq:pu simple final} 130 | \frac{\delta p}{\rho \delta x} = fv 131 | \end{equation} 132 | \begin{equation} 133 | \label{eq:pv simple final} 134 | \frac{\delta p}{\rho \delta y} = -fu 135 | \end{equation} 136 | \end{subequations} 137 | 138 | Since we want to know how the atmosphere moves, we want to get the v and u components of the velocity vector (since $v$ and $u$ are the veolicites in longitude and latitude, if we combine them 139 | in a vector we get the direction of the overall velocity). So it is time to start coding and calculating! If we look back at \autoref{alg:stream1v2}, we can see that we already have a double 140 | for loop. In computer science, having multiple loops is generally considered a bad coding practice as you usually can just reuse the indices of the already existing loop, so you do not need to 141 | create a new one. However this is a special case, since we are calculating new temperatures in the double for loop. If we then also would start to calculate the velocities then we would use new 142 | information and old information at the same time. Since at index $i - 1$ the new temperature has already been calculated, but at the index $i + 1$ the old one is still there. So in order to fix 143 | that we need a second double for loop to ensure that we always use the new temperatures. We display this specific loop in \autoref{alg:stream2}. Do note that everything in \autoref{alg:stream1v2} 144 | is still defined and can still be used, but since we want to focus on the new code, we leave out the old code to keep it concise and to prevent clutter. 145 | 146 | \begin{algorithm}[hbt] 147 | \caption{The main loop of the velocity of the atmosphere calculations} 148 | \label{alg:stream2} 149 | \SetAlgoLined 150 | \For{$lat \in [-nlat, nlat]$}{ 151 | \For{$lon \in [0, nlon]$}{ 152 | $u[lat, lon] \leftarrow -\frac{p[lat + 1, lon] - p[lat - 1, lon]}{\delta y} \cdot \frac{1}{f[lat]\rho}$ \; 153 | $v[lat, lon] \leftarrow \frac{p[lat, lon + 1] - p[lat, lon - 1]}{\delta x[lat]} \cdot \frac{1}{f[lat]\rho}$ \; 154 | } 155 | } 156 | \end{algorithm} 157 | 158 | The gradient calculation is done in \autoref{alg:gradient}. For this to work, we need the circumference of the planet. Herefore we need to assume that the planet is a sphere. While that is not 159 | technically true, it makes little difference in practice and is good enough for our model. The equation for the circumference can be found in \autoref{eq:circumference} \cite{circumference}, 160 | where $r$ is the radius of the planet. Here we also use the f-plane approximation, where the coriolis paramter has one value for the northern hemisphere and one value for the southern hemisphere 161 | \cite{fplane}. 162 | 163 | \begin{equation} 164 | \label{eq:circumference} 165 | 2 \pi r 166 | \end{equation} 167 | 168 | \begin{algorithm} 169 | \caption{Calculating the gradient $\delta x$ (note that this algorithm is obsolete)} 170 | \label{alg:gradient} 171 | \SetAlgoLined 172 | $C \leftarrow 2\pi R$ \; 173 | $\delta y \leftarrow \frac{C}{nlat}$ \; 174 | 175 | \For{$lat \in [-nlat, nlat]$}{ 176 | $\delta x[lat] \leftarrow \delta y \cos(lat \cdot \frac{\pi}{180})$ \; 177 | 178 | \eIf{$lat < 0$}{ 179 | $f[lat] \leftarrow -10^{-4}$ \; 180 | }{ 181 | $f[lat] \leftarrow 10^{-4}$ \; 182 | } 183 | } 184 | \end{algorithm} 185 | 186 | Because of the geometry of the planet and the construction of the longitude latitude grid, we run into some problems when calculating the gradient. Since the planet is not flat ("controversial 187 | I know"\cite{simon}) whenever we reach the end of the longitude we need to loop around to get to the right spot to calculate the gradients (as the planet does not stop at the end of the 188 | longitude line but loops around). So to fix that we use the modulus (mod) function which does the looping for us if we exceed the grid's boundaries. We do haveanother problem though, the poles. 189 | As the latitude grows closer to the poles, they are converging on the center point of the pole. Looping around there is much more difficult so to fix it, we just do not consider that center 190 | point in the main loop. The changed algorithm can be found in \autoref{alg:stream2v2} 191 | 192 | \begin{algorithm}[hbt] 193 | \caption{The main loop of the velocity of the atmosphere calculations} 194 | \label{alg:stream2v2} 195 | \SetAlgoLined 196 | \For{$lat \in [-nlat + 1, nlat - 1]$}{ 197 | \For{$lon \in [0, nlon]$}{ 198 | $u[lat, lon] \leftarrow -\frac{p[(lat + 1) \text{ mod } nlat, lon] - p[(lat -1) \text{ mod } nlat, lon]}{\delta y} \cdot \frac{1}{f[lat]\rho}$ \; 199 | $v[lat, lon] \leftarrow \frac{p[lat, (lon + 1) \text{ mod } nlon] - p[lat, (lon -1) \text{ mod } nlon]}{\delta x[lat]} \cdot \frac{1}{f[lat]\rho}$ \; 200 | } 201 | } 202 | \end{algorithm} 203 | 204 | Do note that the pressure calculation is done between the temperature calculation in \autoref{alg:stream1v2} and the $u, v$ calculations in \autoref{alg:stream2v2}. At this point our model shows 205 | a symmetric vortex around the sun that moves with the sun. This is not very realistic as you usually have convection and air flowing from warm to cold, but we do not have that complexity yet 206 | (due to our single layer atmosphere). -------------------------------------------------------------------------------- /tex-docs/topics/advection.tex: -------------------------------------------------------------------------------- 1 | \section{Advection} \label{sec:adv} 2 | Advection is a fluid flow transporting something with it as it flows. This can be temperature, gas, solids or other fluids. In our case we will be looking at temperature. 3 | 4 | \subsection{Thermal Diffusion} 5 | As of this time, what you notice if you run the model is that the winds only get stronger and stronger (and the model is hence blowing up, which means that the numbers increase so dramatically 6 | that it is no longer realistic). This is because there is no link yet between the velocities of the atmosphere and the temperature. Currently, any air movement does not affect the temperature 7 | in the atmosphere of our model while it does in reality. So we need to change some calculations to account for that. Thermal diffusion helps with spreading out the temperatures and tempering 8 | the winds a bit. 9 | 10 | The diffusion equation, as written in \autoref{eq:diffusion}, describes how the temperature spreads out over time\cite{diffusion}. The symbols in the equation represent: 11 | 12 | \begin{itemize} 13 | \item $u$: A vector consisting out of 4 elements: $x, y, z, t$. $x, y, z$ are the local coordinates and $t$ is time. 14 | \item $\alpha$: The thermal diffusivity constant. 15 | \item $\nabla^2$: The Laplace operator, more information in \autoref{sec:laplace}. 16 | \item $\bar{u}$: The time derivative of $u$, or in symbols $\frac{\delta u}{\delta t}$. 17 | \end{itemize} 18 | 19 | \begin{equation} 20 | \label{eq:diffusion} 21 | \bar{u} = \alpha \nabla^2 u 22 | \end{equation} 23 | 24 | Now to get this into code we need the following algorithms \autoref{alg:laplacian} and \autoref{alg:diffusion}. \autoref{alg:laplacian} implements the laplacian operator, whereas 25 | \autoref{alg:diffusion} implements the diffusion calculations. $\nabla^2$ in \autoref{alg:diffusion} represents the call to \autoref{alg:laplacian}. 26 | 27 | \begin{algorithm} 28 | \caption{The main calculations for calculating the effects of diffusion} 29 | \label{alg:diffusion} 30 | $T_a \leftarrow T_a + \delta t \alpha_a \nabla^2(T_a)$ \; 31 | $T_p \leftarrow T_p + \delta t \alpha_p \nabla^2(T_p)$ \; 32 | \end{algorithm} 33 | 34 | \subsection{Adding in Advection} 35 | With thermal diffusion in place, the temperature will spread out a bit, however air is not transported yet. This means that the winds we simulate are not actually moving any air. Advection is 36 | going to change that. The advection equation is shown in \autoref{eq:advection}. The symbols are: 37 | 38 | \begin{itemize} 39 | \item $\psi$: What is carried along (in our case temperature, \si{K}). 40 | \item $t$: The time (\si{s}). 41 | \item $u$: The fluid velocity vector (\si{ms^{-1}}). 42 | \item $\nabla$: The divergence operator (as explained in \autoref{sec:laplace}). 43 | \end{itemize} 44 | 45 | \begin{equation} 46 | \label{eq:advection} 47 | \frac{\delta \psi}{\delta t} + \nabla \cdot (\psi u) = 0 48 | \end{equation} 49 | 50 | With the divergence functon defined in \autoref{alg:divergence}, we now need to adjust \autoref{alg:diffusion} to incorporate this effect. The resulting algorithm can be found in 51 | \autoref{alg:advection}. Here $\nabla$ represents the function call to \autoref{alg:divergence}. 52 | 53 | \begin{algorithm} 54 | \caption{The main calculations for calculating the effects of advection} 55 | \label{alg:advection} 56 | $T_{add} \leftarrow T_a + \delta t \alpha_a \nabla^2(T_a) + \nabla(T_a)$ \; 57 | $T_a \leftarrow T_a + T_{add}[5:-5, :] \text{ //Only add } T_{add} \text{ to } T_a \text{ for indices in the interval } [-nlat + 5, nlat - 5]$. \; 58 | $T_p \leftarrow T_p + \delta t \alpha_p \nabla^2(T_p)$ \; 59 | \end{algorithm} 60 | 61 | Now that we have the air moving, we also need to account for the moving of the density. This is because moving air to a certain place will change the air density at that place if the air at that 62 | place does not move away at the same rate. Say we are moving air to $x$ at $y \ ms^{-1}$. If air at $x$ moves at a rate $z \ ms^{-1}$ and $z \neq y$ then the air density at $x$ will change. 63 | The equation we will need for that is the mass continuity equation as shown in \autoref{eq:mass continuity} \cite{masscontinue}. 64 | 65 | \begin{equation} 66 | \label{eq:mass continuity} 67 | \frac{\delta \rho}{\delta t} + \nabla \cdot (\rho v) = 0 68 | \end{equation} 69 | 70 | Using this equation means that we will no longer assume that the atmosphere is incompressible. Therefore we need to change a few things in the code. First we need to change the $\rho$ in 71 | \autoref{alg:stream3}. Since $\rho$ is no longer constant we need to access the right value of $\rho$ by specifying the indices. So $\rho$ will change to $\rho[lat, lon]$. Furthermore we need 72 | to calculate $\rho$ after the movement of air has taken place, so we need to change \autoref{alg:advection} as well to include the calculations for $\rho$. The new version can be found in 73 | \autoref{alg:advectionv2}. Again the $\nabla$ represents the call to \autoref{alg:divergence}. 74 | 75 | 76 | \begin{algorithm} 77 | \caption{The main calculations for calculating the effects of advection} 78 | \label{alg:advectionv2} 79 | $T_{add} \leftarrow T_a + \delta t \alpha_a \nabla^2(T_a) + \nabla(T_a)$ \; 80 | $T_a \leftarrow T_a + T_{add}[5:-5, :] \text{ //Only add } T_{add} \text{ to } T_a \text{ for indices in the interval } [-nlat + 5, nlat - 5]$. \; 81 | $\rho \leftarrow \rho + \delta t \nabla \rho$ \; 82 | $T_p \leftarrow T_p + \delta t \alpha_p \nabla^2(T_p)$ \; 83 | \end{algorithm} 84 | 85 | Currently the advection does not work like it should. This is probably due to boundary issues, where we get too close to the poles and it starts freaking out there \cite{simon}. So to fix this 86 | we are going to define boundaries and assume that the advection only works within those boundaries. We only let it change by half of the values. The changes are incorporated in 87 | \autoref{alg:advectionfix}. The reason why we mention this seperately, in contrast to the other fixes that we have incorporated throughout the manual already, is the accompanying change with the 88 | boundary. 89 | 90 | \begin{algorithm} 91 | \caption{The main calculations for calculating the effects of advection} 92 | \label{alg:advectionfix} 93 | $T_{add} \leftarrow T_a + \delta t \alpha_a \nabla^2(T_a) + \nabla(T_a)$ \; 94 | $T_a \leftarrow T_a - 0.5T_{add}[adv\_bound:-adv\_boun, :] \text{ //Only subtract } T_{add} \text{ to } T_a \text{ for indices in the interval } [-nlat + adv\_boun, nlat - adv\_boun]$. \; 95 | $\rho[adv\_boun: -adv\_boun, :] \leftarrow \rho - 0.5(\delta t \nabla \rho) \text{ //Only change the density for indices in the interval } [-nlat + adv\_boun, nlat - adv\_boun]$ \; 96 | $T_p \leftarrow T_p + \delta t \alpha_p \nabla^2(T_p)$ \; 97 | \end{algorithm} 98 | 99 | \subsection{Layers, layers and layers} 100 | With the atmospheric layers, and all matrices that have an extra dimension to account for it, we need to add the correct indices to the advection algorithm \autoref{alg:advectionfix}. Let us 101 | add it, with \autoref{alg:advection layer} as a result. Here the ':' means all indices of the 3 dimensional matrix. Also keep in mind that the potential temperature is described and discussed in 102 | \autoref{sec:thermal pot}. 103 | 104 | \begin{algorithm} 105 | \caption{The main calculations for calculating the effects of advection} 106 | \label{alg:advection layer} 107 | $T_{add} \leftarrow T_a + \delta t \alpha_a \nabla^2(T_{pot}) + \nabla(T_{pot})$ \; 108 | $T_{add} \leftarrow 0.5T_{add}[adv\_boun:-adv\_boun, :, :] \text{ //Only halve } T_{add} \text{ for indices in the interval } [-nlat + adv\_boun, nlat - adv\_boun]$. \; 109 | $T_{add}[-adv\_boun:, :, :] \leftarrow 0 \text{//Only replace by 0 in the interval [0, adv\_boun]}$ \; 110 | $T_{add}[:adv\_boun, :, :] \leftarrow 0 \text{//Only replace by 0 in the interval [nlat - adv\_boun, nlat]}$ \; 111 | $T_{pot} \leftarrow T_{pot} - T_{add}[adv\_boun:-adv\_boun, :, :] \text{ //Only subtract } T_{add} \text{ from } T_{pot} \text{ for indices in the interval }$ 112 | $[-nlat + adv\_boun, nlat - adv\_boun]$. \; 113 | \end{algorithm} 114 | 115 | \subsection{Adiabatic Motion} \label{sec:thermal pot} 116 | Up until now, we have been moving air and the density of the atmosphere. However we have not transported any temperature yet. What this means is that if we have a packet of air (see it as a box 117 | filled with air, but the box is invisible) $P$ which is at the planet surface. There $P$ has temperature $T_1$. If we then move $P$ to a layer higher up in the atmosphere, $P$ will still have 118 | the same temperature, which is wrong because the density differs. Due to this difference, there is either more or less pressure applied to the box of air which means that $P$ will contract or 119 | expand. This obviously changes it's temperature as the air molecules are closer together/further away from each other. Therefore the energy spread is different which affects the temperature. 120 | For a visual representation, please consult \autoref{fig:thermal potential}. 121 | 122 | \begin{figure} 123 | \label{fig:thermal potential} 124 | \centering 125 | \includegraphics[width=0.9\textwidth]{figures/potential_temperature.jpg} 126 | \caption{Visual representation of why we need Thermal Potential \cite{simon}.} 127 | \end{figure} 128 | 129 | As seen in \autoref{fig:thermal potential}, the packet of air (next to $T_1$) moves up into a higher layer of the atmosphere and ends up at the top (next to $T_2$). The packet has grown bigger, 130 | as the density in that atmospheric layer is lower and hence the air expands. Because the same energy is in that packet, but it has expanded, the temperature of that packet drops which gives us 131 | $T_2 < T_1$. The light blue graph in the background shows how the density behaves the higher you go, meaning at the far right (when we are at the highest point) the density is quite low and at 132 | the far left (when we are at the planet surface) the density is quite high. The yellow graph shows the temperature of the air at that height, which behaves similarly to the density graph. 133 | 134 | Thermal potential or potential temperature, is the temperature that the packet of air would be if it is at the planet surface. So take the packet at $T_2$ in \autoref{fig:thermal potential}. If we move that down to the 135 | surface, then it would be temperature $T$ which is then equal to the thermal potential. The equation corresponding to thermal potential is shown in \autoref{eq:thermal potential} 136 | \cite{thermalPotential}. The symbols in \autoref{eq:thermal potential} mean: 137 | 138 | \begin{itemize} 139 | \item $T$: The temperature of the packet of air (\si{K}). 140 | \item $p_0$: Reference pressure, usually the pressure at the planet surface (\si{Pa}). 141 | \item $p$: Pressure of the packet of air (\si{Pa}). 142 | \item $R$: Gas constant as defined in \autoref{sec:gas constant} (\si{JK^{-1}mol^{-1}}). 143 | \item $C_a$: Specific heat capacity of air at a constant pressure (\si{Jkg^{-1}K^{-1}}). 144 | \end{itemize} 145 | 146 | \begin{subequations} 147 | \begin{equation} 148 | \label{eq:thermal potential} 149 | \theta = T(\frac{p_0}{p})^{\frac{R}{C_a}} 150 | \end{equation} 151 | \begin{equation} 152 | \label{eq:potential temp} 153 | T = \theta(\frac{p}{p_0})^{\frac{R}{C_a}} 154 | \end{equation} 155 | \end{subequations} 156 | 157 | If we now re-arrange \autoref{eq:thermal potential} so that we have $T$ on one side and the rest on the other side, we get \autoref{eq:potential temp}. With this we can convert temperature into 158 | potential temperature and vice versa. The whole process of moving temperature around is called adiabatic motion. Now it is time to get this into code. For this to work we need to translate both 159 | \autoref{eq:thermal potential} and \autoref{eq:potential temp} into code and create an overarching function that calls the previously mentioned equations. Let us start with 160 | \autoref{eq:thermal potential} which is described in \autoref{alg:temp to pot}. Note that $\frac{R}{C_a}$ does not change as they are constants, therefore we can precompute them which saves 161 | a bit of time (namely $O(n)$ divisions, where $n$ is the length of $p$). Also note that we can inverse the process by inserting a minus in the exponent and swapping $T_a$ 162 | and $\theta$, which can easily be done in the call to \autoref{alg:temp to pot}. 163 | 164 | \begin{algorithm} 165 | \caption{Converting temperature into potential temperature} 166 | \label{alg:temp to pot} 167 | \SetKwInOut{Input}{Input} 168 | \SetKwInOut{Output}{Output} 169 | \Input{temperature of the atmosphere (or potential temperature) $T_a$, air pressure $p$, boolean $back$} 170 | \Output{potential temperature $\theta$} 171 | \uIf{$back$}{ 172 | $\kappa \leftarrow \frac{R}{C_a}$ \; 173 | }\uElse{ 174 | $\kappa \leftarrow -\frac{R}{C_a}$ \; 175 | } 176 | \For{$k \leftarrow 0$ \KwTo $p.length$}{ 177 | $\theta[:, :, k] \leftarrow T_a[:, :, k] (\frac{p_z[k]}{p_z[0]})^{\kappa}$ 178 | } 179 | \Return{$\theta$} 180 | \end{algorithm} 181 | 182 | Now we only need to do one more thing, replace the algorithm that calculates $T_{pot}$ as a result of advection. Let us do that in \autoref{alg:advection pot}, where \texttt{ThermalAdv} is the 183 | call to \autoref{alg:divergence}. 184 | 185 | \begin{algorithm} 186 | \caption{The main calculations for calculating the effects of advection} 187 | \label{alg:advection pot} 188 | $T_{add} \leftarrow T_{pot} + \nabla(T_{pot}, p_z)$\; 189 | $T_{add} \leftarrow 0.5T_{add}[adv\_boun:-adv\_boun, :, :] \text{ //Only halve } T_{add} \text{ for indices in the interval } [-nlat + adv\_boun, nlat - adv\_boun]$. \; 190 | $T_{add}[-adv\_boun:, :, :] \leftarrow 0 \text{//Only replace by 0 in the interval [0, adv\_boun]}$ \; 191 | $T_{add}[:adv\_boun, :, :] \leftarrow 0 \text{//Only replace by 0 in the interval [nlat - adv\_boun, nlat]}$ \; 192 | $T_{pot} \leftarrow T_{pot} - T_{add}[adv\_boun:-adv\_boun, :, :] \text{ //Only subtract } T_{add} \text{ from } T_{pot} \text{ for indices in the interval }$ 193 | $[-nlat + adv\_boun, nlat - adv\_boun]$. \; 194 | \end{algorithm} -------------------------------------------------------------------------------- /tex-docs/references.bib: -------------------------------------------------------------------------------- 1 | @misc {twitch, 2 | howpublished="\url{https://www.twitch.tv/drsimonclark}", 3 | journal={Twitch}, 4 | author={Clark, Simon} 5 | } 6 | 7 | @misc{playlist, 8 | howpublished="\url{https://www.twitch.tv/collections/ewOcca6DExadGg}", 9 | journal={Twitch}, 10 | author={Clark, Simon}, 11 | editor={Reed, CaptainEditor} 12 | } 13 | 14 | @misc{simon, 15 | author={Clark, Simon} 16 | } 17 | 18 | @misc{polarPlane, 19 | howpublished="\url{https://www.twitch.tv/videos/715183053}", 20 | author={Clark, Simon}, 21 | journal={Twitch}, 22 | year={2020}, 23 | month={Aug} 24 | } 25 | 26 | @misc{SI, 27 | title={SI Units}, 28 | howpublished="\url{https://www.nist.gov/pml/weights-and-measures/metric-si/si-units}", 29 | journal={NIST}, 30 | publisher={National Institute of Standards and Technology, a U.S. Department of Commerce}, 31 | author={Isabel Chavez}, 32 | year={2019}, 33 | month={Nov} 34 | } 35 | 36 | %My Physics Book 37 | @inbook{stefan-boltzmann, 38 | place={Harlow}, 39 | title={Sears and Zemanskys University physics with modern physics}, 40 | publisher={Pearson Education}, 41 | author={Young, Hugh D. and Freedman, Roger A. and Ford, A. Lewis}, 42 | year={2016}, 43 | chapter={39}, 44 | pages={1328}, 45 | edition={14th global} 46 | } 47 | 48 | @inbook{specificHeat, 49 | place={Harlow}, 50 | title={Sears and Zemanskys University physics with modern physics}, 51 | publisher={Pearson Education}, 52 | author={Young, Hugh D. and Freedman, Roger A. and Ford, A. Lewis}, 53 | year={2016}, 54 | chapter={17}, 55 | pages={581}, 56 | edition={14th global} 57 | } 58 | 59 | @inbook{thermo1, 60 | place={Harlow}, 61 | title={Sears and Zemanskys University physics with modern physics}, 62 | publisher={Pearson Education}, 63 | author={Young, Hugh D. and Freedman, Roger A. and Ford, A. Lewis}, 64 | year={2016}, 65 | chapter={19}, 66 | pages={648}, 67 | edition={14th global} 68 | } 69 | 70 | @inbook{idealGas, 71 | place={Harlow}, 72 | title={Sears and Zemanskys University physics with modern physics}, 73 | publisher={Pearson Education}, 74 | author={Young, Hugh D. and Freedman, Roger A. and Ford, A. Lewis}, 75 | year={2016}, 76 | chapter={18}, 77 | pages={610}, 78 | edition={14th global} 79 | } 80 | 81 | @inbook{densityAir, 82 | place={Harlow}, 83 | title={Sears and Zemanskys University physics with modern physics}, 84 | publisher={Pearson Education}, 85 | author={Young, Hugh D. and Freedman, Roger A. and Ford, A. Lewis}, 86 | year={2016}, 87 | chapter={12}, 88 | pages={394}, 89 | edition={14th global} 90 | } 91 | 92 | @inbook{potential, 93 | place={Harlow}, 94 | title={Sears and Zemanskys University physics with modern physics}, 95 | publisher={Pearson Education}, 96 | author={Young, Hugh D. and Freedman, Roger A. and Ford, A. Lewis}, 97 | year={2016}, 98 | chapter={7}, 99 | pages={227-247}, 100 | edition={14th global} 101 | } 102 | 103 | %Simon's physics book 104 | @inbook{momentumeqs, 105 | place={Cambridge}, 106 | edition={2nd}, 107 | DOI={10.1017/9781107588417.003}, 108 | title={Atmospheric and Oceanic Fluid Dynamics: Fundamentals and Large-Scale Circulation}, 109 | publisher={Cambridge University Press}, 110 | author={Vallis, Geoffrey K.}, 111 | year={2017}, 112 | chapter={2}, 113 | pages={69} 114 | } 115 | 116 | @inbook{diffusion, 117 | place={Cambridge}, 118 | edition={2nd}, 119 | DOI={10.1017/9781107588417.003}, 120 | title={Atmospheric and Oceanic Fluid Dynamics: Fundamentals and Large-Scale Circulation}, 121 | publisher={Cambridge University Press}, 122 | author={Vallis, Geoffrey K.}, 123 | year={2017}, 124 | chapter={13}, 125 | pages={473} 126 | } 127 | 128 | @inbook{masscontinue, 129 | place={Cambridge}, 130 | edition={2nd}, 131 | DOI={10.1017/9781107588417.003}, 132 | title={Atmospheric and Oceanic Fluid Dynamics: Fundamentals and Large-Scale Circulation}, 133 | publisher={Cambridge University Press}, 134 | author={Vallis, Geoffrey K.}, 135 | year={2017}, 136 | chapter={1}, 137 | pages={8} 138 | } 139 | 140 | @inbook{hydrostatic, 141 | place={Cambridge}, 142 | edition={2nd}, 143 | DOI={10.1017/9781107588417.003}, 144 | title={Atmospheric and Oceanic Fluid Dynamics: Fundamentals and Large-Scale Circulation}, 145 | publisher={Cambridge University Press}, 146 | author={Vallis, Geoffrey K.}, 147 | year={2017}, 148 | chapter={1}, 149 | pages={12} 150 | } 151 | 152 | @inbook{thermalPotential, 153 | place={Cambridge}, 154 | edition={2nd}, 155 | DOI={10.1017/9781107588417.003}, 156 | title={Atmospheric and Oceanic Fluid Dynamics: Fundamentals and Large-Scale Circulation}, 157 | publisher={Cambridge University Press}, 158 | author={Vallis, Geoffrey K.}, 159 | year={2017}, 160 | chapter={1}, 161 | pages={24} 162 | } 163 | 164 | %Calculus 165 | @inbook{areaCircle, 166 | place={Don Mills, Ont.}, 167 | title={Calculus a complete course}, 168 | publisher={Pearson}, 169 | author={Adams, Robert Alexander and Essex, Christopher}, 170 | year={2018}, 171 | chapter={1}, 172 | pages={62}, 173 | edition={9th} 174 | } 175 | 176 | @inbook{areaSphere, 177 | place={Don Mills, Ont.}, 178 | title={Calculus a complete course}, 179 | publisher={Pearson}, 180 | author={Adams, Robert Alexander and Essex, Christopher}, 181 | year={2018}, 182 | chapter={7}, 183 | pages={412}, 184 | edition={9th} 185 | } 186 | 187 | @inbook{vectorscalarfields, 188 | place={Don Mills, Ont.}, 189 | title={Calculus a complete course}, 190 | publisher={Pearson}, 191 | author={Adams, Robert Alexander and Essex, Christopher}, 192 | year={2018}, 193 | chapter={15}, 194 | pages={867}, 195 | edition={9th} 196 | } 197 | 198 | @inbook{laplacian, 199 | place={Don Mills, Ont.}, 200 | title={Calculus a complete course}, 201 | publisher={Pearson}, 202 | author={Adams, Robert Alexander and Essex, Christopher}, 203 | year={2018}, 204 | chapter={16}, 205 | pages={923}, 206 | edition={9th} 207 | } 208 | 209 | %Misc book sources 210 | @inbook{uniformdist, 211 | place={Hoboken, NJ}, 212 | edition={7th}, 213 | title={Applied statistics and probability for engineers}, 214 | publisher={Wiley}, 215 | author={Montgomery, Douglas C. and Runger, George C.}, 216 | year={2018}, 217 | chapter={3}, 218 | pages={49} 219 | } 220 | 221 | @article{fft, 222 | title={An algorithm for the machine calculation of complex Fourier series}, 223 | volume={19}, 224 | DOI={10.1090/s0025-5718-1965-0178586-1}, 225 | number={90}, 226 | journal={Mathematics of Computation}, 227 | author={Cooley, James W. and Tukey, John W.}, 228 | year={1965}, 229 | pages={297–297} 230 | } 231 | 232 | %General internet sources 233 | @misc{latlong, 234 | title={Geographic coordinate system}, 235 | howpublished="\url{https://en.wikipedia.org/wiki/Geographic_coordinate_system#Latitude_and_longitude}", 236 | journal={Wikipedia}, 237 | publisher={Wikimedia Foundation}, 238 | author={Tobias Hoevekamp}, 239 | year={2020}, 240 | month={Jul} 241 | } 242 | 243 | @misc{equinox, 244 | title={Equinox}, 245 | howpublished="\url{https://en.wikipedia.org/wiki/Equinox}", 246 | journal={Wikipedia}, 247 | publisher={Wikimedia Foundation}, 248 | author={Palmen, Karl}, 249 | year={2020}, 250 | month={Jun} 251 | } 252 | 253 | @misc{eulerFormula, 254 | title={Equinox}, 255 | howpublished="\url{https://en.wikipedia.org/wiki/Euler%27s_formula}", 256 | journal={Wikipedia}, 257 | publisher={Wikimedia Foundation}, 258 | author={Boldt, Axel}, 259 | year={2020}, 260 | month={September} 261 | } 262 | 263 | @misc{mole, 264 | title={SI Units - Amount of Substance}, 265 | howpublished="\url{https://www.nist.gov/pml/weights-and-measures/si-units-amount-substance}", 266 | journal={NIST}, 267 | publisher={National Institute of Standards and Technology, a U.S. Department of Commerce}, 268 | author={Chavez, Isabel}, 269 | year={2019}, 270 | month={Nov} 271 | } 272 | 273 | @misc{specificGasConstantAir, 274 | title={Gas constant}, 275 | howpublished="\url{https://en.wikipedia.org/wiki/Gas_constant#Specific_gas_constant}", 276 | journal={Wikipedia}, 277 | publisher={Wikimedia Foundation}, 278 | author={192.2.69.128 (Anonymous Internet User)}, 279 | year={2020}, 280 | month={Jun} 281 | } 282 | 283 | @misc{coriolis, 284 | title={Coriolis frequency}, 285 | howpublished="\url{https://en.wikipedia.org/wiki/Coriolis_frequency}", 286 | journal={Wikipedia}, 287 | publisher={Wikimedia Foundation}, 288 | author={Kjkolb}, 289 | year={2020}, 290 | month={Jun} 291 | } 292 | 293 | @misc{circumference, 294 | title={Circumference}, 295 | howpublished="\url{https://en.wikipedia.org/wiki/Circumference}", 296 | journal={Wikipedia}, 297 | publisher={Wikimedia Foundation}, 298 | author={MBManie}, 299 | year={2020}, 300 | month={Jun} 301 | } 302 | 303 | @misc{albedo, 304 | title={Albedo}, 305 | howpublished="\url{https://en.wikipedia.org/wiki/Albedo}", 306 | journal={Wikipedia}, 307 | publisher={Wikimedia Foundation}, 308 | author={155.42.27.xxx (Anonymous Internet User)}, 309 | year={2020}, 310 | month={Jun} 311 | } 312 | 313 | @misc{fplane, 314 | title={F-plane}, 315 | howpublished="\url{https://en.wikipedia.org/wiki/F-plane}", 316 | journal={Wikipedia}, 317 | publisher={Wikimedia Foundation}, 318 | author={Johnson, Nathan}, 319 | year={2020}, 320 | month={Apr} 321 | } 322 | 323 | @book{usatmosp, 324 | place={Washington, D.C.}, 325 | title={U.S. standard atmosphere}, 326 | publisher={National Oceanic and Atmospheric Administration}, 327 | author={National Aeronautics and Space Administration}, 328 | year={1976} 329 | } 330 | 331 | @misc{interpolation, 332 | title={Interpolation}, 333 | howpublished = "\url{https://en.wikipedia.org/wiki/Interpolation}", 334 | journal={Wikipedia}, 335 | publisher={Wikimedia Foundation}, 336 | author={Beldin, Dick}, 337 | year={2020}, 338 | month={May} 339 | } 340 | 341 | @article{isca, 342 | title={Isca, v1.0: a framework for the global modelling of the atmospheres of Earth and other planets at varying levels of complexity}, 343 | volume={11}, 344 | DOI={10.5194/gmd-11-843-2018}, 345 | number={3}, 346 | journal={Geoscientific Model Development}, 347 | author={Vallis, Geoffrey K. and Colyer, Greg and Geen, Ruth and Gerber, Edwin and Jucker, Martin and Maher, Penelope and Paterson, Alexander and Pietschnig, Marianne and Penn, James and Thomson, Stephen I. and et al.}, 348 | year={2018}, 349 | pages={843–859} 350 | } 351 | 352 | @article{greyRad, 353 | title={A Gray-Radiation Aquaplanet Moist GCM. Part I: Static Stability and Eddy Scale}, 354 | volume={63}, 355 | DOI={10.1175/jas3753.1}, 356 | number={10}, 357 | journal={Journal of the Atmospheric Sciences}, 358 | author={Frierson, Dargan M. W. and Held, Isaac M. and Zurita-Gotor, Pablo}, 359 | year={2006}, 360 | pages={2548–2566} 361 | } 362 | 363 | @misc{claudeGit, 364 | title={Planet-Factory/claude}, 365 | howpublished="\url{https://github.com/Planet-Factory/claude}", 366 | journal={GitHub}, 367 | author={Clark, Simon}, 368 | editor={Reid, Peter and Marsden, Aren}, 369 | translator={Baggen, Sam} 370 | } 371 | 372 | @misc{nomGit, 373 | title={TechWizzart/claude}, 374 | howpublished="\url{https://github.com/TechWizzart/claude}", 375 | journal={GitHub}, 376 | author={Baggen, Sam} 377 | } 378 | 379 | @misc{pythagoras, 380 | title={Pythagorean theorem}, 381 | howpublished="\url{https://en.wikipedia.org/wiki/Pythagorean_theorem}", 382 | journal={Wikipedia}, 383 | publisher={Wikimedia Foundation}, 384 | author={Boldt, Axel}, 385 | year={2001}, 386 | month={Sep} 387 | } 388 | 389 | @article{numpy, 390 | title={The NumPy array: a structure for efficient numerical computation}, 391 | author={Van Der Walt, Stefan and Colbert, S Chris and Varoquaux, Gael}, 392 | journal={Computing in Science \& Engineering}, 393 | volume={13}, 394 | number={2}, 395 | pages={22}, 396 | year={2011}, 397 | publisher={IEEE Computer Society} 398 | } 399 | 400 | @inbook{cancellation, 401 | author = {Thomas H. Cormen and Charles E and Leiserson and Ronald L. Rivest and Clifford Stein}, 402 | title = {Introduction to Algorithms}, 403 | edition = {3rd}, 404 | chapter = {30}, 405 | pages = {907}, 406 | publisher = {MIT Press}, 407 | year = {2009} 408 | } 409 | 410 | @misc{geopot, 411 | author = "{145.254.33.233}", 412 | title = "Geopotential height", 413 | year = "2003", 414 | howpublished = "\url{https://en.wikipedia.org/w/index.php?title=Geopotential_height&oldid=985531215}", 415 | journal={Wikipedia}, 416 | publisher={Wikimedia Foundation}, 417 | month={Jan} 418 | } 419 | 420 | @misc{verticalcoords, 421 | title={Vertical Coordinates}, 422 | journal={Atmospheric and Oceanic Modeling}, 423 | author={Adcroft, Alistair}, 424 | year={2004} 425 | } 426 | 427 | @misc{meshgridGFG, 428 | title={Numpy Meshgrid function}, 429 | url={https://www.geeksforgeeks.org/numpy-meshgrid-function/}, 430 | journal={GeeksforGeeks}, 431 | author={Gayen, Arijit Kumar}, 432 | year={2019}, 433 | month={Apr} 434 | } 435 | 436 | @misc{meshgridDoc, 437 | title={numpy.meshgrid¶}, 438 | url={https://numpy.org/doc/stable/reference/generated/numpy.meshgrid.html}, 439 | journal={numpy.meshgrid - NumPy v1.19 Manual}, 440 | author={The SciPy community}, 441 | year={2020}, 442 | month={Jun} 443 | } 444 | 445 | @misc{bivariatespline, 446 | title={scipy.interpolate.RectBivariateSpline¶}, 447 | url={https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.RectBivariateSpline.html}, 448 | journal={scipy.interpolate.RectBivariateSpline - SciPy v1.5.4 Reference Guide}, 449 | author={The SciPy community}, 450 | year={2020}, 451 | month={Nov} 452 | } -------------------------------------------------------------------------------- /claude_top_level_library.pyx: -------------------------------------------------------------------------------- 1 | # claude_top_level_library 2 | 3 | import claude_low_level_library as low_level 4 | import numpy as np 5 | cimport numpy as np 6 | cimport cython 7 | 8 | ctypedef np.float64_t DTYPE_f 9 | 10 | cpdef laplacian_2d(np.ndarray a,np.ndarray dx,DTYPE_f dy): 11 | cdef np.ndarray a_dx = (np.roll(a, -1, axis=1) - np.roll(a, 1, axis=1)) / dx[:, None] 12 | cdef np.ndarray output = (np.roll(a_dx, -1, axis=1) - np.roll(a_dx, 1, axis=1)) / dx[:, None] 13 | 14 | shift_south = np.pad(a, ((1,0), (0,0)), 'reflect', reflect_type='odd')[:-1,:] 15 | shift_north = np.pad(a, ((0,1), (0,0)), 'reflect', reflect_type='odd')[1:,:] 16 | a_dy = (shift_north - shift_south)/dy 17 | 18 | shift_south = np.pad(a_dy, ((1,0), (0,0)), 'reflect', reflect_type='odd')[:-1,:] 19 | shift_north = np.pad(a_dy, ((0,1), (0,0)), 'reflect', reflect_type='odd')[1:,:] 20 | output += (shift_north - shift_south)/dy 21 | 22 | return output 23 | 24 | cpdef laplacian_3d(np.ndarray a,np.ndarray dx,DTYPE_f dy,np.ndarray pressure_levels): 25 | cdef np.ndarray output = low_level.scalar_gradient_x_matrix(low_level.scalar_gradient_x_matrix(a,dx),dx) + low_level.scalar_gradient_y_matrix(low_level.scalar_gradient_y_matrix(a,dy),dy) + low_level.scalar_gradient_z_matrix_primitive(low_level.scalar_gradient_z_matrix_primitive(a, pressure_levels),pressure_levels) 26 | return output 27 | 28 | cpdef divergence_with_scalar(np.ndarray a, np.ndarray u, np.ndarray v, np.ndarray w, np.ndarray dx, DTYPE_f dy, np.ndarray lat, np.ndarray lon, np.ndarray pressure_levels, DTYPE_f polar_grid_resolution, tuple indices, tuple coords, tuple grids, tuple grid_velocities): 29 | ''' divergence of (a*u) where a is a scalar field and u is the atmospheric velocity field ''' 30 | # https://scicomp.stackexchange.com/questions/27737/advection-equation-with-finite-difference-importance-of-forward-backward-or-ce 31 | 32 | cdef np.ndarray output = np.zeros_like(u) 33 | output += (u + abs(u))*(a - np.roll(a, 1, axis=1))/dx[:, None, None] + (u - abs(u))*(np.roll(a, -1, axis=1) - a)/dx[:, None, None] 34 | output += (v + abs(v))*(a - np.pad(a, ((1,0), (0,0), (0,0)), 'reflect', reflect_type='odd')[:-1,:,:])/dy + (v - abs(v))*(np.pad(a, ((0,1), (0,0), (0,0)), 'reflect', reflect_type='odd')[1:,:,:] - a)/dy 35 | 36 | output += 0.5*(w + abs(w))*(a - np.pad(a, ((0,0), (0,0), (1,0)), 'reflect', reflect_type='odd')[:,:,:-1])/(pressure_levels - np.pad(pressure_levels, ((1,0)), 'reflect', reflect_type='odd')[:-1]) 37 | output += 0.5*(w - abs(w))*(np.pad(a, ((0,0), (0,0), (0,1)), 'reflect', reflect_type='odd')[:,:,:-1] - a)/(np.pad(pressure_levels, ((0,1)), 'reflect', reflect_type='odd')[1:] - pressure_levels) 38 | 39 | ### POLAR PLANE ADDITION ### 40 | 41 | pole_low_index_N,pole_high_index_N,pole_low_index_S,pole_high_index_S = indices[:] 42 | grid_lat_coords_N,grid_lon_coords_N,grid_x_values_N,grid_y_values_N,polar_x_coords_N,polar_y_coords_N,grid_lat_coords_S,grid_lon_coords_S,grid_x_values_S,grid_y_values_S,polar_x_coords_S,polar_y_coords_S = coords[:] 43 | grid_length_N,grid_length_S = grids[:] 44 | x_dot_N,y_dot_N,x_dot_S,y_dot_S = grid_velocities[:] 45 | 46 | # project field a onto north pole cartesian grid 47 | north_polar_plane_field = low_level.beam_me_up(lat[pole_low_index_N:],lon,np.flip(a[pole_low_index_N:,:,:],axis=1),grid_length_N,grid_lat_coords_N,grid_lon_coords_N) 48 | w_N = low_level.beam_me_up(lat[pole_low_index_N:],lon,np.flip(w[pole_low_index_N:,:,:],axis=1),grid_length_N,grid_lat_coords_N,grid_lon_coords_N) 49 | 50 | # advect temperature field, isolate field to subtract from existing temperature field (CARTESIAN) 51 | north_polar_plane_addition = low_level.polar_plane_advect(north_polar_plane_field,x_dot_N,y_dot_N,w_N,polar_grid_resolution) 52 | 53 | # project addition to temperature field onto polar grid (POLAR) [NB flip is due to north pole and definition of planar coordinates] 54 | north_reprojected_addition = np.flip(low_level.beam_me_down(lon,north_polar_plane_addition,pole_low_index_N,grid_x_values_N,grid_y_values_N,polar_x_coords_N,polar_y_coords_N),axis=1) 55 | # recombine output from polar planes with output from lat-lon grid 56 | output[pole_low_index_N:,:,:] = low_level.combine_data(pole_low_index_N,pole_high_index_N,output[pole_low_index_N:,:,:],north_reprojected_addition,lat) 57 | 58 | ### 59 | south_polar_plane_field = low_level.beam_me_up(lat[:pole_low_index_S],lon,a[:pole_low_index_S,:,:],grid_length_S,grid_lat_coords_S,grid_lon_coords_S) 60 | w_S = low_level.beam_me_up(lat[:pole_low_index_S],lon,w[:pole_low_index_S,:,:],grid_length_S,grid_lat_coords_S,grid_lon_coords_S) 61 | south_polar_plane_addition = low_level.polar_plane_advect(south_polar_plane_field,x_dot_S,y_dot_S,w_S,polar_grid_resolution) 62 | south_reprojected_addition = low_level.beam_me_down(lon,south_polar_plane_addition,pole_low_index_S,grid_x_values_S,grid_y_values_S,polar_x_coords_S,polar_y_coords_S) 63 | output[:pole_low_index_S,:,:] = low_level.combine_data(pole_low_index_S,pole_high_index_S,output[:pole_low_index_S,:,:],south_reprojected_addition,lat) 64 | 65 | return output 66 | 67 | cpdef radiation_calculation(np.ndarray temperature_world, np.ndarray potential_temperature, np.ndarray pressure_levels, np.ndarray heat_capacity_earth, np.ndarray albedo, DTYPE_f insolation, np.ndarray lat, np.ndarray lon, np.int_t t, np.int_t dt, DTYPE_f day, DTYPE_f year, DTYPE_f axial_tilt): 68 | # calculate change in temperature of ground and atmosphere due to radiative imbalance 69 | cdef np.int_t nlat,nlon,nlevels,k 70 | cdef DTYPE_f fl = 0.1 71 | cdef DTYPE_f inv_day = 1/day 72 | 73 | nlat = lat.shape[0] 74 | nlon = lon.shape[0] 75 | nlevels = pressure_levels.shape[0] 76 | 77 | cdef np.ndarray temperature_atmos = low_level.theta_to_t(potential_temperature,pressure_levels) 78 | cdef np.ndarray sun_lat = low_level.surface_optical_depth_array(lat) 79 | cdef np.ndarray optical_depth = np.outer(sun_lat, (fl*(pressure_levels/pressure_levels[0]) + (1-fl)*(pressure_levels/pressure_levels[0])**4)) 80 | 81 | # calculate upward longwave flux, bc is thermal radiation at surface 82 | cpdef np.ndarray upward_radiation = np.zeros((nlat,nlon,nlevels)) 83 | upward_radiation[:,:,0] = low_level.thermal_radiation_matrix(temperature_world) 84 | for k in np.arange(1,nlevels): 85 | upward_radiation[:,:,k] = (upward_radiation[:,:,k-1] - (optical_depth[:,None,k]-optical_depth[:,None,k-1])*(low_level.thermal_radiation_matrix(temperature_atmos[:,:,k])))/(1 + optical_depth[:,None,k-1] - optical_depth[:,None,k]) 86 | 87 | # calculate downward longwave flux, bc is zero at TOA (in model) 88 | cpdef np.ndarray downward_radiation = np.zeros((nlat,nlon,nlevels)) 89 | downward_radiation[:,:,-1] = 0 90 | for k in np.arange(0,nlevels-1)[::-1]: 91 | downward_radiation[:,:,k] = (downward_radiation[:,:,k+1] - low_level.thermal_radiation_matrix(temperature_atmos[:,:,k])*(optical_depth[:,None,k+1]-optical_depth[:,None,k]))/(1 + optical_depth[:,None,k] - optical_depth[:,None,k+1]) 92 | 93 | # gradient of difference provides heating at each level 94 | cpdef np.ndarray Q = np.zeros((nlat,nlon,nlevels)) 95 | cpdef np.ndarray z_gradient = low_level.scalar_gradient_z_matrix_primitive(upward_radiation - downward_radiation, pressure_levels) 96 | cpdef np.ndarray solar_matrix = low_level.solar_matrix(75,lat,lon,t,day, year, axial_tilt) 97 | for k in np.arange(nlevels): 98 | Q[:,:,k] = -287*temperature_atmos[:,:,k]*z_gradient[:,:,k]/(1000*pressure_levels[None,None,k]) 99 | # approximate SW heating of ozone 100 | if pressure_levels[k] < 400*100: 101 | Q[:,:,k] += solar_matrix*inv_day*(100/pressure_levels[k]) 102 | 103 | temperature_atmos += Q*dt 104 | 105 | # update surface temperature with shortwave radiation flux 106 | temperature_world += dt*((1-albedo)*(low_level.solar_matrix(insolation,lat,lon,t, day, year, axial_tilt) + downward_radiation[:,:,0]) - upward_radiation[:,:,0])/heat_capacity_earth 107 | 108 | return temperature_world, low_level.t_to_theta(temperature_atmos,pressure_levels) 109 | 110 | cpdef velocity_calculation(np.ndarray u,np.ndarray v,np.ndarray w,np.ndarray pressure_levels,np.ndarray geopotential,np.ndarray potential_temperature,np.ndarray coriolis,DTYPE_f gravity,np.ndarray dx,DTYPE_f dy,DTYPE_f dt,np.int_t sponge_index): 111 | 112 | # calculate acceleration of atmosphere using primitive equations on beta-plane 113 | cpdef np.ndarray u_temp = dt*(-(u+abs(u))*(u - np.roll(u, 1, axis=1))/dx[:, None, None] - (u-abs(u))*(np.roll(u, -1, axis=1) - u)/dx[:, None, None] 114 | - (v+abs(v))*(u - np.pad(u, ((1,0), (0,0), (0,0)), 'reflect', reflect_type='odd')[:-1,:,:])/dy - (v-abs(v))*(np.pad(u, ((0,1), (0,0), (0,0)), 'reflect', reflect_type='odd')[1:,:,:] - u)/dy 115 | - 0.5*(w+abs(w))*(u - np.pad(u, ((0,0), (0,0), (1,0)), 'reflect', reflect_type='odd')[:,:,:-1])/(pressure_levels - np.pad(pressure_levels, (1,0), 'reflect', reflect_type='odd')[:-1]) - 0.5*(w-abs(w))*(np.pad(u, ((0,0), (0,0), (0,1)), 'reflect', reflect_type='odd')[:,:,1:] - u)/(pressure_levels - np.pad(pressure_levels, (0,1), 'reflect', reflect_type='odd')[1:]) 116 | + coriolis[:, None, None]*v - low_level.scalar_gradient_x_matrix(geopotential, dx) - 1E-5*u) 117 | 118 | cpdef np.ndarray v_temp = dt*(-(u+abs(u))*(v - np.roll(v, 1, axis=1))/dx[:, None, None] - (u-abs(u))*(np.roll(v, -1, axis=1) - v)/dx[:, None, None] 119 | - (v+abs(v))*(v - np.pad(v, ((1,0), (0,0), (0,0)), 'reflect', reflect_type='odd')[:-1,:,:])/dy - (v-abs(v))*(np.pad(v, ((0,1), (0,0), (0,0)), 'reflect', reflect_type='odd')[1:,:,:] - v)/dy 120 | - 0.5*(w+abs(w))*(v - np.pad(v, ((0,0), (0,0), (1,0)), 'reflect', reflect_type='odd')[:,:,:-1])/(pressure_levels - np.pad(pressure_levels, (1,0), 'reflect', reflect_type='odd')[:-1]) - 0.5*(w-abs(w))*(np.pad(v, ((0,0), (0,0), (0,1)), 'reflect', reflect_type='odd')[:,:,1:] - v)/(pressure_levels - np.pad(pressure_levels, (0,1), 'reflect', reflect_type='odd')[1:]) 121 | - coriolis[:, None, None]*u - low_level.scalar_gradient_y_matrix(geopotential, dy) - 1E-5*v) 122 | 123 | # advection only in sponge layer 124 | cpdef np.ndarray u_temp_sponge = dt*(-(u+abs(u))*(u - np.roll(u, 1, axis=1))/dx[:, None, None] - (u-abs(u))*(np.roll(u, -1, axis=1) - u)/dx[:, None, None] 125 | - (v+abs(v))*(u - np.pad(u, ((1,0), (0,0), (0,0)), 'reflect', reflect_type='odd')[:-1,:,:])/dy - (v-abs(v))*(np.pad(u, ((0,1), (0,0), (0,0)), 'reflect', reflect_type='odd')[1:,:,:] - u)/dy 126 | - 0.5*(w+abs(w))*(u - np.pad(u, ((0,0), (0,0), (1,0)), 'reflect', reflect_type='odd')[:,:,:-1])/(pressure_levels - np.pad(pressure_levels, (1,0), 'reflect', reflect_type='odd')[:-1]) - 0.05*(w-abs(w))*(np.pad(u, ((0,0), (0,0), (0,1)), 'reflect', reflect_type='odd')[:,:,1:] - u)/(pressure_levels - np.pad(pressure_levels, (0,1), 'reflect', reflect_type='odd')[1:]) 127 | - 1E-3*u) 128 | cpdef np.ndarray v_temp_sponge = dt*(-(u+abs(u))*(v - np.roll(v, 1, axis=1))/dx[:, None, None] - (u-abs(u))*(np.roll(v, -1, axis=1) - v)/dx[:, None, None] 129 | - (v+abs(v))*(v - np.pad(v, ((1,0), (0,0), (0,0)), 'reflect', reflect_type='odd')[:-1,:,:])/dy - (v-abs(v))*(np.pad(v, ((0,1), (0,0), (0,0)), 'reflect', reflect_type='odd')[1:,:,:] - v)/dy 130 | - 0.5*(w+abs(w))*(v - np.pad(v, ((0,0), (0,0), (1,0)), 'reflect', reflect_type='odd')[:,:,:-1])/(pressure_levels - np.pad(pressure_levels, (1,0), 'reflect', reflect_type='odd')[:-1]) - 0.05*(w-abs(w))*(np.pad(v, ((0,0), (0,0), (0,1)), 'reflect', reflect_type='odd')[:,:,1:] - v)/(pressure_levels - np.pad(pressure_levels, (0,1), 'reflect', reflect_type='odd')[1:]) 131 | - 1E-3*v) 132 | 133 | cpdef np.ndarray u_add = np.zeros_like(u_temp) 134 | cpdef np.ndarray v_add = np.zeros_like(v_temp) 135 | 136 | u_add[:,:,:sponge_index] += u_temp[:,:,:sponge_index] 137 | v_add[:,:,:sponge_index] += v_temp[:,:,:sponge_index] 138 | 139 | ### 140 | 141 | u_add[:,:,sponge_index:] += u_temp_sponge[:,:,sponge_index:] 142 | v_add[:,:,sponge_index:] += v_temp_sponge[:,:,sponge_index:] 143 | 144 | return u_add,v_add 145 | 146 | cpdef w_calculation(np.ndarray u,np.ndarray v,np.ndarray w,np.ndarray pressure_levels,np.ndarray geopotential,np.ndarray potential_temperature,np.ndarray coriolis,DTYPE_f gravity,np.ndarray dx,DTYPE_f dy,DTYPE_f dt,tuple indices,tuple coords,tuple grids,tuple grid_velocities,DTYPE_f polar_grid_resolution,np.ndarray lat,np.ndarray lon): 147 | cdef np.ndarray w_temp = np.zeros_like(u) 148 | cdef np.ndarray temperature_atmos = low_level.theta_to_t(potential_temperature,pressure_levels) 149 | 150 | cdef np.int_t nlevels, k 151 | nlevels = len(pressure_levels) 152 | 153 | cdef np.ndarray flow_divergence = low_level.scalar_gradient_x_matrix(u, dx) + low_level.scalar_gradient_y_matrix(v, dy) 154 | 155 | for k in range(nlevels): 156 | w_temp[:,:,k] = - np.trapz(flow_divergence[:,:,k:],pressure_levels[k:]) 157 | 158 | w_temp[-1:,:,:] = 0 159 | w_temp[:1,:,:] = 0 160 | 161 | w_temp[0,:,:] = np.mean(w[1,:,:],axis=0) 162 | w_temp[-1,:,:] = np.mean(w[-2,:,:],axis=0) 163 | 164 | # pole_low_index_N,pole_high_index_N,pole_low_index_S,pole_high_index_S = indices[:] 165 | # grid_lat_coords_N,grid_lon_coords_N,grid_x_values_N,grid_y_values_N,polar_x_coords_N,polar_y_coords_N,grid_lat_coords_S,grid_lon_coords_S,grid_x_values_S,grid_y_values_S,polar_x_coords_S,polar_y_coords_S = coords[:] 166 | # grid_length_N,grid_length_S = grids[:] 167 | # x_dot_N,y_dot_N,x_dot_S,y_dot_S = grid_velocities[:] 168 | 169 | # theta_N = low_level.beam_me_up(lat[pole_low_index_N:],lon,potential_temperature[pole_low_index_N:,:,:],grids[0],grid_lat_coords_N,grid_lon_coords_N) 170 | # w_N = low_level.w_plane(x_dot_N,y_dot_N,theta_N,pressure_levels,polar_grid_resolution) 171 | # w_N = np.flip(low_level.beam_me_down(lon,w_N,pole_low_index_N, grid_x_values_N, grid_y_values_N,polar_x_coords_N, polar_y_coords_N),axis=1) 172 | # w_temp[pole_low_index_N:,:,:] = low_level.combine_data(pole_low_index_N,pole_high_index_N,w[pole_low_index_N:,:,:],w_N,lat) 173 | 174 | # w_S = low_level.w_plane(x_dot_S,y_dot_S,low_level.beam_me_up(lat[:pole_low_index_S],lon,potential_temperature[:pole_low_index_S,:,:],grids[1],grid_lat_coords_S,grid_lon_coords_S),pressure_levels,polar_grid_resolution) 175 | # w_S = low_level.beam_me_down(lon,w_S,pole_low_index_S, grid_x_values_S, grid_y_values_S,polar_x_coords_S, polar_y_coords_S) 176 | # w_temp[:pole_low_index_S,:,:] = low_level.combine_data(pole_low_index_S,pole_high_index_S,w[:pole_low_index_S,:,:],w_S,lat) 177 | 178 | return w_temp 179 | 180 | cpdef smoothing_3D(np.ndarray a,DTYPE_f smooth_parameter, DTYPE_f vert_smooth_parameter=0.5): 181 | cdef np.int_t nlat = a.shape[0] 182 | cdef np.int_t nlon = a.shape[1] 183 | cdef np.int_t nlevels = a.shape[2] 184 | smooth_parameter *= 0.5 185 | cdef np.ndarray test = np.fft.fftn(a) 186 | test[int(nlat*smooth_parameter):int(nlat*(1-smooth_parameter)),:,:] = 0 187 | test[:,int(nlon*smooth_parameter):int(nlon*(1-smooth_parameter)),:] = 0 188 | test[:,:,int(nlevels*vert_smooth_parameter):int(nlevels*(1-vert_smooth_parameter))] = 0 189 | return np.fft.ifftn(test).real 190 | 191 | cpdef polar_planes(np.ndarray u,np.ndarray v,np.ndarray u_add,np.ndarray v_add,np.ndarray potential_temperature,np.ndarray geopotential,tuple grid_velocities,tuple indices,tuple grids,tuple coords,np.ndarray coriolis_plane_N,np.ndarray coriolis_plane_S,DTYPE_f grid_side_length,np.ndarray pressure_levels,np.ndarray lat,np.ndarray lon,DTYPE_f dt,DTYPE_f polar_grid_resolution,DTYPE_f gravity,np.int_t sponge_index): 192 | 193 | x_dot_N,y_dot_N,x_dot_S,y_dot_S = grid_velocities[:] 194 | pole_low_index_N,pole_high_index_N,pole_low_index_S,pole_high_index_S = indices[:] 195 | grid_length_N,grid_length_S = grids[:] 196 | grid_lat_coords_N,grid_lon_coords_N,grid_x_values_N,grid_y_values_N,polar_x_coords_N,polar_y_coords_N,grid_lat_coords_S,grid_lon_coords_S,grid_x_values_S,grid_y_values_S,polar_x_coords_S,polar_y_coords_S = coords[:] 197 | 198 | ### north pole ### 199 | 200 | north_geopotential_data = np.flip(geopotential[pole_low_index_N:,:,:],axis=1) 201 | north_polar_plane_geopotential = low_level.beam_me_up(lat[pole_low_index_N:],lon,north_geopotential_data,grid_length_N,grid_lat_coords_N,grid_lon_coords_N) 202 | north_polar_plane_temperature = low_level.beam_me_up(lat[pole_low_index_N:],lon,np.flip(potential_temperature[pole_low_index_N:,:,:],axis=1),grid_length_N,grid_lat_coords_N,grid_lon_coords_N) 203 | 204 | # calculate local velocity on Cartesian grid (CARTESIAN) 205 | x_dot_add,y_dot_add = low_level.grid_velocities(north_polar_plane_geopotential,grid_side_length,coriolis_plane_N,x_dot_N,y_dot_N,polar_grid_resolution,sponge_index,north_polar_plane_temperature,pressure_levels) 206 | 207 | x_dot_add *= dt 208 | y_dot_add *= dt 209 | 210 | x_dot_N += x_dot_add 211 | y_dot_N += y_dot_add 212 | 213 | # project velocities onto polar grid (POLAR) 214 | reproj_u_N, reproj_v_N = low_level.project_velocities_north(lon,x_dot_add,y_dot_add,pole_low_index_N,pole_high_index_N,grid_x_values_N,grid_y_values_N,polar_x_coords_N,polar_y_coords_N) 215 | 216 | # combine velocities with those calculated on polar grid (POLAR) 217 | reproj_u_N = low_level.combine_data(pole_low_index_N,pole_high_index_N,u_add[pole_low_index_N:,:,:],-reproj_u_N,lat) 218 | reproj_v_N = low_level.combine_data(pole_low_index_N,pole_high_index_N,v_add[pole_low_index_N:,:,:],reproj_v_N,lat) 219 | 220 | # add the combined velocities to the global velocity arrays 221 | u_add[pole_low_index_N:,:,:] = reproj_u_N 222 | v_add[pole_low_index_N:,:,:] = reproj_v_N 223 | 224 | ################################################################### 225 | 226 | ### south pole ### 227 | 228 | south_geopotential_data = geopotential[:pole_low_index_S,:,:] 229 | south_polar_plane_geopotential = low_level.beam_me_up(lat[:pole_low_index_S],lon,south_geopotential_data,grid_length_S,grid_lat_coords_S,grid_lon_coords_S) 230 | south_polar_plane_temperature = low_level.beam_me_up(lat[:pole_low_index_S],lon,potential_temperature[:pole_low_index_S,:,:],grid_length_S,grid_lat_coords_S,grid_lon_coords_S) 231 | 232 | x_dot_add,y_dot_add = low_level.grid_velocities(south_polar_plane_geopotential,grid_side_length,coriolis_plane_S,x_dot_S,y_dot_S,polar_grid_resolution,sponge_index,south_polar_plane_temperature,pressure_levels) 233 | 234 | x_dot_add *= dt 235 | y_dot_add *= dt 236 | 237 | x_dot_S += x_dot_add 238 | y_dot_S += y_dot_add 239 | 240 | reproj_u_S, reproj_v_S = low_level.project_velocities_south(lon,x_dot_add,y_dot_add,pole_low_index_S,pole_high_index_S,grid_x_values_S,grid_y_values_S,polar_x_coords_S,polar_y_coords_S) 241 | 242 | reproj_u_S = low_level.combine_data(pole_low_index_S,pole_high_index_S,u_add[:pole_low_index_S,:,:],reproj_u_S,lat) 243 | reproj_v_S = low_level.combine_data(pole_low_index_S,pole_high_index_S,v_add[:pole_low_index_S,:,:],reproj_v_S,lat) 244 | 245 | u_add[:pole_low_index_S,:,:] = reproj_u_S 246 | v_add[:pole_low_index_S,:,:] = reproj_v_S 247 | 248 | return u_add,v_add,x_dot_N,y_dot_N,x_dot_S,y_dot_S 249 | 250 | cpdef update_plane_velocities(np.ndarray lat,np.ndarray lon,np.int_t pole_low_index_N,np.int_t pole_low_index_S,np.ndarray new_u_N,np.ndarray new_v_N,tuple grids,np.ndarray grid_lat_coords_N,np.ndarray grid_lon_coords_N,np.ndarray new_u_S,np.ndarray new_v_S,np.ndarray grid_lat_coords_S,np.ndarray grid_lon_coords_S): 251 | ''' re-project combined velocites to polar plane (prevent discontinuity at the boundary)''' 252 | x_dot_N,y_dot_N = low_level.upload_velocities(lat[pole_low_index_N:],lon,new_u_N,new_v_N,grids[0],grid_lat_coords_N,grid_lon_coords_N) 253 | x_dot_S,y_dot_S = low_level.upload_velocities(lat[:pole_low_index_S],lon,new_u_S,new_v_S,grids[1],grid_lat_coords_S,grid_lon_coords_S) 254 | return x_dot_N,y_dot_N,x_dot_S,y_dot_S 255 | 256 | -------------------------------------------------------------------------------- /tex-docs/topics/velocity.tex: -------------------------------------------------------------------------------- 1 | \section{Air Velocity} \label{sec:velocity} 2 | Did you ever feel the wind blow? Most probably. That's what we will be calculating here. How hard the wind will blow. This is noted as velocity, how fast something moves. 3 | 4 | \subsection{Equation of State and the Incompressible Atmosphere} 5 | The equation of state relates one or more variables in a dynamical system (like the atmosphere) to another. The most common equation of state in the atmosphere is the ideal gas equation as 6 | described by \autoref{eq:ideal gas} \cite{idealGas}. The symbols in that equation represent: 7 | 8 | \begin{itemize} 9 | \item $p$: The gas pressure (\si{Pa}). 10 | \item $V$: The volume of the gas (\si{m^3}). 11 | \item $n$: The amount of moles in the gas (\si{mol}). 12 | \item $R$: The Gas constant as defined in \autoref{sec:gas constant} (\si{JK^{-1}mol^{-1}}) \cite{idealGas}. 13 | \item $T$: The temperature opf the gas ($K$). 14 | \end{itemize} 15 | 16 | If we divide everything in \autoref{eq:ideal gas} by $V$ and set it to be unit (in this case, set it to be exactly $1$ \si{m^3}) we can add in the molar mass in both the top and bottom parts of 17 | the division as show in \autoref{eq:gas unit}. We can then replace $\frac{nm}{V}$ by $\rho$ the density of the gas (\si{kgm^{-3}}) and $\frac{R}{m}$ by $R_s$ the specific gas constant (gas 18 | constant that varies per gas in \si{JK^{-1}mol^{-1}}) as shown in \autoref{eq:state gas}. The resulting equation is the equation of state that you get that most atmospheric physicists use when 19 | talking about the atmosphere \cite{simon}. 20 | 21 | \begin{subequations} 22 | \begin{equation} 23 | \label{eq:ideal gas} 24 | pV = nRT 25 | \end{equation} 26 | \begin{equation} 27 | \label{eq:gas unit} 28 | p = \frac{nR}{V}T = \frac{nmR}{Vm}T 29 | \end{equation} 30 | \begin{equation} 31 | \label{eq:state gas} 32 | p = \rho R_sT 33 | \end{equation} 34 | \end{subequations} 35 | 36 | The pressure is quite important, as air moves from a high pressure point to a low pressure point. So if we know the density and the temperature, then we know the pressure and we can work out 37 | where the air will be moving to (i.e. how the wind will blow). In our current model, we know the atmospheric temperature but we do not know the density. For simplicities sake, we will now assume 38 | that the atmosphere is Incompressible, meaning that we have a constant density. Obviously we know that air can be compressed and hence our atmosphere can be compressed too but that is not 39 | important enough to account for yet, especially considering the current complexity of our model. 40 | 41 | The code that corresponds to this is quite simple, the only change that we need to make in \autoref{eq:state gas} is that we need to replace $T$ by $T_a$, the temperature of the atmosphere. As 42 | $T_a$ is a matrix (known to programmers as a double array), $p$ will be a matrix as well. Now we only need to fill in some values. $\rho = 1.2$\cite{densityAir}, $R_s = 287$\cite{specificGasConstantAir}. 43 | 44 | \subsection{The Momentum Equations} \label{sec:momentum} 45 | The momentum equations are a set of equations that describe the flow of a fluid on the surface of a rotating body. For our model we will use the f-plane approximation. The equations corresponding 46 | to the f-plane approximation are given in \autoref{eq:x momentum} and \autoref{eq:y momentum} \cite{momentumeqs}. Note that we are ignoring vertical movement, as this does not have a significant 47 | effect on the whole flow. All the symbols in \autoref{eq:x momentum} and \autoref{eq:y momentum} mean: 48 | 49 | \begin{itemize} 50 | \item $u$: The east to west velocity (\si{ms^{-1}}). 51 | \item $t$: The time (\si{s}). 52 | \item $f$: The coriolis parameter as in \autoref{eq:coriolis}. 53 | \item $v$: The north to south velocity (\si{ms^{-1}}). 54 | \item $\rho$: The density of the atmosphere (\si{kgm^{-3}}). 55 | \item $p$: The atmospheric pressure (\si{Pa}). 56 | \item $x$: The local longitude coordinate (\si{m}). 57 | \item $y$: The local latitude coordinate (\si{m}). 58 | \end{itemize} 59 | 60 | If we then define a vector $\bar{u}$ as $(u, v, 0)$, we can rewrite both \autoref{eq:x momentum} as \autoref{eq:x momentum laplace}. Here $\nabla u$ is the gradient of $u$ in both $x$ and $y$ 61 | directions. Then if we write out $\nabla u$ we get \autoref{eq:x momentum final}. Similarly, if we want to get $\partial v$ instead of $\partial u$ we rewrite \autoref{eq:y momentum} to get 62 | \autoref{eq:y momentum laplace} and \autoref{eq:y momentum final}. 63 | 64 | \begin{subequations} 65 | \begin{equation} 66 | \label{eq:x momentum} 67 | \frac{Du}{Dt} - fv = -\frac{1}{\rho} \frac{\partial p}{\partial x} 68 | \end{equation} 69 | \begin{equation} 70 | \label{eq:y momentum} 71 | \frac{Dv}{Dt} - fu = -\frac{1}{\rho} \frac{\partial p}{\partial y} 72 | \end{equation} 73 | \begin{equation} 74 | \label{eq:x momentum laplace} 75 | \frac{\partial u}{\partial t} + \bar{u} \cdot \nabla u - fv = -\frac{1}{\rho}\frac{\partial p}{\partial x} 76 | \end{equation} 77 | \begin{equation} 78 | \label{eq:y momentum laplace} 79 | \frac{\partial v}{\partial t} + \bar{u} \cdot \nabla v - fu = -\frac{1}{\rho}\frac{\partial p}{\partial y} 80 | \end{equation} 81 | \begin{equation} 82 | \label{eq:x momentum final} 83 | \frac{\partial u}{\partial t} + u\frac{\partial u}{\partial x} + v\frac{\partial u}{\partial y} - fv = -\frac{1}{\rho}\frac{\partial p}{\partial x} 84 | \end{equation} 85 | \begin{equation} 86 | \label{eq:y momentum final} 87 | \frac{\partial v}{\partial t} + u\frac{\partial v}{\partial x} + v\frac{\partial v}{\partial y} - fu = -\frac{1}{\rho}\frac{\partial p}{\partial y} 88 | \end{equation} 89 | \end{subequations} 90 | 91 | With the gradient functions defined in \autoref{alg:gradient x} and \autoref{alg:gradient y}, we can move on to the main code for the momentum equations. The main loop is shown in 92 | \autoref{alg:stream3}. Do note that this loop replaces the one in \autoref{alg:stream2v2} as these calculate the same thing, but the new algorithm does it better. 93 | 94 | \begin{algorithm} 95 | \caption{Calculating the flow of the atmosphere (wind)} 96 | \label{alg:stream3} 97 | $S_{xu} \leftarrow \texttt{gradient\_x}(u, lat, lon)$ \; 98 | $S_{yu} \leftarrow \texttt{gradient\_y}(u, lat, lon)$ \; 99 | $S_{xv} \leftarrow \texttt{gradient\_x}(v, lat, lon)$ \; 100 | $S_{yv} \leftarrow \texttt{gradient\_y}(v, lat, lon)$ \; 101 | $S_{px} \leftarrow \texttt{gradient\_x}(p, lat, lon)$ \; 102 | $S_{py} \leftarrow \texttt{gradient\_x}(p, lat, lon)$ \; 103 | \For{$lat \leftarrow 1$ \KwTo $nlat - 1$}{ 104 | \For{$lon \leftarrow 0$ \KwTo $nlon$}{ 105 | $u[lat, lon] \leftarrow u[lat, lon] + \delta t \frac{-u[lat, lon]S_{xu} - v[lat, lon]S_{yu} + f[lat]v[lat, lon] - S_{px}}{\rho}$ \; 106 | $v[lat, lon] \leftarrow v[lat, lon] + \delta t \frac{-u[lat, lon]S_{xv} - v[lat, lon]S_{yv} - f[lat]u[lat, lon] - S_{py}}{\rho}$ \; 107 | } 108 | } 109 | \end{algorithm} 110 | 111 | \subsection{Improving the Coriolis Parameter} 112 | Another change introduced is in the coriolis parameter. Up until now it has been a constant, however we know that it varies along the latitude. So let's make it vary over the latitude. Recall 113 | \autoref{eq:coriolis}, where $\Theta$ is the latitude. Coriolis ($f$) is currently defined in \autoref{alg:gradient}, so let's replace it with \autoref{alg:coriolis}. 114 | 115 | \begin{algorithm} 116 | \caption{Calculating the coriolis force} 117 | \label{alg:coriolis} 118 | \SetAlgoLined 119 | $\Omega \leftarrow 7.2921 \cdot 10^{-5}$ \; 120 | 121 | \For{$lat \leftarrow -nlat$ \KwTo $nlat$}{ 122 | $f[lat] \leftarrow 2\Omega \sin(lat \frac{\pi}{180})$ \; 123 | } 124 | \end{algorithm} 125 | 126 | \subsection{Adding Friction} 127 | In order to simulate friction, we multiply the speeds $u$ and $v$ by $0.99$. Of course there are equations for friction but that gets complicated very fast, so instead we just assume that we 128 | have a constant friction factor. This multiplication is done directly after \autoref{alg:stream3} in \autoref{alg:stream4v1}. 129 | 130 | \subsection{Adding in Layers} 131 | With adding in atmospheric layers we need to add vertical winds, or in other words add the $w$ component of the velocity vectors. We do that by editing \autoref{alg:stream3}. We change it to 132 | \autoref{alg:velocity}. Here we use gravity ($g$) instead of the coriolis force ($f$) and calculate the change in pressure. Therefore we need to store a copy of the pressure before we do any 133 | calculations. This needs to be a copy due to aliasing \footnote{Aliasing is assigning a different name to a variable, while it remains the same variable. Take for instance that we declare a 134 | variable $x$ and set it to be $4$. Then we say $y \leftarrow x$, which you might think is the same as saying they $y \leftarrow 4$ but behind the screen it is pointing to $x$. So if $x$ changes, 135 | then so does $y$.}. Since we use pressure as the vertical coordinate, we must be able to convert that into meters (why we opted for pressure is explained in \autoref{sec:rad layers}) in order to 136 | be able to say something sensible about it. To do that we need the concept of geopotential height. 137 | 138 | \subsubsection{Dimensionless Pressure} 139 | Geopotential height is similar to geometric height, except that it also accounts for the variation in gravity over the planet \cite{geopot}. One could say that geopotential height is the 140 | "gravity adjusted" height. That means that it is similar to the height, but not exactly the same. Height is a linear function, whereas the geopotential height is not, though it is very similar 141 | to a linear function if you would plot it. Now to convert easily to and from potential temperature into temperature, we need another function which is known as the Exner function. The Exner 142 | function is a dimensionless \footnote{Being dimensionless means that there is no dimension (unit) attached to the number. This is useful for many applications and is even used in daily life. For 143 | instance when comparing price rises of different products, it is way more useful to talk about percentages (who are unitless) instead of how much you physically pay more (with your favourite 144 | currency as the unit).} pressure. The Exner function is shown in \autoref{eq:exner} \cite{verticalcoords}. The symbols in the equation are: 145 | 146 | \begin{itemize} 147 | \item $c_p$: The specific heat capacity of the atmosphere. 148 | \item $p$: Pressure (\si{Pa}). 149 | \item $p_0$: Reference pressure to define the potential temperature (\si{Pa}). 150 | \item $R$: The gas constant $8.3144621$ (\si{J(mol)^{-1}K}). 151 | \item $T$: The absolute temperature (\si{K}). 152 | \item $\theta$: the potential temperature (\si{K}). 153 | \end{itemize} 154 | 155 | Since the right hand side contains what we want to convert to and from, we can do some basic rewriting, which tells us what we need to code to convert potential temperature in absolute 156 | temperature and vice versa. This is shown in \autoref{eq:temp exner} and \autoref{eq:potential temp exner} respectively. 157 | 158 | \begin{subequations} 159 | \begin{equation} 160 | \label{eq:exner} 161 | \Pi = c_p(\frac{p}{p_0})^{\frac{R}{c_p}} = \frac{T}{\theta} 162 | \end{equation} 163 | \begin{equation} 164 | \label{eq:temp exner} 165 | T = \Pi\theta 166 | \end{equation} 167 | \begin{equation} 168 | \label{eq:potential temp exner} 169 | \theta = \frac{T}{\Pi} 170 | \end{equation} 171 | \end{subequations} 172 | 173 | Now onto some code. Let us initialise $\Pi$ before we do any other calculations. This code is already present in the control panel section (\autoref{sec:cp}) as that is where it belongs, so for 174 | further details please have a look at the code there. Now onto the geopotential height. 175 | 176 | \subsubsection{Geopotential Height} 177 | As stated before, geopotential height is similar to geometric height, except that it also accounts for the variation in gravity over the planet. One could say that geopotential height is the 178 | "gravity adjusted" height. That means that it is similar to the height, but not exactly the same. Height is a linear function, whereas the geopotential height is not, though it is very similar 179 | to a linear function if you would plot it. Now one could ask why we would discuss dimensionless pressure before geopotential height. The answer is quite simple, in order to define geopotential 180 | height, we need the Exner function to define it. Or rather, we need that function to convert potential temperature into geopotential height. How those three are related is shown in 181 | \autoref{eq:geopot}. Then with a little transformation we can define how the geopotential height will look like, as shown in \autoref{eq:geopot final}. The symbols in both equations are: 182 | 183 | \begin{itemize} 184 | \item $\Pi$: The Exner function. 185 | \item $\theta$: Potential temperature (\si{K}). 186 | \end{itemize} 187 | 188 | \begin{subequations} 189 | \begin{equation} 190 | \label{eq:geopot} 191 | \theta + \frac{\delta\Phi}{\delta\Pi} = 0 192 | \end{equation} 193 | \begin{equation} 194 | \label{eq:geopot final} 195 | \delta\Phi = -\theta\delta\Pi 196 | \end{equation} 197 | \end{subequations} 198 | 199 | Now to turn this into code we need to be careful about a few things. First we are talking about a change in geopotential height here, so defining one level of geopotential height means that it 200 | is dependent on the level below it. Second this calculation needs potential temperature and therefore it should be passed along to the velocity calculations function. With those two things out 201 | of the way, we get the code as shown in \autoref{alg:geopot}. Note that \texttt{Smooth3D} refers to \autoref{alg:smooth}. 202 | 203 | \begin{algorithm} 204 | \caption{Calculating the geopotential height} 205 | \label{alg:geopot} 206 | \For{$level \leftarrow 1$ \KwTo $nlevels$}{ 207 | $\Phi[:, :, level] \leftarrow \Phi[:, :, level - 1] - T_{pot}[:, :, level](\Pi[level] - \Pi[level - 1])$ \; 208 | } 209 | $\Phi \leftarrow \texttt{Smooth3D}(\Phi, smooth_t)$ \; 210 | \end{algorithm} 211 | 212 | \subsubsection{Finally Adding in the Layers} 213 | Now with the geopotential height and dimensionless pressure out of the way, we need to use those two concepts to add layers to the velocity calculations. Before we dive into the code however, 214 | there are slight changes that we need to discuss. The equation shown in \autoref{eq:velocity} is the primitive equation (as discussed in \autoref{sec:primitive}). The momentum equations are 215 | gesostrphic momentum, which are a special form of the primitive equation. Since this whole system must remain in equilibrium, we need to set the right hand side to $0$ as shown in 216 | \autoref{eq:vel eq}. Now let us rewrite \autoref{eq:velocity} into \autoref{eq:velocity int}. We replaze $z$ with pressure as that is our vertical coordinate. $\omega$ is the velocity of the 217 | pressure field, as defined in \autoref{eq:vert vel}. Note that $p_k$ is the pressure for layer $k$ and $p_0$ is the pressure at the planet surface. Now we need to turn the velocity of the 218 | pressure field into the velocity of a packet of air (see it as a box of air being moved), which is done in \autoref{eq:vertical velocity}. Here $\rho$ is the density and $g$ is gravity 219 | (\si{ms^{-2}}). 220 | 221 | \begin{subequations} 222 | \begin{equation} 223 | \label{eq:velocity} 224 | \frac{\delta T}{\delta x} + \frac{\delta T}{ \delta y} + \frac{\delta T}{\delta z} = \nabla T 225 | \end{equation} 226 | \begin{equation} 227 | \label{eq:vel eq} 228 | \nabla T = 0 229 | \end{equation} 230 | \begin{equation} 231 | \label{eq:velocity int} 232 | \frac{\delta u}{\delta x} + \frac{\delta v}{\delta y} + \frac{\delta\omega}{\delta p} = 0 233 | \end{equation} 234 | \begin{equation} 235 | \label{eq:vert vel} 236 | \omega_k = -\int^{p_k}_{p_0}\frac{\delta u}{\delta x} + \frac{\delta v}{\delta y} dp 237 | \end{equation} 238 | \begin{equation} 239 | \label{eq:vertical velocity} 240 | w = \omega \rho g 241 | \end{equation} 242 | \end{subequations} 243 | 244 | But I hear you say, what is the density? Since we have moved to pressure coordinates we can actually calculate the density rather than store it. This is done in \autoref{eq:density}, where each 245 | symbol means: 246 | 247 | \begin{itemize} 248 | \item $\rho$: The density of the atmosphere. 249 | \item $p$: The pressure of the atmosphere (\si{Pa}). 250 | \item $c$: Specific heat capacity of the atmosphere (\si{JKg^{-1}K^{-1}}). 251 | \item $T$: Temperature of the atmosphere (\si{K}). 252 | \end{itemize} 253 | 254 | \begin{equation} 255 | \label{eq:density} 256 | \rho = \frac{p}{cT} 257 | \end{equation} 258 | 259 | Finally, let us convert \autoref{eq:vertical velocity} to code in \autoref{alg:velocity}. Here $T_{trans}$ is a call to the algorithm as described in \autoref{alg:temp to pot}. 260 | \texttt{gradient\_x}, \texttt{gradient\_y} and \texttt{gradient\_z} are calls to \autoref{alg:gradient x}, \autoref{alg:gradient y} and \autoref{alg:gradient z} respectively. 261 | 262 | \begin{algorithm} 263 | \caption{Calculating the flow of the atmosphere (wind)} 264 | \label{alg:velocity} 265 | //\texttt{The following variables are function calls to algorithms and their shorthand notations are used in the loops}\\ 266 | $S_{xu} \leftarrow \texttt{gradient\_x}(u, lat, lon, layer)$ \; 267 | $S_{yu} \leftarrow \texttt{gradient\_y}(u, lat, lon, layer)$ \; 268 | $S_{xv} \leftarrow \texttt{gradient\_x}(v, lat, lon, layer)$ \; 269 | $S_{yv} \leftarrow \texttt{gradient\_y}(v, lat, lon, layer)$ \; 270 | $S_{zu} \leftarrow \texttt{gradient\_z}(u[lat, lon], p_z, layer)$ \; 271 | $S_{zv} \leftarrow \texttt{gradient\_z}(v[lat, lon], p_z, layer)$ \; 272 | $S_{px} \leftarrow \texttt{gradient\_x}(geopot, lat, lon, layer)$ \; 273 | $S_{py} \leftarrow \texttt{gradient\_y}(geopot, lat, lon, layer)$ \; 274 | 275 | //\texttt{The following variables are real variables}\\ 276 | $nlat \leftarrow \Phi.length$ \; 277 | $nlon \leftarrow \Phi[0].length$ \; 278 | $u_t \leftarrow $ array like $u$ \; 279 | $v_t \leftarrow $ array like $v$ \; 280 | $w_t \leftarrow $ array like $w$ \; 281 | \For{$lat \leftarrow 1$ \KwTo $nlat - 1$}{ 282 | \For{$lon \leftarrow 0$ \KwTo $nlon$}{ 283 | \For{$layer \leftarrow 0$ \KwTo $nlevels$}{ 284 | $u_t[lat, lon, layer] \leftarrow u[lat, lon, layer] + \delta t \frac{-u[lat, lon, layer]S_{xu} - v[lat, lon, layer]S_{yu} + f[lat]v[lat, lon, layer] - S_{px}} 285 | {10^5u[lat, lon, layer]}$ \; 286 | $v_t[lat, lon, layer] \leftarrow v[lat, lon, layer] + \delta t \frac{-u[lat, lon, layer]S_{xv} - v[lat, lon, layer]S_{yv} - f[lat]u[lat, lon, layer] - S_{py}} 287 | {10^5v[lat, lon, layer]}$ \; 288 | } 289 | } 290 | } 291 | 292 | $T_a \leftarrow T_{trans}(T_{pot}, p_z, \texttt{False})$ \; 293 | 294 | \For{$lat \leftarrow 2$ \KwTo $nlat - 2$}{ 295 | \For{$lon \leftarrow 0$ \KwTo $nlon$}{ 296 | \For{$level \leftarrow 1$ \KwTo $nlevels$}{ 297 | $w_t[lat, lon, level] \leftarrow w_t[lat, lon, level - 1] - \frac{(p_z[level] - p_z[level - 1])p_z[level]g(S_{xu} + S_{yv})}{C_pT_a[lat, lon, layer]}$ \; 298 | } 299 | } 300 | } 301 | 302 | $u \leftarrow u + u_t$ \; 303 | $v \leftarrow v + v_t$ \; 304 | $w \leftarrow w + w_t$ \; 305 | $p_0 \leftarrow copy(p)$ \; 306 | \end{algorithm} -------------------------------------------------------------------------------- /claude_low_level_library.pyx: -------------------------------------------------------------------------------- 1 | # claude low level library 2 | 3 | import numpy as np 4 | cimport numpy as np 5 | cimport cython 6 | from scipy.interpolate import interp2d, RectBivariateSpline 7 | 8 | ctypedef np.float64_t DTYPE_f 9 | cdef float inv_180 = np.pi/180 10 | cdef float inv_90 = np.pi/90 11 | cdef DTYPE_f sigma = 5.67E-8 12 | 13 | # define various useful differential functions: 14 | # gradient of scalar field a in the local x direction at point i,j 15 | cpdef scalar_gradient_x(np.ndarray a, np.ndarray dx, np.int_t nlon, np.int_t i, np.int_t j, np.int_t k): 16 | return (a[i,(j+1)%nlon,k]-a[i,(j-1)%nlon,k])/(dx[i]) 17 | 18 | cpdef scalar_gradient_x_matrix(np.ndarray a,np.ndarray dx): 19 | cdef np.ndarray output = (np.roll(a, -1, axis=1) - np.roll(a, 1, axis=1)) / dx[:, None, None] 20 | return output 21 | 22 | cpdef scalar_gradient_x_matrix_primitive(np.ndarray a,np.ndarray dx): 23 | cdef np.ndarray output = np.zeros_like(a) 24 | cdef np.int_t i,j,k 25 | for i in range(a.shape[0]): 26 | for j in range(a.shape[1]): 27 | for k in range(a.shape[2]): 28 | output[i,j,k] = scalar_gradient_x(a,dx,a.shape[1],i,j,k) 29 | 30 | output[0,:,:] *= 0 31 | output[-1,:,:] *= 0 32 | return output 33 | 34 | cpdef scalar_gradient_x_2D(np.ndarray a,np.ndarray dx,np.int_t nlon,np.int_t i,np.int_t j): 35 | return (a[i,(j+1)%nlon]-a[i,(j-1)%nlon])/dx[i] 36 | 37 | # gradient of scalar field a in the local y direction at point i,j 38 | cpdef scalar_gradient_y(np.ndarray a,DTYPE_f dy,np.int_t nlat,np.int_t i,np.int_t j,np.int_t k): 39 | if i == 0: 40 | return 2*(a[i+1,j,k]-a[i,j,k])/dy 41 | elif i == nlat-1: 42 | return 2*(a[i,j,k]-a[i-1,j,k])/dy 43 | else: 44 | return (a[i+1,j,k]-a[i-1,j,k])/dy 45 | 46 | cpdef scalar_gradient_y_matrix(np.ndarray a,DTYPE_f dy): 47 | shift_south = np.pad(a, ((1,0), (0,0), (0,0)), 'reflect', reflect_type='odd')[:-1,:,:] 48 | shift_north = np.pad(a, ((0,1), (0,0), (0,0)), 'reflect', reflect_type='odd')[1:,:,:] 49 | return (shift_north - shift_south)/dy 50 | 51 | cpdef scalar_gradient_y_matrix_primitive(np.ndarray a,DTYPE_f dy): 52 | cdef np.ndarray output = np.zeros_like(a) 53 | cdef np.int_t i,j,k 54 | for i in range(a.shape[0]): 55 | for j in range(a.shape[1]): 56 | for k in range(a.shape[2]): 57 | output[i,j,k] = scalar_gradient_y(a,dy,a.shape[0],i,j,k) 58 | return output 59 | 60 | cpdef scalar_gradient_y_2D(np.ndarray a,DTYPE_f dy,np.int_t nlat,np.int_t i,np.int_t j): 61 | if i == 0: 62 | return 2*(a[i+1,j]-a[i,j])/dy 63 | elif i == nlat-1: 64 | return 2*(a[i,j]-a[i-1,j])/dy 65 | else: 66 | return (a[i+1,j]-a[i-1,j])/dy 67 | 68 | cpdef scalar_gradient_z_1D(np.ndarray a,np.ndarray pressure_levels,np.int_t k): 69 | cdef np.int_t nlevels = len(pressure_levels) 70 | if k == 0: 71 | return -(a[k+1]-a[k])/(pressure_levels[k+1]-pressure_levels[k]) 72 | elif k == nlevels-1: 73 | return -(a[k]-a[k-1])/(pressure_levels[k]-pressure_levels[k-1]) 74 | else: 75 | return -(a[k+1]-a[k-1])/(pressure_levels[k+1]-pressure_levels[k-1]) 76 | 77 | cpdef scalar_gradient_z_3D(np.ndarray a,np.ndarray pressure_levels,np.int_t k): 78 | cdef np.int_t nlevels = len(pressure_levels) 79 | if k == 0: 80 | return -(a[:,:,k+1]-a[:,:,k])/(pressure_levels[k+1]-pressure_levels[k]) 81 | elif k == nlevels-1: 82 | return -(a[:,:,k]-a[:,:,k-1])/(pressure_levels[k]-pressure_levels[k-1]) 83 | else: 84 | return -(a[:,:,k+1]-a[:,:,k-1])/(pressure_levels[k+1]-pressure_levels[k-1]) 85 | 86 | cpdef scalar_gradient_z_matrix(np.ndarray a, np.ndarray pressure_levels): 87 | shift_up = np.pad(a, ((0,0), (0,0), (1,0)), 'edge')[:,:,:-1] 88 | shift_down = np.pad(a, ((0,0), (0,0), (0,1)), 'edge')[:,:,1:] 89 | shift_pressure_up = np.pad(pressure_levels, (1,0), 'edge')[:-1] 90 | shift_pressure_down = np.pad(pressure_levels, (0,1), 'edge')[1:] 91 | return - (shift_down - shift_up) / (shift_pressure_down - shift_pressure_up) 92 | 93 | cpdef scalar_gradient_z_matrix_primitive(np.ndarray a, np.ndarray pressure_levels): 94 | cdef np.ndarray output = np.zeros_like(a) 95 | cdef np.int_t i,j,k 96 | for k in range(a.shape[2]): 97 | output[:,:,k] = scalar_gradient_z_3D(a,pressure_levels,k) 98 | return output 99 | 100 | cpdef surface_optical_depth(DTYPE_f lat): 101 | return 4# + np.cos(lat*inv_90)*2 102 | 103 | cpdef surface_optical_depth_array(np.ndarray lat): 104 | return np.full_like(lat, 4)# + np.cos(lat*inv_90)*2 105 | 106 | cpdef thermal_radiation(DTYPE_f a): 107 | return sigma*(a**4) 108 | 109 | cpdef thermal_radiation_matrix(np.ndarray a): 110 | return sigma*(a**4) 111 | 112 | # power incident on (lat,lon) at time t 113 | cpdef solar(DTYPE_f insolation,DTYPE_f lat,DTYPE_f lon,np.int_t t,DTYPE_f day,DTYPE_f year,DTYPE_f axial_tilt): 114 | cdef float sun_longitude = -t % day 115 | cdef float sun_latitude = axial_tilt*np.cos(t*2*np.pi/year) 116 | cdef float value = insolation*np.cos((lat-sun_latitude)*inv_180) 117 | cdef float lon_diff, cos_lon 118 | 119 | if value < 0: 120 | return 0 121 | else: 122 | sun_longitude *= 360/day 123 | lon_diff = lon-sun_longitude 124 | cos_lon = np.cos(lon_diff*inv_180) 125 | value *= cos_lon 126 | 127 | if value < 0: 128 | if lat + sun_latitude > 90: 129 | return insolation*np.cos((lat+sun_latitude)*inv_180)*cos_lon 130 | elif lat + sun_latitude < -90: 131 | return insolation*np.cos((lat+sun_latitude)*inv_180)*cos_lon 132 | else: 133 | return 0 134 | else: 135 | return value 136 | 137 | cpdef solar_matrix(DTYPE_f insolation, np.ndarray lat, np.ndarray lon, np.int_t t, DTYPE_f day, DTYPE_f year, DTYPE_f axial_tilt): 138 | cdef float sun_longitude = -t % day 139 | # cdef float sun_latitude = axial_tilt*np.cos(t*2*np.pi/year) 140 | cdef float sun_latitude = axial_tilt*np.sin(t*2*np.pi/year) 141 | cdef np.ndarray values = insolation*np.cos((lat-sun_latitude)*inv_180) 142 | cdef np.ndarray lon_diff, cos_lon 143 | 144 | values = np.fmax(values,0) 145 | 146 | sun_longitude *= 360/day 147 | lon_diff = lon-sun_longitude 148 | cos_lon = np.cos(lon_diff*inv_180) 149 | values = np.outer(values, cos_lon) 150 | 151 | cdef np.ndarray in_range_mask = np.logical_and((lat + sun_latitude > -90), (lat + sun_latitude < 90)) 152 | cdef np.ndarray mask1 = np.logical_and(values < 0, np.logical_not(in_range_mask)[:,None]) 153 | cdef np.ndarray mask2 = np.logical_and(values < 0, in_range_mask[:,None]) 154 | cdef np.int_t i 155 | for i in range(lat.shape[0]): 156 | values[i,:][mask1[i,:]] = insolation*np.cos((lat[i]+sun_latitude)*inv_180)*cos_lon[mask1[i,:]] 157 | values[i,:][mask2[i,:]] = 0 158 | 159 | return values 160 | 161 | cpdef profile(np.ndarray a): 162 | return np.mean(np.mean(a,axis=0),axis=0) 163 | 164 | cpdef t_to_theta(np.ndarray temperature_atmos, np.ndarray pressure_levels): 165 | cdef DTYPE_f inv_p0 = 1/pressure_levels[0] 166 | 167 | return temperature_atmos*(pressure_levels*inv_p0)**(-0.286) 168 | 169 | cpdef theta_to_t(np.ndarray theta, np.ndarray pressure_levels): 170 | cdef DTYPE_f inv_p0 = 1/pressure_levels[0] 171 | 172 | return theta*(pressure_levels*inv_p0)**(0.286) 173 | 174 | ######################################################################################################## 175 | 176 | cpdef beam_me_up_2D(np.ndarray lats,np.ndarray lon,np.ndarray data,np.int_t grid_size,np.ndarray grid_lat_coords,np.ndarray grid_lon_coords): 177 | '''Projects data on lat-lon grid to x-y polar grid''' 178 | f = RectBivariateSpline(lats, lon, data) 179 | cdef np.ndarray polar_plane = f(grid_lat_coords,grid_lon_coords,grid=False).reshape((grid_size,grid_size)) 180 | return polar_plane 181 | 182 | cpdef beam_me_up(np.ndarray lats,np.ndarray lon,np.ndarray data,np.int_t grid_size,np.ndarray grid_lat_coords,np.ndarray grid_lon_coords): 183 | '''Projects data on lat-lon grid to x-y polar grid''' 184 | cdef np.ndarray polar_plane = np.zeros((grid_size,grid_size,data.shape[2])) 185 | cdef np.int_t k 186 | for k in range(data.shape[2]): 187 | f = RectBivariateSpline(lats, lon, data[:,:,k]) 188 | polar_plane[:,:,k] = f(grid_lat_coords,grid_lon_coords,grid=False).reshape((grid_size,grid_size)) 189 | return polar_plane 190 | 191 | cpdef beam_me_down(lon,data,np.int_t pole_low_index, grid_x_values, grid_y_values,polar_x_coords, polar_y_coords): 192 | '''projects data from x-y polar grid onto lat-lon grid''' 193 | cdef np.ndarray resample = np.zeros((int(len(polar_x_coords)/len(lon)),len(lon),data.shape[2])) 194 | cdef np.int_t k 195 | for k in range(data.shape[2]): 196 | f = RectBivariateSpline(x=grid_x_values, y=grid_y_values, z=data[:,:,k]) 197 | resample[:,:,k] = f(polar_x_coords,polar_y_coords,grid=False).reshape((int(len(polar_x_coords)/len(lon)),len(lon))) 198 | return resample 199 | 200 | cpdef combine_data(np.int_t pole_low_index,np.int_t pole_high_index,np.ndarray polar_data,np.ndarray reprojected_data,np.ndarray lat): 201 | cdef np.ndarray output = np.zeros_like(polar_data) 202 | cdef np.int_t overlap = abs(pole_low_index - pole_high_index) 203 | cdef DTYPE_f scale_reprojected_data, scale_polar_data 204 | cdef np.int_t nlat = len(lat) 205 | cdef np.int_t k,i 206 | 207 | if lat[pole_low_index] < 0: # SOUTH POLE 208 | for k in range(output.shape[2]): 209 | for i in range(pole_low_index): 210 | 211 | if i < pole_high_index: 212 | scale_polar_data = 0.0 213 | scale_reprojected_data = 1.0 214 | else: 215 | scale_polar_data = (i+1-pole_high_index)/overlap 216 | scale_reprojected_data = 1 - (i+1-pole_high_index)/overlap 217 | 218 | output[i,:,k] = scale_reprojected_data*reprojected_data[i,:,k] + scale_polar_data*polar_data[i,:,k] 219 | 220 | else: # NORTH POLE 221 | # polar_data = np.roll(polar_data,int(polar_data.shape[1]/2),axis=1) 222 | for k in range(output.shape[2]): 223 | for i in range(nlat-pole_low_index): 224 | 225 | if i + pole_low_index + 1 > pole_high_index: 226 | scale_polar_data = 0.0 227 | scale_reprojected_data = 1.0 228 | else: 229 | scale_polar_data = 1 - i/overlap 230 | scale_reprojected_data = i/overlap 231 | 232 | output[i,:,k] = scale_reprojected_data*reprojected_data[i,:,k] + scale_polar_data*polar_data[i,:,k] 233 | return output 234 | 235 | cpdef grid_x_gradient_matrix(np.ndarray data,DTYPE_f polar_grid_resolution): 236 | cdef np.ndarray shift_east = np.pad(data, ((0,0), (1,0), (0,0)), 'reflect', reflect_type='odd')[:,:-1,:] 237 | cdef np.ndarray shift_west = np.pad(data, ((0,0), (0,1), (0,0)), 'reflect', reflect_type='odd')[:,1:,:] 238 | return (shift_west - shift_east) / (2 * polar_grid_resolution) 239 | 240 | cpdef grid_y_gradient_matrix(np.ndarray data,DTYPE_f polar_grid_resolution): 241 | cdef np.ndarray shift_south = np.pad(data, ((1,0), (0,0), (0,0)), 'reflect', reflect_type='odd')[:-1,:,:] 242 | cdef np.ndarray shift_north = np.pad(data, ((0,1), (0,0), (0,0)), 'reflect', reflect_type='odd')[1:,:,:] 243 | return (shift_north - shift_south) / (2 * polar_grid_resolution) 244 | 245 | cpdef grid_p_gradient_matrix(np.ndarray data, np.ndarray pressure_levels): 246 | cpdef np.ndarray shift_up = np.pad(data, ((0,0), (0,0), (1,0)), 'edge')[:,:,:-1] 247 | cpdef np.ndarray shift_down = np.pad(data, ((0,0), (0,0), (0,1)), 'edge')[:,:,1:] 248 | cpdef np.ndarray shift_pressures_up = np.pad(pressure_levels, (1,0), 'edge')[:-1] 249 | cpdef np.ndarray shift_pressures_down = np.pad(pressure_levels, (0,1), 'edge')[1:] 250 | 251 | return (shift_down - shift_up)/(shift_pressures_down - shift_pressures_up) 252 | 253 | cpdef grid_velocities(np.ndarray polar_plane,np.int_t grid_side_length,np.ndarray coriolis_plane,np.ndarray x_dot,np.ndarray y_dot,DTYPE_f polar_grid_resolution, np.int_t sponge_index, np.ndarray temperature, np.ndarray pressure_levels): 254 | 255 | cdef np.ndarray x_dot_add = np.zeros_like(x_dot) 256 | cdef np.ndarray y_dot_add = np.zeros_like(y_dot) 257 | 258 | # cdef np.ndarray w = w_plane(x_dot,y_dot,temperature,pressure_levels,polar_grid_resolution) 259 | 260 | x_dot_add -= 0.5*(x_dot + abs(x_dot))*(x_dot - np.pad(x_dot,((0,0), (1,0), (0,0)), 'reflect', reflect_type='odd')[:,:-1,:])/polar_grid_resolution + 0.5*(x_dot - abs(x_dot))*(np.pad(x_dot, ((0,0), (0,1), (0,0)), 'reflect', reflect_type='odd')[:,1:,:] - x_dot)/polar_grid_resolution 261 | x_dot_add -= 0.5*(y_dot + abs(y_dot))*(x_dot - np.pad(x_dot,((1,0), (0,0), (0,0)), 'reflect', reflect_type='odd')[:-1,:,:])/polar_grid_resolution + 0.5*(y_dot - abs(y_dot))*(np.pad(x_dot, ((0,1), (0,0), (0,0)), 'reflect', reflect_type='odd')[1:,:,:] - x_dot)/polar_grid_resolution 262 | # x_dot_add -= 0.5*(w + abs(w))*(x_dot - np.pad(x_dot,((0,0), (0,0), (1,0)), 'reflect', reflect_type='odd')[:,:,:-1])/(pressure_levels - np.pad(pressure_levels,(1,0), 'reflect', reflect_type='odd')[:-1]) + 0.5*(w - abs(w))*(np.pad(x_dot, ((0,0), (0,0), (0,1)), 'reflect', reflect_type='odd')[:,:,1:] - x_dot)/(np.pad(pressure_levels,(0,1), 'reflect', reflect_type='odd')[1:] - pressure_levels) 263 | x_dot_add += coriolis_plane[:,:,None]*y_dot - grid_x_gradient_matrix(polar_plane,polar_grid_resolution) - 1E-5*x_dot 264 | 265 | y_dot_add -= 0.5*(x_dot + abs(x_dot))*(y_dot - np.pad(y_dot,((0,0), (1,0), (0,0)), 'reflect', reflect_type='odd')[:,:-1,:])/polar_grid_resolution + 0.5*(x_dot - abs(x_dot))*(np.pad(y_dot, ((0,0), (0,1), (0,0)), 'reflect', reflect_type='odd')[:,1:,:] - y_dot)/polar_grid_resolution 266 | y_dot_add -= 0.5*(y_dot + abs(y_dot))*(y_dot - np.pad(y_dot,((1,0), (0,0), (0,0)), 'reflect', reflect_type='odd')[:-1,:,:])/polar_grid_resolution + 0.5*(y_dot - abs(y_dot))*(np.pad(y_dot, ((0,1), (0,0), (0,0)), 'reflect', reflect_type='odd')[1:,:,:] - y_dot)/polar_grid_resolution 267 | # x_dot_add -= 0.5*(w + abs(w))*(y_dot - np.pad(y_dot,((0,0), (0,0), (1,0)), 'reflect', reflect_type='odd')[:,:,:-1])/(pressure_levels - np.pad(pressure_levels,(1,0), 'reflect', reflect_type='odd')[:-1]) + 0.5*(w - abs(w))*(np.pad(y_dot, ((0,0), (0,0), (0,1)), 'reflect', reflect_type='odd')[:,:,1:] - y_dot)/(np.pad(pressure_levels,(0,1), 'reflect', reflect_type='odd')[1:] - pressure_levels) 268 | y_dot_add += - coriolis_plane[:,:,None]*x_dot - grid_y_gradient_matrix(polar_plane,polar_grid_resolution) - 1E-5*y_dot 269 | 270 | x_dot_add[:,:,sponge_index:] *= 0 271 | y_dot_add[:,:,sponge_index:] *= 0 272 | 273 | # sponge layer 274 | x_dot_add[:,:,sponge_index:] -= 0.5*(x_dot[:,:,sponge_index:] + abs(x_dot[:,:,sponge_index:]))*(x_dot[:,:,sponge_index:] - np.pad(x_dot[:,:,sponge_index:],((0,0), (1,0), (0,0)), 'reflect', reflect_type='odd')[:,:-1,:])/polar_grid_resolution + 0.5*(x_dot[:,:,sponge_index:] - abs(x_dot[:,:,sponge_index:]))*(np.pad(x_dot[:,:,sponge_index:], ((0,0), (0,1), (0,0)), 'reflect', reflect_type='odd')[:,1:,:] - x_dot[:,:,sponge_index:])/polar_grid_resolution 275 | x_dot_add[:,:,sponge_index:] -= 0.5*(y_dot[:,:,sponge_index:] + abs(y_dot[:,:,sponge_index:]))*(x_dot[:,:,sponge_index:] - np.pad(x_dot[:,:,sponge_index:],((1,0), (0,0), (0,0)), 'reflect', reflect_type='odd')[:-1,:,:])/polar_grid_resolution + 0.5*(y_dot[:,:,sponge_index:] - abs(y_dot[:,:,sponge_index:]))*(np.pad(x_dot[:,:,sponge_index:], ((0,1), (0,0), (0,0)), 'reflect', reflect_type='odd')[1:,:,:] - x_dot[:,:,sponge_index:])/polar_grid_resolution 276 | # x_dot_add[:,:,sponge_index:] -= 0.5*(w[:,:,sponge_index:] + abs(w[:,:,sponge_index:]))*(x_dot[:,:,sponge_index:] - np.pad(x_dot[:,:,sponge_index:],((0,0), (0,0), (1,0)), 'reflect', reflect_type='odd')[:,:,:-1])/(pressure_levels[sponge_index:] - np.pad(pressure_levels[sponge_index:],(1,0), 'reflect', reflect_type='odd')[:-1]) + 0.5*(w[:,:,sponge_index:] - abs(w[:,:,sponge_index:]))*(np.pad(x_dot[:,:,sponge_index:], ((0,0), (0,0), (0,1)), 'reflect', reflect_type='odd')[:,:,1:] - x_dot[:,:,sponge_index:])/(np.pad(pressure_levels[sponge_index:],(0,1), 'reflect', reflect_type='odd')[1:] - pressure_levels[sponge_index:]) 277 | x_dot_add[:,:,sponge_index:] -= 1E-3*x_dot[:,:,sponge_index:] 278 | 279 | y_dot_add[:,:,sponge_index:] -= 0.5*(x_dot[:,:,sponge_index:] + abs(x_dot[:,:,sponge_index:]))*(y_dot[:,:,sponge_index:] - np.pad(y_dot[:,:,sponge_index:],((0,0), (1,0), (0,0)), 'reflect', reflect_type='odd')[:,:-1,:])/polar_grid_resolution + 0.5*(x_dot[:,:,sponge_index:] - abs(x_dot[:,:,sponge_index:]))*(np.pad(y_dot[:,:,sponge_index:], ((0,0), (0,1), (0,0)), 'reflect', reflect_type='odd')[:,1:,:] - y_dot[:,:,sponge_index:])/polar_grid_resolution 280 | y_dot_add[:,:,sponge_index:] -= 0.5*(y_dot[:,:,sponge_index:] + abs(y_dot[:,:,sponge_index:]))*(y_dot[:,:,sponge_index:] - np.pad(y_dot[:,:,sponge_index:],((1,0), (0,0), (0,0)), 'reflect', reflect_type='odd')[:-1,:,:])/polar_grid_resolution + 0.5*(y_dot[:,:,sponge_index:] - abs(y_dot[:,:,sponge_index:]))*(np.pad(y_dot[:,:,sponge_index:], ((0,1), (0,0), (0,0)), 'reflect', reflect_type='odd')[1:,:,:] - y_dot[:,:,sponge_index:])/polar_grid_resolution 281 | # y_dot_add[:,:,sponge_index:] -= 0.5*(w[:,:,sponge_index:] + abs(w[:,:,sponge_index:]))*(y_dot[:,:,sponge_index:] - np.pad(y_dot[:,:,sponge_index:],((0,0), (0,0), (1,0)), 'reflect', reflect_type='odd')[:,:,:-1])/(pressure_levels[sponge_index:] - np.pad(pressure_levels[sponge_index:],(1,0), 'reflect', reflect_type='odd')[:-1]) + 0.5*(w[:,:,sponge_index:] - abs(w[:,:,sponge_index:]))*(np.pad(y_dot[:,:,sponge_index:], ((0,0), (0,0), (0,1)), 'reflect', reflect_type='odd')[:,:,1:] - y_dot[:,:,sponge_index:])/(np.pad(pressure_levels[sponge_index:],(0,1), 'reflect', reflect_type='odd')[1:] - pressure_levels[sponge_index:]) 282 | y_dot_add[:,:,sponge_index:] -= 1E-3*y_dot[:,:,sponge_index:] 283 | 284 | return x_dot_add,y_dot_add 285 | 286 | cpdef project_velocities_north(np.ndarray lon,np.ndarray x_dot,np.ndarray y_dot,np.int_t pole_low_index_N,np.int_t pole_high_index_N,np.ndarray grid_x_values_N,np.ndarray grid_y_values_N,list polar_x_coords_N,list polar_y_coords_N): 287 | 288 | cdef np.ndarray reproj_x_dot = beam_me_down(lon,x_dot,pole_low_index_N,grid_x_values_N,grid_y_values_N,polar_x_coords_N,polar_y_coords_N) 289 | cdef np.ndarray reproj_y_dot = beam_me_down(lon,y_dot,pole_low_index_N,grid_x_values_N,grid_y_values_N,polar_x_coords_N,polar_y_coords_N) 290 | 291 | cdef np.ndarray reproj_u = + reproj_x_dot*np.sin(lon[None,:,None]*np.pi/180) + reproj_y_dot*np.cos(lon[None,:,None]*np.pi/180) 292 | cdef np.ndarray reproj_v = + reproj_x_dot*np.cos(lon[None,:,None]*np.pi/180) - reproj_y_dot*np.sin(lon[None,:,None]*np.pi/180) 293 | 294 | reproj_u = np.flip(reproj_u,axis=1) 295 | reproj_v = np.flip(reproj_v,axis=1) 296 | 297 | return reproj_u, reproj_v 298 | 299 | cpdef project_velocities_south(np.ndarray lon,np.ndarray x_dot,np.ndarray y_dot,np.int_t pole_low_index_S,np.int_t pole_high_index_S,np.ndarray grid_x_values_S,np.ndarray grid_y_values_S,list polar_x_coords_S,list polar_y_coords_S): 300 | cdef np.ndarray reproj_x_dot = beam_me_down(lon,x_dot,pole_low_index_S,grid_x_values_S,grid_y_values_S,polar_x_coords_S,polar_y_coords_S) 301 | cdef np.ndarray reproj_y_dot = beam_me_down(lon,y_dot,pole_low_index_S,grid_x_values_S,grid_y_values_S,polar_x_coords_S,polar_y_coords_S) 302 | 303 | cdef np.ndarray reproj_u = + reproj_x_dot*np.sin(lon[None,:,None]*np.pi/180) + reproj_y_dot*np.cos(lon[None,:,None]*np.pi/180) 304 | cdef np.ndarray reproj_v = - reproj_x_dot*np.cos(lon[None,:,None]*np.pi/180) + reproj_y_dot*np.sin(lon[None,:,None]*np.pi/180) 305 | 306 | return reproj_u, reproj_v 307 | 308 | cpdef polar_plane_advect(np.ndarray data,np.ndarray x_dot,np.ndarray y_dot, np.ndarray w, DTYPE_f polar_grid_resolution): 309 | 310 | cpdef np.ndarray output = np.zeros_like(data) 311 | 312 | output += 0.5*(x_dot + abs(x_dot))*(data - np.pad(data, ((0,0), (1,0), (0,0)), 'reflect', reflect_type='odd')[:,:-1,:])/polar_grid_resolution 313 | output += 0.5*(x_dot - abs(x_dot))*(np.pad(data, ((0,0), (0,1), (0,0)), 'reflect', reflect_type='odd')[:,1:,:] - data)/polar_grid_resolution 314 | 315 | output += 0.5*(y_dot + abs(y_dot))*(data - np.pad(data, ((1,0), (0,0), (0,0)), 'reflect', reflect_type='odd')[:-1,:,:])/polar_grid_resolution 316 | output += 0.5*(y_dot - abs(y_dot))*(np.pad(data, ((0,1), (0,0), (0,0)), 'reflect', reflect_type='odd')[1:,:,:] - data)/polar_grid_resolution 317 | 318 | output += 0.5*(w + abs(w))*(data - np.pad(data, ((1,0), (0,0), (0,0)), 'reflect', reflect_type='odd')[:-1,:,:])/polar_grid_resolution 319 | output += 0.5*(w - abs(w))*(np.pad(data, ((0,1), (0,0), (0,0)), 'reflect', reflect_type='odd')[1:,:,:] - data)/polar_grid_resolution 320 | 321 | return output 322 | 323 | cpdef upload_velocities(np.ndarray lat,np.ndarray lon,np.ndarray reproj_u,np.ndarray reproj_v,np.int_t grid_size,np.ndarray grid_lat_coords,np.ndarray grid_lon_coords): 324 | 325 | cdef np.ndarray grid_u = beam_me_up(lat,lon,reproj_u,grid_size,grid_lat_coords,grid_lon_coords) 326 | cdef np.ndarray grid_v = beam_me_up(lat,lon,reproj_v,grid_size,grid_lat_coords,grid_lon_coords) 327 | 328 | cdef np.int_t nlevels = reproj_u.shape[2] 329 | 330 | cdef np.ndarray x_dot = np.zeros((grid_size,grid_size,nlevels)) 331 | cdef np.ndarray y_dot = np.zeros((grid_size,grid_size,nlevels)) 332 | 333 | grid_lon_coords = grid_lon_coords.reshape((grid_size,grid_size)) 334 | 335 | if lat[0] < 0: 336 | for k in range(nlevels): 337 | x_dot[:,:,k] = grid_u[:,:,k]*np.sin(grid_lon_coords*np.pi/180) - grid_v[:,:,k]*np.cos(grid_lon_coords*np.pi/180) 338 | y_dot[:,:,k] = grid_u[:,:,k]*np.cos(grid_lon_coords*np.pi/180) + grid_v[:,:,k]*np.sin(grid_lon_coords*np.pi/180) 339 | else: 340 | for k in range(nlevels): 341 | x_dot[:,:,k] = -grid_u[:,:,k]*np.sin(grid_lon_coords*np.pi/180) + grid_v[:,:,k]*np.cos(grid_lon_coords*np.pi/180) 342 | y_dot[:,:,k] = -grid_u[:,:,k]*np.cos(grid_lon_coords*np.pi/180) - grid_v[:,:,k]*np.sin(grid_lon_coords*np.pi/180) 343 | 344 | return x_dot,y_dot 345 | 346 | cpdef w_plane(np.ndarray x_dot,np.ndarray y_dot,np.ndarray temperature,np.ndarray pressure_levels,DTYPE_f polar_grid_resolution): 347 | ''' calculates vertical velocity omega on a given cartesian polar plane''' 348 | 349 | cdef np.ndarray w_temp = np.zeros_like(x_dot) 350 | cdef np.int_t k 351 | temperature = theta_to_t(temperature,pressure_levels) 352 | 353 | cdef np.ndarray flow_divergence = grid_x_gradient_matrix(x_dot, polar_grid_resolution) + grid_y_gradient_matrix(y_dot, polar_grid_resolution) 354 | 355 | for k in np.arange(1,len(pressure_levels)).tolist(): 356 | w_temp[:,:,k] = - np.trapz(flow_divergence[:,:,k:],pressure_levels[k:]) 357 | 358 | return w_temp -------------------------------------------------------------------------------- /tex-docs/topics/util_funcs.tex: -------------------------------------------------------------------------------- 1 | \section{Utility Functions} 2 | With the control panel defined and explained, let us move over to some utility functions. Functions that can be used in all kinds of calculations, which we might need more often. In general it 3 | concerns functions like calculating the gradient, the lacplacian or interpolation. 4 | 5 | \subsection{Gradients} 6 | Let us define the gradient in the $x, y$ and $z$ directions. The functions can be found in \autoref{alg:gradient x}, \autoref{alg:gradient y} and \autoref{alg:gradient z}. We use these functions 7 | in various other algorithms as the gradient (also known as derivative) is often used in physics. It denotes the rate of change, how much something changes over time. Velocity for instance denotes 8 | how far you move in a given time. Which is a rate of change, how much your distance to a given point changes over time. 9 | 10 | In \autoref{alg:gradient z} $a.dimensions$ is the attribute that tells us how deeply nested the array $a$ is. If the result is $1$ we have just a normal array, if it is $2$ we have a double array 11 | (an array at each index of the array) which is also called a matrix and if it is $3$ we have a triple array. We need this because we have a one-dimensional case, for when we do not use multiple 12 | layers and a three-dimensional case for when we do use multiple layers. This distinction is needed to avoid errors being thrown when running the model with one or multiple layers. 13 | 14 | This same concept can be seen in \autoref{alg:gradient x} and \autoref{alg:gradient y}, though here we check if $k$ is defined or \texttt{NULL}. We do this as sometimes we want to use this 15 | function for matrices that does not have the third dimension. Hence we define a default value for $k$ which is \texttt{NULL}. \texttt{NULL} is a special value in computer science. It represents 16 | nothing. This can be useful sometimes if you declare a variable to be something but it is referring to something that has been deleted or it is returned when some function fails. It usually 17 | indicates that something special is going on. So here we use it in the special case where we do not want to consider the third dimension in the gradient. We also use forward differencing 18 | (calculating the gradient by taking the difference of the cell and the next/previous cell, multiplied by $2$ to keep it fair) in \autoref{alg:gradient y} as that gives better results for the 19 | calculations we will do later on. 20 | 21 | \begin{algorithm}[hbt] 22 | \caption{Calculating the gradient in the $x$ direction} 23 | \label{alg:gradient x} 24 | \SetKwInOut{Input}{Input} 25 | \SetKwInOut{Output}{Output} 26 | \Input{Matrix (double array) $a$, first index $i$, second index $j$, third index $k$ with default value \texttt{NULL}} 27 | \Output{Gradient in the $x$ direction} 28 | \eIf{$k = \texttt{NULL}$}{ 29 | $grad \leftarrow \frac{a[i, (j + 1)\text{ mod } nlon] - a[i, (j - 1) \text{ mod } nlon]}{\delta x[i]}$ \; 30 | }{ 31 | $grad \leftarrow \frac{a[i, (j + 1)\text{ mod } nlon, k] - a[i, (j - 1) \text{ mod } nlon, k]}{\delta x[i]}$ \; 32 | } 33 | \Return{$grad$} \; 34 | \end{algorithm} 35 | 36 | \begin{algorithm}[hbt] 37 | \caption{Calculating the gradient in the $y$ direction} 38 | \label{alg:gradient y} 39 | \SetKwInOut{Input}{Input} 40 | \SetKwInOut{Output}{Output} 41 | \Input{Matrix (double array) $a$, first index $i$, second index $j$, third index $k$ with default value \texttt{NULL}} 42 | \Output{Gradient in the $y$ direction} 43 | \eIf{$k = \texttt{NULL}$}{ 44 | \uIf{$i == 0$}{ 45 | $grad \leftarrow 2 \frac{a[i + 1, j] - a[i, j]}{\delta y}$ \; 46 | }\uElseIf{$i = nlat - 1$}{ 47 | $grad \leftarrow 2 \frac{a[i, j] - a[i - 1, j]}{\delta y}$ \; 48 | }\uElse{ 49 | $grad \leftarrow \frac{a[i + 1, j] - a[i - 1 j]}{\delta y}$ \; 50 | } 51 | }{ 52 | \uIf{$i = 0$}{ 53 | $grad \leftarrow 2 \frac{a[i + 1, j, k] - a[i, j, k]}{\delta y}$ \; 54 | }\uElseIf{$i = nlat - 1$}{ 55 | $grad \leftarrow 2 \frac{a[i, j, k] - a[i - 1, j, k]}{\delta y}$ \; 56 | }\uElse{ 57 | $grad \leftarrow \frac{a[i + 1, j] - a[i - 1 j]}{\delta y}$ \; 58 | } 59 | } 60 | \Return $grad$ \; 61 | \end{algorithm} 62 | 63 | \begin{algorithm}[hbt] 64 | \caption{Calculating the gradient in the $z$ direction} 65 | \label{alg:gradient z} 66 | \SetKwInOut{Input}{Input} 67 | \SetKwInOut{Output}{Output} 68 | \Input{Array $a$, array $p_z$, index $k$} 69 | \Output{Gradient in the $z$ direction} 70 | $nlevels \leftarrow p_z.length$ \; 71 | \uIf{$k = 0$}{ 72 | $grad \leftarrow \frac{a[k + 1] - a[k]}{p_z[k + 1] - p_z[k]}$ \; 73 | }\uElseIf{$k = nlevels - 1$}{ 74 | $grad \leftarrow \frac{a[k] - a[k - 1]}{p_z[k] - p_z[k - 1]}$ \; 75 | }\uElse{ 76 | $grad \leftarrow \frac{a[k + 1] - a[k - 1]}{p_z[k + 1] - p_z[k - 1]}$ \; 77 | } 78 | 79 | \Return $-grad$ \; 80 | \end{algorithm} 81 | 82 | \subsection{Laplacian Operator} \label{sec:laplace} 83 | The Laplacian operator ($\nabla^2$, sometimes also seen as $\Delta$) has two definitions, one for a vector field and one for a scalar field. The two concepts are not indpendent, a vector field 84 | is composed of scalar fields \cite{vectorscalarfields}. Let us define a vector field first. A vector field is a function whose domain and range are a subset of the Eucledian $\mathbb{R}^3$ space. 85 | A scalar field is then a function consisting out of several real variables (meaning that the variables can only take real numbers as valid values). So for instance the circle equation 86 | $x^2 + y^2 = r^2$ is a scalar field as $x, y$ and $r$ are only allowed to take real numbers as their values. 87 | 88 | With the vector and scalar fields defined, let us take a look at the Laplacian operator. For a scalar field $\phi$ the laplacian operator is defined as the divergence of the gradient of $\phi$ 89 | \cite{laplacian}. But what are the divergence and gradient? The gradient is defined in \autoref{eq:gradient} and the divergence is defined in \autoref{eq:divergence}. Here $\phi$ is a vector 90 | with components $x, y, z$ and $\Phi$ is a vector field with components $x, y, z$. $\Phi_1, \Phi_2$ and $\Phi_3$ refer to the functions that result in the corresponding $x, y$ and $z$ values 91 | \cite{vectorscalarfields}. Also, $i, j$ and $k$ are the basis vectors of $\mathbb{R^3}$, and the multiplication of each term with their basis vector results in $\Phi_1, \Phi_2$ and $\Phi_3$ 92 | respectively. If we then combine the two we get the Laplacian operator, as in \autoref{eq:laplacian scalar}. 93 | 94 | \begin{subequations} 95 | \begin{equation} 96 | \label{eq:gradient} 97 | \text{grad } \phi = \nabla \phi = \frac{\delta \phi}{\delta x}i + \frac{\delta \phi}{\delta y}j + \frac{\delta \phi}{\delta z}k 98 | \end{equation} 99 | \begin{equation} 100 | \label{eq:divergence} 101 | \text{div} \Phi = \nabla \cdot \Phi = \frac{\delta \Phi_1}{\delta x} + \frac{\delta \Phi_2}{\delta y} + \frac{\delta \Phi_3}{\delta z} 102 | \end{equation} 103 | \begin{equation} 104 | \label{eq:laplacian scalar} 105 | \nabla^2 \phi = \nabla \cdot \nabla \phi = \frac{\delta^2 \phi}{\delta x^2} + \frac{\delta^2 \phi}{\delta y^2} + \frac{\delta^2 \phi}{\delta z^2} 106 | \end{equation} 107 | \end{subequations} 108 | 109 | For a vector field $\Phi$ the Laplacian operator is defined as in \autoref{eq:laplacian vector}. Which essential boils down to taking the Laplacian operator of each function and multiply it by 110 | the basis vector. 111 | 112 | \begin{equation} 113 | \label{eq:laplacian vector} 114 | \nabla^2 \Phi = (\nabla^2 \Phi_1)i + (\nabla^2 \Phi_2)j + (\nabla^2 \Phi_3)k 115 | \end{equation} 116 | 117 | The code can be found in \autoref{alg:laplacian}. $\Delta_x$ and $\Delta_y$ in \autoref{alg:laplacian} represents the calls to \autoref{alg:gradient x} and \autoref{alg:gradient y} 118 | respectively. 119 | 120 | \begin{algorithm}[hbt] 121 | \caption{Calculate the laplacian operator over a matrix a} 122 | \label{alg:laplacian} 123 | \SetKwInOut{Input}{Input} 124 | \SetKwInOut{Output}{Output} 125 | \Input{A matrix (double array) a} 126 | \Output{A matrix (double array) with results for the laplacian operator for each element} 127 | \eIf{$a.dimensions = 2$}{ 128 | \For{$lat \leftarrow 1$ \KwTo $nlat - 1$}{ 129 | \For{$lon \leftarrow 0$ \KwTo $nlon$}{ 130 | $output[lat, lon] \leftarrow \frac{\Delta_x(a, lat, (lon + 1) \text{ mod } nlon) - \Delta_x(a, lat, (lon - 1) \text{ mod } nlon)}{\delta x[lat]} + \frac{\Delta_y(a, lat + 1, lon) - 131 | \Delta_y(a, lat - 1, lon)}{\delta y}$\; 132 | } 133 | } 134 | }{ 135 | \For{$lat \leftarrow 1$ \KwTo $nlat - 1$}{ 136 | \For{$lon \leftarrow 0$ \KwTo $nlon$}{ 137 | \For{$k \leftarrow 0$ \KwTo $nlevels - 1$}{ 138 | $output[lat, lon, k] \leftarrow \frac{\Delta_x(a, lat, (lon + 1) \text{ mod } nlon, k) - \Delta_x(a, lat, (lon - 1) \text{ mod } nlon, k)}{\delta x[lat]} + \frac{\Delta_y(a, 139 | lat + 1, lon, k) - \Delta_y(a, lat - 1, lon, k)}{\delta y} + \frac{\Delta_z(a, lat, lon, k + 1) - \Delta_z(a, lat, lon, k + 1)}{2\delta z[k]}$\; 140 | } 141 | } 142 | } 143 | } 144 | 145 | \Return{$ouput$} \; 146 | \end{algorithm} 147 | 148 | \subsection{Divergence} 149 | As we expect to use the divergence operator more often throughout our model, let us define a seperate function for it in \autoref{alg:divergence}. $\Delta_x$ and $\Delta_y$ in 150 | \autoref{alg:divergence} represents the calls to \autoref{alg:gradient x} and \autoref{alg:gradient y} respectively. We do the multiplication with the velocity vectors $u, v$ and $w$ here already, 151 | as we expect that we might use it in combination with the divergence operator more frequently. What those vectors are and represent we will discuss in \autoref{sec:momentum}. 152 | 153 | \begin{algorithm}[!hbt] 154 | \caption{Calculate the result of the divergence operator on a vector} 155 | \label{alg:divergence} 156 | \SetKwInOut{Input}{Input} 157 | \SetKwInOut{Output}{Output} 158 | \Input{A matrix (triple array) $a$, pressure field $p_z$} 159 | \Output{A matrix (triple array) containing the result of the divergence operator taken over that element} 160 | \For{$i \leftarrow 0$ \KwTo $a.length$}{ 161 | \For{$j \leftarrow 0$ \KwTo $a[i].length$}{ 162 | \For{$k \leftarrow 0$ \KwTo $a[i, j].length$}{ 163 | $output[i, j, k] \leftarrow \Delta_x(au, i, j, k) + \Delta_y(av, i, j, k) + \Delta_z(aw[i, j, :], p_z, k)$ \; 164 | } 165 | } 166 | } 167 | \Return{$output$} \; 168 | \end{algorithm} 169 | 170 | \subsection{Interpolation} \label{sec:interpolation} 171 | Interpolation is a form of estimation, where one has a set of data points and desires to know the values of other data points that are not in the original set of data points\cite{interpolation}. 172 | Based on the original data points, it is estimated what the values of the new data points will be. There are various forms of interpolation like linear interpolation, polynomial interpolation 173 | and spline interpolation. The CLAuDE model uses linear interpolation which is specified in \autoref{eq:interpolation}. Here $z$ is the point inbetween the known data points $x$ and $y$. 174 | $\lambda$ is the factor that tells us how close $z$ is to $y$ in the interval $[0, 1]$. If $z$ is very close to $y$, $\lambda$ will have the value on the larger end of the interval, like 0.9. 175 | Whereas if $z$ is close to $x$ then $\lambda$ will have a value on the lower end of the interval, like 0.1. 176 | 177 | \begin{equation} 178 | \label{eq:interpolation} 179 | z = (1 - \lambda)x + \lambda y 180 | \end{equation} 181 | 182 | \subsection{3D smoothing} \label{sec:3dsmooth} 183 | As you can imagine the temperature, pressure and the like vary quite a lot over the whole planet. Which is something that we kind of want but not really. What we really want is to limit how 184 | much variety we allow to exist. For this we are going to use Fast Fourier Transforms, also known as FFTs. A Fourier Transform decomposes a wave into its frequences. The fast bit comes from the 185 | algorithm we use to calculate it. This is because doing it via the obvious way is very slow, in the order of $O(n^2)$ (for what that means, please visit \autoref{sec:runtime}). Whereas if we use 186 | the FFT, we reduce the running time to $O(n\log(n))$. There are various ways to calculate the FFT, but we use the Cooley–Tukey algorithm \cite{fft}. To explain it, let us first dive into a normal 187 | Fourier Transform. 188 | 189 | The best way to explain what a Fourier Transform does is to apply it to a sound. Sound is vibrations travelling through the air that reach your air and make the inner part of your air vibrate. 190 | If you plot the air pressure reaching your air versus the time, the result will have the form of a sinoidal wave. However, it is only a straight forward sinoidal wave (as if you plotted the 191 | $\cos$ function) if the tone is pure. That is often not the case, and sounds are combinations of tones. This gives waves that are sinoidal but not very alike to the $\cos$ function. The FT will 192 | transform this "unpure" wave and splits them up into a set of waves that are all of pure tone. To do that we need complex numbers which are explained here \autoref{sec:complex}. 193 | 194 | With that explanation out of the way, we now know that with Euler's formula (\autoref{eq:euler}) we can rotate on the complex plane. If we rotate one full circle per second, the formula changes 195 | to \autoref{eq:euler rotate}, as the circumference of the unit circle is $2\pi$ and $t$ is in seconds. This rotates in the clock-wise direction, but we want to rotate in the clockwise direction, 196 | so we need to add a $-$ to the exponent. If we also want to control how fast the rotation happens (which is called the frequency) then we change the equation to \autoref{eq:euler freq}. Note that 197 | the frequency unit is $Hz$ which is defined as $s^{-1}$, which means that a frequency of $10 Hz$ means 10 revolutions per second. Now we get our wave which we call $g(t)$ and plonk it in front 198 | of the equation up until now. Which results in \autoref{eq:euler wave}. Visually, this means that we take the sound wave and wrap it around the origin. This might sound strange at first but bear 199 | with me. If you track the center of mass (the average of all the points that form the graph) you will notice that it hovers around the origin. If you now change the frequency of the rotation ($f$) 200 | you will see that the center of mass moves a bit, usually around the origin. However, if the frequency of the rotation matches a frequency of the wave, then the center of mass is suddenly a 201 | relatively long distance away from the origin. This indicates that we have found a frequency that composes the sound wave. Now how do we track the center of mass? That is done using integration, 202 | as in \autoref{eq:euler int}. Now to get to the final form, we forget about the fraction part. This means that the center of mass will still hover around the origin for the main part of the 203 | rotation, but has a huge value for when the rotation is at the same frequency as one of the waves in the sound wave. The larger the difference between $t_2$ and $t_1$, the larger the value of the 204 | Fourier Transform. The final equation is given in \autoref{eq:ft}. 205 | 206 | \begin{subequations} 207 | \begin{equation} 208 | \label{eq:euler} 209 | e^{ix} = \cos(x) + i\sin(x) 210 | \end{equation} 211 | \begin{equation} 212 | \label{eq:euler rotate} 213 | e^{2\pi it} 214 | \end{equation} 215 | \begin{equation} 216 | \label{eq:euler freq} 217 | e^{-2\pi ift} 218 | \end{equation} 219 | \begin{equation} 220 | \label{eq:euler wave} 221 | g(t)e^{-2\pi ift} 222 | \end{equation} 223 | \begin{equation} 224 | \label{eq:euler int} 225 | \frac{1}{t_2 - t_1}\int^{t_2}_{t_1}g(t)e^{-2\pi ift} 226 | \end{equation} 227 | \begin{equation} 228 | \label{eq:ft} 229 | \hat{g}(f) = \int^{t_2}_{t_1}g(t)e^{-2\pi ift} 230 | \end{equation} 231 | \end{subequations} 232 | 233 | These Fourier Transforms have the great property that if you add them together you still have the relatively large distances of the center of mass to the origin at the original frequencies. It 234 | is this property that enables us to find the frequencies that compose a sound wave. 235 | 236 | Before moving on we first need to discuss some important properties of FTs. Actually, these properties only apply to complex roots of unity (the $e^{2\pi ift}$ part is a complex root of unity). 237 | Due to the FT only being a coefficient $g(t)$ in front of a complex root of unity all these properties apply to FTs as well. Let us first start with the cancellation lemma as described in 238 | \autoref{lemma:cancellation} \cite{cancellation}. Note that $\omega^k_n = e^{\frac{2\pi k}{n}}$ which is very similar to the form we discussed previously if you realise that $f = \frac{1}{T}$. 239 | So replace $k$ by $t$ and $n$ by $T = \frac{1}{f}$ and you get the form we have discussed earlier. 240 | 241 | \begin{lemma}[Cancellation Lemma] \label{lemma:cancellation} 242 | $\omega^{dk}_{dn} = \omega^k_n$, for all $k \geq 0, n \geq 0$ and $d > 0$. 243 | \end{lemma} 244 | 245 | With that we also need to talk about the halving lemma (\autoref{lemma:halving}). This means that $(\omega^{k + \frac{n}{2}}_n)^2 = (\omega^k_n)^2$. 246 | 247 | \begin{lemma}[Halving Lemma] \label{lemma:halving} 248 | If $n > 0$ is even, then the squares of the $n$-complex $n^{\text{th}}$ roots of unity are the $\frac{n}{2}$-complex $\frac{n}{2}^{\text{th}}$ roots of unity. 249 | \end{lemma} 250 | 251 | Now that we know what a Fourier Transform is, we need to make it a Fast Fourier Transform, as you can imagine that calculating such a thing is quite difficult. Some smart people have thought 252 | about this and they came up with quite a fast algorithm, the Cooley-Tukey algorithm \cite{fft}, named after the people that thought of it. They use something we know as a Discrete Fourier 253 | Transform which is described by \autoref{eq:dft}. Here $N$ is the total amount of samples from the continuous sound wave. This means that $0 \leq k \leq N - 1$ and $0 \leq n \leq N - 1$. 254 | 255 | Now with the DFT out of the way we can discuss the algorithm. Before we do, we assume that we have an input of $n$ elements, where $n$ is an exact power of $2$. Meaning $n = 2^k$ for some 256 | integer $k$. 257 | 258 | \begin{algorithm} 259 | \caption{One dimensional Fast Fourier Transformation} 260 | \label{alg:FFT} 261 | \SetKwInOut{Input}{Input} 262 | \SetKwInOut{Output}{Output} 263 | \Input{array $A$} 264 | \Output{array $B$ with length $A.length - 1$ containing the DFT} 265 | $n \leftarrow A.length$ \; 266 | \uIf{$n = 1$}{ 267 | \Return{$A$} \; 268 | } 269 | $\omega_n \leftarrow e^{\frac{2\pi i}{n}}$ \; 270 | $\omega \leftarrow 1$ \; 271 | $a^0 \leftarrow (A[0], A[2], \dots, A[n - 2])$ \; 272 | $a^1 \leftarrow (A[1], A[3], \dots, A[n - 1])$ \; 273 | $y^0 \leftarrow $ \texttt{FFT}($a^0$) \; 274 | $y^1 \leftarrow $ \texttt{FFT}($a^1$) \; 275 | \For{$k \leftarrow 0$ \KwTo $\frac{n}{2} - 1$}{ 276 | $B[k] \leftarrow y^0[k] + \omega y^1[k]$ \; 277 | $B[k + \frac{n}{2}] \leftarrow y^0[k] - \omega y^1[k]$ \; 278 | $\omega \leftarrow \omega \omega_n$ \; 279 | } 280 | \Return{B} 281 | \end{algorithm} 282 | 283 | There is just this one problem we have, the algorithm in \autoref{alg:FFT} can only handle one dimension, and we need to support multidimensional arrays. So let us define a multidimensional FFT 284 | first in \autoref{eq:NFFT}. Here $N$ and $M$ are the amount of indices for the dimensions, where $M$ are the amount of indices for the first dimension and $N$ are the amount of indices for the 285 | second dimension. This can of course be extended in the same way for a $p$-dimensional array. 286 | 287 | \begin{equation} 288 | \label{eq:NFFT} 289 | X_{k,l} = \sum_{m = 0}^{M - 1}\sum_{n = 0}^{N - 1} x_{m, n}e^{-2i\pi(\frac{mk}{M} + \frac{nl}{N})} 290 | \end{equation} 291 | 292 | It is at this point that the algorithm becomes very complicated. Therefore I would like to invite you to use a library for these kinds of calculations, like Numpy \cite{numpy} for Python. If you 293 | really want to use your own made version, or if you want to understand how the libraries sort of do it, then you may continue. I still advise you to use a library, as other people have made the 294 | code for you. 295 | 296 | We can calculate the result of such a multidimensional FFT by computing one-dimensional FFTs along each dimension in turn. Meaning we first calculate the result of the FFT along one dimension, 297 | then we do that for the second dimension and so forth. Now the order of calculating the FFTs along each dimension does not matter. So you can calculate the third dimension first, and the first 298 | dimension after that if you wish. This has the advantage that we can program a generic function without keeping track of the order of the dimensions. Now, the code is quite complicated but it 299 | boils down to this: calculate the FFT along one dimension, then repeat it for the second dimension and multiply the two together and keep repeating that for all dimensions. 300 | 301 | With that out of the way, we need to create a smoothing operation out of it. We do this in \autoref{alg:smooth}. Keep in mind that \texttt{FFT} the call is to the 302 | multidimensional Fast Fourier Transform algorithm, \texttt{IFFT} the call to the inverse of the multidimensional Fast Fourier Transform algorithm (also on the TODO list) and that the $int()$ 303 | function ensures that the number in brackets is an integer. Also note that the inverse of the FFT might give complex answers, and we only want real answers which the $.real$ ensures. We only 304 | take the real part and return that. $v$ is an optional parameter with default value $0.5$ which does absolutely nothing in this algorithm. 305 | 306 | \begin{algorithm} 307 | \caption{Smoothing function} 308 | \label{alg:smooth} 309 | \SetKwInOut{Input}{Input} 310 | \SetKwInOut{Output}{Output} 311 | \Input{Array $a$, smoothing factor $s$, vertical smoothing factor $v \leftarrow 0.5$} 312 | \Output{Array $A$ with less variation} 313 | $nlat \leftarrow a.length$ \; 314 | $nlon \leftarrow a[0].length$ \; 315 | $nlevels \leftarrow a[0][0].length$ \; 316 | $temp \leftarrow \texttt{FFT}(a)$ \; 317 | $temp[int(nlat s):int(nlat(1 - s)),:,:] \leftarrow 0$ \; 318 | $temp[:,int(nlon s):int(nlon(1 - s)),:] \leftarrow 0$ \; 319 | $temp[:,:,int(nlevels v):int(nlevels(1 - v))] \leftarrow 0$ \; 320 | \Return $\texttt{IFFT}(temp).real$ \; 321 | \end{algorithm} -------------------------------------------------------------------------------- /toy_model.py: -------------------------------------------------------------------------------- 1 | # CLimate Analysis using Digital Estimations (CLAuDE) 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | import time, sys, pickle 6 | import claude_low_level_library as low_level 7 | import claude_top_level_library as top_level 8 | # from twitch import prime_sub 9 | 10 | ######## CONTROL ######## 11 | 12 | day = 60*60*24 # define length of day (used for calculating Coriolis as well) (s) 13 | resolution = 3 # how many degrees between latitude and longitude gridpoints 14 | planet_radius = 6.4E6 # define the planet's radius (m) 15 | insolation = 1370 # TOA radiation from star (W m^-2) 16 | gravity = 9.81 # define surface gravity for planet (m s^-2) 17 | axial_tilt = 23.5 # tilt of rotational axis w.r.t. solar plane 18 | year = 365*day # length of year (s) 19 | 20 | pressure_levels = np.array([1000,950,900,800,700,600,500,400,350,300,250,200,150,100,75,50,25,10,5,2,1]) 21 | pressure_levels *= 100 22 | nlevels = len(pressure_levels) 23 | 24 | dt_spinup = 60*17.2 # timestep for initial period where the model only calculates radiative effects 25 | dt_main = 60*7.2 # timestep for the main sequence where the model calculates velocities/advection 26 | spinup_length = 0*day # how long the model should only calculate radiative effects 27 | 28 | ### 29 | 30 | smoothing = False # you probably won't need this, but there is the option to smooth out fields using FFTs (NB this slows down computation considerably and can introduce nonphysical errors) 31 | smoothing_parameter_t = 1.0 32 | smoothing_parameter_u = 0.9 33 | smoothing_parameter_v = 0.9 34 | smoothing_parameter_w = 0.3 35 | smoothing_parameter_add = 0.3 36 | 37 | ### 38 | 39 | save = False # save current state to file? 40 | load = False # load initial state from file? 41 | 42 | save_frequency = 100 # write to file after this many timesteps have passed 43 | plot_frequency = 10 # how many timesteps between plots (set this low if you want realtime plots, set this high to improve performance) 44 | 45 | ### 46 | 47 | above = False # display top down view of a pole? showing polar plane data and regular gridded data 48 | pole = 's' # which pole to display - 'n' for north, 's' for south 49 | above_level = 17 # which vertical level to display over the pole 50 | 51 | plot = True 52 | diagnostic = False # display raw fields for diagnostic purposes 53 | level_plots = False # display plots of output on vertical levels? 54 | nplots = 3 # how many levels you want to see plots of (evenly distributed through column) 55 | top = 17 # top pressure level to display (i.e. trim off sponge layer) 56 | 57 | verbose = False # print times taken to calculate specific processes each timestep 58 | 59 | ### 60 | 61 | pole_lower_latitude_limit = -75 # how far north polar plane data is calculated from the south pole (do not set this beyond 45!) [mirrored to north pole as well] 62 | pole_higher_latitude_limit = -85 # how far south regular gridded data is calculated (do not set beyond about 80) [also mirrored to north pole] 63 | sponge_layer = 10 # at what pressure should the equations of motion stop being applied (to absorb upwelling atmospheric waves) [hPa] 64 | 65 | ### 66 | 67 | # define coordinate arrays 68 | lat = np.arange(-90,91,resolution) 69 | lon = np.arange(0,360,resolution) 70 | nlat = len(lat) 71 | nlon = len(lon) 72 | lon_plot, lat_plot = np.meshgrid(lon, lat) 73 | heights_plot, lat_z_plot = np.meshgrid(lat,pressure_levels[:top]/100) 74 | temperature_world = np.zeros((nlat,nlon)) 75 | 76 | ########################## 77 | 78 | if not load: 79 | # initialise arrays for various physical fields 80 | temperature_world += 290 81 | potential_temperature = np.zeros((nlat,nlon,nlevels)) 82 | u = np.zeros_like(potential_temperature) 83 | v = np.zeros_like(potential_temperature) 84 | w = np.zeros_like(potential_temperature) 85 | atmosp_addition = np.zeros_like(potential_temperature) 86 | 87 | # read temperature and density in from standard atmosphere 88 | f = open("standard_atmosphere.txt", "r") 89 | standard_temp = [] 90 | standard_pressure = [] 91 | for x in f: 92 | h, t, r, p = x.split() 93 | standard_temp.append(float(t)) 94 | standard_pressure.append(float(p)) 95 | f.close() 96 | 97 | # density_profile = np.interp(x=heights/1E3,xp=standard_height,fp=standard_density) 98 | temp_profile = np.interp(x=pressure_levels[::-1],xp=standard_pressure[::-1],fp=standard_temp[::-1])[::-1] 99 | for k in range(nlevels): 100 | potential_temperature[:,:,k] = temp_profile[k] 101 | 102 | potential_temperature = low_level.t_to_theta(potential_temperature,pressure_levels) 103 | geopotential = np.zeros_like(potential_temperature) 104 | 105 | initial_setup = True 106 | if initial_setup: 107 | sigma = np.zeros_like(pressure_levels) 108 | kappa = 287/1000 109 | for i in range(len(sigma)): 110 | sigma[i] = 1E3*(pressure_levels[i]/pressure_levels[0])**kappa 111 | 112 | heat_capacity_earth = np.zeros_like(temperature_world) + 1E6 113 | 114 | # heat_capacity_earth[15:36,30:60] = 1E7 115 | # heat_capacity_earth[30:40,80:90] = 1E7 116 | 117 | albedo_variance = 0.001 118 | albedo = np.random.uniform(-albedo_variance,albedo_variance, (nlat, nlon)) + 0.2 119 | # albedo = np.zeros((nlat, nlon)) + 0.2 120 | 121 | specific_gas = 287 122 | thermal_diffusivity_roc = 1.5E-6 123 | 124 | # define planet size and various geometric constants 125 | circumference = 2*np.pi*planet_radius 126 | circle = np.pi*planet_radius**2 127 | sphere = 4*np.pi*planet_radius**2 128 | 129 | # define how far apart the gridpoints are: note that we use central difference derivatives, and so these distances are actually twice the distance between gridboxes 130 | dy = circumference/nlat 131 | dx = np.zeros(nlat) 132 | coriolis = np.zeros(nlat) # also define the coriolis parameter here 133 | angular_speed = 2*np.pi/day 134 | for i in range(nlat): 135 | dx[i] = dy*np.cos(lat[i]*np.pi/180) 136 | coriolis[i] = angular_speed*np.sin(lat[i]*np.pi/180) 137 | 138 | sponge_index = np.where(pressure_levels < sponge_layer*100)[0][0] 139 | 140 | setup_grids = True 141 | if setup_grids: 142 | 143 | grid_pad = 2 144 | 145 | pole_low_index_S = np.where(lat > pole_lower_latitude_limit)[0][0] 146 | pole_high_index_S = np.where(lat > pole_higher_latitude_limit)[0][0] 147 | 148 | # initialise grid 149 | polar_grid_resolution = dx[pole_low_index_S] 150 | size_of_grid = planet_radius*np.cos(lat[pole_low_index_S+grid_pad]*np.pi/180.0) 151 | 152 | ### south pole ### 153 | grid_x_values_S = np.arange(-size_of_grid,size_of_grid,polar_grid_resolution) 154 | grid_y_values_S = np.arange(-size_of_grid,size_of_grid,polar_grid_resolution) 155 | grid_xx_S,grid_yy_S = np.meshgrid(grid_x_values_S,grid_y_values_S) 156 | 157 | grid_side_length = len(grid_x_values_S) 158 | 159 | grid_lat_coords_S = (-np.arccos(((grid_xx_S**2 + grid_yy_S**2)**0.5)/planet_radius)*180.0/np.pi).flatten() 160 | grid_lon_coords_S = (180.0 - np.arctan2(grid_yy_S,grid_xx_S)*180.0/np.pi).flatten() 161 | 162 | polar_x_coords_S = [] 163 | polar_y_coords_S = [] 164 | for i in range(pole_low_index_S): 165 | for j in range(nlon): 166 | polar_x_coords_S.append( planet_radius*np.cos(lat[i]*np.pi/180.0)*np.sin(lon[j]*np.pi/180.0) ) 167 | polar_y_coords_S.append(-planet_radius*np.cos(lat[i]*np.pi/180.0)*np.cos(lon[j]*np.pi/180.0) ) 168 | 169 | ### north pole ### 170 | 171 | pole_low_index_N = np.where(lat < -pole_lower_latitude_limit)[0][-1] 172 | pole_high_index_N = np.where(lat < -pole_higher_latitude_limit)[0][-1] 173 | 174 | grid_x_values_N = np.arange(-size_of_grid,size_of_grid,polar_grid_resolution) 175 | grid_y_values_N = np.arange(-size_of_grid,size_of_grid,polar_grid_resolution) 176 | grid_xx_N,grid_yy_N = np.meshgrid(grid_x_values_N,grid_y_values_N) 177 | 178 | grid_lat_coords_N = (np.arccos((grid_xx_N**2 + grid_yy_N**2)**0.5/planet_radius)*180.0/np.pi).flatten() 179 | grid_lon_coords_N = (180.0 - np.arctan2(grid_yy_N,grid_xx_N)*180.0/np.pi).flatten() 180 | 181 | polar_x_coords_N = [] 182 | polar_y_coords_N = [] 183 | 184 | for i in np.arange(pole_low_index_N,nlat): 185 | for j in range(nlon): 186 | polar_x_coords_N.append( planet_radius*np.cos(lat[i]*np.pi/180.0)*np.sin(lon[j]*np.pi/180.0) ) 187 | polar_y_coords_N.append(-planet_radius*np.cos(lat[i]*np.pi/180.0)*np.cos(lon[j]*np.pi/180.0) ) 188 | 189 | indices = (pole_low_index_N,pole_high_index_N,pole_low_index_S,pole_high_index_S) 190 | grids = (grid_xx_N.shape[0],grid_xx_S.shape[0]) 191 | 192 | # create Coriolis data on north and south planes 193 | data = np.zeros((nlat-pole_low_index_N+grid_pad,nlon)) 194 | for i in np.arange(pole_low_index_N-grid_pad,nlat): 195 | data[i-pole_low_index_N,:] = coriolis[i] 196 | coriolis_plane_N = low_level.beam_me_up_2D(lat[(pole_low_index_N-grid_pad):],lon,data,grids[0],grid_lat_coords_N,grid_lon_coords_N) 197 | data = np.zeros((pole_low_index_S+grid_pad,nlon)) 198 | for i in range(pole_low_index_S+grid_pad): 199 | data[i,:] = coriolis[i] 200 | coriolis_plane_S = low_level.beam_me_up_2D(lat[:(pole_low_index_S+grid_pad)],lon,data,grids[1],grid_lat_coords_S,grid_lon_coords_S) 201 | 202 | x_dot_N = np.zeros((grids[0],grids[0],nlevels)) 203 | y_dot_N = np.zeros((grids[0],grids[0],nlevels)) 204 | x_dot_S = np.zeros((grids[1],grids[1],nlevels)) 205 | y_dot_S = np.zeros((grids[1],grids[1],nlevels)) 206 | 207 | coords = grid_lat_coords_N,grid_lon_coords_N,grid_x_values_N,grid_y_values_N,polar_x_coords_N,polar_y_coords_N,grid_lat_coords_S,grid_lon_coords_S,grid_x_values_S,grid_y_values_S,polar_x_coords_S,polar_y_coords_S 208 | 209 | ####################################################################################################################################################################################################################### 210 | 211 | # INITIATE TIME 212 | t = 0.0 213 | 214 | if load: 215 | # load in previous save file 216 | potential_temperature,temperature_world,u,v,w,x_dot_N,y_dot_N,x_dot_S,y_dot_S,t,albedo,tracer = pickle.load(open("save_file.p","rb")) 217 | 218 | sample_level = 15 219 | tracer = np.zeros_like(potential_temperature) 220 | 221 | last_plot = t-0.1 222 | last_save = t-0.1 223 | 224 | if plot: 225 | if not diagnostic: 226 | # set up plot 227 | f, ax = plt.subplots(2,figsize=(9,9)) 228 | f.canvas.set_window_title('CLAuDE') 229 | ax[0].contourf(lon_plot, lat_plot, temperature_world, cmap='seismic') 230 | ax[0].streamplot(lon_plot, lat_plot, u[:,:,0], v[:,:,0], color='white',density=1) 231 | test = ax[1].contourf(heights_plot, lat_z_plot, np.transpose(np.mean(low_level.theta_to_t(potential_temperature,pressure_levels),axis=1))[:top,:], cmap='seismic',levels=15) 232 | ax[1].contour(heights_plot,lat_z_plot, np.transpose(np.mean(u,axis=1))[:top,:], colors='white',levels=20,linewidths=1,alpha=0.8) 233 | ax[1].quiver(heights_plot, lat_z_plot, np.transpose(np.mean(v,axis=1))[:top,:],np.transpose(np.mean(100*w,axis=1))[:top,:],color='black') 234 | plt.subplots_adjust(left=0.1, right=0.75) 235 | ax[0].set_title('Surface temperature') 236 | ax[0].set_xlim(lon.min(),lon.max()) 237 | ax[1].set_title('Atmosphere temperature') 238 | ax[1].set_xlim(lat.min(),lat.max()) 239 | ax[1].set_ylim((pressure_levels.max()/100,pressure_levels[:top].min()/100)) 240 | ax[1].set_yscale('log') 241 | ax[1].set_ylabel('Pressure (hPa)') 242 | ax[1].set_xlabel('Latitude') 243 | cbar_ax = f.add_axes([0.85, 0.15, 0.05, 0.7]) 244 | f.colorbar(test, cax=cbar_ax) 245 | cbar_ax.set_title('Temperature (K)') 246 | f.suptitle( 'Time ' + str(round(t/day,2)) + ' days' ) 247 | 248 | else: 249 | # set up plot 250 | f, ax = plt.subplots(2,2,figsize=(9,9)) 251 | f.canvas.set_window_title('CLAuDE') 252 | ax[0,0].contourf(heights_plot, lat_z_plot, np.transpose(np.mean(u,axis=1))[:top,:], cmap='seismic') 253 | ax[0,0].set_title('u') 254 | ax[0,1].contourf(heights_plot, lat_z_plot, np.transpose(np.mean(v,axis=1))[:top,:], cmap='seismic') 255 | ax[0,1].set_title('v') 256 | ax[1,0].contourf(heights_plot, lat_z_plot, np.transpose(np.mean(w,axis=1))[:top,:], cmap='seismic') 257 | ax[1,0].set_title('w') 258 | ax[1,1].contourf(heights_plot, lat_z_plot, np.transpose(np.mean(atmosp_addition,axis=1))[:top,:], cmap='seismic') 259 | ax[1,1].set_title('atmosp_addition') 260 | for axis in ax.ravel(): 261 | axis.set_ylim((pressure_levels.max()/100,pressure_levels[:top].min()/100)) 262 | axis.set_yscale('log') 263 | f.suptitle( 'Time ' + str(round(t/day,2)) + ' days' ) 264 | 265 | if level_plots: 266 | 267 | level_divisions = int(np.floor(nlevels/nplots)) 268 | level_plots_levels = range(nlevels)[::level_divisions][::-1] 269 | 270 | g, bx = plt.subplots(nplots,figsize=(9,8),sharex=True) 271 | g.canvas.set_window_title('CLAuDE pressure levels') 272 | for k, z in zip(range(nplots), level_plots_levels): 273 | z += 1 274 | bx[k].contourf(lon_plot, lat_plot, potential_temperature[:,:,z], cmap='seismic') 275 | bx[k].set_title(str(pressure_levels[z]/100)+' hPa') 276 | bx[k].set_ylabel('Latitude') 277 | bx[-1].set_xlabel('Longitude') 278 | 279 | plt.ion() 280 | plt.show() 281 | plt.pause(2) 282 | 283 | if not diagnostic: 284 | ax[0].cla() 285 | ax[1].cla() 286 | if level_plots: 287 | for k in range(nplots): 288 | bx[k].cla() 289 | else: 290 | ax[0,0].cla() 291 | ax[0,1].cla() 292 | ax[1,0].cla() 293 | ax[1,1].cla() 294 | 295 | if above: 296 | g,gx = plt.subplots(1,3, figsize=(15,5)) 297 | plt.ion() 298 | plt.show() 299 | 300 | def plotting_routine(): 301 | 302 | quiver_padding = int(12/resolution) 303 | 304 | if plot: 305 | if verbose: before_plot = time.time() 306 | # update plot 307 | if not diagnostic: 308 | 309 | # field = temperature_world 310 | field = np.copy(w)[:,:,sample_level] 311 | # field = np.copy(atmosp_addition)[:,:,sample_level] 312 | test = ax[0].contourf(lon_plot, lat_plot, field, cmap='seismic',levels=15) 313 | ax[0].contour(lon_plot, lat_plot, tracer[:,:,sample_level], alpha=0.5, antialiased=True, levels=np.arange(0.01,1.01,0.01)) 314 | if velocity: ax[0].quiver(lon_plot[::quiver_padding,::quiver_padding], lat_plot[::quiver_padding,::quiver_padding], u[::quiver_padding,::quiver_padding,sample_level], v[::quiver_padding,::quiver_padding,sample_level], color='white') 315 | ax[0].set_xlim((lon.min(),lon.max())) 316 | ax[0].set_ylim((lat.min(),lat.max())) 317 | ax[0].set_ylabel('Latitude') 318 | ax[0].axhline(y=0,color='black',alpha=0.3) 319 | ax[0].set_xlabel('Longitude') 320 | 321 | ### 322 | 323 | test = ax[1].contourf(heights_plot, lat_z_plot, np.transpose(np.mean(low_level.theta_to_t(potential_temperature,pressure_levels),axis=1))[:top,:], cmap='seismic',levels=15) 324 | # test = ax[1].contourf(heights_plot, lat_z_plot, np.transpose(np.mean(atmosp_addition,axis=1))[:top,:], cmap='seismic',levels=15) 325 | # test = ax[1].contourf(heights_plot, lat_z_plot, np.transpose(np.mean(potential_temperature,axis=1)), cmap='seismic',levels=15) 326 | ax[1].contour(heights_plot, lat_z_plot, np.transpose(np.mean(tracer,axis=1))[:top,:], alpha=0.5, antialiased=True, levels=np.arange(0.001,1.01,0.01)) 327 | 328 | if velocity: 329 | ax[1].contour(heights_plot,lat_z_plot, np.transpose(np.mean(u,axis=1))[:top,:], colors='white',levels=20,linewidths=1,alpha=0.8) 330 | ax[1].quiver(heights_plot, lat_z_plot, np.transpose(np.mean(v,axis=1))[:top,:],np.transpose(np.mean(-10*w,axis=1))[:top,:],color='black') 331 | ax[1].set_title('$\it{Atmospheric} \quad \it{temperature}$') 332 | ax[1].set_xlim((-90,90)) 333 | ax[1].set_ylim((pressure_levels.max()/100,pressure_levels[:top].min()/100)) 334 | ax[1].set_ylabel('Pressure (hPa)') 335 | ax[1].set_xlabel('Latitude') 336 | ax[1].set_yscale('log') 337 | f.colorbar(test, cax=cbar_ax) 338 | cbar_ax.set_title('Temperature (K)') 339 | f.suptitle( 'Time ' + str(round(t/day,2)) + ' days' ) 340 | 341 | else: 342 | ax[0,0].contourf(heights_plot, lat_z_plot, np.transpose(np.mean(u,axis=1))[:top,:], cmap='seismic') 343 | ax[0,0].set_title('u') 344 | ax[0,1].contourf(heights_plot, lat_z_plot, np.transpose(np.mean(v,axis=1))[:top,:], cmap='seismic') 345 | ax[0,1].set_title('v') 346 | ax[1,0].contourf(heights_plot, lat_z_plot, np.transpose(np.mean(w,axis=1))[:top,:], cmap='seismic') 347 | ax[1,0].set_title('w') 348 | ax[1,1].contourf(heights_plot, lat_z_plot, np.transpose(np.mean(atmosp_addition,axis=1))[:top,:], cmap='seismic') 349 | ax[1,1].set_title('atmosp_addition') 350 | for axis in ax.ravel(): 351 | axis.set_ylim((pressure_levels.max()/100,pressure_levels[:top].min()/100)) 352 | axis.set_yscale('log') 353 | f.suptitle( 'Time ' + str(round(t/day,2)) + ' days' ) 354 | 355 | if level_plots: 356 | for k, z in zip(range(nplots), level_plots_levels): 357 | z += 1 358 | bx[k].contourf(lon_plot, lat_plot, potential_temperature[:,:,z], cmap='seismic',levels=15) 359 | bx[k].quiver(lon_plot[::quiver_padding,::quiver_padding], lat_plot[::quiver_padding,::quiver_padding], u[::quiver_padding,::quiver_padding,z], v[::quiver_padding,::quiver_padding,z], color='white') 360 | bx[k].set_title(str(round(pressure_levels[z]/100))+' hPa') 361 | bx[k].set_ylabel('Latitude') 362 | bx[k].set_xlim((lon.min(),lon.max())) 363 | bx[k].set_ylim((lat.min(),lat.max())) 364 | bx[-1].set_xlabel('Longitude') 365 | 366 | if above and velocity: 367 | gx[0].set_title('Original data') 368 | gx[1].set_title('Polar plane') 369 | gx[2].set_title('Reprojected data') 370 | g.suptitle( 'Time ' + str(round(t/day,2)) + ' days' ) 371 | 372 | if pole == 's': 373 | gx[0].set_title('temperature') 374 | gx[0].contourf(lon,lat[:pole_low_index_S],potential_temperature[:pole_low_index_S,:,above_level]) 375 | 376 | gx[1].set_title('polar_plane_advect') 377 | polar_temps = low_level.beam_me_up(lat[:pole_low_index_S],lon,potential_temperature[:pole_low_index_S,:,:],grids[1],grid_lat_coords_S,grid_lon_coords_S) 378 | output = low_level.beam_me_up_2D(lat[:pole_low_index_S],lon,w[:pole_low_index_S,:,above_level],grids[1],grid_lat_coords_S,grid_lon_coords_S) 379 | 380 | gx[1].contourf(grid_x_values_S/1E3,grid_y_values_S/1E3,output) 381 | gx[1].contour(grid_x_values_S/1E3,grid_y_values_S/1E3,polar_temps[:,:,above_level],colors='white',levels=20,linewidths=1,alpha=0.8) 382 | gx[1].quiver(grid_x_values_S/1E3,grid_y_values_S/1E3,x_dot_S[:,:,above_level],y_dot_S[:,:,above_level]) 383 | 384 | gx[1].add_patch(plt.Circle((0,0),planet_radius*np.cos(lat[pole_low_index_S]*np.pi/180.0)/1E3,color='r',fill=False)) 385 | gx[1].add_patch(plt.Circle((0,0),planet_radius*np.cos(lat[pole_high_index_S]*np.pi/180.0)/1E3,color='r',fill=False)) 386 | 387 | gx[2].set_title('south_addition_smoothed') 388 | # gx[2].contourf(lon,lat[:pole_low_index_S],south_addition_smoothed[:pole_low_index_S,:,above_level]) 389 | gx[2].contourf(lon,lat[:pole_low_index_S],u[:pole_low_index_S,:,above_level]) 390 | gx[2].quiver(lon[::5],lat[:pole_low_index_S],u[:pole_low_index_S,::5,above_level],v[:pole_low_index_S,::5,above_level]) 391 | else: 392 | gx[0].set_title('temperature') 393 | gx[0].contourf(lon,lat[pole_low_index_N:],potential_temperature[pole_low_index_N:,:,above_level]) 394 | 395 | gx[1].set_title('polar_plane_advect') 396 | polar_temps = low_level.beam_me_up(lat[pole_low_index_N:],lon,np.flip(potential_temperature[pole_low_index_N:,:,:],axis=1),grids[0],grid_lat_coords_N,grid_lon_coords_N) 397 | output = low_level.beam_me_up_2D(lat[pole_low_index_N:],lon,atmosp_addition[pole_low_index_N:,:,above_level],grids[0],grid_lat_coords_N,grid_lon_coords_N) 398 | output = low_level.beam_me_up_2D(lat[pole_low_index_N:],lon,w[pole_low_index_N:,:,above_level],grids[0],grid_lat_coords_N,grid_lon_coords_N) 399 | 400 | gx[1].contourf(grid_x_values_N/1E3,grid_y_values_N/1E3,output) 401 | gx[1].contour(grid_x_values_N/1E3,grid_y_values_N/1E3,polar_temps[:,:,above_level],colors='white',levels=20,linewidths=1,alpha=0.8) 402 | gx[1].quiver(grid_x_values_N/1E3,grid_y_values_N/1E3,x_dot_N[:,:,above_level],y_dot_N[:,:,above_level]) 403 | 404 | gx[1].add_patch(plt.Circle((0,0),planet_radius*np.cos(lat[pole_low_index_N]*np.pi/180.0)/1E3,color='r',fill=False)) 405 | gx[1].add_patch(plt.Circle((0,0),planet_radius*np.cos(lat[pole_high_index_N]*np.pi/180.0)/1E3,color='r',fill=False)) 406 | 407 | gx[2].set_title('south_addition_smoothed') 408 | # gx[2].contourf(lon,lat[pole_low_index_N:],north_addition_smoothed[:,:,above_level]) 409 | gx[2].contourf(lon,lat[pole_low_index_N:],u[pole_low_index_N:,:,above_level]) 410 | gx[2].quiver(lon[::5],lat[pole_low_index_N:],u[pole_low_index_N:,::5,above_level],v[pole_low_index_N:,::5,above_level]) 411 | 412 | # clear plots 413 | if plot or above: plt.pause(0.001) 414 | if plot: 415 | if not diagnostic: 416 | ax[0].cla() 417 | ax[1].cla() 418 | cbar_ax.cla() 419 | 420 | else: 421 | ax[0,0].cla() 422 | ax[0,1].cla() 423 | ax[1,0].cla() 424 | ax[1,1].cla() 425 | if level_plots: 426 | for k in range(nplots): 427 | bx[k].cla() 428 | if verbose: 429 | time_taken = float(round(time.time() - before_plot,3)) 430 | print('Plotting: ',str(time_taken),'s') 431 | if above: 432 | gx[0].cla() 433 | gx[1].cla() 434 | gx[2].cla() 435 | 436 | while True: 437 | 438 | initial_time = time.time() 439 | 440 | if t < spinup_length: 441 | dt = dt_spinup 442 | velocity = False 443 | else: 444 | dt = dt_main 445 | velocity = True 446 | 447 | # print current time in simulation to command line 448 | print("+++ t = " + str(round(t/day,2)) + " days +++") 449 | print('T: ',round(temperature_world.max()-273.15,1),' - ',round(temperature_world.min()-273.15,1),' C') 450 | print('U: ',round(u[:,:,:sponge_index-1].max(),2),' - ',round(u[:,:,:sponge_index-1].min(),2),' V: ',round(v[:,:,:sponge_index-1].max(),2),' - ',round(v[:,:,:sponge_index-1].min(),2),' W: ',round(w[:,:,:sponge_index-1].max(),2),' - ',round(w[:,:,:sponge_index-1].min(),4)) 451 | 452 | tracer[40,50,sample_level] = 1 453 | tracer[20,50,sample_level] = 1 454 | 455 | if verbose: before_radiation = time.time() 456 | temperature_world, potential_temperature = top_level.radiation_calculation(temperature_world, potential_temperature, pressure_levels, heat_capacity_earth, albedo, insolation, lat, lon, t, dt, day, year, axial_tilt) 457 | if smoothing: potential_temperature = top_level.smoothing_3D(potential_temperature,smoothing_parameter_t) 458 | if verbose: 459 | time_taken = float(round(time.time() - before_radiation,3)) 460 | print('Radiation: ',str(time_taken),'s') 461 | 462 | diffusion = top_level.laplacian_2d(temperature_world,dx,dy) 463 | diffusion[0,:] = np.mean(diffusion[1,:],axis=0) 464 | diffusion[-1,:] = np.mean(diffusion[-2,:],axis=0) 465 | temperature_world -= dt*1E-5*diffusion 466 | 467 | # update geopotential field 468 | geopotential = np.zeros_like(potential_temperature) 469 | for k in np.arange(1,nlevels): geopotential[:,:,k] = geopotential[:,:,k-1] - potential_temperature[:,:,k]*(sigma[k]-sigma[k-1]) 470 | 471 | if velocity: 472 | 473 | if verbose: before_velocity = time.time() 474 | 475 | u_add,v_add = top_level.velocity_calculation(u,v,w,pressure_levels,geopotential,potential_temperature,coriolis,gravity,dx,dy,dt,sponge_index) 476 | 477 | if verbose: 478 | time_taken = float(round(time.time() - before_velocity,3)) 479 | print('Velocity: ',str(time_taken),'s') 480 | 481 | if verbose: before_projection = time.time() 482 | 483 | grid_velocities = (x_dot_N,y_dot_N,x_dot_S,y_dot_S) 484 | 485 | u_add,v_add,x_dot_N,y_dot_N,x_dot_S,y_dot_S = top_level.polar_planes(u,v,u_add,v_add,potential_temperature,geopotential,grid_velocities,indices,grids,coords,coriolis_plane_N,coriolis_plane_S,grid_side_length,pressure_levels,lat,lon,dt,polar_grid_resolution,gravity,sponge_index) 486 | 487 | u += u_add 488 | v += v_add 489 | 490 | if smoothing: u = top_level.smoothing_3D(u,smoothing_parameter_u) 491 | if smoothing: v = top_level.smoothing_3D(v,smoothing_parameter_v) 492 | 493 | x_dot_N,y_dot_N,x_dot_S,y_dot_S = top_level.update_plane_velocities(lat,lon,pole_low_index_N,pole_low_index_S,np.flip(u[pole_low_index_N:,:,:],axis=1),np.flip(v[pole_low_index_N:,:,:],axis=1),grids,grid_lat_coords_N,grid_lon_coords_N,u[:pole_low_index_S,:,:],v[:pole_low_index_S,:,:],grid_lat_coords_S,grid_lon_coords_S) 494 | grid_velocities = (x_dot_N,y_dot_N,x_dot_S,y_dot_S) 495 | 496 | if verbose: 497 | time_taken = float(round(time.time() - before_projection,3)) 498 | print('Projection: ',str(time_taken),'s') 499 | 500 | ### allow for thermal advection in the atmosphere 501 | if verbose: before_advection = time.time() 502 | 503 | if verbose: before_w = time.time() 504 | # using updated u,v fields calculated w 505 | # https://www.sjsu.edu/faculty/watkins/omega.htm 506 | w = -top_level.w_calculation(u,v,w,pressure_levels,geopotential,potential_temperature,coriolis,gravity,dx,dy,dt,indices,coords,grids,grid_velocities,polar_grid_resolution,lat,lon) 507 | if smoothing: w = top_level.smoothing_3D(w,smoothing_parameter_w,0.25) 508 | 509 | w[:,:,18:] *= 0 510 | # w[:1,:,:] *= 0 511 | # w[-1:,:,:] *= 0 512 | 513 | # plt.semilogy(w[5,25,:sponge_index],pressure_levels[:sponge_index]) 514 | # plt.gca().invert_yaxis() 515 | # plt.show() 516 | 517 | if verbose: 518 | time_taken = float(round(time.time() - before_w,3)) 519 | print('Calculate w: ',str(time_taken),'s') 520 | 521 | ################################# 522 | 523 | atmosp_addition = top_level.divergence_with_scalar(potential_temperature,u,v,w,dx,dy,lat,lon,pressure_levels,polar_grid_resolution,indices,coords,grids,grid_velocities) 524 | 525 | if smoothing: atmosp_addition = top_level.smoothing_3D(atmosp_addition,smoothing_parameter_add) 526 | 527 | atmosp_addition[:,:,sponge_index-1] *= 0.5 528 | atmosp_addition[:,:,sponge_index:] *= 0 529 | 530 | potential_temperature -= dt*atmosp_addition 531 | 532 | ################################################################### 533 | 534 | tracer_addition = top_level.divergence_with_scalar(tracer,u,v,w,dx,dy,lat,lon,pressure_levels,polar_grid_resolution,indices,coords,grids,grid_velocities) 535 | tracer -= dt*tracer_addition 536 | 537 | diffusion = top_level.laplacian_3d(potential_temperature,dx,dy,pressure_levels) 538 | diffusion[0,:,:] = np.mean(diffusion[1,:,:],axis=0) 539 | diffusion[-1,:,:] = np.mean(diffusion[-2,:,:],axis=0) 540 | potential_temperature -= dt*1E-4*diffusion 541 | 542 | courant = w*dt 543 | for k in range(nlevels-1): 544 | courant[:,:,k] /= (pressure_levels[k+1] - pressure_levels[k]) 545 | print('Courant max: ',round(abs(courant).max(),3)) 546 | 547 | ################################################################### 548 | 549 | if verbose: 550 | time_taken = float(round(time.time() - before_advection,3)) 551 | print('Advection: ',str(time_taken),'s') 552 | 553 | if t-last_plot >= plot_frequency*dt: 554 | plotting_routine() 555 | last_plot = t 556 | 557 | if save: 558 | if t-last_save >= save_frequency*dt: 559 | pickle.dump((potential_temperature,temperature_world,u,v,w,x_dot_N,y_dot_N,x_dot_S,y_dot_S,t,albedo,tracer), open("save_file.p","wb")) 560 | last_save = t 561 | 562 | if np.isnan(u.max()): 563 | sys.exit() 564 | 565 | # advance time by one timestep 566 | t += dt 567 | 568 | time_taken = float(round(time.time() - initial_time,3)) 569 | 570 | print('Time: ',str(time_taken),'s') 571 | # print('777777777777777777') --------------------------------------------------------------------------------